Merge remote-tracking branch 'egui/master' into dynamic-grid

This commit is contained in:
René Rössler 2022-03-23 14:51:55 +01:00
commit e5aa546b98
88 changed files with 2148 additions and 1997 deletions

85
.cargo/config.toml Normal file
View file

@ -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",
]

View file

@ -19,7 +19,7 @@ Examples: `Vec2, Pos2, Rect, lerp, remap`
Example: `Shape::Circle { center, radius, fill, stroke }` 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` ### `egui_extras`
This adds additional features on top of `egui`. This adds additional features on top of `egui`.

View file

@ -7,14 +7,22 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
## Unreleased ## Unreleased
### Added ⭐ ### 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)). * 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 🔧 ### Changed 🔧
* `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)). * `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)).
* Renamed `Frame::margin` to `Frame::inner_margin`.
### Fixed 🐛 ### Fixed 🐛
* Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)). * 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 ## 0.17.0 - 2022-02-22 - Improved font selection and image handling

99
Cargo.lock generated
View file

@ -81,6 +81,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "approx"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "arrayref" name = "arrayref"
version = "0.3.6" version = "0.3.6"
@ -442,6 +451,16 @@ dependencies = [
"libc", "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]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.19" version = "0.4.19"
@ -993,9 +1012,9 @@ dependencies = [
"epi", "epi",
"glow", "glow",
"image", "image",
"parking_lot 0.12.0",
"poll-promise", "poll-promise",
"rfd", "rfd",
"three-d",
] ]
[[package]] [[package]]
@ -1053,6 +1072,7 @@ dependencies = [
"poll-promise", "poll-promise",
"serde", "serde",
"syntect", "syntect",
"tracing",
"unicode_names2", "unicode_names2",
] ]
@ -1063,7 +1083,6 @@ dependencies = [
"chrono", "chrono",
"egui", "egui",
"image", "image",
"parking_lot 0.12.0",
"resvg", "resvg",
"serde", "serde",
"tiny-skia", "tiny-skia",
@ -1489,6 +1508,16 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 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]] [[package]]
name = "glow" name = "glow"
version = "0.11.2" version = "0.11.2"
@ -1607,6 +1636,11 @@ name = "half"
version = "1.8.2" version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
dependencies = [
"num-traits",
"serde",
"zerocopy",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
@ -1810,6 +1844,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "libm"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db"
[[package]] [[package]]
name = "line-wrap" name = "line-wrap"
version = "0.1.1" version = "0.1.1"
@ -2125,6 +2165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"libm",
] ]
[[package]] [[package]]
@ -3041,6 +3082,18 @@ dependencies = [
"unicode-xid", "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]] [[package]]
name = "syntect" name = "syntect"
version = "4.6.0" version = "4.6.0"
@ -3129,6 +3182,25 @@ dependencies = [
"once_cell", "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]] [[package]]
name = "time" name = "time"
version = "0.1.43" version = "0.1.43"
@ -3473,6 +3545,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"serde",
"serde_json",
"wasm-bindgen-macro", "wasm-bindgen-macro",
] ]
@ -3947,6 +4021,27 @@ dependencies = [
"zvariant", "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]] [[package]]
name = "zvariant" name = "zvariant"
version = "3.1.2" version = "3.1.2"

View file

@ -5,8 +5,8 @@
[![Documentation](https://docs.rs/egui/badge.svg)](https://docs.rs/egui) [![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/) [![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) [![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) [![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) [![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) [![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) 👈 👉 [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) * 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 * 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 * 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. 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). 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? ### 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 <https://github.com/emilk/egui/blob/master/egui_glium/examples/native_texture.rs>. If you want to embed 3D into an egui view there are two options.
#### `Shape::Callback`
Examples:
* <https://github.com/emilk/egui/blob/master/eframe/examples/custom_3d_three-d.rs>
* <https://github.com/emilk/egui/blob/master/eframe/examples/custom_3d_glow.rs>
`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): <https://github.com/emilk/egui/blob/master/egui_glium/examples/native_texture.rs>.
## Other ## Other

View file

@ -5,8 +5,11 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG
## Unreleased ## 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)). * 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 ## 0.17.0 - 2022-02-22

View file

@ -71,6 +71,6 @@ image = { version = "0.24", default-features = false, features = [
"jpeg", "jpeg",
"png", "png",
] } ] }
parking_lot = "0.12"
poll-promise = "0.1" poll-promise = "0.1"
rfd = "0.8" rfd = "0.8"
three-d = { git = "https://github.com/asny/three-d", rev = "fa475673e284e05b2f4e068769dce3ec5bcabc8d", default-features = false } # 2022-03-22

View file

@ -1,6 +1,15 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![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)] #[derive(Default)]
struct MyApp { struct MyApp {
@ -8,17 +17,13 @@ struct MyApp {
is_exiting: bool, is_exiting: bool,
} }
impl epi::App for MyApp { impl eframe::App for MyApp {
fn name(&self) -> &str {
"Confirm exit"
}
fn on_exit_event(&mut self) -> bool { fn on_exit_event(&mut self) -> bool {
self.is_exiting = true; self.is_exiting = true;
self.can_exit 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| { egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Try to close the window"); 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);
}

View file

@ -2,68 +2,87 @@
//! //!
//! This is very advanced usage, and you need to be careful. //! 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) //! * [`bevy_egui`](https://github.com/mvlabat/bevy_egui)
//! * [`three-d`](https://github.com/asny/three-d) //! * [`three-d`](https://github.com/asny/three-d)
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![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; 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 { struct MyApp {
rotating_triangle: Arc<Mutex<Option<RotatingTriangle>>>, /// Behind an `Arc<Mutex<…>>` so we can pass it to [`egui::PaintCallback`] and paint later.
rotating_triangle: Arc<Mutex<RotatingTriangle>>,
angle: f32, angle: f32,
} }
impl epi::App for MyApp { impl MyApp {
fn name(&self) -> &str { fn new(cc: &eframe::CreationContext<'_>) -> Self {
"Custom 3D painting inside an egui window" 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| { egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Here is some 3D stuff:"); ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("The triangle is being painted using ");
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
ui.label(" (OpenGL).");
});
egui::ScrollArea::both().show(ui, |ui| { egui::Frame::canvas(ui.style()).show(ui, |ui| {
egui::Frame::dark_canvas(ui.style()).show(ui, |ui| {
self.custom_painting(ui); self.custom_painting(ui);
}); });
ui.label("Drag to rotate!"); ui.label("Drag to rotate!");
}); });
}); }
let mut frame = egui::Frame::window(&*ctx.style()); fn on_exit(&mut self, gl: &glow::Context) {
frame.fill = frame.fill.linear_multiply(0.5); // transparent self.rotating_triangle.lock().destroy(gl);
egui::Window::new("3D stuff in a window")
.frame(frame)
.show(ctx, |ui| {
self.custom_painting(ui);
});
} }
} }
impl MyApp { impl MyApp {
fn custom_painting(&mut self, ui: &mut egui::Ui) { fn custom_painting(&mut self, ui: &mut egui::Ui) {
let (rect, response) = 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; self.angle += response.drag_delta().x * 0.01;
// Clone locals so we can move them into the paint callback:
let angle = self.angle; let angle = self.angle;
let rotating_triangle = self.rotating_triangle.clone(); let rotating_triangle = self.rotating_triangle.clone();
let callback = egui::epaint::PaintCallback { let callback = egui::PaintCallback {
rect, 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::<egui_glow::Painter>() { if let Some(painter) = render_ctx.downcast_ref::<egui_glow::Painter>() {
let mut rotating_triangle = rotating_triangle.lock(); rotating_triangle.lock().paint(painter.gl(), angle);
let rotating_triangle = rotating_triangle
.get_or_insert_with(|| RotatingTriangle::new(painter.gl()));
rotating_triangle.paint(painter.gl(), angle);
} else { } else {
eprintln!("Can't do custom painting because we are not using a glow context"); 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 fn destroy(&self, gl: &glow::Context) {
#[allow(unused)]
fn destroy(self, gl: &glow::Context) {
use glow::HasContext as _; use glow::HasContext as _;
unsafe { unsafe {
gl.delete_program(self.program); 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);
}

View file

@ -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::<egui_glow::Painter>() {
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<R>(
gl: &std::rc::Rc<glow::Context>,
f: impl FnOnce(&three_d::Context) -> R,
) -> R {
use std::cell::RefCell;
thread_local! {
pub static THREE_D: RefCell<Option<three_d::Context>> = 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();
}

View file

@ -1,29 +1,17 @@
use eframe::{egui, epi}; #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
struct MyApp { use eframe::egui;
text: String,
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native(
"egui example: custom font",
options,
Box::new(|cc| Box::new(MyApp::new(cc))),
);
} }
impl Default for MyApp { fn setup_custom_fonts(ctx: &egui::Context) {
fn default() -> Self {
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<epi::glow::Context>,
) {
// Start with the default fonts (we will be adding to them rather than replacing them). // Start with the default fonts (we will be adding to them rather than replacing them).
let mut fonts = egui::FontDefinitions::default(); let mut fonts = egui::FontDefinitions::default();
@ -50,17 +38,26 @@ impl epi::App for MyApp {
// Tell egui to use these fonts: // Tell egui to use these fonts:
ctx.set_fonts(fonts); ctx.set_fonts(fonts);
} }
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { struct MyApp {
text: String,
}
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 eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("egui using custom fonts"); ui.heading("egui using custom fonts");
ui.text_edit_multiline(&mut self.text); ui.text_edit_multiline(&mut self.text);
}); });
} }
} }
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native(Box::new(MyApp::default()), options);
}

View file

@ -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);
});
}

View file

@ -1,12 +1,16 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![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 egui_extras::RetainedImage;
use poll_promise::Promise; use poll_promise::Promise;
fn main() { fn main() {
let options = eframe::NativeOptions::default(); 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)] #[derive(Default)]
@ -15,23 +19,19 @@ struct MyApp {
promise: Option<Promise<ehttp::Result<RetainedImage>>>, promise: Option<Promise<ehttp::Result<RetainedImage>>>,
} }
impl epi::App for MyApp { impl eframe::App for MyApp {
fn name(&self) -> &str { fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
"Download and show an image with eframe/egui"
}
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
let promise = self.promise.get_or_insert_with(|| { let promise = self.promise.get_or_insert_with(|| {
// Begin download. // Begin download.
// We download the image using `ehttp`, a library that works both in WASM and on native. // 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. // 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 (sender, promise) = Promise::new();
let request = ehttp::Request::get("https://picsum.photos/seed/1.759706314/1024"); let request = ehttp::Request::get("https://picsum.photos/seed/1.759706314/1024");
ehttp::fetch(request, move |response| { ehttp::fetch(request, move |response| {
let image = response.and_then(parse_response); let image = response.and_then(parse_response);
sender.send(image); // send the results back to the UI thread. 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 promise
}); });
@ -50,6 +50,7 @@ impl epi::App for MyApp {
} }
} }
#[allow(clippy::needless_pass_by_value)]
fn parse_response(response: ehttp::Response) -> Result<RetainedImage, String> { fn parse_response(response: ehttp::Response) -> Result<RetainedImage, String> {
let content_type = response.content_type().unwrap_or_default(); let content_type = response.content_type().unwrap_or_default();
if content_type.starts_with("image/") { if content_type.starts_with("image/") {

View file

@ -1,6 +1,18 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![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)] #[derive(Default)]
struct MyApp { struct MyApp {
@ -8,12 +20,8 @@ struct MyApp {
picked_path: Option<String>, picked_path: Option<String>,
} }
impl epi::App for MyApp { impl eframe::App for MyApp {
fn name(&self) -> &str { fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
"Native file dialogs and drag-and-drop files"
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.label("Drag-and-drop files onto the window!"); 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);
}

View file

@ -1,6 +1,15 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![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 { struct MyApp {
name: String, name: String,
@ -16,12 +25,8 @@ impl Default for MyApp {
} }
} }
impl epi::App for MyApp { impl eframe::App for MyApp {
fn name(&self) -> &str { fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
"My egui App"
}
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("My egui Application"); ui.heading("My egui Application");
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -39,8 +44,3 @@ impl epi::App for MyApp {
frame.set_window_size(ctx.used_size()); frame.set_window_size(ctx.used_size());
} }
} }
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native(Box::new(MyApp::default()), options);
}

View file

@ -1,8 +1,20 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![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 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 { struct MyApp {
image: RetainedImage, image: RetainedImage,
} }
@ -19,16 +31,18 @@ impl Default for MyApp {
} }
} }
impl epi::App for MyApp { impl eframe::App for MyApp {
fn name(&self) -> &str { fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
"Show an image with eframe/egui"
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("This is an image:"); ui.heading("This is an image:");
self.image.show(ui); 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.heading("This is an image you can click:");
ui.add(egui::ImageButton::new( ui.add(egui::ImageButton::new(
self.image.texture_id(ctx), 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);
}

View file

@ -4,7 +4,19 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![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 { struct MyApp {
svg_image: egui_extras::RetainedImage, svg_image: egui_extras::RetainedImage,
@ -22,12 +34,8 @@ impl Default for MyApp {
} }
} }
impl epi::App for MyApp { impl eframe::App for MyApp {
fn name(&self) -> &str { fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
"svg example"
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("SVG example"); ui.heading("SVG example");
ui.label("The SVG is rasterized and displayed as a texture."); 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);
}

View file

@ -16,27 +16,32 @@
//! //!
//! ## Usage, native: //! ## Usage, native:
//! ``` no_run //! ``` 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)] //! #[derive(Default)]
//! struct MyEguiApp {} //! struct MyEguiApp {}
//! //!
//! impl epi::App for MyEguiApp { //! impl MyEguiApp {
//! fn name(&self) -> &str { //! fn new(cc: &eframe::CreationContext<'_>) -> Self {
//! "My egui App" //! // 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| { //! egui::CentralPanel::default().show(ctx, |ui| {
//! ui.heading("Hello World!"); //! 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")] //! #[cfg(target_arch = "wasm32")]
//! #[wasm_bindgen] //! #[wasm_bindgen]
//! pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> { //! pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
//! let app = MyEguiApp::default(); //! eframe::start_web(canvas_id, Box::new(|cc| Box::new(MyApp::new(cc))))
//! eframe::start_web(canvas_id, Box::new(app))
//! } //! }
//! ``` //! ```
// 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)] #![allow(clippy::needless_doctest_main)]
// Re-export all useful libraries:
pub use {egui, egui::emath, egui::epaint, epi}; pub use {egui, egui::emath, egui::epaint, epi};
#[cfg(not(target_arch = "wasm32"))] // Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is:
pub use epi::NativeOptions; pub use epi::*;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// When compiling for web // When compiling for web
@ -79,20 +75,6 @@ pub use egui_web::wasm_bindgen;
/// Install event listeners to register different input events /// Install event listeners to register different input events
/// and start running the given app. /// 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 /// ``` no_run
/// #[cfg(target_arch = "wasm32")] /// #[cfg(target_arch = "wasm32")]
/// use wasm_bindgen::prelude::*; /// use wasm_bindgen::prelude::*;
@ -104,45 +86,55 @@ pub use egui_web::wasm_bindgen;
/// #[cfg(target_arch = "wasm32")] /// #[cfg(target_arch = "wasm32")]
/// #[wasm_bindgen] /// #[wasm_bindgen]
/// pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> { /// pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
/// let app = MyEguiApp::default(); /// eframe::start_web(canvas_id, Box::new(|cc| Box::new(MyEguiApp::new(cc))))
/// eframe::start_web(canvas_id, Box::new(app))
/// } /// }
/// ``` /// ```
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub fn start_web(canvas_id: &str, app: Box<dyn epi::App>) -> Result<(), wasm_bindgen::JsValue> { pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bindgen::JsValue> {
egui_web::start(canvas_id, app)?; egui_web::start(canvas_id, app_creator)?;
Ok(()) Ok(())
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// When compiling natively // 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: /// Call from `fn main` like this:
/// ``` no_run /// ``` 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)] /// #[derive(Default)]
/// struct MyEguiApp {} /// struct MyEguiApp {}
/// ///
/// impl epi::App for MyEguiApp { /// impl MyEguiApp {
/// fn name(&self) -> &str { /// fn new(cc: &eframe::CreationContext<'_>) -> Self {
/// "My egui App" /// // 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| { /// egui::CentralPanel::default().show(ctx, |ui| {
/// ui.heading("Hello World!"); /// 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"))] #[cfg(not(target_arch = "wasm32"))]
pub fn run_native(app: Box<dyn epi::App>, native_options: epi::NativeOptions) -> ! { #[allow(clippy::needless_pass_by_value)]
egui_glow::run(app, &native_options) pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! {
egui_glow::run(app_name, &native_options, app_creator)
} }

View file

@ -45,7 +45,6 @@ serialize = ["egui/serialize", "serde"]
[dependencies] [dependencies]
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
"single_threaded",
"tracing", "tracing",
] } ] }
instant = { version = "0.1", features = ["wasm-bindgen"] } instant = { version = "0.1", features = ["wasm-bindgen"] }

View file

@ -21,6 +21,10 @@ pub fn window_builder(
max_window_size, max_window_size,
resizable, resizable,
transparent, 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; } = native_options;
let window_icon = icon_data.clone().and_then(load_icon); 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`]. /// Everything needed to make a winit-based integration for [`epi`].
pub struct EpiIntegration { pub struct EpiIntegration {
frame: epi::Frame, pub frame: epi::Frame,
persistence: crate::epi::Persistence, pub persistence: crate::epi::Persistence,
pub egui_ctx: egui::Context, pub egui_ctx: egui::Context,
pending_full_output: egui::FullOutput, pending_full_output: egui::FullOutput,
egui_winit: crate::State, egui_winit: crate::State,
pub app: Box<dyn epi::App>,
/// When set, it is time to quit /// When set, it is time to quit
quit: bool, quit: bool,
can_drag_window: bool, can_drag_window: bool,
@ -232,10 +235,7 @@ impl EpiIntegration {
integration_name: &'static str, integration_name: &'static str,
max_texture_side: usize, max_texture_side: usize,
window: &winit::window::Window, window: &winit::window::Window,
gl: &std::rc::Rc<glow::Context>,
repaint_signal: std::sync::Arc<dyn epi::backend::RepaintSignal>,
persistence: crate::epi::Persistence, persistence: crate::epi::Persistence,
app: Box<dyn epi::App>,
) -> Self { ) -> Self {
let egui_ctx = egui::Context::default(); let egui_ctx = egui::Context::default();
@ -252,7 +252,6 @@ impl EpiIntegration {
native_pixels_per_point: Some(crate::native_pixels_per_point(window)), native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
}, },
output: Default::default(), output: Default::default(),
repaint_signal,
}); });
if prefer_dark_mode == Some(true) { if prefer_dark_mode == Some(true) {
@ -261,41 +260,21 @@ impl EpiIntegration {
egui_ctx.set_visuals(egui::Visuals::light()); egui_ctx.set_visuals(egui::Visuals::light());
} }
let mut slf = Self { Self {
frame, frame,
persistence, persistence,
egui_ctx, egui_ctx,
egui_winit: crate::State::new(max_texture_side, window), egui_winit: crate::State::new(max_texture_side, window),
pending_full_output: Default::default(), pending_full_output: Default::default(),
app,
quit: false, quit: false,
can_drag_window: false, can_drag_window: false,
}; }
slf.setup(window, gl);
if slf.app.warm_up_enabled() {
slf.warm_up(window);
} }
slf pub fn warm_up(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
}
fn setup(&mut self, window: &winit::window::Window, gl: &std::rc::Rc<glow::Context>) {
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) {
let saved_memory: egui::Memory = self.egui_ctx.memory().clone(); let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
self.egui_ctx.memory().set_everything_is_visible(true); 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.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.memory() = saved_memory; // We don't want to remember that windows were huge.
self.egui_ctx.clear_animations(); self.egui_ctx.clear_animations();
@ -306,11 +285,11 @@ impl EpiIntegration {
self.quit 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}; use winit::event::{ElementState, MouseButton, WindowEvent};
match event { 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::Destroyed => self.quit = true,
WindowEvent::MouseInput { WindowEvent::MouseInput {
button: MouseButton::Left, button: MouseButton::Left,
@ -323,12 +302,16 @@ impl EpiIntegration {
self.egui_winit.on_event(&self.egui_ctx, event); 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 frame_start = instant::Instant::now();
let raw_input = self.egui_winit.take_egui_input(window); let raw_input = self.egui_winit.take_egui_input(window);
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { 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); self.pending_full_output.append(full_output);
let full_output = std::mem::take(&mut self.pending_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 app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
self.can_drag_window = false; self.can_drag_window = false;
if app_output.quit { 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); 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); .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 self.persistence
.maybe_autosave(&mut *self.app, &self.egui_ctx, window); .maybe_autosave(&mut *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);
} }
} }

View file

@ -3,84 +3,6 @@
//! The library translates winit events to egui, handled copy/paste, //! The library translates winit events to egui, handled copy/paste,
//! updates the cursor, open links clicked in egui, etc. //! 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)] #![allow(clippy::manual_range_contains)]
pub use winit; pub use winit;

View file

@ -20,7 +20,7 @@ all-features = true
[features] [features]
default = ["default_fonts", "single_threaded"] default = ["default_fonts"]
# add compatibility with https://crates.io/crates/cint # add compatibility with https://crates.io/crates/cint
cint = ["epaint/cint"] cint = ["epaint/cint"]
@ -46,11 +46,6 @@ persistence = ["serde", "epaint/serialize", "ron"]
# implement serde on most types. # implement serde on most types.
serialize = ["serde", "epaint/serialize"] 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] [dependencies]
epaint = { version = "0.17.0", path = "../epaint", default-features = false } epaint = { version = "0.17.0", path = "../epaint", default-features = false }

View file

@ -7,8 +7,10 @@ use epaint::*;
#[derive(Clone, Copy, Debug, Default, PartialEq)] #[derive(Clone, Copy, Debug, Default, PartialEq)]
#[must_use = "You should call .show()"] #[must_use = "You should call .show()"]
pub struct Frame { pub struct Frame {
/// On each side /// Margin within the painted frame.
pub margin: Margin, pub inner_margin: Margin,
/// Margin outside the painted frame.
pub outer_margin: Margin,
pub rounding: Rounding, pub rounding: Rounding,
pub shadow: Shadow, pub shadow: Shadow,
pub fill: Color32, pub fill: Color32,
@ -23,7 +25,7 @@ impl Frame {
/// For when you want to group a few widgets together within a frame. /// For when you want to group a few widgets together within a frame.
pub fn group(style: &Style) -> Self { pub fn group(style: &Style) -> Self {
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, rounding: style.visuals.widgets.noninteractive.rounding,
stroke: style.visuals.widgets.noninteractive.bg_stroke, stroke: style.visuals.widgets.noninteractive.bg_stroke,
..Default::default() ..Default::default()
@ -32,7 +34,7 @@ impl Frame {
pub(crate) fn side_top_panel(style: &Style) -> Self { pub(crate) fn side_top_panel(style: &Style) -> Self {
Self { Self {
margin: Margin::symmetric(8.0, 2.0), inner_margin: Margin::symmetric(8.0, 2.0),
rounding: Rounding::none(), rounding: Rounding::none(),
fill: style.visuals.window_fill(), fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(), stroke: style.visuals.window_stroke(),
@ -42,7 +44,7 @@ impl Frame {
pub(crate) fn central_panel(style: &Style) -> Self { pub(crate) fn central_panel(style: &Style) -> Self {
Self { Self {
margin: Margin::symmetric(8.0, 8.0), inner_margin: Margin::symmetric(8.0, 8.0),
rounding: Rounding::none(), rounding: Rounding::none(),
fill: style.visuals.window_fill(), fill: style.visuals.window_fill(),
stroke: Default::default(), stroke: Default::default(),
@ -52,31 +54,34 @@ impl Frame {
pub fn window(style: &Style) -> Self { pub fn window(style: &Style) -> Self {
Self { Self {
margin: style.spacing.window_margin, inner_margin: style.spacing.window_margin,
rounding: style.visuals.window_rounding, rounding: style.visuals.window_rounding,
shadow: style.visuals.window_shadow, shadow: style.visuals.window_shadow,
fill: style.visuals.window_fill(), fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(), stroke: style.visuals.window_stroke(),
..Default::default()
} }
} }
pub fn menu(style: &Style) -> Self { pub fn menu(style: &Style) -> Self {
Self { Self {
margin: Margin::same(1.0), inner_margin: Margin::same(1.0),
rounding: style.visuals.widgets.noninteractive.rounding, rounding: style.visuals.widgets.noninteractive.rounding,
shadow: style.visuals.popup_shadow, shadow: style.visuals.popup_shadow,
fill: style.visuals.window_fill(), fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(), stroke: style.visuals.window_stroke(),
..Default::default()
} }
} }
pub fn popup(style: &Style) -> Self { pub fn popup(style: &Style) -> Self {
Self { Self {
margin: style.spacing.window_margin, inner_margin: style.spacing.window_margin,
rounding: style.visuals.widgets.noninteractive.rounding, rounding: style.visuals.widgets.noninteractive.rounding,
shadow: style.visuals.popup_shadow, shadow: style.visuals.popup_shadow,
fill: style.visuals.window_fill(), fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(), stroke: style.visuals.window_stroke(),
..Default::default()
} }
} }
@ -86,7 +91,7 @@ impl Frame {
/// and in dark mode this will be very dark. /// and in dark mode this will be very dark.
pub fn canvas(style: &Style) -> Self { pub fn canvas(style: &Style) -> Self {
Self { Self {
margin: Margin::symmetric(10.0, 10.0), inner_margin: Margin::symmetric(10.0, 10.0),
rounding: style.visuals.widgets.noninteractive.rounding, rounding: style.visuals.widgets.noninteractive.rounding,
fill: style.visuals.extreme_bg_color, fill: style.visuals.extreme_bg_color,
stroke: style.visuals.window_stroke(), stroke: style.visuals.window_stroke(),
@ -119,12 +124,23 @@ impl Frame {
self self
} }
/// Margin on each side of the frame. /// Margin within the painted frame.
pub fn margin(mut self, margin: impl Into<Margin>) -> Self { pub fn inner_margin(mut self, inner_margin: impl Into<Margin>) -> Self {
self.margin = margin.into(); self.inner_margin = inner_margin.into();
self self
} }
/// Margin outside the painted frame.
pub fn outer_margin(mut self, outer_margin: impl Into<Margin>) -> Self {
self.outer_margin = outer_margin.into();
self
}
#[deprecated = "Renamed inner_margin in egui 0.18"]
pub fn margin(self, margin: impl Into<Margin>) -> Self {
self.inner_margin(margin)
}
pub fn shadow(mut self, shadow: Shadow) -> Self { pub fn shadow(mut self, shadow: Shadow) -> Self {
self.shadow = shadow; self.shadow = shadow;
self self
@ -150,8 +166,8 @@ impl Frame {
let outer_rect_bounds = ui.available_rect_before_wrap(); let outer_rect_bounds = ui.available_rect_before_wrap();
let mut inner_rect = outer_rect_bounds; let mut inner_rect = outer_rect_bounds;
inner_rect.min += Vec2::new(self.margin.left, self.margin.top); inner_rect.min += self.outer_margin.left_top() + self.inner_margin.left_top();
inner_rect.max -= Vec2::new(self.margin.right, self.margin.bottom); inner_rect.max -= self.outer_margin.right_bottom() + self.inner_margin.right_bottom();
// Make sure we don't shrink to the negative: // Make sure we don't shrink to the negative:
inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x); 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 { pub fn paint(&self, outer_rect: Rect) -> Shape {
let Self { let Self {
margin: _, inner_margin: _,
outer_margin: _,
rounding, rounding,
shadow, shadow,
fill, fill,
@ -210,15 +227,15 @@ impl Frame {
} }
impl Prepared { impl Prepared {
pub fn outer_rect(&self) -> Rect { fn paint_rect(&self) -> Rect {
let mut rect = self.content_ui.min_rect(); let mut rect = self.content_ui.min_rect();
rect.min -= Vec2::new(self.frame.margin.left, self.frame.margin.top); rect.min -= self.frame.inner_margin.left_top();
rect.max += Vec2::new(self.frame.margin.right, self.frame.margin.bottom); rect.max += self.frame.inner_margin.right_bottom();
rect rect
} }
pub fn end(self, ui: &mut Ui) -> Response { pub fn end(self, ui: &mut Ui) -> Response {
let outer_rect = self.outer_rect(); let paint_rect = self.paint_rect();
let Prepared { let Prepared {
frame, frame,
@ -226,11 +243,11 @@ impl Prepared {
.. ..
} = self; } = self;
if ui.is_rect_visible(outer_rect) { if ui.is_rect_visible(paint_rect) {
let shape = frame.paint(outer_rect); let shape = frame.paint(paint_rect);
ui.painter().set(where_to_put_background, shape); ui.painter().set(where_to_put_background, shape);
} }
ui.allocate_rect(outer_rect, Sense::hover()) ui.allocate_rect(paint_rect, Sense::hover())
} }
} }

View file

@ -298,7 +298,7 @@ pub fn popup_below_widget<R>(
// Note: we use a separate clip-rect for this area, so the popup can be outside the parent. // 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 // See https://github.com/emilk/egui/issues/825
let frame = Frame::popup(ui.style()); let frame = Frame::popup(ui.style());
let frame_margin = frame.margin; let frame_margin = frame.inner_margin + frame.outer_margin;
frame frame
.show(ui, |ui| { .show(ui, |ui| {
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| { ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {

View file

@ -301,7 +301,9 @@ impl<'open> Window<'open> {
} else { } else {
0.0 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( interact(
window_interaction, window_interaction,

View file

@ -48,6 +48,7 @@ struct ContextImpl {
/// While positive, keep requesting repaints. Decrement at the end of each frame. /// While positive, keep requesting repaints. Decrement at the end of each frame.
repaint_requests: u32, repaint_requests: u32,
request_repaint_callbacks: Option<Box<dyn Fn() + Send + Sync>>,
} }
impl ContextImpl { impl ContextImpl {
@ -533,11 +534,28 @@ impl Context {
impl Context { impl Context {
/// Call this if there is need to repaint the UI, i.e. if you are showing an animation. /// 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. /// 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. /// 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) { pub fn request_repaint(&self) {
// request two frames of repaint, just to cover some corner cases (frame delays): // 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. /// Tell `egui` which fonts to use.
@ -667,8 +685,19 @@ impl Context {
name: impl Into<String>, name: impl Into<String>,
image: impl Into<ImageData>, image: impl Into<ImageData>,
) -> TextureHandle { ) -> 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_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) 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 // 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. // it takes to tessellate them, so it is not a worth optimization.
let mut tessellation_options = *self.tessellation_options(); let pixels_per_point = self.pixels_per_point();
tessellation_options.pixels_per_point = self.pixels_per_point(); let tessellation_options = *self.tessellation_options();
tessellation_options.aa_size = 1.0 / self.pixels_per_point(); let font_image_size = self.fonts().font_image_size();
let paint_stats = PaintStats::from_shapes(&shapes); let paint_stats = PaintStats::from_shapes(&shapes);
let clipped_primitives = tessellator::tessellate_shapes( let clipped_primitives = tessellator::tessellate_shapes(
shapes, pixels_per_point,
tessellation_options, tessellation_options,
self.fonts().font_image_size(), shapes,
font_image_size,
); );
self.write().paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives); self.write().paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives);
clipped_primitives clipped_primitives

View file

@ -101,11 +101,14 @@ impl Widget for &epaint::stats::PaintStats {
ui.label("Intermediate:"); ui.label("Intermediate:");
label(ui, shapes, "shapes").on_hover_text("Boxes, circles, etc"); 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_path, "paths");
label(ui, shape_mesh, "nested meshes"); label(ui, shape_mesh, "nested meshes");
label(ui, shape_vec, "nested shapes"); 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.add_space(10.0);
ui.label("Text shapes:"); ui.label("Text shapes:");
@ -115,7 +118,7 @@ impl Widget for &epaint::stats::PaintStats {
ui.add_space(10.0); ui.add_space(10.0);
ui.label("Tessellated (and culled):"); 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"); .on_hover_text("Number of separate clip rectangles");
label(ui, vertices, "vertices"); label(ui, vertices, "vertices");
label(ui, indices, "indices").on_hover_text("Three 32-bit indices per triangles"); 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 { fn ui(self, ui: &mut Ui) -> Response {
ui.vertical(|ui| { ui.vertical(|ui| {
let epaint::TessellationOptions { let epaint::TessellationOptions {
pixels_per_point: _, feathering,
aa_size: _, feathering_size_in_pixels,
anti_alias,
coarse_tessellation_culling, coarse_tessellation_culling,
round_text_to_pixels, round_text_to_pixels,
debug_paint_clip_rects, debug_paint_clip_rects,
@ -147,8 +149,15 @@ impl Widget for &mut epaint::TessellationOptions {
bezier_tolerance, bezier_tolerance,
epsilon: _, epsilon: _,
} = self; } = 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."); .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( ui.add(
crate::widgets::Slider::new(bezier_tolerance, 0.0001..=10.0) crate::widgets::Slider::new(bezier_tolerance, 0.0001..=10.0)
.logarithmic(true) .logarithmic(true)

View file

@ -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::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]
@ -386,14 +307,15 @@ pub use epaint::{
color, mutex, color, mutex,
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak}, text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
textures::TexturesDelta, textures::TexturesDelta,
AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, Rgba, Rounding, Shape, AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, PaintCallback,
Stroke, TextureHandle, TextureId, PaintCallbackInfo, Rgba, Rounding, Shape, Stroke, TextureHandle, TextureId,
}; };
pub mod text { pub mod text {
pub use crate::text_edit::CCursorRange;
pub use epaint::text::{ pub use epaint::text::{
FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, LayoutSection, TextFormat, cursor::CCursor, FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob,
TAB_SIZE, LayoutSection, TextFormat, TAB_SIZE,
}; };
} }

View file

@ -277,10 +277,10 @@ impl MenuRoot {
root: &mut MenuRootManager, root: &mut MenuRootManager,
id: Id, id: Id,
) -> MenuResponse { ) -> MenuResponse {
let pointer = &response.ctx.input().pointer; // Lock the input once for the whole function call (see https://github.com/emilk/egui/pull/1380).
if (response.clicked() && root.is_menu_open(id)) let input = response.ctx.input();
|| response.ctx.input().key_pressed(Key::Escape)
{ if (response.clicked() && root.is_menu_open(id)) || input.key_pressed(Key::Escape) {
// menu open and button clicked or esc pressed // menu open and button clicked or esc pressed
return MenuResponse::Close; return MenuResponse::Close;
} else if (response.clicked() && !root.is_menu_open(id)) } else if (response.clicked() && !root.is_menu_open(id))
@ -290,8 +290,8 @@ impl MenuRoot {
// or button hovered while other menu is open // or button hovered while other menu is open
let pos = response.rect.left_bottom(); let pos = response.rect.left_bottom();
return MenuResponse::Create(pos, id); return MenuResponse::Create(pos, id);
} else if pointer.any_pressed() && pointer.primary_down() { } else if input.pointer.any_pressed() && input.pointer.primary_down() {
if let Some(pos) = pointer.interact_pos() { if let Some(pos) = input.pointer.interact_pos() {
if let Some(root) = root.inner.as_mut() { if let Some(root) = root.inner.as_mut() {
if root.id == id { if root.id == id {
// pressed somewhere while this menu is open // pressed somewhere while this menu is open

View file

@ -277,6 +277,8 @@ impl Spacing {
} }
} }
// ----------------------------------------------------------------------------
#[derive(Clone, Copy, Debug, Default, PartialEq)] #[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Margin { pub struct Margin {
@ -312,6 +314,20 @@ impl Margin {
pub fn sum(&self) -> Vec2 { pub fn sum(&self) -> Vec2 {
Vec2::new(self.left + self.right, self.top + self.bottom) 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<f32> for Margin {
fn from(v: f32) -> Self {
Self::same(v)
}
} }
impl From<Vec2> for Margin { impl From<Vec2> for Margin {
@ -320,6 +336,20 @@ impl From<Vec2> 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. /// How and when interaction happens.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]

View file

@ -1546,6 +1546,26 @@ impl Ui {
crate::Frame::group(self.style()).show(self, add_contents) 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<R>(
&mut self,
id_source: impl Hash,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.scope_dyn(Box::new(add_contents), Id::new(id_source))
}
/// Create a scoped child ui. /// Create a scoped child ui.
/// ///
/// You can use this to temporarily change the [`Style`] of a sub-region, for instance: /// You can use this to temporarily change the [`Style`] of a sub-region, for instance:
@ -1559,16 +1579,17 @@ impl Ui {
/// # }); /// # });
/// ``` /// ```
pub fn scope<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> { pub fn scope<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
self.scope_dyn(Box::new(add_contents)) self.scope_dyn(Box::new(add_contents), Id::new("child"))
} }
fn scope_dyn<'c, R>( fn scope_dyn<'c, R>(
&mut self, &mut self,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>, add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
id_source: Id,
) -> InnerResponse<R> { ) -> InnerResponse<R> {
let child_rect = self.available_rect_before_wrap(); let child_rect = self.available_rect_before_wrap();
let next_auto_id_source = self.next_auto_id_source; 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`. 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 ret = add_contents(&mut child_ui);
let response = self.allocate_rect(child_ui.min_rect(), Sense::hover()); let response = self.allocate_rect(child_ui.min_rect(), Sense::hover());

View file

@ -1,4 +1,5 @@
use crate::*; use crate::*;
use emath::Rot2;
/// An widget to show an image of a given size. /// An widget to show an image of a given size.
/// ///
@ -36,6 +37,7 @@ pub struct Image {
bg_fill: Color32, bg_fill: Color32,
tint: Color32, tint: Color32,
sense: Sense, sense: Sense,
rotation: Option<(Rot2, Vec2)>,
} }
impl Image { impl Image {
@ -47,6 +49,7 @@ impl Image {
bg_fill: Default::default(), bg_fill: Default::default(),
tint: Color32::WHITE, tint: Color32::WHITE,
sense: Sense::hover(), sense: Sense::hover(),
rotation: None,
} }
} }
@ -75,6 +78,17 @@ impl Image {
self.sense = sense; self.sense = sense;
self 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 { impl Image {
@ -88,10 +102,11 @@ impl Image {
let Self { let Self {
texture_id, texture_id,
uv, uv,
size: _, size,
bg_fill, bg_fill,
tint, tint,
sense: _, sense: _,
rotation,
} = self; } = self;
if *bg_fill != Default::default() { if *bg_fill != Default::default() {
@ -104,6 +119,9 @@ impl Image {
// TODO: builder pattern for Mesh // TODO: builder pattern for Mesh
let mut mesh = Mesh::with_texture(*texture_id); let mut mesh = Mesh::with_texture(*texture_id);
mesh.add_rect_with_uv(rect, *uv, *tint); 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)); ui.painter().add(Shape::mesh(mesh));
} }
} }

View file

@ -239,11 +239,12 @@ impl Widget for &mut LegendWidget {
legend_ui legend_ui
.scope(|ui| { .scope(|ui| {
let background_frame = Frame { 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, rounding: ui.style().visuals.window_rounding,
shadow: epaint::Shadow::default(), shadow: epaint::Shadow::default(),
fill: ui.style().visuals.extreme_bg_color, fill: ui.style().visuals.extreme_bg_color,
stroke: ui.style().visuals.window_stroke(), stroke: ui.style().visuals.window_stroke(),
..Default::default()
} }
.multiply_with_opacity(config.background_alpha); .multiply_with_opacity(config.background_alpha);
background_frame background_frame

View file

@ -8,13 +8,14 @@ use epaint::color::Hsva;
use epaint::util::FloatOrd; use epaint::util::FloatOrd;
use items::PlotItem; use items::PlotItem;
use legend::LegendWidget; use legend::LegendWidget;
use transform::{PlotBounds, ScreenTransform}; use transform::ScreenTransform;
pub use items::{ pub use items::{
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape, Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape,
Orientation, PlotImage, Points, Polygon, Text, VLine, Value, Values, Orientation, PlotImage, Points, Polygon, Text, VLine, Value, Values,
}; };
pub use legend::{Corner, Legend}; pub use legend::{Corner, Legend};
pub use transform::PlotBounds;
mod items; mod items;
mod legend; mod legend;
@ -767,6 +768,11 @@ impl PlotUi {
self.response.hovered() 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. /// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area.
pub fn pointer_coordinate(&self) -> Option<Value> { pub fn pointer_coordinate(&self) -> Option<Value> {
// We need to subtract the drag delta to keep in sync with the frame-delayed screen transform: // We need to subtract the drag delta to keep in sync with the frame-delayed screen transform:

View file

@ -455,10 +455,14 @@ impl<'a> Slider<'a> {
fn value_ui(&mut self, ui: &mut Ui, position_range: RangeInclusive<f32>) -> Response { fn value_ui(&mut self, ui: &mut Ui, position_range: RangeInclusive<f32>) -> Response {
// If `DragValue` is controlled from the keyboard and `step` is defined, set speed to `step` // 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 let change = {
+ ui.input().num_presses(Key::ArrowRight) as i32 // Hold one lock rather than 4 (see https://github.com/emilk/egui/pull/1380).
- ui.input().num_presses(Key::ArrowDown) as i32 let input = ui.input();
- ui.input().num_presses(Key::ArrowLeft) as i32;
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 { let speed = match self.step {
Some(step) if change != 0 => step, Some(step) if change != 0 => step,
_ => self.current_gradient(&position_range), _ => self.current_gradient(&position_range),

View file

@ -1,7 +1,4 @@
// Forbid warnings in release builds: //! Demo app for egui
#![cfg_attr(not(debug_assertions), deny(warnings))]
#![forbid(unsafe_code)]
#![warn(clippy::all, rust_2018_idioms)]
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use eframe::wasm_bindgen::{self, prelude::*}; 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: // Redirect tracing to console.log and friends:
tracing_wasm::set_as_global_default(); tracing_wasm::set_as_global_default();
let app = egui_demo_lib::WrapApp::default(); eframe::start_web(
eframe::start_web(canvas_id, Box::new(app)) canvas_id,
Box::new(|cc| Box::new(egui_demo_lib::WrapApp::new(cc))),
)
} }

View file

@ -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), windows_subsystem = "windows")] // hide console window on Windows in release
#![cfg_attr(not(debug_assertions), deny(warnings))]
#![forbid(unsafe_code)]
#![warn(clippy::all, rust_2018_idioms)]
// When compiling natively: // When compiling natively:
fn main() { fn main() {
// Log to stdout (if you run with `RUST_LOG=debug`). // Log to stdout (if you run with `RUST_LOG=debug`).
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let app = egui_demo_lib::WrapApp::default();
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
// Let's show off that we support transparent windows // Let's show off that we support transparent windows
transparent: true, transparent: true,
drag_and_drop_support: true, drag_and_drop_support: true,
..Default::default() ..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))),
);
} }

View file

@ -45,6 +45,7 @@ epi = { version = "0.17.0", path = "../epi" }
chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] } chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] }
enum-map = { version = "2", features = ["serde"] } enum-map = { version = "2", features = ["serde"] }
tracing = "0.1"
unicode_names2 = { version = "0.5.0", default-features = false } unicode_names2 = { version = "0.5.0", default-features = false }
# feature "http": # feature "http":

View file

@ -17,7 +17,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
demo_windows.ui(ctx); demo_windows.ui(ctx);
}); });
ctx.tessellate(full_output.shapes) ctx.tessellate(full_output.shapes)
}) });
}); });
c.bench_function("demo_no_tessellate", |b| { c.bench_function("demo_no_tessellate", |b| {
@ -25,14 +25,14 @@ pub fn criterion_benchmark(c: &mut Criterion) {
ctx.run(RawInput::default(), |ctx| { ctx.run(RawInput::default(), |ctx| {
demo_windows.ui(ctx); demo_windows.ui(ctx);
}) })
}) });
}); });
let full_output = ctx.run(RawInput::default(), |ctx| { let full_output = ctx.run(RawInput::default(), |ctx| {
demo_windows.ui(ctx); demo_windows.ui(ctx);
}); });
c.bench_function("demo_only_tessellate", |b| { 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| { ctx.run(RawInput::default(), |ctx| {
demo_windows.ui(ctx); demo_windows.ui(ctx);
}) })
}) });
}); });
} }
@ -56,12 +56,12 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("label &str", |b| { c.bench_function("label &str", |b| {
b.iter(|| { b.iter(|| {
ui.label("the quick brown fox jumps over the lazy dog"); ui.label("the quick brown fox jumps over the lazy dog");
}) });
}); });
c.bench_function("label format!", |b| { c.bench_function("label format!", |b| {
b.iter(|| { b.iter(|| {
ui.label("the quick brown fox jumps over the lazy dog".to_owned()); 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(); let rect = ui.max_rect();
b.iter(|| { b.iter(|| {
painter.rect(rect, 2.0, egui::Color32::RED, (1.0, egui::Color32::WHITE)); 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, wrap_width,
); );
layout(&mut locked_fonts.fonts, job.into()) layout(&mut locked_fonts.fonts, job.into())
}) });
}); });
} }
c.bench_function("text_layout_cached", |b| { c.bench_function("text_layout_cached", |b| {
@ -119,19 +119,19 @@ pub fn criterion_benchmark(c: &mut Criterion) {
color, color,
wrap_width, wrap_width,
) )
}) });
}); });
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, 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 mut mesh = egui::epaint::Mesh::default();
let text_shape = TextShape::new(egui::Pos2::ZERO, galley); let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
let font_image_size = fonts.font_image_size(); let font_image_size = fonts.font_image_size();
c.bench_function("tessellate_text", |b| { c.bench_function("tessellate_text", |b| {
b.iter(|| { 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(); mesh.clear();
}) });
}); });
} }
} }

View file

@ -30,10 +30,6 @@ impl Default for ColorTest {
} }
impl epi::App for ColorTest { impl epi::App for ColorTest {
fn name(&self) -> &str {
"🎨 Color test"
}
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
if frame.is_web() { if frame.is_web() {
@ -132,9 +128,9 @@ impl ColorTest {
ui.separator(); ui.separator();
// TODO: another ground truth where we do the alpha-blending against the background also. self.show_gradients(ui, BLACK, (BLACK, WHITE));
// TODO: exactly the same thing, but with vertex colors (no textures) ui.separator();
self.show_gradients(ui, WHITE, (TRANSPARENT, BLACK)); self.show_gradients(ui, WHITE, (BLACK, TRANSPARENT));
ui.separator(); ui.separator();
self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE)); self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE));
ui.separator(); ui.separator();
@ -149,6 +145,10 @@ impl ColorTest {
ui.separator(); ui.separator();
pixel_test(ui); pixel_test(ui);
ui.separator();
fine_line_test(ui);
} }
fn show_gradients(&mut self, ui: &mut Ui, bg_fill: Color32, (left, right): (Color32, Color32)) { 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) { 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."); 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 pixels_per_point = ui.ctx().pixels_per_point();
let num_squares: u32 = 8; let num_squares: u32 = 8;
let size_pixels = Vec2::new( let size_pixels = Vec2::new(
@ -379,7 +385,71 @@ fn pixel_test(ui: &mut Ui) {
), ),
Vec2::splat(size as f32) / pixels_per_point, 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; 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);
}

View file

@ -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)] #[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(default))]
@ -10,28 +6,6 @@ pub struct DemoApp {
} }
impl epi::App for 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<epi::glow::Context>,
) {
#[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) { fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
self.demo_windows.ui(ctx); self.demo_windows.ui(ctx);
} }

View file

@ -64,6 +64,7 @@ impl super::View for TextEdit {
anything_selected, anything_selected,
egui::Label::new("Press ctrl+T to toggle the case of selected text (cmd+T on Mac)"), egui::Label::new("Press ctrl+T to toggle the case of selected text (cmd+T on Mac)"),
); );
if ui if ui
.input_mut() .input_mut()
.consume_key(egui::Modifiers::COMMAND, egui::Key::T) .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); 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`.
}
}
});
} }
} }

View file

@ -33,10 +33,6 @@ impl Default for FractalClock {
} }
impl epi::App for FractalClock { impl epi::App for FractalClock {
fn name(&self) -> &str {
"🕑 Fractal Clock"
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::CentralPanel::default() egui::CentralPanel::default()
.frame(Frame::dark_canvas(&ctx.style())) .frame(Frame::dark_canvas(&ctx.style()))

View file

@ -54,10 +54,6 @@ impl Default for HttpApp {
} }
impl epi::App for HttpApp { impl epi::App for HttpApp {
fn name(&self) -> &str {
"⬇ HTTP"
}
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) { fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| { egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| {
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); 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 { if trigger_fetch {
let ctx = ctx.clone(); let ctx = ctx.clone();
let frame = frame.clone();
let (sender, promise) = Promise::new(); let (sender, promise) = Promise::new();
let request = ehttp::Request::get(&self.url); let request = ehttp::Request::get(&self.url);
ehttp::fetch(request, move |response| { 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)); let resource = response.map(|response| Resource::from_response(&ctx, response));
sender.send(resource); sender.send(resource);
}); });

View file

@ -54,10 +54,6 @@ pub struct BackendPanel {
#[cfg_attr(feature = "serde", serde(skip))] #[cfg_attr(feature = "serde", serde(skip))]
pixels_per_point: Option<f32>, pixels_per_point: Option<f32>,
/// 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))] #[cfg_attr(feature = "serde", serde(skip))]
frame_history: crate::frame_history::FrameHistory, frame_history: crate::frame_history::FrameHistory,
@ -70,8 +66,6 @@ impl Default for BackendPanel {
open: false, open: false,
run_mode: Default::default(), run_mode: Default::default(),
pixels_per_point: 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(), frame_history: Default::default(),
egui_windows: Default::default(), egui_windows: Default::default(),
} }
@ -157,17 +151,6 @@ impl BackendPanel {
ui.hyperlink("https://github.com/emilk/egui"); ui.hyperlink("https://github.com/emilk/egui");
ui.separator(); 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()); show_integration_name(ui, &frame.info());

View file

@ -30,10 +30,6 @@ impl Default for EasyMarkEditor {
} }
impl epi::App for EasyMarkEditor { impl epi::App for EasyMarkEditor {
fn name(&self) -> &str {
"🖹 EasyMark editor"
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| { egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);

View file

@ -2,85 +2,6 @@
//! //!
//! The demo-code is also used in benchmarks and tests. //! 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::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]

View file

@ -277,7 +277,7 @@ impl CodeTheme {
ui.data().insert_persisted(selected_id, selected_tt); ui.data().insert_persisted(selected_id, selected_tt);
egui::Frame::group(ui.style()) egui::Frame::group(ui.style())
.margin(egui::Vec2::splat(2.0)) .inner_margin(egui::Vec2::splat(2.0))
.show(ui, |ui| { .show(ui, |ui| {
// ui.group(|ui| { // ui.group(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Small); ui.style_mut().override_text_style = Some(egui::TextStyle::Small);

View file

@ -12,14 +12,26 @@ pub struct Apps {
} }
impl Apps { impl Apps {
fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut dyn epi::App)> { fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &str, &mut dyn epi::App)> {
vec![ vec![
("demo", &mut self.demo as &mut dyn epi::App), ("✨ Demos", "demo", &mut self.demo as &mut dyn epi::App),
("easymark", &mut self.easy_mark_editor as &mut dyn epi::App), (
"🖹 EasyMark editor",
"easymark",
&mut self.easy_mark_editor as &mut dyn epi::App,
),
#[cfg(feature = "http")] #[cfg(feature = "http")]
("http", &mut self.http as &mut dyn epi::App), ("⬇ 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), "🕑 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() .into_iter()
} }
@ -37,33 +49,22 @@ pub struct WrapApp {
dropped_files: Vec<egui::DroppedFile>, dropped_files: Vec<egui::DroppedFile>,
} }
impl epi::App for WrapApp { impl WrapApp {
fn name(&self) -> &str { pub fn new(_cc: &epi::CreationContext<'_>) -> Self {
"egui demo apps"
}
fn setup(
&mut self,
_ctx: &egui::Context,
_frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>,
_gl: &std::rc::Rc<epi::glow::Context>,
) {
#[cfg(feature = "persistence")] #[cfg(feature = "persistence")]
if let Some(storage) = _storage { if let Some(storage) = _cc.storage {
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default(); return epi::get_value(storage, epi::APP_KEY).unwrap_or_default();
} }
Self::default()
} }
}
impl epi::App for WrapApp {
#[cfg(feature = "persistence")] #[cfg(feature = "persistence")]
fn save(&mut self, storage: &mut dyn epi::Storage) { fn save(&mut self, storage: &mut dyn epi::Storage) {
epi::set_value(storage, epi::APP_KEY, self); 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 { fn clear_color(&self) -> egui::Rgba {
egui::Rgba::TRANSPARENT // we set a `CentralPanel` fill color in `demo_windows.rs` 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; 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() { if anchor == self.selected_anchor || ctx.memory().everything_is_visible() {
app.update(ctx, frame); app.update(ctx, frame);
found_anchor = true; found_anchor = true;
@ -138,9 +139,9 @@ impl WrapApp {
ui.checkbox(&mut self.backend_panel.open, "💻 Backend"); ui.checkbox(&mut self.backend_panel.open, "💻 Backend");
ui.separator(); ui.separator();
for (anchor, app) in self.apps.iter_mut() { for (name, anchor, _app) in self.apps.iter_mut() {
if ui if ui
.selectable_label(self.selected_anchor == anchor, app.name()) .selectable_label(self.selected_anchor == anchor, name)
.clicked() .clicked()
{ {
self.selected_anchor = anchor.to_owned(); self.selected_anchor = anchor.to_owned();

View file

@ -36,10 +36,7 @@ datepicker = ["chrono"]
persistence = ["serde"] persistence = ["serde"]
[dependencies] [dependencies]
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ egui = { version = "0.17.0", path = "../egui", default-features = false }
"single_threaded",
] }
parking_lot = "0.12"
# Optional dependencies: # Optional dependencies:
@ -49,7 +46,7 @@ chrono = { version = "0.4", optional = true }
# Add support for loading images with the `image` crate. # 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: # 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", features = ["jpeg", "png"] }
image = { version = "0.24", optional = true, default-features = false, features = [] } image = { version = "0.24", optional = true, default-features = false }
# svg feature # svg feature
resvg = { version = "0.22", optional = true } resvg = { version = "0.22", optional = true }

View file

@ -1,4 +1,4 @@
use parking_lot::Mutex; use egui::mutex::Mutex;
/// An image to be shown in egui. /// An image to be shown in egui.
/// ///

View file

@ -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`. //! 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::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]

View file

@ -47,7 +47,6 @@ screen_reader = ["egui-winit/screen_reader"]
[dependencies] [dependencies]
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
"convert_bytemuck", "convert_bytemuck",
"single_threaded",
] } ] }
egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false } egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false }

View file

@ -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 #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use glium::glutin; 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<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)
}
fn main() { fn main() {
let event_loop = glutin::event_loop::EventLoop::with_user_event(); let event_loop = glutin::event_loop::EventLoop::with_user_event();
let display = create_display(&event_loop); 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)
}

View file

@ -4,24 +4,6 @@
use glium::glutin; 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() { fn main() {
let event_loop = glutin::event_loop::EventLoop::with_user_event(); let event_loop = glutin::event_loop::EventLoop::with_user_event();
let display = create_display(&event_loop); 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()
}

View file

@ -2,88 +2,8 @@
//! //!
//! The main type you want to use is [`EguiGlium`]. //! 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. //! 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::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]

View file

@ -3,6 +3,8 @@ All notable changes to the `egui_glow` integration will be noted in this file.
## Unreleased ## Unreleased
* Improved logging on rendering failures.
* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
## 0.17.0 - 2022-02-22 ## 0.17.0 - 2022-02-22

View file

@ -57,7 +57,6 @@ winit = ["egui-winit", "glutin"]
[dependencies] [dependencies]
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
"convert_bytemuck", "convert_bytemuck",
"single_threaded",
] } ] }
epi = { version = "0.17.0", path = "../epi", optional = true } epi = { version = "0.17.0", path = "../epi", optional = true }

View file

@ -1,42 +1,7 @@
//! Example how to use pure `egui_glow` without [`epi`]. //! Example how to use pure `egui_glow` without [`epi`].
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#![allow(unsafe_code)]
fn create_display(
event_loop: &glutin::event_loop::EventLoop<()>,
) -> (
glutin::WindowedContext<glutin::PossiblyCurrent>,
glow::Context,
) {
let window_builder = glutin::window::WindowBuilder::new()
.with_resizable(true)
.with_inner_size(glutin::dpi::LogicalSize {
width: 800.0,
height: 600.0,
})
.with_title("egui_glow example");
let gl_window = unsafe {
glutin::ContextBuilder::new()
.with_depth_buffer(0)
.with_srgb(true)
.with_stencil_buffer(0)
.with_vsync(true)
.build_windowed(window_builder, event_loop)
.unwrap()
.make_current()
.unwrap()
};
let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) };
unsafe {
use glow::HasContext as _;
gl.enable(glow::FRAMEBUFFER_SRGB);
}
(gl_window, gl)
}
fn main() { fn main() {
let mut clear_color = [0.1, 0.1, 0.1]; 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<glutin::PossiblyCurrent>,
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)
}

View file

@ -3,16 +3,9 @@ use egui_winit::winit;
struct RequestRepaintEvent; struct RequestRepaintEvent;
struct GlowRepaintSignal(std::sync::Mutex<winit::event_loop::EventLoopProxy<RequestRepaintEvent>>);
impl epi::backend::RepaintSignal for GlowRepaintSignal {
fn request_repaint(&self) {
self.0.lock().unwrap().send_event(RequestRepaintEvent).ok();
}
}
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn create_display( fn create_display(
native_options: &NativeOptions,
window_builder: winit::window::WindowBuilder, window_builder: winit::window::WindowBuilder,
event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>, event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>,
) -> ( ) -> (
@ -21,10 +14,11 @@ fn create_display(
) { ) {
let gl_window = unsafe { let gl_window = unsafe {
glutin::ContextBuilder::new() glutin::ContextBuilder::new()
.with_depth_buffer(0) .with_depth_buffer(native_options.depth_buffer)
.with_multisampling(native_options.multisampling)
.with_srgb(true) .with_srgb(true)
.with_stencil_buffer(0) .with_stencil_buffer(native_options.stencil_buffer)
.with_vsync(true) .with_vsync(native_options.vsync)
.build_windowed(window_builder, event_loop) .build_windowed(window_builder, event_loop)
.unwrap() .unwrap()
.make_current() .make_current()
@ -33,11 +27,6 @@ fn create_display(
let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) }; 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) (gl_window, gl)
} }
@ -47,31 +36,43 @@ pub use epi::NativeOptions;
/// Run an egui app /// Run an egui app
#[allow(unsafe_code)] #[allow(unsafe_code)]
pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! { 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 persistence = egui_winit::epi::Persistence::from_app_name(app_name);
let window_settings = persistence.load_window_settings(); let window_settings = persistence.load_window_settings();
let window_builder = 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 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 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, "") let mut painter = crate::Painter::new(gl.clone(), None, "")
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error)); .unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
let mut integration = egui_winit::epi::EpiIntegration::new( let mut integration = egui_winit::epi::EpiIntegration::new(
"egui_glow", "egui_glow",
painter.max_texture_side(), painter.max_texture_side(),
gl_window.window(), gl_window.window(),
&gl,
repaint_signal,
persistence, 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; let mut is_focused = true;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
@ -90,7 +91,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
needs_repaint, needs_repaint,
textures_delta, textures_delta,
shapes, shapes,
} = integration.update(gl_window.window()); } = integration.update(app.as_mut(), gl_window.window());
integration.handle_platform_output(gl_window.window(), platform_output); integration.handle_platform_output(gl_window.window(), platform_output);
@ -98,7 +99,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
// paint: // paint:
{ {
let color = integration.app.clear_color(); let color = app.clear_color();
unsafe { unsafe {
use glow::HasContext as _; use glow::HasContext as _;
gl.disable(glow::SCISSOR_TEST); gl.disable(glow::SCISSOR_TEST);
@ -126,7 +127,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
}; };
} }
integration.maybe_autosave(gl_window.window()); integration.maybe_autosave(app.as_mut(), gl_window.window());
}; };
match event { match event {
@ -145,7 +146,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
gl_window.resize(physical_size); gl_window.resize(physical_size);
} }
integration.on_event(&event); integration.on_event(app.as_mut(), &event);
if integration.should_quit() { if integration.should_quit() {
*control_flow = winit::event_loop::ControlFlow::Exit; *control_flow = winit::event_loop::ControlFlow::Exit;
} }
@ -153,7 +154,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead
} }
winit::event::Event::LoopDestroyed => { 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(); painter.destroy();
} }
winit::event::Event::UserEvent(RequestRepaintEvent) => { winit::event::Event::UserEvent(RequestRepaintEvent) => {

View file

@ -5,85 +5,6 @@
//! This library is an [`epi`] backend. //! This library is an [`epi`] backend.
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead. //! 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::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]
@ -93,7 +14,7 @@ pub use painter::Painter;
mod misc_util; mod misc_util;
mod post_process; mod post_process;
mod shader_version; mod shader_version;
mod vao_emulate; mod vao;
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] #[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
pub mod winit; pub mod winit;
@ -105,3 +26,62 @@ mod epi_backend;
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))] #[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
pub use epi_backend::{run, NativeOptions}; 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",
_ => "<unknown>",
};
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,
);
}
}
}

View file

@ -1,18 +1,6 @@
#![allow(unsafe_code)] #![allow(unsafe_code)]
use glow::HasContext;
use std::option::Option::Some;
pub fn check_for_gl_error(gl: &glow::Context, context: &str) { use glow::HasContext as _;
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
);
}
}
pub(crate) unsafe fn compile_shader( pub(crate) unsafe fn compile_shader(
gl: &glow::Context, gl: &glow::Context,
@ -50,105 +38,3 @@ pub(crate) unsafe fn link_program<'a, T: IntoIterator<Item = &'a glow::Shader>>(
Err(gl.get_program_info_log(program)) 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
}
}
}

View file

@ -6,13 +6,14 @@ use egui::{
emath::Rect, emath::Rect,
epaint::{Color32, Mesh, Primitive, Vertex}, epaint::{Color32, Mesh, Primitive, Vertex},
}; };
use glow::HasContext; use glow::HasContext as _;
use memoffset::offset_of; 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::post_process::PostProcess;
use crate::shader_version::ShaderVersion; use crate::shader_version::ShaderVersion;
use crate::vao_emulate; use crate::vao;
pub use glow::Context; pub use glow::Context;
@ -36,12 +37,12 @@ pub struct Painter {
u_sampler: glow::UniformLocation, u_sampler: glow::UniformLocation,
is_webgl_1: bool, is_webgl_1: bool,
is_embedded: bool, is_embedded: bool,
vertex_array: crate::misc_util::VAO, vao: crate::vao::VertexArrayObject,
srgb_support: bool, srgb_support: bool,
/// The filter used for subsequent textures. /// The filter used for subsequent textures.
texture_filter: TextureFilter, texture_filter: TextureFilter,
post_process: Option<PostProcess>, post_process: Option<PostProcess>,
vertex_buffer: glow::Buffer, vbo: glow::Buffer,
element_array_buffer: glow::Buffer, element_array_buffer: glow::Buffer,
textures: HashMap<egui::TextureId, glow::Texture>, textures: HashMap<egui::TextureId, glow::Texture>,
@ -95,11 +96,10 @@ impl Painter {
pp_fb_extent: Option<[i32; 2]>, pp_fb_extent: Option<[i32; 2]>,
shader_prefix: &str, shader_prefix: &str,
) -> Result<Painter, String> { ) -> Result<Painter, String> {
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 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 shader_version = ShaderVersion::get(&gl);
let is_webgl_1 = shader_version == ShaderVersion::Es100; let is_webgl_1 = shader_version == ShaderVersion::Es100;
let header = shader_version.version(); let header = shader_version.version();
@ -117,7 +117,6 @@ impl Painter {
Some(PostProcess::new( Some(PostProcess::new(
gl.clone(), gl.clone(),
shader_prefix, shader_prefix,
support_vao,
is_webgl_1, is_webgl_1,
width, width,
height, height,
@ -168,48 +167,45 @@ impl Painter {
gl.delete_shader(frag); gl.delete_shader(frag);
let u_screen_size = gl.get_uniform_location(program, "u_screen_size").unwrap(); 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 u_sampler = gl.get_uniform_location(program, "u_sampler").unwrap();
let vertex_buffer = gl.create_buffer()?;
let element_array_buffer = gl.create_buffer()?; let vbo = gl.create_buffer()?;
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
let a_pos_loc = gl.get_attrib_location(program, "a_pos").unwrap(); 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_tc_loc = gl.get_attrib_location(program, "a_tc").unwrap();
let a_srgba_loc = gl.get_attrib_location(program, "a_srgba").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::<Vertex>() as i32; let stride = std::mem::size_of::<Vertex>() as i32;
let position_buffer_info = vao_emulate::BufferInfo { let buffer_infos = vec![
vao::BufferInfo {
location: a_pos_loc, location: a_pos_loc,
vector_size: 2, vector_size: 2,
data_type: glow::FLOAT, data_type: glow::FLOAT,
normalized: false, normalized: false,
stride, stride,
offset: offset_of!(Vertex, pos) as i32, offset: offset_of!(Vertex, pos) as i32,
}; },
let tex_coord_buffer_info = vao_emulate::BufferInfo { vao::BufferInfo {
location: a_tc_loc, location: a_tc_loc,
vector_size: 2, vector_size: 2,
data_type: glow::FLOAT, data_type: glow::FLOAT,
normalized: false, normalized: false,
stride, stride,
offset: offset_of!(Vertex, uv) as i32, offset: offset_of!(Vertex, uv) as i32,
}; },
let color_buffer_info = vao_emulate::BufferInfo { vao::BufferInfo {
location: a_srgba_loc, location: a_srgba_loc,
vector_size: 4, vector_size: 4,
data_type: glow::UNSIGNED_BYTE, data_type: glow::UNSIGNED_BYTE,
normalized: false, normalized: false,
stride, stride,
offset: offset_of!(Vertex, color) as i32, 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); let vao = crate::vao::VertexArrayObject::new(&gl, vbo, buffer_infos);
vertex_array.add_new_attribute(&gl, color_buffer_info);
check_for_gl_error(&gl, "after Painter::new"); let element_array_buffer = gl.create_buffer()?;
check_for_gl_error!(&gl, "after Painter::new");
Ok(Painter { Ok(Painter {
gl, gl,
@ -219,11 +215,11 @@ impl Painter {
u_sampler, u_sampler,
is_webgl_1, is_webgl_1,
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300), is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
vertex_array, vao,
srgb_support, srgb_support,
texture_filter: Default::default(), texture_filter: Default::default(),
post_process, post_process,
vertex_buffer, vbo,
element_array_buffer, element_array_buffer,
textures: Default::default(), textures: Default::default(),
#[cfg(feature = "epi")] #[cfg(feature = "epi")]
@ -251,9 +247,13 @@ impl Painter {
self.gl.enable(glow::SCISSOR_TEST); self.gl.enable(glow::SCISSOR_TEST);
// egui outputs mesh in both winding orders // egui outputs mesh in both winding orders
self.gl.disable(glow::CULL_FACE); 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.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( self.gl.blend_func_separate(
// egui outputs colors with premultiplied alpha: // egui outputs colors with premultiplied alpha:
glow::ONE, glow::ONE,
@ -264,6 +264,11 @@ impl Painter {
glow::ONE, 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 width_in_points = width_in_pixels as f32 / pixels_per_point;
let height_in_points = height_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); .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.uniform_1_i32(Some(&self.u_sampler), 0);
self.gl.active_texture(glow::TEXTURE0); self.gl.active_texture(glow::TEXTURE0);
self.vertex_array.bind_vertex_array(&self.gl);
self.vao.bind(&self.gl);
self.gl self.gl
.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer)); .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) (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: // Restore state:
unsafe { unsafe {
@ -381,8 +396,9 @@ impl Painter {
} }
} }
} }
unsafe { unsafe {
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_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
if let Some(ref post_process) = self.post_process { if let Some(ref post_process) = self.post_process {
@ -391,7 +407,7 @@ impl Painter {
self.gl.disable(glow::SCISSOR_TEST); 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()); debug_assert!(mesh.is_valid());
if let Some(texture) = self.get_texture(mesh.texture_id) { if let Some(texture) = self.get_texture(mesh.texture_id) {
unsafe { unsafe {
self.gl self.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
self.gl.buffer_data_u8_slice( self.gl.buffer_data_u8_slice(
glow::ARRAY_BUFFER, glow::ARRAY_BUFFER,
bytemuck::cast_slice(&mesh.vertices), bytemuck::cast_slice(&mesh.vertices),
@ -427,6 +442,8 @@ impl Painter {
0, 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]) { fn upload_texture_srgb(&mut self, pos: Option<[usize; 2]>, [w, h]: [usize; 2], data: &[u8]) {
assert_eq!(data.len(), w * h * 4); 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 { unsafe {
self.gl.tex_parameter_i32( self.gl.tex_parameter_i32(
glow::TEXTURE_2D, glow::TEXTURE_2D,
@ -508,7 +538,7 @@ impl Painter {
glow::TEXTURE_WRAP_T, glow::TEXTURE_WRAP_T,
glow::CLAMP_TO_EDGE as i32, 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 (internal_format, src_format) = if self.is_webgl_1 {
let format = if self.srgb_support { let format = if self.srgb_support {
@ -536,7 +566,7 @@ impl Painter {
glow::UNSIGNED_BYTE, glow::UNSIGNED_BYTE,
glow::PixelUnpackData::Slice(data), 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 { } else {
let border = 0; let border = 0;
self.gl.tex_image_2d( self.gl.tex_image_2d(
@ -550,7 +580,7 @@ impl Painter {
glow::UNSIGNED_BYTE, glow::UNSIGNED_BYTE,
Some(data), 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() { for tex in self.textures.values() {
self.gl.delete_texture(*tex); 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); self.gl.delete_buffer(self.element_array_buffer);
for t in &self.textures_to_destroy { for t in &self.textures_to_destroy {
self.gl.delete_texture(*t); self.gl.delete_texture(*t);

View file

@ -1,7 +1,8 @@
#![allow(unsafe_code)] #![allow(unsafe_code)]
use crate::misc_util::{check_for_gl_error, compile_shader, link_program}; use crate::check_for_gl_error;
use crate::vao_emulate::BufferInfo; use crate::misc_util::{compile_shader, link_program};
use glow::HasContext; use crate::vao::BufferInfo;
use glow::HasContext as _;
/// Uses a framebuffer to render everything in linear color space and convert it back to `sRGB` /// Uses a framebuffer to render everything in linear color space and convert it back to `sRGB`
/// in a separate "post processing" step /// in a separate "post processing" step
@ -9,7 +10,7 @@ pub(crate) struct PostProcess {
gl: std::rc::Rc<glow::Context>, gl: std::rc::Rc<glow::Context>,
pos_buffer: glow::Buffer, pos_buffer: glow::Buffer,
index_buffer: glow::Buffer, index_buffer: glow::Buffer,
vertex_array: crate::misc_util::VAO, vao: crate::vao::VertexArrayObject,
is_webgl_1: bool, is_webgl_1: bool,
texture: glow::Texture, texture: glow::Texture,
texture_size: (i32, i32), texture_size: (i32, i32),
@ -21,7 +22,6 @@ impl PostProcess {
pub(crate) unsafe fn new( pub(crate) unsafe fn new(
gl: std::rc::Rc<glow::Context>, gl: std::rc::Rc<glow::Context>,
shader_prefix: &str, shader_prefix: &str,
need_to_emulate_vao: bool,
is_webgl_1: bool, is_webgl_1: bool,
width: i32, width: i32,
height: i32, height: i32,
@ -77,7 +77,7 @@ impl PostProcess {
glow::UNSIGNED_BYTE, glow::UNSIGNED_BYTE,
None, None,
); );
check_for_gl_error(&gl, "post process texture initialization"); check_for_gl_error!(&gl, "post process texture initialization");
gl.framebuffer_texture_2d( gl.framebuffer_texture_2d(
glow::FRAMEBUFFER, glow::FRAMEBUFFER,
@ -124,35 +124,31 @@ impl PostProcess {
let a_pos_loc = gl let a_pos_loc = gl
.get_attrib_location(program, "a_pos") .get_attrib_location(program, "a_pos")
.ok_or_else(|| "failed to get location of a_pos".to_string())?; .ok_or_else(|| "failed to get location of a_pos".to_string())?;
let mut vertex_array = if need_to_emulate_vao { let vao = crate::vao::VertexArrayObject::new(
crate::misc_util::VAO::emulated() &gl,
} else { pos_buffer,
crate::misc_util::VAO::native(&gl) vec![BufferInfo {
};
vertex_array.bind_vertex_array(&gl);
vertex_array.bind_buffer(&gl, &pos_buffer);
let buffer_info_a_pos = BufferInfo {
location: a_pos_loc, location: a_pos_loc,
vector_size: 2, vector_size: 2,
data_type: glow::FLOAT, data_type: glow::FLOAT,
normalized: false, normalized: false,
stride: 0, stride: 0,
offset: 0, offset: 0,
}; }],
vertex_array.add_new_attribute(&gl, buffer_info_a_pos); );
let index_buffer = gl.create_buffer()?; let index_buffer = gl.create_buffer()?;
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_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.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, &indices, glow::STATIC_DRAW);
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None); 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 { Ok(PostProcess {
gl, gl,
pos_buffer, pos_buffer,
index_buffer, index_buffer,
vertex_array, vao,
is_webgl_1, is_webgl_1,
texture, texture,
texture_size: (width, height), texture_size: (width, height),
@ -190,6 +186,8 @@ impl PostProcess {
self.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo)); self.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo));
self.gl.clear_color(0.0, 0.0, 0.0, 0.0); self.gl.clear_color(0.0, 0.0, 0.0, 0.0);
self.gl.clear(glow::COLOR_BUFFER_BIT); self.gl.clear(glow::COLOR_BUFFER_BIT);
check_for_gl_error!(&self.gl, "PostProcess::begin");
} }
pub(crate) unsafe fn bind(&self) { pub(crate) unsafe fn bind(&self) {
@ -209,16 +207,18 @@ impl PostProcess {
.get_uniform_location(self.program, "u_sampler") .get_uniform_location(self.program, "u_sampler")
.unwrap(); .unwrap();
self.gl.uniform_1_i32(Some(&u_sampler_loc), 0); 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 self.gl
.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer)); .bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer));
self.gl self.gl
.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0); .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_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
self.gl.bind_texture(glow::TEXTURE_2D, None); self.gl.bind_texture(glow::TEXTURE_2D, None);
self.gl.use_program(None); self.gl.use_program(None);
check_for_gl_error!(&self.gl, "PostProcess::end");
} }
pub(crate) unsafe fn destroy(&self) { pub(crate) unsafe fn destroy(&self) {

View file

@ -1,6 +1,5 @@
#![allow(unsafe_code)] #![allow(unsafe_code)]
use glow::HasContext;
use std::convert::TryInto; use std::convert::TryInto;
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@ -14,6 +13,7 @@ pub(crate) enum ShaderVersion {
impl ShaderVersion { impl ShaderVersion {
pub(crate) fn get(gl: &glow::Context) -> Self { pub(crate) fn get(gl: &glow::Context) -> Self {
use glow::HasContext as _;
let shading_lang_string = let shading_lang_string =
unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) }; unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) };
let shader_version = Self::parse(&shading_lang_string); let shader_version = Self::parse(&shading_lang_string);

154
egui_glow/src/vao.rs Normal file
View file

@ -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<crate::glow::VertexArray>,
vbo: glow::Buffer,
buffer_infos: Vec<BufferInfo>,
}
impl VertexArrayObject {
#[allow(clippy::needless_pass_by_value)] // false positive
pub(crate) unsafe fn new(
gl: &glow::Context,
vbo: glow::Buffer,
buffer_infos: Vec<BufferInfo>,
) -> 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
}
}
}

View file

@ -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<glow::Buffer>,
buffer_infos: Vec<BufferInfo>,
}
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);
}
}
}

View file

@ -3,8 +3,9 @@ All notable changes to the `egui_web` integration will be noted in this file.
## Unreleased ## 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)). * 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 ## 0.17.0 - 2022-02-22

View file

@ -43,7 +43,6 @@ screen_reader = ["tts"]
[dependencies] [dependencies]
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
"convert_bytemuck", "convert_bytemuck",
"single_threaded",
"tracing", "tracing",
] } ] }
egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false } egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false }

View file

@ -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 { fn web_location() -> epi::Location {
@ -72,7 +66,7 @@ fn web_location() -> epi::Location {
let query_map = parse_query_map(&query) let query_map = parse_query_map(&query)
.iter() .iter()
.map(|(k, v)| (k.to_string(), v.to_string())) .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect(); .collect();
epi::Location { epi::Location {
@ -145,13 +139,11 @@ pub struct AppRunner {
} }
impl AppRunner { impl AppRunner {
pub fn new(canvas_id: &str, app: Box<dyn epi::App>) -> Result<Self, JsValue> { pub fn new(canvas_id: &str, app_creator: epi::AppCreator) -> Result<Self, JsValue> {
let painter = WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?; let painter = WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?;
let prefer_dark_mode = crate::prefer_dark_mode(); let prefer_dark_mode = crate::prefer_dark_mode();
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
let frame = epi::Frame::new(epi::backend::FrameData { let frame = epi::Frame::new(epi::backend::FrameData {
info: epi::IntegrationInfo { info: epi::IntegrationInfo {
name: "egui_web", name: "egui_web",
@ -163,10 +155,19 @@ impl AppRunner {
native_pixels_per_point: Some(native_pixels_per_point()), native_pixels_per_point: Some(native_pixels_per_point()),
}, },
output: Default::default(), output: Default::default(),
repaint_signal: needs_repaint.clone(),
}); });
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
let egui_ctx = egui::Context::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); load_memory(&egui_ctx);
if prefer_dark_mode == Some(true) { if prefer_dark_mode == Some(true) {
egui_ctx.set_visuals(egui::Visuals::dark()); egui_ctx.set_visuals(egui::Visuals::dark());
@ -176,6 +177,13 @@ impl AppRunner {
let storage = LocalStorage::default(); 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 { let mut runner = Self {
frame, frame,
egui_ctx, egui_ctx,
@ -193,11 +201,6 @@ impl AppRunner {
runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side()); 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) Ok(runner)
} }
@ -326,8 +329,8 @@ impl AppRunner {
/// Install event listeners to register different input events /// Install event listeners to register different input events
/// and start running the given app. /// and start running the given app.
pub fn start(canvas_id: &str, app: Box<dyn epi::App>) -> Result<AppRunnerRef, JsValue> { pub fn start(canvas_id: &str, app_creator: epi::AppCreator) -> Result<AppRunnerRef, JsValue> {
let mut runner = AppRunner::new(canvas_id, app)?; let mut runner = AppRunner::new(canvas_id, app_creator)?;
runner.warm_up()?; runner.warm_up()?;
start_runner(runner) start_runner(runner)
} }

View file

@ -50,7 +50,7 @@ impl WrappedGlowPainter {
pub fn clear(&mut self, clear_color: Rgba) { pub fn clear(&mut self, clear_color: Rgba) {
let canvas_dimension = [self.canvas.width(), self.canvas.height()]; 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( pub fn paint_primitives(

View file

@ -3,16 +3,8 @@
//! This library is an [`epi`] backend. //! This library is an [`epi`] backend.
//! //!
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead. //! 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: #![allow(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>`
#![cfg_attr(not(debug_assertions), deny(warnings))]
#![forbid(unsafe_code)]
#![warn(clippy::all, rustdoc::missing_crate_level_docs, rust_2018_idioms)]
pub mod backend; pub mod backend;
mod glow_wrapping; 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) !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. /// This function used to avoid displaying linear color with `sRGB` supported systems.
fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool { fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool {

View file

@ -29,6 +29,7 @@ impl Default for ScreenReader {
impl ScreenReader { impl ScreenReader {
#[cfg(not(feature = "screen_reader"))] #[cfg(not(feature = "screen_reader"))]
#[allow(clippy::unused_self)]
pub fn speak(&mut self, _text: &str) {} pub fn speak(&mut self, _text: &str) {}
#[cfg(feature = "screen_reader")] #[cfg(feature = "screen_reader")]

View file

@ -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 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("position", "absolute").ok()?;
canvas_style.set_property("top", &new_pos_percent).ok()?; canvas_style.set_property("top", &new_pos_percent).ok()?;
@ -217,8 +217,8 @@ pub fn move_text_cursor(cursor: Option<egui::Pos2>, canvas_id: &str) -> Option<(
let x = (x - canvas.offset_width() as f32 / 2.0) let x = (x - canvas.offset_width() as f32 / 2.0)
.min(canvas.client_width() as f32 - bounding_rect.width() as f32); .min(canvas.client_width() as f32 - bounding_rect.width() as f32);
style.set_property("position", "absolute").ok()?; style.set_property("position", "absolute").ok()?;
style.set_property("top", &(y.to_string() + "px")).ok()?; style.set_property("top", &format!("{}px", y)).ok()?;
style.set_property("left", &(x.to_string() + "px")).ok() style.set_property("left", &format!("{}px", x)).ok()
}) })
} else { } else {
style.set_property("position", "absolute").ok()?; style.set_property("position", "absolute").ok()?;

View file

@ -15,85 +15,6 @@
//! For that, use something else ([`glam`](https://docs.rs/glam), [`nalgebra`](https://docs.rs/nalgebra), …) //! 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`. //! 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::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]

View file

@ -4,6 +4,9 @@ All notable changes to the epaint crate will be documented in this file.
## Unreleased ## Unreleased
* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)). * 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 ## 0.17.0 - 2022-02-22

View file

@ -27,7 +27,7 @@ all-features = true
[features] [features]
default = ["default_fonts", "multi_threaded"] default = ["default_fonts"]
# implement bytemuck on most types. # implement bytemuck on most types.
convert_bytemuck = ["bytemuck", "emath/bytemuck"] convert_bytemuck = ["bytemuck", "emath/bytemuck"]
@ -47,25 +47,26 @@ mint = ["emath/mint"]
# implement serde on most types. # implement serde on most types.
serialize = ["serde", "ahash/serde", "emath/serde"] 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] [dependencies]
emath = { version = "0.17.0", path = "../emath" } emath = { version = "0.17.0", path = "../emath" }
ab_glyph = "0.2.11" ab_glyph = "0.2.11"
ahash = { version = "0.7", default-features = false, features = ["std"] } 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"] } bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
cint = { version = "^0.2.2", optional = true } cint = { version = "^0.2.2", optional = true }
nohash-hasher = "0.2" 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"] } 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] [dev-dependencies]
criterion = { version = "0.3", default-features = false } criterion = { version = "0.3", default-features = false }

View file

@ -5,85 +5,6 @@
//! Create some [`Shape`]:s and pass them to [`tessellate_shapes`] to generate [`Mesh`]:es //! 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). //! 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::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]
@ -110,7 +31,10 @@ pub use {
image::{AlphaImage, ColorImage, ImageData, ImageDelta}, image::{AlphaImage, ColorImage, ImageData, ImageDelta},
mesh::{Mesh, Mesh16, Vertex}, mesh::{Mesh, Mesh16, Vertex},
shadow::Shadow, shadow::Shadow,
shape::{CircleShape, PaintCallback, PathShape, RectShape, Rounding, Shape, TextShape}, shape::{
CircleShape, PaintCallback, PaintCallbackInfo, PathShape, RectShape, Rounding, Shape,
TextShape,
},
stats::PaintStats, stats::PaintStats,
stroke::Stroke, stroke::Stroke,
tessellator::{tessellate_shapes, TessellationOptions, Tessellator}, tessellator::{tessellate_shapes, TessellationOptions, Tessellator},

View file

@ -91,17 +91,29 @@ impl Mesh {
if self.is_empty() { if self.is_empty() {
*self = other; *self = other;
} else { } 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!( assert_eq!(
self.texture_id, other.texture_id, self.texture_id, other.texture_id,
"Can't merge Mesh using different textures" "Can't merge Mesh using different textures"
); );
} else {
self.texture_id = other.texture_id;
}
let index_offset = self.vertices.len() as u32; let index_offset = self.vertices.len() as u32;
self.indices self.indices
.extend(other.indices.iter().map(|index| index + index_offset)); .extend(other.indices.iter().map(|index| index + index_offset));
self.vertices.extend(other.vertices.iter()); self.vertices.extend(other.vertices.iter());
} }
}
#[inline(always)] #[inline(always)]
pub fn colored_vertex(&mut self, pos: Pos2, color: Color32) { pub fn colored_vertex(&mut self, pos: Pos2, color: Color32) {
@ -242,6 +254,15 @@ impl Mesh {
v.pos += delta; 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);
}
}
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View file

@ -1,16 +1,13 @@
//! Helper module that wraps some Mutex types with different implementations. //! 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))] #[cfg(not(debug_assertions))]
mod mutex_impl { 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)] #[derive(Default)]
pub struct Mutex<T>(parking_lot::Mutex<T>); pub struct Mutex<T>(parking_lot::Mutex<T>);
@ -30,10 +27,12 @@ mod mutex_impl {
} }
} }
#[cfg(feature = "multi_threaded")] #[cfg(not(target_arch = "wasm32"))]
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
mod mutex_impl { 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)] #[derive(Default)]
pub struct Mutex<T>(parking_lot::Mutex<T>); pub struct Mutex<T>(parking_lot::Mutex<T>);
@ -111,7 +110,7 @@ mod mutex_impl {
} }
} }
#[cfg(feature = "multi_threaded")] #[cfg(not(target_arch = "wasm32"))]
mod rw_lock_impl { mod rw_lock_impl {
/// The lock you get from [`RwLock::read`]. /// The lock you get from [`RwLock::read`].
pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard; pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard;
@ -119,7 +118,9 @@ mod rw_lock_impl {
/// The lock you get from [`RwLock::write`]. /// The lock you get from [`RwLock::write`].
pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard; 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)] #[derive(Default)]
pub struct RwLock<T>(parking_lot::RwLock<T>); pub struct RwLock<T>(parking_lot::RwLock<T>);
@ -141,18 +142,20 @@ mod rw_lock_impl {
} }
} }
#[cfg(feature = "multi_threaded")] #[cfg(not(target_arch = "wasm32"))]
mod arc_impl { mod arc_impl {
pub use std::sync::Arc; pub use std::sync::Arc;
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#[cfg(not(feature = "multi_threaded"))] #[cfg(target_arch = "wasm32")]
mod mutex_impl { mod mutex_impl {
// `atomic_refcell` will panic if multiple threads try to access the same value // `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)] #[derive(Default)]
pub struct Mutex<T>(atomic_refcell::AtomicRefCell<T>); pub struct Mutex<T>(atomic_refcell::AtomicRefCell<T>);
@ -173,7 +176,7 @@ mod mutex_impl {
} }
} }
#[cfg(not(feature = "multi_threaded"))] #[cfg(target_arch = "wasm32")]
mod rw_lock_impl { mod rw_lock_impl {
// `atomic_refcell` will panic if multiple threads try to access the same value // `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`]. /// The lock you get from [`RwLock::write`].
pub use atomic_refcell::AtomicRefMut as RwLockWriteGuard; 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)] #[derive(Default)]
pub struct RwLock<T>(atomic_refcell::AtomicRefCell<T>); pub struct RwLock<T>(atomic_refcell::AtomicRefCell<T>);
@ -206,7 +211,7 @@ mod rw_lock_impl {
} }
} }
#[cfg(not(feature = "multi_threaded"))] #[cfg(target_arch = "wasm32")]
mod arc_impl { 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::rc::Rc as Arc; // TODO(emilk): optimize single threaded code by using `Rc` instead of `Arc`.
pub use std::sync::Arc; pub use std::sync::Arc;

View file

@ -63,11 +63,15 @@ impl Shadow {
use crate::tessellator::*; use crate::tessellator::*;
let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color); let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color);
let mut tessellator = Tessellator::from_options(TessellationOptions { let pixels_per_point = 1.0; // doesn't matter here
aa_size: extrusion, let mut tessellator = Tessellator::new(
anti_alias: true, pixels_per_point,
TessellationOptions {
feathering: true,
feathering_size_in_pixels: extrusion * pixels_per_point,
..Default::default() ..Default::default()
}); },
);
let mut mesh = Mesh::default(); let mut mesh = Mesh::default();
tessellator.tessellate_rect(&rect, &mut mesh); tessellator.tessellate_rect(&rect, &mut mesh);
mesh mesh

View file

@ -2,7 +2,7 @@
use crate::{ use crate::{
text::{FontId, Fonts, Galley}, text::{FontId, Fonts, Galley},
Color32, Mesh, Stroke, Color32, Mesh, Stroke, TextureId,
}; };
use emath::*; use emath::*;
@ -184,6 +184,12 @@ impl Shape {
Self::Mesh(mesh) 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) /// The visual bounding rectangle (includes stroke widths)
pub fn visual_bounding_rect(&self) -> Rect { pub fn visual_bounding_rect(&self) -> Rect {
match self { 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. /// If you want to paint some 3D shapes inside an egui region, you can use this.
/// ///
/// This is advanced usage, and is backend specific. /// This is advanced usage, and is backend specific.
@ -644,21 +696,22 @@ pub struct PaintCallback {
/// Where to paint. /// Where to paint.
pub rect: Rect, 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. /// The argument is the render context, and what it contains depends on the backend.
/// In `eframe` it will be `egui_glow::Painter`. /// 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 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. /// such as the bound shader program and vertex array.
pub callback: std::sync::Arc<dyn Fn(&dyn std::any::Any) + Send + Sync>, pub callback: std::sync::Arc<dyn Fn(&PaintCallbackInfo, &dyn std::any::Any) + Send + Sync>,
} }
impl PaintCallback { impl PaintCallback {
#[inline] #[inline]
pub fn call(&self, render_ctx: &dyn std::any::Any) { pub fn call(&self, info: &PaintCallbackInfo, render_ctx: &dyn std::any::Any) {
(self.callback)(render_ctx); (self.callback)(info, render_ctx);
} }
} }

View file

@ -126,17 +126,17 @@ impl AllocInfo {
pub fn format(&self, what: &str) -> String { pub fn format(&self, what: &str) -> String {
if self.num_allocs() == 0 { if self.num_allocs() == 0 {
format!("{:6} {:14}", 0, what) format!("{:6} {:16}", 0, what)
} else if self.num_allocs() == 1 { } else if self.num_allocs() == 1 {
format!( format!(
"{:6} {:14} {} 1 allocation", "{:6} {:16} {} 1 allocation",
self.num_elements, self.num_elements,
what, what,
self.megabytes() self.megabytes()
) )
} else if self.element_size != ElementSize::Heterogenous { } else if self.element_size != ElementSize::Heterogenous {
format!( format!(
"{:6} {:14} {} {:3} allocations", "{:6} {:16} {} {:3} allocations",
self.num_elements(), self.num_elements(),
what, what,
self.megabytes(), self.megabytes(),
@ -144,7 +144,7 @@ impl AllocInfo {
) )
} else { } else {
format!( format!(
"{:6} {:14} {} {:3} allocations", "{:6} {:16} {} {:3} allocations",
"", "",
what, what,
self.megabytes(), self.megabytes(),

View file

@ -163,23 +163,17 @@ impl Path {
} }
/// Open-ended. /// Open-ended.
pub fn stroke_open(&self, stroke: Stroke, options: &TessellationOptions, out: &mut Mesh) { pub fn stroke_open(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) {
stroke_path(&self.0, PathType::Open, stroke, options, out); stroke_path(feathering, &self.0, PathType::Open, stroke, out);
} }
/// A closed path (returning to the first point). /// A closed path (returning to the first point).
pub fn stroke_closed(&self, stroke: Stroke, options: &TessellationOptions, out: &mut Mesh) { pub fn stroke_closed(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) {
stroke_path(&self.0, PathType::Closed, stroke, options, out); stroke_path(feathering, &self.0, PathType::Closed, stroke, out);
} }
pub fn stroke( pub fn stroke(&self, feathering: f32, path_type: PathType, stroke: Stroke, out: &mut Mesh) {
&self, stroke_path(feathering, &self.0, path_type, stroke, out);
path_type: PathType,
stroke: Stroke,
options: &TessellationOptions,
out: &mut Mesh,
) {
stroke_path(&self.0, path_type, stroke, options, out);
} }
/// The path is taken to be closed (i.e. returning to the start again). /// 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. /// Calling this may reverse the vertices in the path if they are wrong winding order.
/// ///
/// The preferred winding order is clockwise. /// The preferred winding order is clockwise.
pub fn fill(&mut self, color: Color32, options: &TessellationOptions, out: &mut Mesh) { pub fn fill(&mut self, feathering: f32, color: Color32, out: &mut Mesh) {
fill_closed_path(&mut self.0, color, options, out); 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", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(default))]
pub struct TessellationOptions { pub struct TessellationOptions {
/// Size of a point in pixels (DPI scaling), e.g. 2.0. Used to snap text to pixel boundaries. /// Use "feathering" to smooth out the edges of shapes as a form of anti-aliasing.
pub pixels_per_point: f32, ///
/// Feathering works by making each edge into a thin gradient into transparency.
/// The size of a pixel (in points), used for anti-aliasing (smoothing of edges). /// The size of this edge is controlled by [`Self::feathering_size_in_pixels`].
/// This is normally the inverse of [`Self::pixels_per_point`], ///
/// but you can make it larger if you want more blurry edges. /// This makes shapes appear smoother, but requires more triangles and is therefore slower.
pub aa_size: f32, ///
/// Anti-aliasing makes shapes appear smoother, but requires more triangles and is therefore slower.
/// This setting does not affect text. /// This setting does not affect text.
///
/// Default: `true`. /// 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. /// If `true` (default) cull certain primitives before tessellating them.
/// This likely makes /// This likely makes
@ -326,9 +325,8 @@ pub struct TessellationOptions {
impl Default for TessellationOptions { impl Default for TessellationOptions {
fn default() -> Self { fn default() -> Self {
Self { Self {
pixels_per_point: 1.0, feathering: true,
aa_size: 1.0, feathering_size_in_pixels: 1.0,
anti_alias: true,
coarse_tessellation_culling: true, coarse_tessellation_culling: true,
round_text_to_pixels: true, round_text_to_pixels: true,
debug_paint_text_rects: false, 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 { fn cw_signed_area(path: &[PathPoint]) -> f64 {
if let Some(last) = path.last() { if let Some(last) = path.last() {
let mut previous = last.pos; 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. /// Calling this may reverse the vertices in the path if they are wrong winding order.
/// ///
/// The preferred winding order is clockwise. /// The preferred winding order is clockwise.
fn fill_closed_path( fn fill_closed_path(feathering: f32, path: &mut [PathPoint], color: Color32, out: &mut Mesh) {
path: &mut [PathPoint],
color: Color32,
options: &TessellationOptions,
out: &mut Mesh,
) {
if color == Color32::TRANSPARENT { if color == Color32::TRANSPARENT {
return; return;
} }
let n = path.len() as u32; let n = path.len() as u32;
if options.anti_alias { if feathering > 0.0 {
if cw_signed_area(path) < 0.0 { if cw_signed_area(path) < 0.0 {
// Wrong winding order - fix: // Wrong winding order - fix:
path.reverse(); path.reverse();
@ -415,7 +387,7 @@ fn fill_closed_path(
let mut i0 = n - 1; let mut i0 = n - 1;
for i1 in 0..n { for i1 in 0..n {
let p1 = &path[i1 as usize]; 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);
out.colored_vertex(p1.pos + dm, color_outer); out.colored_vertex(p1.pos + dm, color_outer);
out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0); 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. /// Tessellate the given path as a stroke with thickness.
fn stroke_path( fn stroke_path(
feathering: f32,
path: &[PathPoint], path: &[PathPoint],
path_type: PathType, path_type: PathType,
stroke: Stroke, stroke: Stroke,
options: &TessellationOptions,
out: &mut Mesh, out: &mut Mesh,
) { ) {
let n = path.len() as u32; let n = path.len() as u32;
@ -452,21 +424,21 @@ fn stroke_path(
let idx = out.vertices.len() as u32; let idx = out.vertices.len() as u32;
if options.anti_alias { if feathering > 0.0 {
let color_inner = stroke.color; let color_inner = stroke.color;
let color_outer = Color32::TRANSPARENT; let color_outer = Color32::TRANSPARENT;
let thin_line = stroke.width <= options.aa_size; let thin_line = stroke.width <= feathering;
if thin_line { if thin_line {
/* /*
We paint the line using three edges: outer, inner, outer. We paint the line using three edges: outer, inner, outer.
. o i o outer, inner, outer . o i o outer, inner, outer
. |---| aa_size (pixel width) . |---| feathering (pixel width)
*/ */
// Fade out as it gets thinner: // 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 { if color_inner == Color32::TRANSPARENT {
return; return;
} }
@ -480,9 +452,9 @@ fn stroke_path(
let p1 = &path[i1 as usize]; let p1 = &path[i1 as usize];
let p = p1.pos; let p = p1.pos;
let n = p1.normal; 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, color_inner);
out.colored_vertex(p - n * options.aa_size, color_outer); out.colored_vertex(p - n * feathering, color_outer);
if connect_with_previous { if connect_with_previous {
out.add_triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0); 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 We paint the line using four edges: outer, inner, inner, outer
. o i p i o outer, inner, point, inner, outer . o i p i o outer, inner, point, inner, outer
. |---| aa_size (pixel width) . |---| feathering (pixel width)
. |--------------| width . |--------------| width
. |---------| outer_rad . |---------| outer_rad
. |-----| inner_rad . |-----| inner_rad
*/ */
let inner_rad = 0.5 * (stroke.width - options.aa_size); let inner_rad = 0.5 * (stroke.width - feathering);
let outer_rad = 0.5 * (stroke.width + options.aa_size); let outer_rad = 0.5 * (stroke.width + feathering);
match path_type { match path_type {
PathType::Closed => { PathType::Closed => {
@ -542,7 +514,7 @@ fn stroke_path(
// | aa | | aa | // | aa | | aa |
// _________________ ___ // _________________ ___
// | \ added / | aa_size // | \ added / | feathering
// | \ ___p___ / | ___ // | \ ___p___ / | ___
// | | | | // | | | |
// | | opa | | // | | opa | |
@ -558,7 +530,7 @@ fn stroke_path(
let end = &path[0]; let end = &path[0];
let p = end.pos; let p = end.pos;
let n = end.normal; 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 * outer_rad + back_extrude, color_outer);
out.colored_vertex(p + n * inner_rad, color_inner); out.colored_vertex(p + n * inner_rad, color_inner);
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 end = &path[i1 as usize];
let p = end.pos; let p = end.pos;
let n = end.normal; 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 * outer_rad + back_extrude, color_outer);
out.colored_vertex(p + n * inner_rad, color_inner); out.colored_vertex(p + n * inner_rad, color_inner);
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 { if thin_line {
// Fade out thin lines rather than making them thinner // Fade out thin lines rather than making them thinner
let radius = options.aa_size / 2.0; let radius = feathering / 2.0;
let color = mul_color(stroke.color, stroke.width / options.aa_size); let color = mul_color(stroke.color, stroke.width / feathering);
if color == Color32::TRANSPARENT { if color == Color32::TRANSPARENT {
return; return;
} }
@ -677,7 +649,10 @@ fn mul_color(color: Color32, factor: f32) -> Color32 {
/// ///
/// Se also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`]. /// Se also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`].
pub struct Tessellator { pub struct Tessellator {
pixels_per_point: f32,
options: TessellationOptions, options: TessellationOptions,
/// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled
feathering: f32,
/// Only used for culling /// Only used for culling
clip_rect: Rect, clip_rect: Rect,
scratchpad_points: Vec<Pos2>, scratchpad_points: Vec<Pos2>,
@ -686,24 +661,43 @@ pub struct Tessellator {
impl Tessellator { impl Tessellator {
/// Create a new [`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 { Self {
pixels_per_point,
options, options,
feathering,
clip_rect: Rect::EVERYTHING, clip_rect: Rect::EVERYTHING,
scratchpad_points: Default::default(), scratchpad_points: Default::default(),
scratchpad_path: 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`]. /// Tessellate a single [`Shape`] into a [`Mesh`].
/// ///
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles). /// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles).
/// * `shape`: the shape to tessellate. /// * `shape`: the shape to tessellate.
/// * `out`: triangles are appended to this. /// * `out`: triangles are appended to this.
pub fn tessellate_shape(&mut self, tex_size: [usize; 2], shape: Shape, out: &mut Mesh) { 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 { match shape {
Shape::Noop => {} Shape::Noop => {}
Shape::Vec(vec) => { Shape::Vec(vec) => {
@ -711,26 +705,8 @@ impl Tessellator {
self.tessellate_shape(tex_size, shape, out); self.tessellate_shape(tex_size, shape, out);
} }
} }
Shape::Circle(CircleShape { Shape::Circle(circle) => {
center, self.tessellate_circle(circle, out);
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::Mesh(mesh) => { Shape::Mesh(mesh) => {
if !mesh.is_valid() { if !mesh.is_valid() {
@ -738,44 +714,29 @@ impl Tessellator {
return; 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; return;
} }
out.append(mesh); out.append(mesh);
} }
Shape::LineSegment { points, stroke } => { Shape::LineSegment { points, stroke } => self.tessellate_line(points, stroke, out),
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::Path(path_shape) => { Shape::Path(path_shape) => {
self.tessellate_path(path_shape, out); self.tessellate_path(&path_shape, out);
} }
Shape::Rect(rect_shape) => { Shape::Rect(rect_shape) => {
self.tessellate_rect(&rect_shape, out); self.tessellate_rect(&rect_shape, out);
} }
Shape::Text(text_shape) => { 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()); let rect = text_shape.galley.rect.translate(text_shape.pos.to_vec2());
self.tessellate_rect( self.tessellate_rect(
&RectShape::stroke(rect.expand(0.5), 2.0, (0.5, Color32::GREEN)), &RectShape::stroke(rect.expand(0.5), 2.0, (0.5, Color32::GREEN)),
out, out,
); );
} }
self.tessellate_text(tex_size, text_shape, out); self.tessellate_text(tex_size, &text_shape, out);
} }
Shape::QuadraticBezier(quadratic_shape) => { Shape::QuadraticBezier(quadratic_shape) => {
self.tessellate_quadratic_bezier(quadratic_shape, out); 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, &mut self,
quadratic_shape: QuadraticBezierShape, quadratic_shape: QuadraticBezierShape,
out: &mut Mesh, out: &mut Mesh,
@ -812,11 +1033,11 @@ impl Tessellator {
); );
} }
pub(crate) fn tessellate_cubic_bezier( /// Tessellate a single [`CubicBezierShape`] into a [`Mesh`].
&mut self, ///
cubic_shape: CubicBezierShape, /// * `cubic_shape`: the shape to tessellate.
out: &mut Mesh, /// * `out`: triangles are appended to this.
) { pub fn tessellate_cubic_bezier(&mut self, cubic_shape: CubicBezierShape, out: &mut Mesh) {
let options = &self.options; let options = &self.options;
let clip_rect = self.clip_rect; let clip_rect = self.clip_rect;
if options.coarse_tessellation_culling if options.coarse_tessellation_culling
@ -858,177 +1079,15 @@ impl Tessellator {
closed, closed,
"You asked to fill a path that is not closed. That makes no sense." "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 { let typ = if closed {
PathType::Closed PathType::Closed
} else { } else {
PathType::Open 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 self.scratchpad_path
.add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]); .stroke(self.feathering, typ, stroke, out);
self.scratchpad_path
.stroke_open(underline, &self.options, out);
}
}
} }
} }
@ -1037,8 +1096,9 @@ impl Tessellator {
/// The given shapes will tessellated in the same order as they are given. /// The given shapes will tessellated in the same order as they are given.
/// They will be batched together by clip rectangle. /// 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 /// * `options`: tessellation quality
/// * `shapes`: what to tessellate
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles) /// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles)
/// ///
/// The implementation uses a [`Tessellator`]. /// The implementation uses a [`Tessellator`].
@ -1046,11 +1106,12 @@ impl Tessellator {
/// ## Returns /// ## Returns
/// A list of clip rectangles with matching [`Mesh`]. /// A list of clip rectangles with matching [`Mesh`].
pub fn tessellate_shapes( pub fn tessellate_shapes(
shapes: Vec<ClippedShape>, pixels_per_point: f32,
options: TessellationOptions, options: TessellationOptions,
shapes: Vec<ClippedShape>,
tex_size: [usize; 2], tex_size: [usize; 2],
) -> Vec<ClippedPrimitive> { ) -> Vec<ClippedPrimitive> {
let mut tessellator = Tessellator::from_options(options); let mut tessellator = Tessellator::new(pixels_per_point, options);
let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default(); let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();

View file

@ -599,10 +599,8 @@ fn add_hline(point_scale: PointScale, [start, stop]: [Pos2; 2], stroke: Stroke,
if antialiased { if antialiased {
let mut path = crate::tessellator::Path::default(); // TODO: reuse this to avoid re-allocations. let mut path = crate::tessellator::Path::default(); // TODO: reuse this to avoid re-allocations.
path.add_line_segment([start, stop]); path.add_line_segment([start, stop]);
let options = crate::tessellator::TessellationOptions::from_pixels_per_point( let feathering = 1.0 / point_scale.pixels_per_point();
point_scale.pixels_per_point(), path.stroke_open(feathering, stroke, mesh);
);
path.stroke_open(stroke, &options, mesh);
} else { } else {
// Thin lines often lost, so this is a bad idea // Thin lines often lost, so this is a bad idea

View file

@ -28,9 +28,7 @@ persistence = ["ron", "serde", "egui/persistence"]
[dependencies] [dependencies]
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [ egui = { version = "0.17.0", path = "../egui", default-features = false }
"single_threaded",
] }
glow = "0.11" glow = "0.11"
tracing = "0.1" tracing = "0.1"

View file

@ -6,87 +6,6 @@
//! //!
//! Start by looking at the [`App`] trait, and implement [`App::update`]. //! 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. #![warn(missing_docs)] // Let's keep `epi` well-documented.
/// File storage which can be used by native backends. /// 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}; 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<dyn FnOnce(&CreationContext<'_>) -> Box<dyn App>>;
/// 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<glow::Context>,
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
/// 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, /// 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. /// 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`, /// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
/// or call [`Frame::request_repaint`] at any time (e.g. from another thread).
fn update(&mut self, ctx: &egui::Context, frame: &Frame); 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<glow::Context>,
) {
}
/// Called on shutdown, and perhaps at regular intervals. Allows you to save state. /// Called on shutdown, and perhaps at regular intervals. Allows you to save state.
/// ///
/// Only called when the "persistence" feature is enabled. /// 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: /// 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` /// * Linux: `/home/UserName/.local/share/APPNAME`
/// * macOS: `/Users/UserName/Library/Application Support/APPNAME` /// * macOS: `/Users/UserName/Library/Application Support/APPNAME`
/// * Windows: `C:\Users\UserName\AppData\Roaming\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) {} fn save(&mut self, _storage: &mut dyn Storage) {}
/// Called before an exit that can be aborted. /// Called before an exit that can be aborted.
@ -156,17 +80,14 @@ pub trait App {
true true
} }
/// Called once on shutdown (before or after [`Self::save`]). If you need to abort an exit use /// Called once on shutdown, after [`Self::save`].
/// [`Self::on_exit_event`] ///
fn on_exit(&mut self) {} /// If you need to abort an exit use [`Self::on_exit_event`].
fn on_exit(&mut self, _gl: &glow::Context) {}
// --------- // ---------
// Settings: // 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`] /// Time between automatic calls to [`Self::save`]
fn auto_save_interval(&self) -> std::time::Duration { fn auto_save_interval(&self) -> std::time::Duration {
std::time::Duration::from_secs(30) std::time::Duration::from_secs(30)
@ -174,13 +95,12 @@ pub trait App {
/// The size limit of the web app canvas. /// 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. /// A large canvas can lead to bad frame rates on some older browsers on some platforms
/// In particular, Firefox on Mac and Linux is really bad at handling large WebGL canvases: /// (see <https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0>).
/// <https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0> (unfixed since 2014).
fn max_size_points(&self) -> egui::Vec2 { 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`. /// 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 control the transparency with [`App::clear_color()`].
/// You should avoid having a [`egui::CentralPanel`], or make sure its frame is also transparent. /// You should avoid having a [`egui::CentralPanel`], or make sure its frame is also transparent.
pub transparent: bool, 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 { impl Default for NativeOptions {
@ -279,6 +225,10 @@ impl Default for NativeOptions {
max_window_size: None, max_window_size: None,
resizable: true, resizable: true,
transparent: false, transparent: false,
vsync: true,
multisampling: 0,
depth_buffer: 0,
stencil_buffer: 0,
} }
} }
} }
@ -359,13 +309,6 @@ impl Frame {
self.lock().output.drag_window = true; 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 /// for integrations only: call once per frame
pub fn take_app_output(&self) -> crate::backend::AppOutput { pub fn take_app_output(&self) -> crate::backend::AppOutput {
std::mem::take(&mut self.lock().output) std::mem::take(&mut self.lock().output)
@ -524,14 +467,6 @@ pub const APP_KEY: &str = "app";
pub mod backend { pub mod backend {
use super::*; 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. /// The data required by [`Frame`] each frame.
pub struct FrameData { pub struct FrameData {
/// Information about the integration. /// Information about the integration.
@ -539,9 +474,6 @@ pub mod backend {
/// Where the app can issue commands back to the integration. /// Where the app can issue commands back to the integration.
pub output: AppOutput, 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<dyn RepaintSignal>,
} }
/// Action that can be taken by the user app. /// Action that can be taken by the user app.

View file

@ -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 cargo doc --document-private-items --no-deps --all-features
(cd emath && cargo check --no-default-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)
(cd epaint && cargo check --no-default-features --features "multi_threaded") (cd epaint && cargo check --no-default-features --release)
(cd epaint && cargo check --no-default-features --features "single_threaded" --release) (cd egui && cargo check --no-default-features --features "serialize")
(cd epaint && cargo check --no-default-features --features "multi_threaded" --release)
(cd egui && cargo check --no-default-features --features "multi_threaded,serialize")
(cd eframe && cargo check --no-default-features) (cd eframe && cargo check --no-default-features)
(cd epi && cargo check --no-default-features) (cd epi && cargo check --no-default-features)
(cd egui_demo_lib && cargo check --no-default-features) (cd egui_demo_lib && cargo check --no-default-features)
(cd egui_extras && cargo check --no-default-features) (cd egui_extras && cargo check --no-default-features)
(cd egui_web && 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_glium && cargo check --no-default-features)
(cd egui_glow && cargo check --no-default-features) (cd egui_glow && cargo check --no-default-features)