Merge remote-tracking branch 'egui/master' into dynamic-grid
This commit is contained in:
commit
e5aa546b98
88 changed files with 2148 additions and 1997 deletions
85
.cargo/config.toml
Normal file
85
.cargo/config.toml
Normal 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",
|
||||
]
|
|
@ -19,7 +19,7 @@ Examples: `Vec2, Pos2, Rect, lerp, remap`
|
|||
|
||||
Example: `Shape::Circle { center, radius, fill, stroke }`
|
||||
|
||||
Depends on `emath`, [`ab_glyph`](https://crates.io/crates/ab_glyph), [`atomic_refcell`](https://crates.io/crates/atomic_refcell), [`ahash`](https://crates.io/crates/ahash).
|
||||
Depends on `emath`.
|
||||
|
||||
### `egui_extras`
|
||||
This adds additional features on top of `egui`.
|
||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -7,14 +7,22 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
|||
## Unreleased
|
||||
|
||||
### Added ⭐
|
||||
* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||
* Added `Shape::Callback` for backend-specific painting, [with an example](https://github.com/emilk/egui/blob/master/eframe/examples/custom_3d_three-d.rs) ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||
* Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)).
|
||||
* `Context::request_repaint` will wake up UI thread, if integrations has called `Context::set_request_repaint_callback` ([#1366](https://github.com/emilk/egui/pull/1366)).
|
||||
* Added `Ui::push_id` ([#1374](https://github.com/emilk/egui/pull/1374)).
|
||||
* Added `Frame::outer_margin`.
|
||||
|
||||
### Changed 🔧
|
||||
* `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||
* Renamed `Frame::margin` to `Frame::inner_margin`.
|
||||
|
||||
### Fixed 🐛
|
||||
* Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)).
|
||||
* Fixed ui code that could lead to a deadlock ([#1380](https://github.com/emilk/egui/pull/1380)).
|
||||
|
||||
### Removed 🔥
|
||||
* Removed the `single_threaded/multi_threaded` flags - egui is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)).
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22 - Improved font selection and image handling
|
||||
|
|
99
Cargo.lock
generated
99
Cargo.lock
generated
|
@ -81,6 +81,15 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.6"
|
||||
|
@ -442,6 +451,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cgmath"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
|
@ -993,9 +1012,9 @@ dependencies = [
|
|||
"epi",
|
||||
"glow",
|
||||
"image",
|
||||
"parking_lot 0.12.0",
|
||||
"poll-promise",
|
||||
"rfd",
|
||||
"three-d",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1053,6 +1072,7 @@ dependencies = [
|
|||
"poll-promise",
|
||||
"serde",
|
||||
"syntect",
|
||||
"tracing",
|
||||
"unicode_names2",
|
||||
]
|
||||
|
||||
|
@ -1063,7 +1083,6 @@ dependencies = [
|
|||
"chrono",
|
||||
"egui",
|
||||
"image",
|
||||
"parking_lot 0.12.0",
|
||||
"resvg",
|
||||
"serde",
|
||||
"tiny-skia",
|
||||
|
@ -1489,6 +1508,16 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "gloo-timers"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glow"
|
||||
version = "0.11.2"
|
||||
|
@ -1607,6 +1636,11 @@ name = "half"
|
|||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"serde",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
|
@ -1810,6 +1844,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db"
|
||||
|
||||
[[package]]
|
||||
name = "line-wrap"
|
||||
version = "0.1.1"
|
||||
|
@ -2125,6 +2165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3041,6 +3082,18 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syntect"
|
||||
version = "4.6.0"
|
||||
|
@ -3129,6 +3182,25 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "three-d"
|
||||
version = "0.11.0"
|
||||
source = "git+https://github.com/asny/three-d?rev=fa475673e284e05b2f4e068769dce3ec5bcabc8d#fa475673e284e05b2f4e068769dce3ec5bcabc8d"
|
||||
dependencies = [
|
||||
"cgmath",
|
||||
"gl_generator",
|
||||
"gloo-timers",
|
||||
"glow",
|
||||
"half",
|
||||
"js-sys",
|
||||
"log",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.43"
|
||||
|
@ -3473,6 +3545,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
|
@ -3947,6 +4021,27 @@ dependencies = [
|
|||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0fbc82b82efe24da867ee52e015e58178684bd9dd64c34e66bdf21da2582a9f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "3.1.2"
|
||||
|
|
24
README.md
24
README.md
|
@ -5,8 +5,8 @@
|
|||
[](https://docs.rs/egui)
|
||||
[](https://github.com/rust-secure-code/safety-dance/)
|
||||
[](https://github.com/emilk/egui/actions?workflow=CI)
|
||||

|
||||

|
||||
[](https://github.com/emilk/egui/blob/master/LICENSE-MIT)
|
||||
[](https://github.com/emilk/egui/blob/master/LICENSE-APACHE)
|
||||
[](https://discord.gg/JFcEma9bJq)
|
||||
|
||||
👉 [Click to run the web demo](https://www.egui.rs/#demo) 👈
|
||||
|
@ -85,7 +85,7 @@ On Fedora Rawhide you need to run:
|
|||
* Extensible: [easy to write your own widgets for egui](https://github.com/emilk/egui/blob/master/egui_demo_lib/src/apps/demo/toggle_switch.rs)
|
||||
* Modular: You should be able to use small parts of egui and combine them in new ways
|
||||
* Safe: there is no `unsafe` code in egui
|
||||
* Minimal dependencies: [`ab_glyph`](https://crates.io/crates/ab_glyph) [`ahash`](https://crates.io/crates/ahash) [`atomic_refcell`](https://crates.io/crates/atomic_refcell), [`nohash-hasher`](https://crates.io/crates/nohash-hasher)
|
||||
* Minimal dependencies: [`ab_glyph`](https://crates.io/crates/ab_glyph) [`ahash`](https://crates.io/crates/ahash) [`nohash-hasher`](https://crates.io/crates/nohash-hasher) [`parking_lot`](https://crates.io/crates/parking_lot)
|
||||
|
||||
egui is *not* a framework. egui is a library you call into, not an environment you program for.
|
||||
|
||||
|
@ -338,9 +338,23 @@ On Linux and Mac, Firefox will copy the WebGL render target from GPU, to CPU and
|
|||
To alleviate the above mentioned performance issues the default max-width of an egui web app is 1024 points. You can change this by overriding the `fn max_size_points` of [`epi::App`](https://docs.rs/epi/latest/epi/trait.App.html).
|
||||
|
||||
### How do I render 3D stuff in an egui area?
|
||||
egui can't do 3D graphics itself, but if you use a 3D library (e.g. [`glium`](https://github.com/glium/glium) using [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium), or [`miniquad`](https://github.com/not-fl3/miniquad) using [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad)) you can render your 3D content to a texture, then display it using [`ui.image(…)`](https://docs.rs/egui/latest/egui/struct.Ui.html#method.image). You first need to convert the native texture to an [`egui::TextureId`](https://docs.rs/egui/latest/egui/enum.TextureId.html), and how to do this depends on the integration you use (e.g. [`register_glium_texture`](https://docs.rs/epi/latest/epi/trait.NativeTexture.html#tymethod.register_native_texture)).
|
||||
There are multiple ways to combine egui with 3D. The simplest way is to use a 3D library and have egui sit on top of the 3D view. See for instance [`bevy_egui`](https://github.com/mvlabat/bevy_egui) or [`three-d`](https://github.com/asny/three-d).
|
||||
|
||||
There is an example for showing a native glium texture in an egui window at <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
|
||||
|
|
|
@ -5,8 +5,11 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)).
|
||||
* Change default for `NativeOptions::drag_and_drop_support` to `true` ([#1329](https://github.com/emilk/egui/pull/1329)).
|
||||
* Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)).
|
||||
* Removed `Frame::request_repaint` - just call `egui::Context::request_repaint` for the same effect ([#1366](https://github.com/emilk/egui/pull/1366)).
|
||||
* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)).
|
||||
* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
|
|
|
@ -71,6 +71,6 @@ image = { version = "0.24", default-features = false, features = [
|
|||
"jpeg",
|
||||
"png",
|
||||
] }
|
||||
parking_lot = "0.12"
|
||||
poll-promise = "0.1"
|
||||
rfd = "0.8"
|
||||
three-d = { git = "https://github.com/asny/three-d", rev = "fa475673e284e05b2f4e068769dce3ec5bcabc8d", default-features = false } # 2022-03-22
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(
|
||||
"Confirm exit",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MyApp {
|
||||
|
@ -8,17 +17,13 @@ struct MyApp {
|
|||
is_exiting: bool,
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
fn name(&self) -> &str {
|
||||
"Confirm exit"
|
||||
}
|
||||
|
||||
impl eframe::App for MyApp {
|
||||
fn on_exit_event(&mut self) -> bool {
|
||||
self.is_exiting = true;
|
||||
self.can_exit
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("Try to close the window");
|
||||
});
|
||||
|
@ -42,8 +47,3 @@ impl epi::App for MyApp {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
||||
|
|
|
@ -2,68 +2,87 @@
|
|||
//!
|
||||
//! This is very advanced usage, and you need to be careful.
|
||||
//!
|
||||
//! If you want an easier way to show 3D graphics with egui, take a look at:
|
||||
//! If you want an easier way to show 3D graphics with egui, take a look at the `custom_3d_three-d.rs` example.
|
||||
//!
|
||||
//! If you are content of having egui sit on top of a 3D background, take a look at:
|
||||
//!
|
||||
//! * [`bevy_egui`](https://github.com/mvlabat/bevy_egui)
|
||||
//! * [`three-d`](https://github.com/asny/three-d)
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use eframe::{egui, epi};
|
||||
use eframe::egui;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use egui::mutex::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Default)]
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(350.0, 380.0)),
|
||||
multisampling: 8,
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"Custom 3D painting in eframe using glow",
|
||||
options,
|
||||
Box::new(|cc| Box::new(MyApp::new(cc))),
|
||||
);
|
||||
}
|
||||
|
||||
struct MyApp {
|
||||
rotating_triangle: Arc<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,
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
fn name(&self) -> &str {
|
||||
"Custom 3D painting inside an egui window"
|
||||
impl MyApp {
|
||||
fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
Self {
|
||||
rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(&cc.gl))),
|
||||
angle: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("Here is some 3D stuff:");
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("The triangle is being painted using ");
|
||||
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
|
||||
ui.label(" (OpenGL).");
|
||||
});
|
||||
|
||||
egui::ScrollArea::both().show(ui, |ui| {
|
||||
egui::Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
self.custom_painting(ui);
|
||||
});
|
||||
ui.label("Drag to rotate!");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let mut frame = egui::Frame::window(&*ctx.style());
|
||||
frame.fill = frame.fill.linear_multiply(0.5); // transparent
|
||||
egui::Window::new("3D stuff in a window")
|
||||
.frame(frame)
|
||||
.show(ctx, |ui| {
|
||||
self.custom_painting(ui);
|
||||
});
|
||||
fn on_exit(&mut self, gl: &glow::Context) {
|
||||
self.rotating_triangle.lock().destroy(gl);
|
||||
}
|
||||
}
|
||||
|
||||
impl MyApp {
|
||||
fn custom_painting(&mut self, ui: &mut egui::Ui) {
|
||||
let (rect, response) =
|
||||
ui.allocate_exact_size(egui::Vec2::splat(256.0), egui::Sense::drag());
|
||||
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
|
||||
|
||||
self.angle += response.drag_delta().x * 0.01;
|
||||
|
||||
// Clone locals so we can move them into the paint callback:
|
||||
let angle = self.angle;
|
||||
let rotating_triangle = self.rotating_triangle.clone();
|
||||
|
||||
let callback = egui::epaint::PaintCallback {
|
||||
let callback = egui::PaintCallback {
|
||||
rect,
|
||||
callback: std::sync::Arc::new(move |render_ctx| {
|
||||
callback: std::sync::Arc::new(move |_info, render_ctx| {
|
||||
if let Some(painter) = render_ctx.downcast_ref::<egui_glow::Painter>() {
|
||||
let mut rotating_triangle = rotating_triangle.lock();
|
||||
let rotating_triangle = rotating_triangle
|
||||
.get_or_insert_with(|| RotatingTriangle::new(painter.gl()));
|
||||
rotating_triangle.paint(painter.gl(), angle);
|
||||
rotating_triangle.lock().paint(painter.gl(), angle);
|
||||
} else {
|
||||
eprintln!("Can't do custom painting because we are not using a glow context");
|
||||
}
|
||||
|
@ -163,9 +182,7 @@ impl RotatingTriangle {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: figure out how to call this in a nice way
|
||||
#[allow(unused)]
|
||||
fn destroy(self, gl: &glow::Context) {
|
||||
fn destroy(&self, gl: &glow::Context) {
|
||||
use glow::HasContext as _;
|
||||
unsafe {
|
||||
gl.delete_program(self.program);
|
||||
|
@ -186,8 +203,3 @@ impl RotatingTriangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
157
eframe/examples/custom_3d_three-d.rs
Normal file
157
eframe/examples/custom_3d_three-d.rs
Normal 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();
|
||||
}
|
|
@ -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 {
|
||||
text: String,
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(
|
||||
"egui example: custom font",
|
||||
options,
|
||||
Box::new(|cc| Box::new(MyApp::new(cc))),
|
||||
);
|
||||
}
|
||||
|
||||
impl Default for MyApp {
|
||||
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>,
|
||||
) {
|
||||
fn setup_custom_fonts(ctx: &egui::Context) {
|
||||
// Start with the default fonts (we will be adding to them rather than replacing them).
|
||||
let mut fonts = egui::FontDefinitions::default();
|
||||
|
||||
|
@ -50,17 +38,26 @@ impl epi::App for MyApp {
|
|||
|
||||
// Tell egui to use these 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| {
|
||||
ui.heading("egui using custom fonts");
|
||||
ui.text_edit_multiline(&mut self.text);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
||||
|
|
117
eframe/examples/custom_window_frame.rs
Normal file
117
eframe/examples/custom_window_frame.rs
Normal 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);
|
||||
});
|
||||
}
|
|
@ -1,12 +1,16 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
use eframe::egui;
|
||||
use egui_extras::RetainedImage;
|
||||
use poll_promise::Promise;
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
eframe::run_native(
|
||||
"Download and show an image with eframe/egui",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -15,23 +19,19 @@ struct MyApp {
|
|||
promise: Option<Promise<ehttp::Result<RetainedImage>>>,
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
fn name(&self) -> &str {
|
||||
"Download and show an image with eframe/egui"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||
let promise = self.promise.get_or_insert_with(|| {
|
||||
// Begin download.
|
||||
// We download the image using `ehttp`, a library that works both in WASM and on native.
|
||||
// We use the `poll-promise` library to communicate with the UI thread.
|
||||
let frame = frame.clone();
|
||||
let ctx = ctx.clone();
|
||||
let (sender, promise) = Promise::new();
|
||||
let request = ehttp::Request::get("https://picsum.photos/seed/1.759706314/1024");
|
||||
ehttp::fetch(request, move |response| {
|
||||
let image = response.and_then(parse_response);
|
||||
sender.send(image); // send the results back to the UI thread.
|
||||
frame.request_repaint(); // wake up UI thread
|
||||
ctx.request_repaint(); // wake up UI thread
|
||||
});
|
||||
promise
|
||||
});
|
||||
|
@ -50,6 +50,7 @@ impl epi::App for MyApp {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn parse_response(response: ehttp::Response) -> Result<RetainedImage, String> {
|
||||
let content_type = response.content_type().unwrap_or_default();
|
||||
if content_type.starts_with("image/") {
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions {
|
||||
drag_and_drop_support: true,
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"Native file dialogs and drag-and-drop files",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MyApp {
|
||||
|
@ -8,12 +20,8 @@ struct MyApp {
|
|||
picked_path: Option<String>,
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
fn name(&self) -> &str {
|
||||
"Native file dialogs and drag-and-drop files"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.label("Drag-and-drop files onto the window!");
|
||||
|
||||
|
@ -93,11 +101,3 @@ impl MyApp {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions {
|
||||
drag_and_drop_support: true,
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(
|
||||
"My egui App",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
}
|
||||
|
||||
struct MyApp {
|
||||
name: String,
|
||||
|
@ -16,12 +25,8 @@ impl Default for MyApp {
|
|||
}
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
fn name(&self) -> &str {
|
||||
"My egui App"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("My egui Application");
|
||||
ui.horizontal(|ui| {
|
||||
|
@ -39,8 +44,3 @@ impl epi::App for MyApp {
|
|||
frame.set_window_size(ctx.used_size());
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,20 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
use eframe::egui;
|
||||
use egui_extras::RetainedImage;
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(500.0, 900.0)),
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"Show an image with eframe/egui",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
}
|
||||
|
||||
struct MyApp {
|
||||
image: RetainedImage,
|
||||
}
|
||||
|
@ -19,16 +31,18 @@ impl Default for MyApp {
|
|||
}
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
fn name(&self) -> &str {
|
||||
"Show an image with eframe/egui"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("This is an image:");
|
||||
self.image.show(ui);
|
||||
|
||||
ui.heading("This is a rotated image:");
|
||||
ui.add(
|
||||
egui::Image::new(self.image.texture_id(ctx), self.image.size_vec2())
|
||||
.rotate(45.0_f32.to_radians(), egui::Vec2::splat(0.5)),
|
||||
);
|
||||
|
||||
ui.heading("This is an image you can click:");
|
||||
ui.add(egui::ImageButton::new(
|
||||
self.image.texture_id(ctx),
|
||||
|
@ -37,8 +51,3 @@ impl epi::App for MyApp {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,19 @@
|
|||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(1000.0, 700.0)),
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"svg example",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
}
|
||||
|
||||
struct MyApp {
|
||||
svg_image: egui_extras::RetainedImage,
|
||||
|
@ -22,12 +34,8 @@ impl Default for MyApp {
|
|||
}
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
fn name(&self) -> &str {
|
||||
"svg example"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("SVG example");
|
||||
ui.label("The SVG is rasterized and displayed as a texture.");
|
||||
|
@ -39,11 +47,3 @@ impl epi::App for MyApp {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(1000.0, 700.0)),
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
||||
|
|
|
@ -16,27 +16,32 @@
|
|||
//!
|
||||
//! ## Usage, native:
|
||||
//! ``` no_run
|
||||
//! use eframe::{epi, egui};
|
||||
//! use eframe::egui;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let native_options = eframe::NativeOptions::default();
|
||||
//! eframe::run_native("My egui App", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))));
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Default)]
|
||||
//! struct MyEguiApp {}
|
||||
//!
|
||||
//! impl epi::App for MyEguiApp {
|
||||
//! fn name(&self) -> &str {
|
||||
//! "My egui App"
|
||||
//! impl MyEguiApp {
|
||||
//! fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
|
||||
//! // Restore app state using cc.storage (requires the "persistence" feature).
|
||||
//! // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
|
||||
//! // for e.g. egui::PaintCallback.
|
||||
//! Self::default()
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
//! impl eframe::App for MyEguiApp {
|
||||
//! fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||
//! egui::CentralPanel::default().show(ctx, |ui| {
|
||||
//! ui.heading("Hello World!");
|
||||
//! });
|
||||
//! }
|
||||
//!}
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let app = MyEguiApp::default();
|
||||
//! let native_options = eframe::NativeOptions::default();
|
||||
//! eframe::run_native(Box::new(app), native_options);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
|
@ -49,26 +54,17 @@
|
|||
//! #[cfg(target_arch = "wasm32")]
|
||||
//! #[wasm_bindgen]
|
||||
//! pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
|
||||
//! let app = MyEguiApp::default();
|
||||
//! eframe::start_web(canvas_id, Box::new(app))
|
||||
//! eframe::start_web(canvas_id, Box::new(|cc| Box::new(MyApp::new(cc))))
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
missing_docs,
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
|
||||
// Re-export all useful libraries:
|
||||
pub use {egui, egui::emath, egui::epaint, epi};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use epi::NativeOptions;
|
||||
// Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is:
|
||||
pub use epi::*;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// When compiling for web
|
||||
|
@ -79,20 +75,6 @@ pub use egui_web::wasm_bindgen;
|
|||
/// Install event listeners to register different input events
|
||||
/// and start running the given app.
|
||||
///
|
||||
/// For performance reasons (on some browsers) the egui canvas does not, by default,
|
||||
/// fill the whole width of the browser.
|
||||
/// This can be changed by overriding [`epi::Frame::max_size_points`].
|
||||
///
|
||||
/// ### Usage, native:
|
||||
/// ``` no_run
|
||||
/// fn main() {
|
||||
/// let app = MyEguiApp::default();
|
||||
/// let native_options = eframe::NativeOptions::default();
|
||||
/// eframe::run_native(Box::new(app), native_options);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Web
|
||||
/// ``` no_run
|
||||
/// #[cfg(target_arch = "wasm32")]
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
|
@ -104,45 +86,55 @@ pub use egui_web::wasm_bindgen;
|
|||
/// #[cfg(target_arch = "wasm32")]
|
||||
/// #[wasm_bindgen]
|
||||
/// pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
|
||||
/// let app = MyEguiApp::default();
|
||||
/// eframe::start_web(canvas_id, Box::new(app))
|
||||
/// eframe::start_web(canvas_id, Box::new(|cc| Box::new(MyEguiApp::new(cc))))
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn start_web(canvas_id: &str, app: Box<dyn epi::App>) -> Result<(), wasm_bindgen::JsValue> {
|
||||
egui_web::start(canvas_id, app)?;
|
||||
pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bindgen::JsValue> {
|
||||
egui_web::start(canvas_id, app_creator)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// When compiling natively
|
||||
|
||||
/// This is how you start a native (desktop) app.
|
||||
///
|
||||
/// The first argument is name of your app, used for the title bar of the native window
|
||||
/// and the save location of persistence (see [`epi::App::save`]).
|
||||
///
|
||||
/// Call from `fn main` like this:
|
||||
/// ``` no_run
|
||||
/// use eframe::{epi, egui};
|
||||
/// use eframe::egui;
|
||||
///
|
||||
/// fn main() {
|
||||
/// let native_options = eframe::NativeOptions::default();
|
||||
/// eframe::run_native("MyApp", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))));
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Default)]
|
||||
/// struct MyEguiApp {}
|
||||
///
|
||||
/// impl epi::App for MyEguiApp {
|
||||
/// fn name(&self) -> &str {
|
||||
/// "My egui App"
|
||||
/// impl MyEguiApp {
|
||||
/// fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
|
||||
/// // Restore app state using cc.storage (requires the "persistence" feature).
|
||||
/// // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
|
||||
/// // for e.g. egui::PaintCallback.
|
||||
/// Self::default()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
/// impl eframe::App for MyEguiApp {
|
||||
/// fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||
/// ui.heading("Hello World!");
|
||||
/// });
|
||||
/// }
|
||||
///}
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = MyEguiApp::default();
|
||||
/// let native_options = eframe::NativeOptions::default();
|
||||
/// eframe::run_native(Box::new(app), native_options);
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn run_native(app: Box<dyn epi::App>, native_options: epi::NativeOptions) -> ! {
|
||||
egui_glow::run(app, &native_options)
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! {
|
||||
egui_glow::run(app_name, &native_options, app_creator)
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@ serialize = ["egui/serialize", "serde"]
|
|||
|
||||
[dependencies]
|
||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||
"single_threaded",
|
||||
"tracing",
|
||||
] }
|
||||
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
||||
|
|
|
@ -21,6 +21,10 @@ pub fn window_builder(
|
|||
max_window_size,
|
||||
resizable,
|
||||
transparent,
|
||||
vsync: _, // used in `fn create_display`
|
||||
multisampling: _, // used in `fn create_display`
|
||||
depth_buffer: _, // used in `fn create_display`
|
||||
stencil_buffer: _, // used in `fn create_display`
|
||||
} = native_options;
|
||||
|
||||
let window_icon = icon_data.clone().and_then(load_icon);
|
||||
|
@ -216,12 +220,11 @@ impl Persistence {
|
|||
|
||||
/// Everything needed to make a winit-based integration for [`epi`].
|
||||
pub struct EpiIntegration {
|
||||
frame: epi::Frame,
|
||||
persistence: crate::epi::Persistence,
|
||||
pub frame: epi::Frame,
|
||||
pub persistence: crate::epi::Persistence,
|
||||
pub egui_ctx: egui::Context,
|
||||
pending_full_output: egui::FullOutput,
|
||||
egui_winit: crate::State,
|
||||
pub app: Box<dyn epi::App>,
|
||||
/// When set, it is time to quit
|
||||
quit: bool,
|
||||
can_drag_window: bool,
|
||||
|
@ -232,10 +235,7 @@ impl EpiIntegration {
|
|||
integration_name: &'static str,
|
||||
max_texture_side: usize,
|
||||
window: &winit::window::Window,
|
||||
gl: &std::rc::Rc<glow::Context>,
|
||||
repaint_signal: std::sync::Arc<dyn epi::backend::RepaintSignal>,
|
||||
persistence: crate::epi::Persistence,
|
||||
app: Box<dyn epi::App>,
|
||||
) -> Self {
|
||||
let egui_ctx = egui::Context::default();
|
||||
|
||||
|
@ -252,7 +252,6 @@ impl EpiIntegration {
|
|||
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
|
||||
},
|
||||
output: Default::default(),
|
||||
repaint_signal,
|
||||
});
|
||||
|
||||
if prefer_dark_mode == Some(true) {
|
||||
|
@ -261,41 +260,21 @@ impl EpiIntegration {
|
|||
egui_ctx.set_visuals(egui::Visuals::light());
|
||||
}
|
||||
|
||||
let mut slf = Self {
|
||||
Self {
|
||||
frame,
|
||||
persistence,
|
||||
egui_ctx,
|
||||
egui_winit: crate::State::new(max_texture_side, window),
|
||||
pending_full_output: Default::default(),
|
||||
app,
|
||||
quit: false,
|
||||
can_drag_window: false,
|
||||
};
|
||||
|
||||
slf.setup(window, gl);
|
||||
if slf.app.warm_up_enabled() {
|
||||
slf.warm_up(window);
|
||||
}
|
||||
}
|
||||
|
||||
slf
|
||||
}
|
||||
|
||||
fn setup(&mut self, window: &winit::window::Window, gl: &std::rc::Rc<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) {
|
||||
pub fn warm_up(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
|
||||
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
|
||||
self.egui_ctx.memory().set_everything_is_visible(true);
|
||||
let full_output = self.update(window);
|
||||
let full_output = self.update(app, window);
|
||||
self.pending_full_output.append(full_output); // Handle it next frame
|
||||
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
||||
self.egui_ctx.clear_animations();
|
||||
|
@ -306,11 +285,11 @@ impl EpiIntegration {
|
|||
self.quit
|
||||
}
|
||||
|
||||
pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) {
|
||||
pub fn on_event(&mut self, app: &mut dyn epi::App, event: &winit::event::WindowEvent<'_>) {
|
||||
use winit::event::{ElementState, MouseButton, WindowEvent};
|
||||
|
||||
match event {
|
||||
WindowEvent::CloseRequested => self.quit = self.app.on_exit_event(),
|
||||
WindowEvent::CloseRequested => self.quit = app.on_exit_event(),
|
||||
WindowEvent::Destroyed => self.quit = true,
|
||||
WindowEvent::MouseInput {
|
||||
button: MouseButton::Left,
|
||||
|
@ -323,12 +302,16 @@ impl EpiIntegration {
|
|||
self.egui_winit.on_event(&self.egui_ctx, event);
|
||||
}
|
||||
|
||||
pub fn update(&mut self, window: &winit::window::Window) -> egui::FullOutput {
|
||||
pub fn update(
|
||||
&mut self,
|
||||
app: &mut dyn epi::App,
|
||||
window: &winit::window::Window,
|
||||
) -> egui::FullOutput {
|
||||
let frame_start = instant::Instant::now();
|
||||
|
||||
let raw_input = self.egui_winit.take_egui_input(window);
|
||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
self.app.update(egui_ctx, &self.frame);
|
||||
app.update(egui_ctx, &self.frame);
|
||||
});
|
||||
self.pending_full_output.append(full_output);
|
||||
let full_output = std::mem::take(&mut self.pending_full_output);
|
||||
|
@ -338,7 +321,7 @@ impl EpiIntegration {
|
|||
app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
|
||||
self.can_drag_window = false;
|
||||
if app_output.quit {
|
||||
self.quit = self.app.on_exit_event();
|
||||
self.quit = app.on_exit_event();
|
||||
}
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
}
|
||||
|
@ -358,15 +341,9 @@ impl EpiIntegration {
|
|||
.handle_platform_output(window, &self.egui_ctx, platform_output);
|
||||
}
|
||||
|
||||
pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
|
||||
pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
|
||||
self.persistence
|
||||
.maybe_autosave(&mut *self.app, &self.egui_ctx, window);
|
||||
}
|
||||
|
||||
pub fn on_exit(&mut self, window: &winit::window::Window) {
|
||||
self.app.on_exit();
|
||||
self.persistence
|
||||
.save(&mut *self.app, &self.egui_ctx, window);
|
||||
.maybe_autosave(&mut *app, &self.egui_ctx, window);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,84 +3,6 @@
|
|||
//! The library translates winit events to egui, handled copy/paste,
|
||||
//! updates the cursor, open links clicked in egui, etc.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::await_holding_lock,
|
||||
clippy::char_lit_as_u8,
|
||||
clippy::checked_conversions,
|
||||
clippy::dbg_macro,
|
||||
clippy::debug_assert_with_mut_call,
|
||||
clippy::disallowed_method,
|
||||
clippy::doc_markdown,
|
||||
clippy::empty_enum,
|
||||
clippy::enum_glob_use,
|
||||
clippy::exit,
|
||||
clippy::expl_impl_clone_on_copy,
|
||||
clippy::explicit_deref_methods,
|
||||
clippy::explicit_into_iter_loop,
|
||||
clippy::fallible_impl_from,
|
||||
clippy::filter_map_next,
|
||||
clippy::flat_map_option,
|
||||
clippy::float_cmp_const,
|
||||
clippy::fn_params_excessive_bools,
|
||||
clippy::from_iter_instead_of_collect,
|
||||
clippy::if_let_mutex,
|
||||
clippy::implicit_clone,
|
||||
clippy::imprecise_flops,
|
||||
clippy::inefficient_to_string,
|
||||
clippy::invalid_upcast_comparisons,
|
||||
clippy::large_digit_groups,
|
||||
clippy::large_stack_arrays,
|
||||
clippy::large_types_passed_by_value,
|
||||
clippy::let_unit_value,
|
||||
clippy::linkedlist,
|
||||
clippy::lossy_float_literal,
|
||||
clippy::macro_use_imports,
|
||||
clippy::manual_ok_or,
|
||||
clippy::map_err_ignore,
|
||||
clippy::map_flatten,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::match_on_vec_items,
|
||||
clippy::match_same_arms,
|
||||
clippy::match_wild_err_arm,
|
||||
clippy::match_wildcard_for_single_variants,
|
||||
clippy::mem_forget,
|
||||
clippy::mismatched_target_os,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::mut_mut,
|
||||
clippy::mutex_integer,
|
||||
clippy::needless_borrow,
|
||||
clippy::needless_continue,
|
||||
clippy::needless_for_each,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::option_option,
|
||||
clippy::path_buf_push_overwrite,
|
||||
clippy::ptr_as_ptr,
|
||||
clippy::ref_option_ref,
|
||||
clippy::rest_pat_in_fully_bound_structs,
|
||||
clippy::same_functions_in_if_condition,
|
||||
clippy::semicolon_if_nothing_returned,
|
||||
clippy::single_match_else,
|
||||
clippy::string_add_assign,
|
||||
clippy::string_add,
|
||||
clippy::string_lit_as_bytes,
|
||||
clippy::string_to_string,
|
||||
clippy::todo,
|
||||
clippy::trait_duplication_in_bounds,
|
||||
clippy::unimplemented,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::unused_self,
|
||||
clippy::useless_transmute,
|
||||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
pub use winit;
|
||||
|
|
|
@ -20,7 +20,7 @@ all-features = true
|
|||
|
||||
|
||||
[features]
|
||||
default = ["default_fonts", "single_threaded"]
|
||||
default = ["default_fonts"]
|
||||
|
||||
# add compatibility with https://crates.io/crates/cint
|
||||
cint = ["epaint/cint"]
|
||||
|
@ -46,11 +46,6 @@ persistence = ["serde", "epaint/serialize", "ron"]
|
|||
# implement serde on most types.
|
||||
serialize = ["serde", "epaint/serialize"]
|
||||
|
||||
# multi_threaded is only needed if you plan to use the same egui::Context
|
||||
# from multiple threads. It comes with a minor performance impact.
|
||||
single_threaded = ["epaint/single_threaded"]
|
||||
multi_threaded = ["epaint/multi_threaded"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
epaint = { version = "0.17.0", path = "../epaint", default-features = false }
|
||||
|
|
|
@ -7,8 +7,10 @@ use epaint::*;
|
|||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[must_use = "You should call .show()"]
|
||||
pub struct Frame {
|
||||
/// On each side
|
||||
pub margin: Margin,
|
||||
/// Margin within the painted frame.
|
||||
pub inner_margin: Margin,
|
||||
/// Margin outside the painted frame.
|
||||
pub outer_margin: Margin,
|
||||
pub rounding: Rounding,
|
||||
pub shadow: Shadow,
|
||||
pub fill: Color32,
|
||||
|
@ -23,7 +25,7 @@ impl Frame {
|
|||
/// For when you want to group a few widgets together within a frame.
|
||||
pub fn group(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: Margin::same(6.0), // symmetric looks best in corners when nesting
|
||||
inner_margin: Margin::same(6.0), // symmetric looks best in corners when nesting
|
||||
rounding: style.visuals.widgets.noninteractive.rounding,
|
||||
stroke: style.visuals.widgets.noninteractive.bg_stroke,
|
||||
..Default::default()
|
||||
|
@ -32,7 +34,7 @@ impl Frame {
|
|||
|
||||
pub(crate) fn side_top_panel(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: Margin::symmetric(8.0, 2.0),
|
||||
inner_margin: Margin::symmetric(8.0, 2.0),
|
||||
rounding: Rounding::none(),
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
|
@ -42,7 +44,7 @@ impl Frame {
|
|||
|
||||
pub(crate) fn central_panel(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: Margin::symmetric(8.0, 8.0),
|
||||
inner_margin: Margin::symmetric(8.0, 8.0),
|
||||
rounding: Rounding::none(),
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: Default::default(),
|
||||
|
@ -52,31 +54,34 @@ impl Frame {
|
|||
|
||||
pub fn window(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: style.spacing.window_margin,
|
||||
inner_margin: style.spacing.window_margin,
|
||||
rounding: style.visuals.window_rounding,
|
||||
shadow: style.visuals.window_shadow,
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn menu(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: Margin::same(1.0),
|
||||
inner_margin: Margin::same(1.0),
|
||||
rounding: style.visuals.widgets.noninteractive.rounding,
|
||||
shadow: style.visuals.popup_shadow,
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn popup(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: style.spacing.window_margin,
|
||||
inner_margin: style.spacing.window_margin,
|
||||
rounding: style.visuals.widgets.noninteractive.rounding,
|
||||
shadow: style.visuals.popup_shadow,
|
||||
fill: style.visuals.window_fill(),
|
||||
stroke: style.visuals.window_stroke(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +91,7 @@ impl Frame {
|
|||
/// and in dark mode this will be very dark.
|
||||
pub fn canvas(style: &Style) -> Self {
|
||||
Self {
|
||||
margin: Margin::symmetric(10.0, 10.0),
|
||||
inner_margin: Margin::symmetric(10.0, 10.0),
|
||||
rounding: style.visuals.widgets.noninteractive.rounding,
|
||||
fill: style.visuals.extreme_bg_color,
|
||||
stroke: style.visuals.window_stroke(),
|
||||
|
@ -119,12 +124,23 @@ impl Frame {
|
|||
self
|
||||
}
|
||||
|
||||
/// Margin on each side of the frame.
|
||||
pub fn margin(mut self, margin: impl Into<Margin>) -> Self {
|
||||
self.margin = margin.into();
|
||||
/// Margin within the painted frame.
|
||||
pub fn inner_margin(mut self, inner_margin: impl Into<Margin>) -> Self {
|
||||
self.inner_margin = inner_margin.into();
|
||||
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 {
|
||||
self.shadow = shadow;
|
||||
self
|
||||
|
@ -150,8 +166,8 @@ impl Frame {
|
|||
let outer_rect_bounds = ui.available_rect_before_wrap();
|
||||
|
||||
let mut inner_rect = outer_rect_bounds;
|
||||
inner_rect.min += Vec2::new(self.margin.left, self.margin.top);
|
||||
inner_rect.max -= Vec2::new(self.margin.right, self.margin.bottom);
|
||||
inner_rect.min += self.outer_margin.left_top() + self.inner_margin.left_top();
|
||||
inner_rect.max -= self.outer_margin.right_bottom() + self.inner_margin.right_bottom();
|
||||
|
||||
// Make sure we don't shrink to the negative:
|
||||
inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x);
|
||||
|
@ -185,7 +201,8 @@ impl Frame {
|
|||
|
||||
pub fn paint(&self, outer_rect: Rect) -> Shape {
|
||||
let Self {
|
||||
margin: _,
|
||||
inner_margin: _,
|
||||
outer_margin: _,
|
||||
rounding,
|
||||
shadow,
|
||||
fill,
|
||||
|
@ -210,15 +227,15 @@ impl Frame {
|
|||
}
|
||||
|
||||
impl Prepared {
|
||||
pub fn outer_rect(&self) -> Rect {
|
||||
fn paint_rect(&self) -> Rect {
|
||||
let mut rect = self.content_ui.min_rect();
|
||||
rect.min -= Vec2::new(self.frame.margin.left, self.frame.margin.top);
|
||||
rect.max += Vec2::new(self.frame.margin.right, self.frame.margin.bottom);
|
||||
rect.min -= self.frame.inner_margin.left_top();
|
||||
rect.max += self.frame.inner_margin.right_bottom();
|
||||
rect
|
||||
}
|
||||
|
||||
pub fn end(self, ui: &mut Ui) -> Response {
|
||||
let outer_rect = self.outer_rect();
|
||||
let paint_rect = self.paint_rect();
|
||||
|
||||
let Prepared {
|
||||
frame,
|
||||
|
@ -226,11 +243,11 @@ impl Prepared {
|
|||
..
|
||||
} = self;
|
||||
|
||||
if ui.is_rect_visible(outer_rect) {
|
||||
let shape = frame.paint(outer_rect);
|
||||
if ui.is_rect_visible(paint_rect) {
|
||||
let shape = frame.paint(paint_rect);
|
||||
ui.painter().set(where_to_put_background, shape);
|
||||
}
|
||||
|
||||
ui.allocate_rect(outer_rect, Sense::hover())
|
||||
ui.allocate_rect(paint_rect, Sense::hover())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
// See https://github.com/emilk/egui/issues/825
|
||||
let frame = Frame::popup(ui.style());
|
||||
let frame_margin = frame.margin;
|
||||
let frame_margin = frame.inner_margin + frame.outer_margin;
|
||||
frame
|
||||
.show(ui, |ui| {
|
||||
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
|
||||
|
|
|
@ -301,7 +301,9 @@ impl<'open> Window<'open> {
|
|||
} else {
|
||||
0.0
|
||||
};
|
||||
let margins = frame.margin.sum() + vec2(0.0, title_bar_height);
|
||||
let margins = frame.outer_margin.sum()
|
||||
+ frame.inner_margin.sum()
|
||||
+ vec2(0.0, title_bar_height);
|
||||
|
||||
interact(
|
||||
window_interaction,
|
||||
|
|
|
@ -48,6 +48,7 @@ struct ContextImpl {
|
|||
|
||||
/// While positive, keep requesting repaints. Decrement at the end of each frame.
|
||||
repaint_requests: u32,
|
||||
request_repaint_callbacks: Option<Box<dyn Fn() + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl ContextImpl {
|
||||
|
@ -533,11 +534,28 @@ impl Context {
|
|||
|
||||
impl Context {
|
||||
/// Call this if there is need to repaint the UI, i.e. if you are showing an animation.
|
||||
///
|
||||
/// If this is called at least once in a frame, then there will be another frame right after this.
|
||||
/// Call as many times as you wish, only one repaint will be issued.
|
||||
///
|
||||
/// If called from outside the UI thread, the UI thread will wake up and run,
|
||||
/// provided the egui integration has set that up via [`Self::set_request_repaint_callback`]
|
||||
/// (this will work on `eframe`).
|
||||
pub fn request_repaint(&self) {
|
||||
// request two frames of repaint, just to cover some corner cases (frame delays):
|
||||
self.write().repaint_requests = 2;
|
||||
let mut ctx = self.write();
|
||||
ctx.repaint_requests = 2;
|
||||
if let Some(callback) = &ctx.request_repaint_callbacks {
|
||||
(callback)();
|
||||
}
|
||||
}
|
||||
|
||||
/// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`].
|
||||
///
|
||||
/// This lets you wake up a sleeping UI thread.
|
||||
pub fn set_request_repaint_callback(&self, callback: impl Fn() + Send + Sync + 'static) {
|
||||
let callback = Box::new(callback);
|
||||
self.write().request_repaint_callbacks = Some(callback);
|
||||
}
|
||||
|
||||
/// Tell `egui` which fonts to use.
|
||||
|
@ -667,8 +685,19 @@ impl Context {
|
|||
name: impl Into<String>,
|
||||
image: impl Into<ImageData>,
|
||||
) -> TextureHandle {
|
||||
let name = name.into();
|
||||
let image = image.into();
|
||||
let max_texture_side = self.input().max_texture_side;
|
||||
crate::egui_assert!(
|
||||
image.width() <= max_texture_side && image.height() <= max_texture_side,
|
||||
"Texture {:?} has size {}x{}, but the maximum texture side is {}",
|
||||
name,
|
||||
image.width(),
|
||||
image.height(),
|
||||
max_texture_side
|
||||
);
|
||||
let tex_mngr = self.tex_manager();
|
||||
let tex_id = tex_mngr.write().alloc(name.into(), image.into());
|
||||
let tex_id = tex_mngr.write().alloc(name, image);
|
||||
TextureHandle::new(tex_mngr, tex_id)
|
||||
}
|
||||
|
||||
|
@ -778,14 +807,16 @@ impl Context {
|
|||
// shapes are the same, but just comparing the shapes takes about 50% of the time
|
||||
// it takes to tessellate them, so it is not a worth optimization.
|
||||
|
||||
let mut tessellation_options = *self.tessellation_options();
|
||||
tessellation_options.pixels_per_point = self.pixels_per_point();
|
||||
tessellation_options.aa_size = 1.0 / self.pixels_per_point();
|
||||
let pixels_per_point = self.pixels_per_point();
|
||||
let tessellation_options = *self.tessellation_options();
|
||||
let font_image_size = self.fonts().font_image_size();
|
||||
|
||||
let paint_stats = PaintStats::from_shapes(&shapes);
|
||||
let clipped_primitives = tessellator::tessellate_shapes(
|
||||
shapes,
|
||||
pixels_per_point,
|
||||
tessellation_options,
|
||||
self.fonts().font_image_size(),
|
||||
shapes,
|
||||
font_image_size,
|
||||
);
|
||||
self.write().paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives);
|
||||
clipped_primitives
|
||||
|
|
|
@ -101,11 +101,14 @@ impl Widget for &epaint::stats::PaintStats {
|
|||
|
||||
ui.label("Intermediate:");
|
||||
label(ui, shapes, "shapes").on_hover_text("Boxes, circles, etc");
|
||||
label(ui, shape_text, "text (mostly cached)");
|
||||
ui.horizontal(|ui| {
|
||||
label(ui, shape_text, "text");
|
||||
ui.small("(mostly cached)");
|
||||
});
|
||||
label(ui, shape_path, "paths");
|
||||
label(ui, shape_mesh, "nested meshes");
|
||||
label(ui, shape_vec, "nested shapes");
|
||||
ui.label(format!("{} callbacks", num_callbacks));
|
||||
ui.label(format!("{:6} callbacks", num_callbacks));
|
||||
ui.add_space(10.0);
|
||||
|
||||
ui.label("Text shapes:");
|
||||
|
@ -115,7 +118,7 @@ impl Widget for &epaint::stats::PaintStats {
|
|||
ui.add_space(10.0);
|
||||
|
||||
ui.label("Tessellated (and culled):");
|
||||
label(ui, clipped_primitives, "clipped_primitives")
|
||||
label(ui, clipped_primitives, "primitives lists")
|
||||
.on_hover_text("Number of separate clip rectangles");
|
||||
label(ui, vertices, "vertices");
|
||||
label(ui, indices, "indices").on_hover_text("Three 32-bit indices per triangles");
|
||||
|
@ -136,9 +139,8 @@ impl Widget for &mut epaint::TessellationOptions {
|
|||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
ui.vertical(|ui| {
|
||||
let epaint::TessellationOptions {
|
||||
pixels_per_point: _,
|
||||
aa_size: _,
|
||||
anti_alias,
|
||||
feathering,
|
||||
feathering_size_in_pixels,
|
||||
coarse_tessellation_culling,
|
||||
round_text_to_pixels,
|
||||
debug_paint_clip_rects,
|
||||
|
@ -147,8 +149,15 @@ impl Widget for &mut epaint::TessellationOptions {
|
|||
bezier_tolerance,
|
||||
epsilon: _,
|
||||
} = self;
|
||||
ui.checkbox(anti_alias, "Antialias")
|
||||
|
||||
ui.checkbox(feathering, "Feathering (antialias)")
|
||||
.on_hover_text("Apply feathering to smooth out the edges of shapes. Turn off for small performance gain.");
|
||||
let feathering_slider = crate::Slider::new(feathering_size_in_pixels, 0.0..=10.0)
|
||||
.smallest_positive(0.1)
|
||||
.logarithmic(true)
|
||||
.text("Feathering size in pixels");
|
||||
ui.add_enabled(*feathering, feathering_slider);
|
||||
|
||||
ui.add(
|
||||
crate::widgets::Slider::new(bezier_tolerance, 0.0001..=10.0)
|
||||
.logarithmic(true)
|
||||
|
|
|
@ -273,85 +273,6 @@
|
|||
//! # });
|
||||
//! ```
|
||||
|
||||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::await_holding_lock,
|
||||
clippy::char_lit_as_u8,
|
||||
clippy::checked_conversions,
|
||||
clippy::dbg_macro,
|
||||
clippy::debug_assert_with_mut_call,
|
||||
clippy::disallowed_method,
|
||||
clippy::doc_markdown,
|
||||
clippy::empty_enum,
|
||||
clippy::enum_glob_use,
|
||||
clippy::exit,
|
||||
clippy::expl_impl_clone_on_copy,
|
||||
clippy::explicit_deref_methods,
|
||||
clippy::explicit_into_iter_loop,
|
||||
clippy::fallible_impl_from,
|
||||
clippy::filter_map_next,
|
||||
clippy::flat_map_option,
|
||||
clippy::float_cmp_const,
|
||||
clippy::fn_params_excessive_bools,
|
||||
clippy::from_iter_instead_of_collect,
|
||||
clippy::if_let_mutex,
|
||||
clippy::implicit_clone,
|
||||
clippy::imprecise_flops,
|
||||
clippy::inefficient_to_string,
|
||||
clippy::invalid_upcast_comparisons,
|
||||
clippy::large_digit_groups,
|
||||
clippy::large_stack_arrays,
|
||||
clippy::large_types_passed_by_value,
|
||||
clippy::let_unit_value,
|
||||
clippy::linkedlist,
|
||||
clippy::lossy_float_literal,
|
||||
clippy::macro_use_imports,
|
||||
clippy::manual_ok_or,
|
||||
clippy::map_err_ignore,
|
||||
clippy::map_flatten,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::match_on_vec_items,
|
||||
clippy::match_same_arms,
|
||||
clippy::match_wild_err_arm,
|
||||
clippy::match_wildcard_for_single_variants,
|
||||
clippy::mem_forget,
|
||||
clippy::mismatched_target_os,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::mut_mut,
|
||||
clippy::mutex_integer,
|
||||
clippy::needless_borrow,
|
||||
clippy::needless_continue,
|
||||
clippy::needless_for_each,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::option_option,
|
||||
clippy::path_buf_push_overwrite,
|
||||
clippy::ptr_as_ptr,
|
||||
clippy::ref_option_ref,
|
||||
clippy::rest_pat_in_fully_bound_structs,
|
||||
clippy::same_functions_in_if_condition,
|
||||
clippy::semicolon_if_nothing_returned,
|
||||
clippy::single_match_else,
|
||||
clippy::string_add_assign,
|
||||
clippy::string_add,
|
||||
clippy::string_lit_as_bytes,
|
||||
clippy::string_to_string,
|
||||
clippy::todo,
|
||||
clippy::trait_duplication_in_bounds,
|
||||
clippy::unimplemented,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::unused_self,
|
||||
clippy::useless_transmute,
|
||||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
|
@ -386,14 +307,15 @@ pub use epaint::{
|
|||
color, mutex,
|
||||
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
|
||||
textures::TexturesDelta,
|
||||
AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, Rgba, Rounding, Shape,
|
||||
Stroke, TextureHandle, TextureId,
|
||||
AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, PaintCallback,
|
||||
PaintCallbackInfo, Rgba, Rounding, Shape, Stroke, TextureHandle, TextureId,
|
||||
};
|
||||
|
||||
pub mod text {
|
||||
pub use crate::text_edit::CCursorRange;
|
||||
pub use epaint::text::{
|
||||
FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, LayoutSection, TextFormat,
|
||||
TAB_SIZE,
|
||||
cursor::CCursor, FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob,
|
||||
LayoutSection, TextFormat, TAB_SIZE,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -277,10 +277,10 @@ impl MenuRoot {
|
|||
root: &mut MenuRootManager,
|
||||
id: Id,
|
||||
) -> MenuResponse {
|
||||
let pointer = &response.ctx.input().pointer;
|
||||
if (response.clicked() && root.is_menu_open(id))
|
||||
|| response.ctx.input().key_pressed(Key::Escape)
|
||||
{
|
||||
// Lock the input once for the whole function call (see https://github.com/emilk/egui/pull/1380).
|
||||
let input = response.ctx.input();
|
||||
|
||||
if (response.clicked() && root.is_menu_open(id)) || input.key_pressed(Key::Escape) {
|
||||
// menu open and button clicked or esc pressed
|
||||
return MenuResponse::Close;
|
||||
} else if (response.clicked() && !root.is_menu_open(id))
|
||||
|
@ -290,8 +290,8 @@ impl MenuRoot {
|
|||
// or button hovered while other menu is open
|
||||
let pos = response.rect.left_bottom();
|
||||
return MenuResponse::Create(pos, id);
|
||||
} else if pointer.any_pressed() && pointer.primary_down() {
|
||||
if let Some(pos) = pointer.interact_pos() {
|
||||
} else if input.pointer.any_pressed() && input.pointer.primary_down() {
|
||||
if let Some(pos) = input.pointer.interact_pos() {
|
||||
if let Some(root) = root.inner.as_mut() {
|
||||
if root.id == id {
|
||||
// pressed somewhere while this menu is open
|
||||
|
|
|
@ -277,6 +277,8 @@ impl Spacing {
|
|||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Margin {
|
||||
|
@ -312,6 +314,20 @@ impl Margin {
|
|||
pub fn sum(&self) -> Vec2 {
|
||||
Vec2::new(self.left + self.right, self.top + self.bottom)
|
||||
}
|
||||
|
||||
pub fn left_top(&self) -> Vec2 {
|
||||
Vec2::new(self.left, self.top)
|
||||
}
|
||||
|
||||
pub fn right_bottom(&self) -> Vec2 {
|
||||
Vec2::new(self.right, self.bottom)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Margin {
|
||||
fn from(v: f32) -> Self {
|
||||
Self::same(v)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
|
|
|
@ -1546,6 +1546,26 @@ impl Ui {
|
|||
crate::Frame::group(self.style()).show(self, add_contents)
|
||||
}
|
||||
|
||||
/// Create a child Ui with an explicit [`Id`].
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// for i in 0..10 {
|
||||
/// // `ui.make_persistent_id("foo")` here will produce the same id each loop.
|
||||
/// ui.push_id(i, |ui| {
|
||||
/// // `ui.make_persistent_id("foo")` here will produce different id:s
|
||||
/// });
|
||||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn push_id<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.
|
||||
///
|
||||
/// 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> {
|
||||
self.scope_dyn(Box::new(add_contents))
|
||||
self.scope_dyn(Box::new(add_contents), Id::new("child"))
|
||||
}
|
||||
|
||||
fn scope_dyn<'c, R>(
|
||||
&mut self,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
id_source: Id,
|
||||
) -> InnerResponse<R> {
|
||||
let child_rect = self.available_rect_before_wrap();
|
||||
let next_auto_id_source = self.next_auto_id_source;
|
||||
let mut child_ui = self.child_ui(child_rect, *self.layout());
|
||||
let mut child_ui = self.child_ui_with_id_source(child_rect, *self.layout(), id_source);
|
||||
self.next_auto_id_source = next_auto_id_source; // HACK: we want `scope` to only increment this once, so that `ui.scope` is equivalent to `ui.allocate_space`.
|
||||
let ret = add_contents(&mut child_ui);
|
||||
let response = self.allocate_rect(child_ui.min_rect(), Sense::hover());
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::*;
|
||||
use emath::Rot2;
|
||||
|
||||
/// An widget to show an image of a given size.
|
||||
///
|
||||
|
@ -36,6 +37,7 @@ pub struct Image {
|
|||
bg_fill: Color32,
|
||||
tint: Color32,
|
||||
sense: Sense,
|
||||
rotation: Option<(Rot2, Vec2)>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
|
@ -47,6 +49,7 @@ impl Image {
|
|||
bg_fill: Default::default(),
|
||||
tint: Color32::WHITE,
|
||||
sense: Sense::hover(),
|
||||
rotation: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,6 +78,17 @@ impl Image {
|
|||
self.sense = sense;
|
||||
self
|
||||
}
|
||||
|
||||
/// Rotate the image about an origin by some angle
|
||||
///
|
||||
/// Positive angle is clockwise.
|
||||
/// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right).
|
||||
///
|
||||
/// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin.
|
||||
pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self {
|
||||
self.rotation = Some((Rot2::from_angle(angle), origin));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Image {
|
||||
|
@ -88,10 +102,11 @@ impl Image {
|
|||
let Self {
|
||||
texture_id,
|
||||
uv,
|
||||
size: _,
|
||||
size,
|
||||
bg_fill,
|
||||
tint,
|
||||
sense: _,
|
||||
rotation,
|
||||
} = self;
|
||||
|
||||
if *bg_fill != Default::default() {
|
||||
|
@ -104,6 +119,9 @@ impl Image {
|
|||
// TODO: builder pattern for Mesh
|
||||
let mut mesh = Mesh::with_texture(*texture_id);
|
||||
mesh.add_rect_with_uv(rect, *uv, *tint);
|
||||
if let Some((rot, origin)) = rotation {
|
||||
mesh.rotate(*rot, rect.min + *origin * *size);
|
||||
}
|
||||
ui.painter().add(Shape::mesh(mesh));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,11 +239,12 @@ impl Widget for &mut LegendWidget {
|
|||
legend_ui
|
||||
.scope(|ui| {
|
||||
let background_frame = Frame {
|
||||
margin: vec2(8.0, 4.0).into(),
|
||||
inner_margin: vec2(8.0, 4.0).into(),
|
||||
rounding: ui.style().visuals.window_rounding,
|
||||
shadow: epaint::Shadow::default(),
|
||||
fill: ui.style().visuals.extreme_bg_color,
|
||||
stroke: ui.style().visuals.window_stroke(),
|
||||
..Default::default()
|
||||
}
|
||||
.multiply_with_opacity(config.background_alpha);
|
||||
background_frame
|
||||
|
|
|
@ -8,13 +8,14 @@ use epaint::color::Hsva;
|
|||
use epaint::util::FloatOrd;
|
||||
use items::PlotItem;
|
||||
use legend::LegendWidget;
|
||||
use transform::{PlotBounds, ScreenTransform};
|
||||
use transform::ScreenTransform;
|
||||
|
||||
pub use items::{
|
||||
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape,
|
||||
Orientation, PlotImage, Points, Polygon, Text, VLine, Value, Values,
|
||||
};
|
||||
pub use legend::{Corner, Legend};
|
||||
pub use transform::PlotBounds;
|
||||
|
||||
mod items;
|
||||
mod legend;
|
||||
|
@ -767,6 +768,11 @@ impl PlotUi {
|
|||
self.response.hovered()
|
||||
}
|
||||
|
||||
/// Returns `true` if the plot was clicked by the primary button.
|
||||
pub fn plot_clicked(&self) -> bool {
|
||||
self.response.clicked()
|
||||
}
|
||||
|
||||
/// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area.
|
||||
pub fn pointer_coordinate(&self) -> Option<Value> {
|
||||
// We need to subtract the drag delta to keep in sync with the frame-delayed screen transform:
|
||||
|
|
|
@ -455,10 +455,14 @@ impl<'a> Slider<'a> {
|
|||
|
||||
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`
|
||||
let change = ui.input().num_presses(Key::ArrowUp) as i32
|
||||
+ ui.input().num_presses(Key::ArrowRight) as i32
|
||||
- ui.input().num_presses(Key::ArrowDown) as i32
|
||||
- ui.input().num_presses(Key::ArrowLeft) as i32;
|
||||
let change = {
|
||||
// Hold one lock rather than 4 (see https://github.com/emilk/egui/pull/1380).
|
||||
let input = ui.input();
|
||||
|
||||
input.num_presses(Key::ArrowUp) as i32 + input.num_presses(Key::ArrowRight) as i32
|
||||
- input.num_presses(Key::ArrowDown) as i32
|
||||
- input.num_presses(Key::ArrowLeft) as i32
|
||||
};
|
||||
let speed = match self.step {
|
||||
Some(step) if change != 0 => step,
|
||||
_ => self.current_gradient(&position_range),
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(clippy::all, rust_2018_idioms)]
|
||||
//! Demo app for egui
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use eframe::wasm_bindgen::{self, prelude::*};
|
||||
|
@ -19,6 +16,8 @@ pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> {
|
|||
// Redirect tracing to console.log and friends:
|
||||
tracing_wasm::set_as_global_default();
|
||||
|
||||
let app = egui_demo_lib::WrapApp::default();
|
||||
eframe::start_web(canvas_id, Box::new(app))
|
||||
eframe::start_web(
|
||||
canvas_id,
|
||||
Box::new(|cc| Box::new(egui_demo_lib::WrapApp::new(cc))),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
//! Demo app for egui
|
||||
|
||||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(clippy::all, rust_2018_idioms)]
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
// When compiling natively:
|
||||
fn main() {
|
||||
// Log to stdout (if you run with `RUST_LOG=debug`).
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let app = egui_demo_lib::WrapApp::default();
|
||||
let options = eframe::NativeOptions {
|
||||
// Let's show off that we support transparent windows
|
||||
transparent: true,
|
||||
drag_and_drop_support: true,
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(Box::new(app), options);
|
||||
eframe::run_native(
|
||||
"egui demo app",
|
||||
options,
|
||||
Box::new(|cc| Box::new(egui_demo_lib::WrapApp::new(cc))),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ epi = { version = "0.17.0", path = "../epi" }
|
|||
|
||||
chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] }
|
||||
enum-map = { version = "2", features = ["serde"] }
|
||||
tracing = "0.1"
|
||||
unicode_names2 = { version = "0.5.0", default-features = false }
|
||||
|
||||
# feature "http":
|
||||
|
|
|
@ -17,7 +17,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
demo_windows.ui(ctx);
|
||||
});
|
||||
ctx.tessellate(full_output.shapes)
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
c.bench_function("demo_no_tessellate", |b| {
|
||||
|
@ -25,14 +25,14 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
ctx.run(RawInput::default(), |ctx| {
|
||||
demo_windows.ui(ctx);
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
let full_output = ctx.run(RawInput::default(), |ctx| {
|
||||
demo_windows.ui(ctx);
|
||||
});
|
||||
c.bench_function("demo_only_tessellate", |b| {
|
||||
b.iter(|| ctx.tessellate(full_output.shapes.clone()))
|
||||
b.iter(|| ctx.tessellate(full_output.shapes.clone()));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
ctx.run(RawInput::default(), |ctx| {
|
||||
demo_windows.ui(ctx);
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -56,12 +56,12 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
c.bench_function("label &str", |b| {
|
||||
b.iter(|| {
|
||||
ui.label("the quick brown fox jumps over the lazy dog");
|
||||
})
|
||||
});
|
||||
});
|
||||
c.bench_function("label format!", |b| {
|
||||
b.iter(|| {
|
||||
ui.label("the quick brown fox jumps over the lazy dog".to_owned());
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -77,7 +77,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
let rect = ui.max_rect();
|
||||
b.iter(|| {
|
||||
painter.rect(rect, 2.0, egui::Color32::RED, (1.0, egui::Color32::WHITE));
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -108,7 +108,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
wrap_width,
|
||||
);
|
||||
layout(&mut locked_fonts.fonts, job.into())
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
c.bench_function("text_layout_cached", |b| {
|
||||
|
@ -119,19 +119,19 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
color,
|
||||
wrap_width,
|
||||
)
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width);
|
||||
let mut tessellator = egui::epaint::Tessellator::from_options(Default::default());
|
||||
let mut tessellator = egui::epaint::Tessellator::new(1.0, Default::default());
|
||||
let mut mesh = egui::epaint::Mesh::default();
|
||||
let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
|
||||
let font_image_size = fonts.font_image_size();
|
||||
c.bench_function("tessellate_text", |b| {
|
||||
b.iter(|| {
|
||||
tessellator.tessellate_text(font_image_size, text_shape.clone(), &mut mesh);
|
||||
tessellator.tessellate_text(font_image_size, &text_shape, &mut mesh);
|
||||
mesh.clear();
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,10 +30,6 @@ impl Default for ColorTest {
|
|||
}
|
||||
|
||||
impl epi::App for ColorTest {
|
||||
fn name(&self) -> &str {
|
||||
"🎨 Color test"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if frame.is_web() {
|
||||
|
@ -132,9 +128,9 @@ impl ColorTest {
|
|||
|
||||
ui.separator();
|
||||
|
||||
// TODO: another ground truth where we do the alpha-blending against the background also.
|
||||
// TODO: exactly the same thing, but with vertex colors (no textures)
|
||||
self.show_gradients(ui, WHITE, (TRANSPARENT, BLACK));
|
||||
self.show_gradients(ui, BLACK, (BLACK, WHITE));
|
||||
ui.separator();
|
||||
self.show_gradients(ui, WHITE, (BLACK, TRANSPARENT));
|
||||
ui.separator();
|
||||
self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE));
|
||||
ui.separator();
|
||||
|
@ -149,6 +145,10 @@ impl ColorTest {
|
|||
ui.separator();
|
||||
|
||||
pixel_test(ui);
|
||||
|
||||
ui.separator();
|
||||
|
||||
fine_line_test(ui);
|
||||
}
|
||||
|
||||
fn show_gradients(&mut self, ui: &mut Ui, bg_fill: Color32, (left, right): (Color32, Color32)) {
|
||||
|
@ -357,6 +357,12 @@ impl TextureManager {
|
|||
fn pixel_test(ui: &mut Ui) {
|
||||
ui.label("Each subsequent square should be one physical pixel larger than the previous. They should be exactly one physical pixel apart. They should be perfectly aligned to the pixel grid.");
|
||||
|
||||
let color = if ui.style().visuals.dark_mode {
|
||||
egui::Color32::WHITE
|
||||
} else {
|
||||
egui::Color32::BLACK
|
||||
};
|
||||
|
||||
let pixels_per_point = ui.ctx().pixels_per_point();
|
||||
let num_squares: u32 = 8;
|
||||
let size_pixels = Vec2::new(
|
||||
|
@ -379,7 +385,71 @@ fn pixel_test(ui: &mut Ui) {
|
|||
),
|
||||
Vec2::splat(size as f32) / pixels_per_point,
|
||||
);
|
||||
painter.rect_filled(rect_points, 0.0, egui::Color32::WHITE);
|
||||
painter.rect_filled(rect_points, 0.0, color);
|
||||
cursor_pixel.x += (1 + size) as f32;
|
||||
}
|
||||
}
|
||||
|
||||
fn fine_line_test(ui: &mut Ui) {
|
||||
ui.label("Some fine lines for testing anti-aliasing and blending:");
|
||||
|
||||
let size = Vec2::new(256.0, 512.0);
|
||||
let (response, painter) = ui.allocate_painter(size, Sense::hover());
|
||||
let rect = response.rect;
|
||||
|
||||
let mut top_half = rect;
|
||||
top_half.set_bottom(top_half.center().y);
|
||||
painter.rect_filled(top_half, 0.0, Color32::BLACK);
|
||||
paint_fine_lines(&painter, top_half, Color32::WHITE);
|
||||
|
||||
let mut bottom_half = rect;
|
||||
bottom_half.set_top(bottom_half.center().y);
|
||||
painter.rect_filled(bottom_half, 0.0, Color32::WHITE);
|
||||
paint_fine_lines(&painter, bottom_half, Color32::BLACK);
|
||||
}
|
||||
|
||||
fn paint_fine_lines(painter: &egui::Painter, mut rect: Rect, color: Color32) {
|
||||
rect = rect.shrink(12.0);
|
||||
for width in [0.5, 1.0, 2.0] {
|
||||
painter.text(
|
||||
rect.left_top(),
|
||||
Align2::CENTER_CENTER,
|
||||
width.to_string(),
|
||||
FontId::monospace(14.0),
|
||||
color,
|
||||
);
|
||||
|
||||
painter.add(egui::epaint::CubicBezierShape::from_points_stroke(
|
||||
[
|
||||
rect.left_top() + Vec2::new(16.0, 0.0),
|
||||
rect.right_top(),
|
||||
rect.right_center(),
|
||||
rect.right_bottom(),
|
||||
],
|
||||
false,
|
||||
Color32::TRANSPARENT,
|
||||
Stroke::new(width, color),
|
||||
));
|
||||
|
||||
rect.min.y += 32.0;
|
||||
rect.max.x -= 32.0;
|
||||
}
|
||||
|
||||
rect.min.y += 16.0;
|
||||
painter.text(
|
||||
rect.left_top(),
|
||||
Align2::LEFT_CENTER,
|
||||
"transparent --> opaque",
|
||||
FontId::monospace(11.0),
|
||||
color,
|
||||
);
|
||||
rect.min.y += 12.0;
|
||||
let mut mesh = Mesh::default();
|
||||
mesh.colored_vertex(rect.left_bottom(), Color32::TRANSPARENT);
|
||||
mesh.colored_vertex(rect.left_top(), Color32::TRANSPARENT);
|
||||
mesh.colored_vertex(rect.right_bottom(), color);
|
||||
mesh.colored_vertex(rect.right_top(), color);
|
||||
mesh.add_triangle(0, 1, 2);
|
||||
mesh.add_triangle(1, 2, 3);
|
||||
painter.add(mesh);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
/// Demonstrates how to make an app using egui.
|
||||
///
|
||||
/// Implements `epi::App` so it can be used with
|
||||
/// [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium), [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) and [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web).
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
|
@ -10,28 +6,6 @@ pub struct DemoApp {
|
|||
}
|
||||
|
||||
impl epi::App for DemoApp {
|
||||
fn name(&self) -> &str {
|
||||
"✨ Demos"
|
||||
}
|
||||
|
||||
fn setup(
|
||||
&mut self,
|
||||
_ctx: &egui::Context,
|
||||
_frame: &epi::Frame,
|
||||
_storage: Option<&dyn epi::Storage>,
|
||||
_gl: &std::rc::Rc<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) {
|
||||
self.demo_windows.ui(ctx);
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ impl super::View for TextEdit {
|
|||
anything_selected,
|
||||
egui::Label::new("Press ctrl+T to toggle the case of selected text (cmd+T on Mac)"),
|
||||
);
|
||||
|
||||
if ui
|
||||
.input_mut()
|
||||
.consume_key(egui::Modifiers::COMMAND, egui::Key::T)
|
||||
|
@ -82,5 +83,29 @@ impl super::View for TextEdit {
|
|||
text.insert_text(&new_text, selected_chars.start);
|
||||
}
|
||||
}
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Move cursor to the:");
|
||||
|
||||
if ui.button("start").clicked() {
|
||||
let text_edit_id = output.response.id;
|
||||
if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) {
|
||||
let ccursor = egui::text::CCursor::new(0);
|
||||
state.set_ccursor_range(Some(egui::text::CCursorRange::one(ccursor)));
|
||||
state.store(ui.ctx(), text_edit_id);
|
||||
ui.ctx().memory().request_focus(text_edit_id); // give focus back to the `TextEdit`.
|
||||
}
|
||||
}
|
||||
|
||||
if ui.button("end").clicked() {
|
||||
let text_edit_id = output.response.id;
|
||||
if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) {
|
||||
let ccursor = egui::text::CCursor::new(text.chars().count());
|
||||
state.set_ccursor_range(Some(egui::text::CCursorRange::one(ccursor)));
|
||||
state.store(ui.ctx(), text_edit_id);
|
||||
ui.ctx().memory().request_focus(text_edit_id); // give focus back to the `TextEdit`.
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,10 +33,6 @@ impl Default for FractalClock {
|
|||
}
|
||||
|
||||
impl epi::App for FractalClock {
|
||||
fn name(&self) -> &str {
|
||||
"🕑 Fractal Clock"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
egui::CentralPanel::default()
|
||||
.frame(Frame::dark_canvas(&ctx.style()))
|
||||
|
|
|
@ -54,10 +54,6 @@ impl Default for HttpApp {
|
|||
}
|
||||
|
||||
impl epi::App for HttpApp {
|
||||
fn name(&self) -> &str {
|
||||
"⬇ HTTP"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| {
|
||||
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
||||
|
@ -78,11 +74,10 @@ impl epi::App for HttpApp {
|
|||
|
||||
if trigger_fetch {
|
||||
let ctx = ctx.clone();
|
||||
let frame = frame.clone();
|
||||
let (sender, promise) = Promise::new();
|
||||
let request = ehttp::Request::get(&self.url);
|
||||
ehttp::fetch(request, move |response| {
|
||||
frame.request_repaint(); // wake up UI thread
|
||||
ctx.request_repaint(); // wake up UI thread
|
||||
let resource = response.map(|response| Resource::from_response(&ctx, response));
|
||||
sender.send(resource);
|
||||
});
|
||||
|
|
|
@ -54,10 +54,6 @@ pub struct BackendPanel {
|
|||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
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))]
|
||||
frame_history: crate::frame_history::FrameHistory,
|
||||
|
||||
|
@ -70,8 +66,6 @@ impl Default for BackendPanel {
|
|||
open: false,
|
||||
run_mode: Default::default(),
|
||||
pixels_per_point: Default::default(),
|
||||
max_size_points_ui: egui::Vec2::new(1024.0, 2048.0),
|
||||
max_size_points_active: egui::Vec2::new(1024.0, 2048.0),
|
||||
frame_history: Default::default(),
|
||||
egui_windows: Default::default(),
|
||||
}
|
||||
|
@ -157,17 +151,6 @@ impl BackendPanel {
|
|||
ui.hyperlink("https://github.com/emilk/egui");
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.add(
|
||||
egui::Slider::new(&mut self.max_size_points_ui.x, 512.0..=f32::INFINITY)
|
||||
.logarithmic(true)
|
||||
.largest_finite(8192.0)
|
||||
.text("Max width"),
|
||||
)
|
||||
.on_hover_text("Maximum width of the egui region of the web page.");
|
||||
if !ui.ctx().is_using_pointer() {
|
||||
self.max_size_points_active = self.max_size_points_ui;
|
||||
}
|
||||
}
|
||||
|
||||
show_integration_name(ui, &frame.info());
|
||||
|
|
|
@ -30,10 +30,6 @@ impl Default for EasyMarkEditor {
|
|||
}
|
||||
|
||||
impl epi::App for EasyMarkEditor {
|
||||
fn name(&self) -> &str {
|
||||
"🖹 EasyMark editor"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||
egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {
|
||||
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
||||
|
|
|
@ -2,85 +2,6 @@
|
|||
//!
|
||||
//! The demo-code is also used in benchmarks and tests.
|
||||
|
||||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::await_holding_lock,
|
||||
clippy::char_lit_as_u8,
|
||||
clippy::checked_conversions,
|
||||
clippy::dbg_macro,
|
||||
clippy::debug_assert_with_mut_call,
|
||||
clippy::disallowed_method,
|
||||
clippy::doc_markdown,
|
||||
clippy::empty_enum,
|
||||
clippy::enum_glob_use,
|
||||
clippy::exit,
|
||||
clippy::expl_impl_clone_on_copy,
|
||||
clippy::explicit_deref_methods,
|
||||
clippy::explicit_into_iter_loop,
|
||||
clippy::fallible_impl_from,
|
||||
clippy::filter_map_next,
|
||||
clippy::flat_map_option,
|
||||
clippy::float_cmp_const,
|
||||
clippy::fn_params_excessive_bools,
|
||||
clippy::from_iter_instead_of_collect,
|
||||
clippy::if_let_mutex,
|
||||
clippy::implicit_clone,
|
||||
clippy::imprecise_flops,
|
||||
clippy::inefficient_to_string,
|
||||
clippy::invalid_upcast_comparisons,
|
||||
clippy::large_digit_groups,
|
||||
clippy::large_stack_arrays,
|
||||
clippy::large_types_passed_by_value,
|
||||
clippy::let_unit_value,
|
||||
clippy::linkedlist,
|
||||
clippy::lossy_float_literal,
|
||||
clippy::macro_use_imports,
|
||||
clippy::manual_ok_or,
|
||||
clippy::map_err_ignore,
|
||||
clippy::map_flatten,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::match_on_vec_items,
|
||||
clippy::match_same_arms,
|
||||
clippy::match_wild_err_arm,
|
||||
clippy::match_wildcard_for_single_variants,
|
||||
clippy::mem_forget,
|
||||
clippy::mismatched_target_os,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::mut_mut,
|
||||
clippy::mutex_integer,
|
||||
clippy::needless_borrow,
|
||||
clippy::needless_continue,
|
||||
clippy::needless_for_each,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::option_option,
|
||||
clippy::path_buf_push_overwrite,
|
||||
clippy::ptr_as_ptr,
|
||||
clippy::ref_option_ref,
|
||||
clippy::rest_pat_in_fully_bound_structs,
|
||||
clippy::same_functions_in_if_condition,
|
||||
clippy::semicolon_if_nothing_returned,
|
||||
clippy::single_match_else,
|
||||
clippy::string_add_assign,
|
||||
clippy::string_add,
|
||||
clippy::string_lit_as_bytes,
|
||||
clippy::string_to_string,
|
||||
clippy::todo,
|
||||
clippy::trait_duplication_in_bounds,
|
||||
clippy::unimplemented,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::unused_self,
|
||||
clippy::useless_transmute,
|
||||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
|
|
|
@ -277,7 +277,7 @@ impl CodeTheme {
|
|||
ui.data().insert_persisted(selected_id, selected_tt);
|
||||
|
||||
egui::Frame::group(ui.style())
|
||||
.margin(egui::Vec2::splat(2.0))
|
||||
.inner_margin(egui::Vec2::splat(2.0))
|
||||
.show(ui, |ui| {
|
||||
// ui.group(|ui| {
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
|
||||
|
|
|
@ -12,14 +12,26 @@ pub struct 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![
|
||||
("demo", &mut self.demo as &mut dyn epi::App),
|
||||
("easymark", &mut self.easy_mark_editor as &mut dyn epi::App),
|
||||
("✨ Demos", "demo", &mut self.demo as &mut dyn epi::App),
|
||||
(
|
||||
"🖹 EasyMark editor",
|
||||
"easymark",
|
||||
&mut self.easy_mark_editor as &mut dyn epi::App,
|
||||
),
|
||||
#[cfg(feature = "http")]
|
||||
("http", &mut self.http as &mut dyn epi::App),
|
||||
("clock", &mut self.clock as &mut dyn epi::App),
|
||||
("colors", &mut self.color_test as &mut dyn epi::App),
|
||||
("⬇ HTTP", "http", &mut self.http as &mut dyn epi::App),
|
||||
(
|
||||
"🕑 Fractal Clock",
|
||||
"clock",
|
||||
&mut self.clock as &mut dyn epi::App,
|
||||
),
|
||||
(
|
||||
"🎨 Color test",
|
||||
"colors",
|
||||
&mut self.color_test as &mut dyn epi::App,
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
|
@ -37,33 +49,22 @@ pub struct WrapApp {
|
|||
dropped_files: Vec<egui::DroppedFile>,
|
||||
}
|
||||
|
||||
impl epi::App for WrapApp {
|
||||
fn name(&self) -> &str {
|
||||
"egui demo apps"
|
||||
}
|
||||
|
||||
fn setup(
|
||||
&mut self,
|
||||
_ctx: &egui::Context,
|
||||
_frame: &epi::Frame,
|
||||
_storage: Option<&dyn epi::Storage>,
|
||||
_gl: &std::rc::Rc<epi::glow::Context>,
|
||||
) {
|
||||
impl WrapApp {
|
||||
pub fn new(_cc: &epi::CreationContext<'_>) -> Self {
|
||||
#[cfg(feature = "persistence")]
|
||||
if let Some(storage) = _storage {
|
||||
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default();
|
||||
if let Some(storage) = _cc.storage {
|
||||
return epi::get_value(storage, epi::APP_KEY).unwrap_or_default();
|
||||
}
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::App for WrapApp {
|
||||
#[cfg(feature = "persistence")]
|
||||
fn save(&mut self, storage: &mut dyn epi::Storage) {
|
||||
epi::set_value(storage, epi::APP_KEY, self);
|
||||
}
|
||||
|
||||
fn max_size_points(&self) -> egui::Vec2 {
|
||||
self.backend_panel.max_size_points_active
|
||||
}
|
||||
|
||||
fn clear_color(&self) -> egui::Rgba {
|
||||
egui::Rgba::TRANSPARENT // we set a `CentralPanel` fill color in `demo_windows.rs`
|
||||
}
|
||||
|
@ -111,7 +112,7 @@ impl epi::App for WrapApp {
|
|||
|
||||
let mut found_anchor = false;
|
||||
|
||||
for (anchor, app) in self.apps.iter_mut() {
|
||||
for (_name, anchor, app) in self.apps.iter_mut() {
|
||||
if anchor == self.selected_anchor || ctx.memory().everything_is_visible() {
|
||||
app.update(ctx, frame);
|
||||
found_anchor = true;
|
||||
|
@ -138,9 +139,9 @@ impl WrapApp {
|
|||
ui.checkbox(&mut self.backend_panel.open, "💻 Backend");
|
||||
ui.separator();
|
||||
|
||||
for (anchor, app) in self.apps.iter_mut() {
|
||||
for (name, anchor, _app) in self.apps.iter_mut() {
|
||||
if ui
|
||||
.selectable_label(self.selected_anchor == anchor, app.name())
|
||||
.selectable_label(self.selected_anchor == anchor, name)
|
||||
.clicked()
|
||||
{
|
||||
self.selected_anchor = anchor.to_owned();
|
||||
|
|
|
@ -36,10 +36,7 @@ datepicker = ["chrono"]
|
|||
persistence = ["serde"]
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||
"single_threaded",
|
||||
] }
|
||||
parking_lot = "0.12"
|
||||
egui = { version = "0.17.0", path = "../egui", default-features = false }
|
||||
|
||||
# Optional dependencies:
|
||||
|
||||
|
@ -49,7 +46,7 @@ chrono = { version = "0.4", optional = true }
|
|||
# Add support for loading images with the `image` crate.
|
||||
# You also need to ALSO opt-in to the image formats you want to support, like so:
|
||||
# image = { version = "0.24", features = ["jpeg", "png"] }
|
||||
image = { version = "0.24", optional = true, default-features = false, features = [] }
|
||||
image = { version = "0.24", optional = true, default-features = false }
|
||||
|
||||
# svg feature
|
||||
resvg = { version = "0.22", optional = true }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use parking_lot::Mutex;
|
||||
use egui::mutex::Mutex;
|
||||
|
||||
/// An image to be shown in egui.
|
||||
///
|
||||
|
|
|
@ -1,84 +1,5 @@
|
|||
//! This is a crate that adds some features on top top of [`egui`](https://github.com/emilk/egui). This crate are for experimental features, and features that require big dependencies that does not belong in `egui`.
|
||||
|
||||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::await_holding_lock,
|
||||
clippy::char_lit_as_u8,
|
||||
clippy::checked_conversions,
|
||||
clippy::dbg_macro,
|
||||
clippy::debug_assert_with_mut_call,
|
||||
clippy::disallowed_method,
|
||||
clippy::doc_markdown,
|
||||
clippy::empty_enum,
|
||||
clippy::enum_glob_use,
|
||||
clippy::exit,
|
||||
clippy::expl_impl_clone_on_copy,
|
||||
clippy::explicit_deref_methods,
|
||||
clippy::explicit_into_iter_loop,
|
||||
clippy::fallible_impl_from,
|
||||
clippy::filter_map_next,
|
||||
clippy::flat_map_option,
|
||||
clippy::float_cmp_const,
|
||||
clippy::fn_params_excessive_bools,
|
||||
clippy::from_iter_instead_of_collect,
|
||||
clippy::if_let_mutex,
|
||||
clippy::implicit_clone,
|
||||
clippy::imprecise_flops,
|
||||
clippy::inefficient_to_string,
|
||||
clippy::invalid_upcast_comparisons,
|
||||
clippy::large_digit_groups,
|
||||
clippy::large_stack_arrays,
|
||||
clippy::large_types_passed_by_value,
|
||||
clippy::let_unit_value,
|
||||
clippy::linkedlist,
|
||||
clippy::lossy_float_literal,
|
||||
clippy::macro_use_imports,
|
||||
clippy::manual_ok_or,
|
||||
clippy::map_err_ignore,
|
||||
clippy::map_flatten,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::match_on_vec_items,
|
||||
clippy::match_same_arms,
|
||||
clippy::match_wild_err_arm,
|
||||
clippy::match_wildcard_for_single_variants,
|
||||
clippy::mem_forget,
|
||||
clippy::mismatched_target_os,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::mut_mut,
|
||||
clippy::mutex_integer,
|
||||
clippy::needless_borrow,
|
||||
clippy::needless_continue,
|
||||
clippy::needless_for_each,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::option_option,
|
||||
clippy::path_buf_push_overwrite,
|
||||
clippy::ptr_as_ptr,
|
||||
clippy::ref_option_ref,
|
||||
clippy::rest_pat_in_fully_bound_structs,
|
||||
clippy::same_functions_in_if_condition,
|
||||
clippy::semicolon_if_nothing_returned,
|
||||
clippy::single_match_else,
|
||||
clippy::string_add_assign,
|
||||
clippy::string_add,
|
||||
clippy::string_lit_as_bytes,
|
||||
clippy::string_to_string,
|
||||
clippy::todo,
|
||||
clippy::trait_duplication_in_bounds,
|
||||
clippy::unimplemented,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::unused_self,
|
||||
clippy::useless_transmute,
|
||||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
|
|
|
@ -47,7 +47,6 @@ screen_reader = ["egui-winit/screen_reader"]
|
|||
[dependencies]
|
||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||
"convert_bytemuck",
|
||||
"single_threaded",
|
||||
] }
|
||||
egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false }
|
||||
|
||||
|
|
|
@ -1,44 +1,9 @@
|
|||
//! Example how to use [epi::NativeTexture] with glium.
|
||||
//! Example how to use [`epi::NativeTexture`] with glium.
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use glium::glutin;
|
||||
|
||||
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
|
||||
let window_builder = glutin::window::WindowBuilder::new()
|
||||
.with_resizable(true)
|
||||
.with_inner_size(glutin::dpi::LogicalSize {
|
||||
width: 800.0,
|
||||
height: 600.0,
|
||||
})
|
||||
.with_title("egui_glium example");
|
||||
|
||||
let context_builder = glutin::ContextBuilder::new()
|
||||
.with_depth_buffer(0)
|
||||
.with_srgb(true)
|
||||
.with_stencil_buffer(0)
|
||||
.with_vsync(true);
|
||||
|
||||
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
|
||||
}
|
||||
|
||||
fn load_glium_image(png_data: &[u8]) -> glium::texture::RawImage2d<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() {
|
||||
let event_loop = glutin::event_loop::EventLoop::with_user_event();
|
||||
let display = create_display(&event_loop);
|
||||
|
@ -127,3 +92,38 @@ fn main() {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
|
||||
let window_builder = glutin::window::WindowBuilder::new()
|
||||
.with_resizable(true)
|
||||
.with_inner_size(glutin::dpi::LogicalSize {
|
||||
width: 800.0,
|
||||
height: 600.0,
|
||||
})
|
||||
.with_title("egui_glium example");
|
||||
|
||||
let context_builder = glutin::ContextBuilder::new()
|
||||
.with_depth_buffer(0)
|
||||
.with_srgb(true)
|
||||
.with_stencil_buffer(0)
|
||||
.with_vsync(true);
|
||||
|
||||
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
|
||||
}
|
||||
|
||||
fn load_glium_image(png_data: &[u8]) -> glium::texture::RawImage2d<'_, u8> {
|
||||
// Load image using the image crate:
|
||||
let image = image::load_from_memory(png_data).unwrap().to_rgba8();
|
||||
let image_dimensions = image.dimensions();
|
||||
|
||||
// Premultiply alpha:
|
||||
let pixels: Vec<_> = image
|
||||
.into_vec()
|
||||
.chunks_exact(4)
|
||||
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||
.flat_map(|color| color.to_array())
|
||||
.collect();
|
||||
|
||||
// Convert to glium image:
|
||||
glium::texture::RawImage2d::from_raw_rgba(pixels, image_dimensions)
|
||||
}
|
||||
|
|
|
@ -4,24 +4,6 @@
|
|||
|
||||
use glium::glutin;
|
||||
|
||||
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
|
||||
let window_builder = glutin::window::WindowBuilder::new()
|
||||
.with_resizable(true)
|
||||
.with_inner_size(glutin::dpi::LogicalSize {
|
||||
width: 800.0,
|
||||
height: 600.0,
|
||||
})
|
||||
.with_title("egui_glium example");
|
||||
|
||||
let context_builder = glutin::ContextBuilder::new()
|
||||
.with_depth_buffer(0)
|
||||
.with_srgb(true)
|
||||
.with_stencil_buffer(0)
|
||||
.with_vsync(true);
|
||||
|
||||
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let event_loop = glutin::event_loop::EventLoop::with_user_event();
|
||||
let display = create_display(&event_loop);
|
||||
|
@ -89,3 +71,21 @@ fn main() {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
|
||||
let window_builder = glutin::window::WindowBuilder::new()
|
||||
.with_resizable(true)
|
||||
.with_inner_size(glutin::dpi::LogicalSize {
|
||||
width: 800.0,
|
||||
height: 600.0,
|
||||
})
|
||||
.with_title("egui_glium example");
|
||||
|
||||
let context_builder = glutin::ContextBuilder::new()
|
||||
.with_depth_buffer(0)
|
||||
.with_srgb(true)
|
||||
.with_stencil_buffer(0)
|
||||
.with_vsync(true);
|
||||
|
||||
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
|
||||
}
|
||||
|
|
|
@ -2,88 +2,8 @@
|
|||
//!
|
||||
//! The main type you want to use is [`EguiGlium`].
|
||||
//!
|
||||
//! This library is an [`epi`] backend.
|
||||
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
|
||||
|
||||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::await_holding_lock,
|
||||
clippy::char_lit_as_u8,
|
||||
clippy::checked_conversions,
|
||||
clippy::dbg_macro,
|
||||
clippy::debug_assert_with_mut_call,
|
||||
clippy::disallowed_method,
|
||||
clippy::doc_markdown,
|
||||
clippy::empty_enum,
|
||||
clippy::enum_glob_use,
|
||||
clippy::exit,
|
||||
clippy::expl_impl_clone_on_copy,
|
||||
clippy::explicit_deref_methods,
|
||||
clippy::explicit_into_iter_loop,
|
||||
clippy::fallible_impl_from,
|
||||
clippy::filter_map_next,
|
||||
clippy::flat_map_option,
|
||||
clippy::float_cmp_const,
|
||||
clippy::fn_params_excessive_bools,
|
||||
clippy::from_iter_instead_of_collect,
|
||||
clippy::if_let_mutex,
|
||||
clippy::implicit_clone,
|
||||
clippy::imprecise_flops,
|
||||
clippy::inefficient_to_string,
|
||||
clippy::invalid_upcast_comparisons,
|
||||
clippy::large_digit_groups,
|
||||
clippy::large_stack_arrays,
|
||||
clippy::large_types_passed_by_value,
|
||||
clippy::let_unit_value,
|
||||
clippy::linkedlist,
|
||||
clippy::lossy_float_literal,
|
||||
clippy::macro_use_imports,
|
||||
clippy::manual_ok_or,
|
||||
clippy::map_err_ignore,
|
||||
clippy::map_flatten,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::match_on_vec_items,
|
||||
clippy::match_same_arms,
|
||||
clippy::match_wild_err_arm,
|
||||
clippy::match_wildcard_for_single_variants,
|
||||
clippy::mem_forget,
|
||||
clippy::mismatched_target_os,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::mut_mut,
|
||||
clippy::mutex_integer,
|
||||
clippy::needless_borrow,
|
||||
clippy::needless_continue,
|
||||
clippy::needless_for_each,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::option_option,
|
||||
clippy::path_buf_push_overwrite,
|
||||
clippy::ptr_as_ptr,
|
||||
clippy::ref_option_ref,
|
||||
clippy::rest_pat_in_fully_bound_structs,
|
||||
clippy::same_functions_in_if_condition,
|
||||
clippy::semicolon_if_nothing_returned,
|
||||
clippy::single_match_else,
|
||||
clippy::string_add_assign,
|
||||
clippy::string_add,
|
||||
clippy::string_lit_as_bytes,
|
||||
clippy::string_to_string,
|
||||
clippy::todo,
|
||||
clippy::trait_duplication_in_bounds,
|
||||
clippy::unimplemented,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::unused_self,
|
||||
clippy::useless_transmute,
|
||||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ All notable changes to the `egui_glow` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Improved logging on rendering failures.
|
||||
* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
|
|
|
@ -57,7 +57,6 @@ winit = ["egui-winit", "glutin"]
|
|||
[dependencies]
|
||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||
"convert_bytemuck",
|
||||
"single_threaded",
|
||||
] }
|
||||
epi = { version = "0.17.0", path = "../epi", optional = true }
|
||||
|
||||
|
|
|
@ -1,42 +1,7 @@
|
|||
//! Example how to use pure `egui_glow` without [`epi`].
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
fn create_display(
|
||||
event_loop: &glutin::event_loop::EventLoop<()>,
|
||||
) -> (
|
||||
glutin::WindowedContext<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)
|
||||
}
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
fn main() {
|
||||
let mut clear_color = [0.1, 0.1, 0.1];
|
||||
|
@ -116,3 +81,34 @@ fn main() {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn create_display(
|
||||
event_loop: &glutin::event_loop::EventLoop<()>,
|
||||
) -> (
|
||||
glutin::WindowedContext<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)
|
||||
}
|
||||
|
|
|
@ -3,16 +3,9 @@ use egui_winit::winit;
|
|||
|
||||
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)]
|
||||
fn create_display(
|
||||
native_options: &NativeOptions,
|
||||
window_builder: winit::window::WindowBuilder,
|
||||
event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>,
|
||||
) -> (
|
||||
|
@ -21,10 +14,11 @@ fn create_display(
|
|||
) {
|
||||
let gl_window = unsafe {
|
||||
glutin::ContextBuilder::new()
|
||||
.with_depth_buffer(0)
|
||||
.with_depth_buffer(native_options.depth_buffer)
|
||||
.with_multisampling(native_options.multisampling)
|
||||
.with_srgb(true)
|
||||
.with_stencil_buffer(0)
|
||||
.with_vsync(true)
|
||||
.with_stencil_buffer(native_options.stencil_buffer)
|
||||
.with_vsync(native_options.vsync)
|
||||
.build_windowed(window_builder, event_loop)
|
||||
.unwrap()
|
||||
.make_current()
|
||||
|
@ -33,11 +27,6 @@ fn create_display(
|
|||
|
||||
let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) };
|
||||
|
||||
unsafe {
|
||||
use glow::HasContext as _;
|
||||
gl.enable(glow::FRAMEBUFFER_SRGB);
|
||||
}
|
||||
|
||||
(gl_window, gl)
|
||||
}
|
||||
|
||||
|
@ -47,31 +36,43 @@ pub use epi::NativeOptions;
|
|||
|
||||
/// Run an egui app
|
||||
#[allow(unsafe_code)]
|
||||
pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
||||
let persistence = egui_winit::epi::Persistence::from_app_name(app.name());
|
||||
pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator) -> ! {
|
||||
let persistence = egui_winit::epi::Persistence::from_app_name(app_name);
|
||||
let window_settings = persistence.load_window_settings();
|
||||
let window_builder =
|
||||
egui_winit::epi::window_builder(native_options, &window_settings).with_title(app.name());
|
||||
egui_winit::epi::window_builder(native_options, &window_settings).with_title(app_name);
|
||||
let event_loop = winit::event_loop::EventLoop::with_user_event();
|
||||
let (gl_window, gl) = create_display(window_builder, &event_loop);
|
||||
let (gl_window, gl) = create_display(native_options, window_builder, &event_loop);
|
||||
let gl = std::rc::Rc::new(gl);
|
||||
|
||||
let repaint_signal = std::sync::Arc::new(GlowRepaintSignal(std::sync::Mutex::new(
|
||||
event_loop.create_proxy(),
|
||||
)));
|
||||
|
||||
let mut painter = crate::Painter::new(gl.clone(), None, "")
|
||||
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
|
||||
|
||||
let mut integration = egui_winit::epi::EpiIntegration::new(
|
||||
"egui_glow",
|
||||
painter.max_texture_side(),
|
||||
gl_window.window(),
|
||||
&gl,
|
||||
repaint_signal,
|
||||
persistence,
|
||||
app,
|
||||
);
|
||||
|
||||
{
|
||||
let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy());
|
||||
integration.egui_ctx.set_request_repaint_callback(move || {
|
||||
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
|
||||
});
|
||||
}
|
||||
|
||||
let mut app = app_creator(&epi::CreationContext {
|
||||
egui_ctx: integration.egui_ctx.clone(),
|
||||
integration_info: integration.frame.info(),
|
||||
storage: integration.persistence.storage(),
|
||||
gl: gl.clone(),
|
||||
});
|
||||
|
||||
if app.warm_up_enabled() {
|
||||
integration.warm_up(app.as_mut(), gl_window.window());
|
||||
}
|
||||
|
||||
let mut is_focused = true;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
|
@ -90,7 +91,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
needs_repaint,
|
||||
textures_delta,
|
||||
shapes,
|
||||
} = integration.update(gl_window.window());
|
||||
} = integration.update(app.as_mut(), gl_window.window());
|
||||
|
||||
integration.handle_platform_output(gl_window.window(), platform_output);
|
||||
|
||||
|
@ -98,7 +99,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
|
||||
// paint:
|
||||
{
|
||||
let color = integration.app.clear_color();
|
||||
let color = app.clear_color();
|
||||
unsafe {
|
||||
use glow::HasContext as _;
|
||||
gl.disable(glow::SCISSOR_TEST);
|
||||
|
@ -126,7 +127,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
};
|
||||
}
|
||||
|
||||
integration.maybe_autosave(gl_window.window());
|
||||
integration.maybe_autosave(app.as_mut(), gl_window.window());
|
||||
};
|
||||
|
||||
match event {
|
||||
|
@ -145,7 +146,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
gl_window.resize(physical_size);
|
||||
}
|
||||
|
||||
integration.on_event(&event);
|
||||
integration.on_event(app.as_mut(), &event);
|
||||
if integration.should_quit() {
|
||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
||||
}
|
||||
|
@ -153,7 +154,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead
|
||||
}
|
||||
winit::event::Event::LoopDestroyed => {
|
||||
integration.on_exit(gl_window.window());
|
||||
integration
|
||||
.persistence
|
||||
.save(&mut *app, &integration.egui_ctx, gl_window.window());
|
||||
app.on_exit(&gl);
|
||||
painter.destroy();
|
||||
}
|
||||
winit::event::Event::UserEvent(RequestRepaintEvent) => {
|
||||
|
|
|
@ -5,85 +5,6 @@
|
|||
//! This library is an [`epi`] backend.
|
||||
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
|
||||
|
||||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![deny(unsafe_code)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::await_holding_lock,
|
||||
clippy::char_lit_as_u8,
|
||||
clippy::checked_conversions,
|
||||
clippy::dbg_macro,
|
||||
clippy::debug_assert_with_mut_call,
|
||||
clippy::disallowed_method,
|
||||
clippy::doc_markdown,
|
||||
clippy::empty_enum,
|
||||
clippy::enum_glob_use,
|
||||
clippy::exit,
|
||||
clippy::expl_impl_clone_on_copy,
|
||||
clippy::explicit_deref_methods,
|
||||
clippy::explicit_into_iter_loop,
|
||||
clippy::fallible_impl_from,
|
||||
clippy::filter_map_next,
|
||||
clippy::flat_map_option,
|
||||
clippy::float_cmp_const,
|
||||
clippy::fn_params_excessive_bools,
|
||||
clippy::from_iter_instead_of_collect,
|
||||
clippy::if_let_mutex,
|
||||
clippy::implicit_clone,
|
||||
clippy::imprecise_flops,
|
||||
clippy::inefficient_to_string,
|
||||
clippy::invalid_upcast_comparisons,
|
||||
clippy::large_digit_groups,
|
||||
clippy::large_stack_arrays,
|
||||
clippy::large_types_passed_by_value,
|
||||
clippy::let_unit_value,
|
||||
clippy::linkedlist,
|
||||
clippy::lossy_float_literal,
|
||||
clippy::macro_use_imports,
|
||||
clippy::manual_ok_or,
|
||||
clippy::map_err_ignore,
|
||||
clippy::map_flatten,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::match_on_vec_items,
|
||||
clippy::match_same_arms,
|
||||
clippy::match_wild_err_arm,
|
||||
clippy::match_wildcard_for_single_variants,
|
||||
clippy::mem_forget,
|
||||
clippy::mismatched_target_os,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::mut_mut,
|
||||
clippy::mutex_integer,
|
||||
clippy::needless_borrow,
|
||||
clippy::needless_continue,
|
||||
clippy::needless_for_each,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::option_option,
|
||||
clippy::path_buf_push_overwrite,
|
||||
clippy::ptr_as_ptr,
|
||||
clippy::ref_option_ref,
|
||||
clippy::rest_pat_in_fully_bound_structs,
|
||||
clippy::same_functions_in_if_condition,
|
||||
clippy::semicolon_if_nothing_returned,
|
||||
clippy::single_match_else,
|
||||
clippy::string_add_assign,
|
||||
clippy::string_add,
|
||||
clippy::string_lit_as_bytes,
|
||||
clippy::string_to_string,
|
||||
clippy::todo,
|
||||
clippy::trait_duplication_in_bounds,
|
||||
clippy::unimplemented,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::unused_self,
|
||||
clippy::useless_transmute,
|
||||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
|
@ -93,7 +14,7 @@ pub use painter::Painter;
|
|||
mod misc_util;
|
||||
mod post_process;
|
||||
mod shader_version;
|
||||
mod vao_emulate;
|
||||
mod vao;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
|
||||
pub mod winit;
|
||||
|
@ -105,3 +26,62 @@ mod epi_backend;
|
|||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
|
||||
pub use epi_backend::{run, NativeOptions};
|
||||
|
||||
/// Check for OpenGL error and report it using `tracing::error`.
|
||||
///
|
||||
/// ``` no_run
|
||||
/// # let glow_context = todo!();
|
||||
/// use egui_glow::check_for_gl_error;
|
||||
/// check_for_gl_error!(glow_context);
|
||||
/// check_for_gl_error!(glow_context, "during painting");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! check_for_gl_error {
|
||||
($gl: expr) => {{
|
||||
$crate::check_for_gl_error_impl($gl, file!(), line!(), "")
|
||||
}};
|
||||
($gl: expr, $context: literal) => {{
|
||||
$crate::check_for_gl_error_impl($gl, file!(), line!(), $context)
|
||||
}};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn check_for_gl_error_impl(gl: &glow::Context, file: &str, line: u32, context: &str) {
|
||||
use glow::HasContext as _;
|
||||
#[allow(unsafe_code)]
|
||||
let error_code = unsafe { gl.get_error() };
|
||||
if error_code != glow::NO_ERROR {
|
||||
let error_str = match error_code {
|
||||
glow::INVALID_ENUM => "GL_INVALID_ENUM",
|
||||
glow::INVALID_VALUE => "GL_INVALID_VALUE",
|
||||
glow::INVALID_OPERATION => "GL_INVALID_OPERATION",
|
||||
glow::STACK_OVERFLOW => "GL_STACK_OVERFLOW",
|
||||
glow::STACK_UNDERFLOW => "GL_STACK_UNDERFLOW",
|
||||
glow::OUT_OF_MEMORY => "GL_OUT_OF_MEMORY",
|
||||
glow::INVALID_FRAMEBUFFER_OPERATION => "GL_INVALID_FRAMEBUFFER_OPERATION",
|
||||
glow::CONTEXT_LOST => "GL_CONTEXT_LOST",
|
||||
0x8031 => "GL_TABLE_TOO_LARGE1",
|
||||
0x9242 => "CONTEXT_LOST_WEBGL",
|
||||
_ => "<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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,6 @@
|
|||
#![allow(unsafe_code)]
|
||||
use glow::HasContext;
|
||||
use std::option::Option::Some;
|
||||
|
||||
pub fn check_for_gl_error(gl: &glow::Context, context: &str) {
|
||||
let error_code = unsafe { gl.get_error() };
|
||||
if error_code != glow::NO_ERROR {
|
||||
tracing::error!(
|
||||
"GL error, at: '{}', code: {} (0x{:X})",
|
||||
context,
|
||||
error_code,
|
||||
error_code
|
||||
);
|
||||
}
|
||||
}
|
||||
use glow::HasContext as _;
|
||||
|
||||
pub(crate) unsafe fn compile_shader(
|
||||
gl: &glow::Context,
|
||||
|
@ -50,105 +38,3 @@ pub(crate) unsafe fn link_program<'a, T: IntoIterator<Item = &'a glow::Shader>>(
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,14 @@ use egui::{
|
|||
emath::Rect,
|
||||
epaint::{Color32, Mesh, Primitive, Vertex},
|
||||
};
|
||||
use glow::HasContext;
|
||||
use glow::HasContext as _;
|
||||
use memoffset::offset_of;
|
||||
|
||||
use crate::misc_util::{check_for_gl_error, compile_shader, link_program};
|
||||
use crate::check_for_gl_error;
|
||||
use crate::misc_util::{compile_shader, link_program};
|
||||
use crate::post_process::PostProcess;
|
||||
use crate::shader_version::ShaderVersion;
|
||||
use crate::vao_emulate;
|
||||
use crate::vao;
|
||||
|
||||
pub use glow::Context;
|
||||
|
||||
|
@ -36,12 +37,12 @@ pub struct Painter {
|
|||
u_sampler: glow::UniformLocation,
|
||||
is_webgl_1: bool,
|
||||
is_embedded: bool,
|
||||
vertex_array: crate::misc_util::VAO,
|
||||
vao: crate::vao::VertexArrayObject,
|
||||
srgb_support: bool,
|
||||
/// The filter used for subsequent textures.
|
||||
texture_filter: TextureFilter,
|
||||
post_process: Option<PostProcess>,
|
||||
vertex_buffer: glow::Buffer,
|
||||
vbo: glow::Buffer,
|
||||
element_array_buffer: glow::Buffer,
|
||||
|
||||
textures: HashMap<egui::TextureId, glow::Texture>,
|
||||
|
@ -95,11 +96,10 @@ impl Painter {
|
|||
pp_fb_extent: Option<[i32; 2]>,
|
||||
shader_prefix: &str,
|
||||
) -> 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 support_vao = crate::misc_util::supports_vao(&gl);
|
||||
let shader_version = ShaderVersion::get(&gl);
|
||||
let is_webgl_1 = shader_version == ShaderVersion::Es100;
|
||||
let header = shader_version.version();
|
||||
|
@ -117,7 +117,6 @@ impl Painter {
|
|||
Some(PostProcess::new(
|
||||
gl.clone(),
|
||||
shader_prefix,
|
||||
support_vao,
|
||||
is_webgl_1,
|
||||
width,
|
||||
height,
|
||||
|
@ -168,48 +167,45 @@ impl Painter {
|
|||
gl.delete_shader(frag);
|
||||
let u_screen_size = gl.get_uniform_location(program, "u_screen_size").unwrap();
|
||||
let u_sampler = gl.get_uniform_location(program, "u_sampler").unwrap();
|
||||
let vertex_buffer = gl.create_buffer()?;
|
||||
let element_array_buffer = gl.create_buffer()?;
|
||||
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
|
||||
|
||||
let vbo = gl.create_buffer()?;
|
||||
|
||||
let a_pos_loc = gl.get_attrib_location(program, "a_pos").unwrap();
|
||||
let a_tc_loc = gl.get_attrib_location(program, "a_tc").unwrap();
|
||||
let a_srgba_loc = gl.get_attrib_location(program, "a_srgba").unwrap();
|
||||
let mut vertex_array = if support_vao {
|
||||
crate::misc_util::VAO::native(&gl)
|
||||
} else {
|
||||
crate::misc_util::VAO::emulated()
|
||||
};
|
||||
vertex_array.bind_vertex_array(&gl);
|
||||
vertex_array.bind_buffer(&gl, &vertex_buffer);
|
||||
|
||||
let stride = std::mem::size_of::<Vertex>() as i32;
|
||||
let position_buffer_info = vao_emulate::BufferInfo {
|
||||
let buffer_infos = vec![
|
||||
vao::BufferInfo {
|
||||
location: a_pos_loc,
|
||||
vector_size: 2,
|
||||
data_type: glow::FLOAT,
|
||||
normalized: false,
|
||||
stride,
|
||||
offset: offset_of!(Vertex, pos) as i32,
|
||||
};
|
||||
let tex_coord_buffer_info = vao_emulate::BufferInfo {
|
||||
},
|
||||
vao::BufferInfo {
|
||||
location: a_tc_loc,
|
||||
vector_size: 2,
|
||||
data_type: glow::FLOAT,
|
||||
normalized: false,
|
||||
stride,
|
||||
offset: offset_of!(Vertex, uv) as i32,
|
||||
};
|
||||
let color_buffer_info = vao_emulate::BufferInfo {
|
||||
},
|
||||
vao::BufferInfo {
|
||||
location: a_srgba_loc,
|
||||
vector_size: 4,
|
||||
data_type: glow::UNSIGNED_BYTE,
|
||||
normalized: false,
|
||||
stride,
|
||||
offset: offset_of!(Vertex, color) as i32,
|
||||
};
|
||||
vertex_array.add_new_attribute(&gl, position_buffer_info);
|
||||
vertex_array.add_new_attribute(&gl, tex_coord_buffer_info);
|
||||
vertex_array.add_new_attribute(&gl, color_buffer_info);
|
||||
check_for_gl_error(&gl, "after Painter::new");
|
||||
},
|
||||
];
|
||||
let vao = crate::vao::VertexArrayObject::new(&gl, vbo, buffer_infos);
|
||||
|
||||
let element_array_buffer = gl.create_buffer()?;
|
||||
|
||||
check_for_gl_error!(&gl, "after Painter::new");
|
||||
|
||||
Ok(Painter {
|
||||
gl,
|
||||
|
@ -219,11 +215,11 @@ impl Painter {
|
|||
u_sampler,
|
||||
is_webgl_1,
|
||||
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
|
||||
vertex_array,
|
||||
vao,
|
||||
srgb_support,
|
||||
texture_filter: Default::default(),
|
||||
post_process,
|
||||
vertex_buffer,
|
||||
vbo,
|
||||
element_array_buffer,
|
||||
textures: Default::default(),
|
||||
#[cfg(feature = "epi")]
|
||||
|
@ -251,9 +247,13 @@ impl Painter {
|
|||
self.gl.enable(glow::SCISSOR_TEST);
|
||||
// egui outputs mesh in both winding orders
|
||||
self.gl.disable(glow::CULL_FACE);
|
||||
self.gl.disable(glow::DEPTH_TEST);
|
||||
|
||||
self.gl.color_mask(true, true, true, true);
|
||||
|
||||
self.gl.enable(glow::BLEND);
|
||||
self.gl.blend_equation(glow::FUNC_ADD);
|
||||
self.gl
|
||||
.blend_equation_separate(glow::FUNC_ADD, glow::FUNC_ADD);
|
||||
self.gl.blend_func_separate(
|
||||
// egui outputs colors with premultiplied alpha:
|
||||
glow::ONE,
|
||||
|
@ -264,6 +264,11 @@ impl Painter {
|
|||
glow::ONE,
|
||||
);
|
||||
|
||||
if !cfg!(target_arch = "wasm32") {
|
||||
self.gl.enable(glow::FRAMEBUFFER_SRGB);
|
||||
check_for_gl_error!(&self.gl, "FRAMEBUFFER_SRGB");
|
||||
}
|
||||
|
||||
let width_in_points = width_in_pixels as f32 / pixels_per_point;
|
||||
let height_in_points = height_in_pixels as f32 / pixels_per_point;
|
||||
|
||||
|
@ -275,11 +280,13 @@ impl Painter {
|
|||
.uniform_2_f32(Some(&self.u_screen_size), width_in_points, height_in_points);
|
||||
self.gl.uniform_1_i32(Some(&self.u_sampler), 0);
|
||||
self.gl.active_texture(glow::TEXTURE0);
|
||||
self.vertex_array.bind_vertex_array(&self.gl);
|
||||
|
||||
self.vao.bind(&self.gl);
|
||||
self.gl
|
||||
.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
|
||||
|
||||
check_for_gl_error!(&self.gl, "prepare_painting");
|
||||
|
||||
(width_in_pixels, height_in_pixels)
|
||||
}
|
||||
|
||||
|
@ -368,7 +375,15 @@ impl Painter {
|
|||
);
|
||||
}
|
||||
|
||||
callback.call(self);
|
||||
let info = egui::PaintCallbackInfo {
|
||||
rect: callback.rect,
|
||||
pixels_per_point,
|
||||
screen_size_px: inner_size,
|
||||
};
|
||||
|
||||
callback.call(&info, self);
|
||||
|
||||
check_for_gl_error!(&self.gl, "callback");
|
||||
|
||||
// Restore state:
|
||||
unsafe {
|
||||
|
@ -381,8 +396,9 @@ impl Painter {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
self.vertex_array.unbind_vertex_array(&self.gl);
|
||||
self.vao.unbind(&self.gl);
|
||||
self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
||||
|
||||
if let Some(ref post_process) = self.post_process {
|
||||
|
@ -391,7 +407,7 @@ impl Painter {
|
|||
|
||||
self.gl.disable(glow::SCISSOR_TEST);
|
||||
|
||||
check_for_gl_error(&self.gl, "painting");
|
||||
check_for_gl_error!(&self.gl, "painting");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -400,8 +416,7 @@ impl Painter {
|
|||
debug_assert!(mesh.is_valid());
|
||||
if let Some(texture) = self.get_texture(mesh.texture_id) {
|
||||
unsafe {
|
||||
self.gl
|
||||
.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
|
||||
self.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
|
||||
self.gl.buffer_data_u8_slice(
|
||||
glow::ARRAY_BUFFER,
|
||||
bytemuck::cast_slice(&mesh.vertices),
|
||||
|
@ -427,6 +442,8 @@ impl Painter {
|
|||
0,
|
||||
);
|
||||
}
|
||||
|
||||
check_for_gl_error!(&self.gl, "paint_mesh");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -485,7 +502,20 @@ impl Painter {
|
|||
|
||||
fn upload_texture_srgb(&mut self, pos: Option<[usize; 2]>, [w, h]: [usize; 2], data: &[u8]) {
|
||||
assert_eq!(data.len(), w * h * 4);
|
||||
assert!(w >= 1 && h >= 1);
|
||||
assert!(
|
||||
w >= 1 && h >= 1,
|
||||
"Got a texture image of size {}x{}. A texture must at least be one texel wide.",
|
||||
w,
|
||||
h
|
||||
);
|
||||
assert!(
|
||||
w <= self.max_texture_side && h <= self.max_texture_side,
|
||||
"Got a texture image of size {}x{}, but the maximum supported texture side is only {}",
|
||||
w,
|
||||
h,
|
||||
self.max_texture_side
|
||||
);
|
||||
|
||||
unsafe {
|
||||
self.gl.tex_parameter_i32(
|
||||
glow::TEXTURE_2D,
|
||||
|
@ -508,7 +538,7 @@ impl Painter {
|
|||
glow::TEXTURE_WRAP_T,
|
||||
glow::CLAMP_TO_EDGE as i32,
|
||||
);
|
||||
check_for_gl_error(&self.gl, "tex_parameter");
|
||||
check_for_gl_error!(&self.gl, "tex_parameter");
|
||||
|
||||
let (internal_format, src_format) = if self.is_webgl_1 {
|
||||
let format = if self.srgb_support {
|
||||
|
@ -536,7 +566,7 @@ impl Painter {
|
|||
glow::UNSIGNED_BYTE,
|
||||
glow::PixelUnpackData::Slice(data),
|
||||
);
|
||||
check_for_gl_error(&self.gl, "tex_sub_image_2d");
|
||||
check_for_gl_error!(&self.gl, "tex_sub_image_2d");
|
||||
} else {
|
||||
let border = 0;
|
||||
self.gl.tex_image_2d(
|
||||
|
@ -550,7 +580,7 @@ impl Painter {
|
|||
glow::UNSIGNED_BYTE,
|
||||
Some(data),
|
||||
);
|
||||
check_for_gl_error(&self.gl, "tex_image_2d");
|
||||
check_for_gl_error!(&self.gl, "tex_image_2d");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -571,7 +601,7 @@ impl Painter {
|
|||
for tex in self.textures.values() {
|
||||
self.gl.delete_texture(*tex);
|
||||
}
|
||||
self.gl.delete_buffer(self.vertex_buffer);
|
||||
self.gl.delete_buffer(self.vbo);
|
||||
self.gl.delete_buffer(self.element_array_buffer);
|
||||
for t in &self.textures_to_destroy {
|
||||
self.gl.delete_texture(*t);
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#![allow(unsafe_code)]
|
||||
use crate::misc_util::{check_for_gl_error, compile_shader, link_program};
|
||||
use crate::vao_emulate::BufferInfo;
|
||||
use glow::HasContext;
|
||||
use crate::check_for_gl_error;
|
||||
use crate::misc_util::{compile_shader, link_program};
|
||||
use crate::vao::BufferInfo;
|
||||
use glow::HasContext as _;
|
||||
|
||||
/// Uses a framebuffer to render everything in linear color space and convert it back to `sRGB`
|
||||
/// in a separate "post processing" step
|
||||
|
@ -9,7 +10,7 @@ pub(crate) struct PostProcess {
|
|||
gl: std::rc::Rc<glow::Context>,
|
||||
pos_buffer: glow::Buffer,
|
||||
index_buffer: glow::Buffer,
|
||||
vertex_array: crate::misc_util::VAO,
|
||||
vao: crate::vao::VertexArrayObject,
|
||||
is_webgl_1: bool,
|
||||
texture: glow::Texture,
|
||||
texture_size: (i32, i32),
|
||||
|
@ -21,7 +22,6 @@ impl PostProcess {
|
|||
pub(crate) unsafe fn new(
|
||||
gl: std::rc::Rc<glow::Context>,
|
||||
shader_prefix: &str,
|
||||
need_to_emulate_vao: bool,
|
||||
is_webgl_1: bool,
|
||||
width: i32,
|
||||
height: i32,
|
||||
|
@ -77,7 +77,7 @@ impl PostProcess {
|
|||
glow::UNSIGNED_BYTE,
|
||||
None,
|
||||
);
|
||||
check_for_gl_error(&gl, "post process texture initialization");
|
||||
check_for_gl_error!(&gl, "post process texture initialization");
|
||||
|
||||
gl.framebuffer_texture_2d(
|
||||
glow::FRAMEBUFFER,
|
||||
|
@ -124,35 +124,31 @@ impl PostProcess {
|
|||
let a_pos_loc = gl
|
||||
.get_attrib_location(program, "a_pos")
|
||||
.ok_or_else(|| "failed to get location of a_pos".to_string())?;
|
||||
let mut vertex_array = if need_to_emulate_vao {
|
||||
crate::misc_util::VAO::emulated()
|
||||
} else {
|
||||
crate::misc_util::VAO::native(&gl)
|
||||
};
|
||||
vertex_array.bind_vertex_array(&gl);
|
||||
vertex_array.bind_buffer(&gl, &pos_buffer);
|
||||
let buffer_info_a_pos = BufferInfo {
|
||||
let vao = crate::vao::VertexArrayObject::new(
|
||||
&gl,
|
||||
pos_buffer,
|
||||
vec![BufferInfo {
|
||||
location: a_pos_loc,
|
||||
vector_size: 2,
|
||||
data_type: glow::FLOAT,
|
||||
normalized: false,
|
||||
stride: 0,
|
||||
offset: 0,
|
||||
};
|
||||
vertex_array.add_new_attribute(&gl, buffer_info_a_pos);
|
||||
}],
|
||||
);
|
||||
|
||||
let index_buffer = gl.create_buffer()?;
|
||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer));
|
||||
gl.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, &indices, glow::STATIC_DRAW);
|
||||
|
||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
||||
check_for_gl_error(&gl, "post process initialization");
|
||||
check_for_gl_error!(&gl, "post process initialization");
|
||||
|
||||
Ok(PostProcess {
|
||||
gl,
|
||||
pos_buffer,
|
||||
index_buffer,
|
||||
vertex_array,
|
||||
vao,
|
||||
is_webgl_1,
|
||||
texture,
|
||||
texture_size: (width, height),
|
||||
|
@ -190,6 +186,8 @@ impl PostProcess {
|
|||
self.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo));
|
||||
self.gl.clear_color(0.0, 0.0, 0.0, 0.0);
|
||||
self.gl.clear(glow::COLOR_BUFFER_BIT);
|
||||
|
||||
check_for_gl_error!(&self.gl, "PostProcess::begin");
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn bind(&self) {
|
||||
|
@ -209,16 +207,18 @@ impl PostProcess {
|
|||
.get_uniform_location(self.program, "u_sampler")
|
||||
.unwrap();
|
||||
self.gl.uniform_1_i32(Some(&u_sampler_loc), 0);
|
||||
self.vertex_array.bind_vertex_array(&self.gl);
|
||||
self.vao.bind(&self.gl);
|
||||
|
||||
self.gl
|
||||
.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer));
|
||||
self.gl
|
||||
.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0);
|
||||
self.vertex_array.unbind_vertex_array(&self.gl);
|
||||
self.vao.unbind(&self.gl);
|
||||
self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
||||
self.gl.bind_texture(glow::TEXTURE_2D, None);
|
||||
self.gl.use_program(None);
|
||||
|
||||
check_for_gl_error!(&self.gl, "PostProcess::end");
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn destroy(&self) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#![allow(unsafe_code)]
|
||||
|
||||
use glow::HasContext;
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -14,6 +13,7 @@ pub(crate) enum ShaderVersion {
|
|||
|
||||
impl ShaderVersion {
|
||||
pub(crate) fn get(gl: &glow::Context) -> Self {
|
||||
use glow::HasContext as _;
|
||||
let shading_lang_string =
|
||||
unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) };
|
||||
let shader_version = Self::parse(&shading_lang_string);
|
||||
|
|
154
egui_glow/src/vao.rs
Normal file
154
egui_glow/src/vao.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,9 @@ All notable changes to the `egui_web` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306))
|
||||
* egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306)).
|
||||
* Remove the "webgl" feature. `egui_web` now always use `glow` (which in turn wraps WebGL) ([#1356](https://github.com/emilk/egui/pull/1356)).
|
||||
* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)).
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
|
|
|
@ -43,7 +43,6 @@ screen_reader = ["tts"]
|
|||
[dependencies]
|
||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||
"convert_bytemuck",
|
||||
"single_threaded",
|
||||
"tracing",
|
||||
] }
|
||||
egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false }
|
||||
|
|
|
@ -50,12 +50,6 @@ impl NeedRepaint {
|
|||
}
|
||||
}
|
||||
|
||||
impl epi::backend::RepaintSignal for NeedRepaint {
|
||||
fn request_repaint(&self) {
|
||||
self.0.store(true, SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
fn web_location() -> epi::Location {
|
||||
|
@ -72,7 +66,7 @@ fn web_location() -> epi::Location {
|
|||
|
||||
let query_map = parse_query_map(&query)
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
|
||||
.collect();
|
||||
|
||||
epi::Location {
|
||||
|
@ -145,13 +139,11 @@ pub struct AppRunner {
|
|||
}
|
||||
|
||||
impl AppRunner {
|
||||
pub fn new(canvas_id: &str, app: Box<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 prefer_dark_mode = crate::prefer_dark_mode();
|
||||
|
||||
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
||||
|
||||
let frame = epi::Frame::new(epi::backend::FrameData {
|
||||
info: epi::IntegrationInfo {
|
||||
name: "egui_web",
|
||||
|
@ -163,10 +155,19 @@ impl AppRunner {
|
|||
native_pixels_per_point: Some(native_pixels_per_point()),
|
||||
},
|
||||
output: Default::default(),
|
||||
repaint_signal: needs_repaint.clone(),
|
||||
});
|
||||
|
||||
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
||||
|
||||
let egui_ctx = egui::Context::default();
|
||||
|
||||
{
|
||||
let needs_repaint = needs_repaint.clone();
|
||||
egui_ctx.set_request_repaint_callback(move || {
|
||||
needs_repaint.0.store(true, SeqCst);
|
||||
});
|
||||
}
|
||||
|
||||
load_memory(&egui_ctx);
|
||||
if prefer_dark_mode == Some(true) {
|
||||
egui_ctx.set_visuals(egui::Visuals::dark());
|
||||
|
@ -176,6 +177,13 @@ impl AppRunner {
|
|||
|
||||
let storage = LocalStorage::default();
|
||||
|
||||
let app = app_creator(&epi::CreationContext {
|
||||
egui_ctx: egui_ctx.clone(),
|
||||
integration_info: frame.info(),
|
||||
storage: Some(&storage),
|
||||
gl: painter.painter.gl().clone(),
|
||||
});
|
||||
|
||||
let mut runner = Self {
|
||||
frame,
|
||||
egui_ctx,
|
||||
|
@ -193,11 +201,6 @@ impl AppRunner {
|
|||
|
||||
runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side());
|
||||
|
||||
let gl = runner.painter.painter.gl();
|
||||
runner
|
||||
.app
|
||||
.setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage), gl);
|
||||
|
||||
Ok(runner)
|
||||
}
|
||||
|
||||
|
@ -326,8 +329,8 @@ impl AppRunner {
|
|||
|
||||
/// Install event listeners to register different input events
|
||||
/// and start running the given app.
|
||||
pub fn start(canvas_id: &str, app: Box<dyn epi::App>) -> Result<AppRunnerRef, JsValue> {
|
||||
let mut runner = AppRunner::new(canvas_id, app)?;
|
||||
pub fn start(canvas_id: &str, app_creator: epi::AppCreator) -> Result<AppRunnerRef, JsValue> {
|
||||
let mut runner = AppRunner::new(canvas_id, app_creator)?;
|
||||
runner.warm_up()?;
|
||||
start_runner(runner)
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ impl WrappedGlowPainter {
|
|||
|
||||
pub fn clear(&mut self, clear_color: Rgba) {
|
||||
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
||||
egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color)
|
||||
egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color);
|
||||
}
|
||||
|
||||
pub fn paint_primitives(
|
||||
|
|
|
@ -3,16 +3,8 @@
|
|||
//! This library is an [`epi`] backend.
|
||||
//!
|
||||
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
|
||||
//!
|
||||
//! ## Specifying the size of the egui canvas
|
||||
//! For performance reasons (on some browsers) the egui canvas does not, by default,
|
||||
//! fill the whole width of the browser.
|
||||
//! This can be changed by overriding [`epi::App::max_size_points`].
|
||||
|
||||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(clippy::all, rustdoc::missing_crate_level_docs, rust_2018_idioms)]
|
||||
#![allow(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>`
|
||||
|
||||
pub mod backend;
|
||||
mod glow_wrapping;
|
||||
|
@ -896,11 +888,11 @@ pub(crate) fn webgl1_requires_brightening(gl: &web_sys::WebGlRenderingContext) -
|
|||
!user_agent.contains("Mac OS X") && crate::is_safari_and_webkit_gtk(gl)
|
||||
}
|
||||
|
||||
/// detecting Safari and webkitGTK.
|
||||
/// detecting Safari and `webkitGTK`.
|
||||
///
|
||||
/// Safari and webkitGTK use unmasked renderer :Apple GPU
|
||||
/// Safari and `webkitGTK` use unmasked renderer :Apple GPU
|
||||
///
|
||||
/// If we detect safari or webkitGTK returns true.
|
||||
/// If we detect safari or `webkitGTKs` returns true.
|
||||
///
|
||||
/// This function used to avoid displaying linear color with `sRGB` supported systems.
|
||||
fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool {
|
||||
|
|
|
@ -29,6 +29,7 @@ impl Default for ScreenReader {
|
|||
|
||||
impl ScreenReader {
|
||||
#[cfg(not(feature = "screen_reader"))]
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn speak(&mut self, _text: &str) {}
|
||||
|
||||
#[cfg(feature = "screen_reader")]
|
||||
|
|
|
@ -161,7 +161,7 @@ pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> {
|
|||
|
||||
let delta = delta.max(-keyboard_fraction); // Don't move it crazy much
|
||||
|
||||
let new_pos_percent = (delta * 100.0).round().to_string() + "%";
|
||||
let new_pos_percent = format!("{}%", (delta * 100.0).round());
|
||||
|
||||
canvas_style.set_property("position", "absolute").ok()?;
|
||||
canvas_style.set_property("top", &new_pos_percent).ok()?;
|
||||
|
@ -217,8 +217,8 @@ pub fn move_text_cursor(cursor: Option<egui::Pos2>, canvas_id: &str) -> Option<(
|
|||
let x = (x - canvas.offset_width() as f32 / 2.0)
|
||||
.min(canvas.client_width() as f32 - bounding_rect.width() as f32);
|
||||
style.set_property("position", "absolute").ok()?;
|
||||
style.set_property("top", &(y.to_string() + "px")).ok()?;
|
||||
style.set_property("left", &(x.to_string() + "px")).ok()
|
||||
style.set_property("top", &format!("{}px", y)).ok()?;
|
||||
style.set_property("left", &format!("{}px", x)).ok()
|
||||
})
|
||||
} else {
|
||||
style.set_property("position", "absolute").ok()?;
|
||||
|
|
|
@ -15,85 +15,6 @@
|
|||
//! For that, use something else ([`glam`](https://docs.rs/glam), [`nalgebra`](https://docs.rs/nalgebra), …)
|
||||
//! and enable the `mint` feature flag in `emath` to enable implicit conversion to/from `emath`.
|
||||
|
||||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::await_holding_lock,
|
||||
clippy::char_lit_as_u8,
|
||||
clippy::checked_conversions,
|
||||
clippy::dbg_macro,
|
||||
clippy::debug_assert_with_mut_call,
|
||||
clippy::disallowed_method,
|
||||
clippy::doc_markdown,
|
||||
clippy::empty_enum,
|
||||
clippy::enum_glob_use,
|
||||
clippy::exit,
|
||||
clippy::expl_impl_clone_on_copy,
|
||||
clippy::explicit_deref_methods,
|
||||
clippy::explicit_into_iter_loop,
|
||||
clippy::fallible_impl_from,
|
||||
clippy::filter_map_next,
|
||||
clippy::flat_map_option,
|
||||
clippy::float_cmp_const,
|
||||
clippy::fn_params_excessive_bools,
|
||||
clippy::from_iter_instead_of_collect,
|
||||
clippy::if_let_mutex,
|
||||
clippy::implicit_clone,
|
||||
clippy::imprecise_flops,
|
||||
clippy::inefficient_to_string,
|
||||
clippy::invalid_upcast_comparisons,
|
||||
clippy::large_digit_groups,
|
||||
clippy::large_stack_arrays,
|
||||
clippy::large_types_passed_by_value,
|
||||
clippy::let_unit_value,
|
||||
clippy::linkedlist,
|
||||
clippy::lossy_float_literal,
|
||||
clippy::macro_use_imports,
|
||||
clippy::manual_ok_or,
|
||||
clippy::map_err_ignore,
|
||||
clippy::map_flatten,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::match_on_vec_items,
|
||||
clippy::match_same_arms,
|
||||
clippy::match_wild_err_arm,
|
||||
clippy::match_wildcard_for_single_variants,
|
||||
clippy::mem_forget,
|
||||
clippy::mismatched_target_os,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::mut_mut,
|
||||
clippy::mutex_integer,
|
||||
clippy::needless_borrow,
|
||||
clippy::needless_continue,
|
||||
clippy::needless_for_each,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::option_option,
|
||||
clippy::path_buf_push_overwrite,
|
||||
clippy::ptr_as_ptr,
|
||||
clippy::ref_option_ref,
|
||||
clippy::rest_pat_in_fully_bound_structs,
|
||||
clippy::same_functions_in_if_condition,
|
||||
clippy::semicolon_if_nothing_returned,
|
||||
clippy::single_match_else,
|
||||
clippy::string_add_assign,
|
||||
clippy::string_add,
|
||||
clippy::string_lit_as_bytes,
|
||||
clippy::string_to_string,
|
||||
clippy::todo,
|
||||
clippy::trait_duplication_in_bounds,
|
||||
clippy::unimplemented,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::unused_self,
|
||||
clippy::useless_transmute,
|
||||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@ All notable changes to the epaint crate will be documented in this file.
|
|||
|
||||
## Unreleased
|
||||
* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||
* Removed the `single_threaded/multi_threaded` flags - epaint is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)).
|
||||
* `Tessellator::from_options` is now `Tessellator::new` ([#1408](https://github.com/emilk/egui/pull/1408)).
|
||||
* Renamed `TessellationOptions::anti_alias` to `feathering` ([#1408](https://github.com/emilk/egui/pull/1408)).
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
|
|
|
@ -27,7 +27,7 @@ all-features = true
|
|||
|
||||
|
||||
[features]
|
||||
default = ["default_fonts", "multi_threaded"]
|
||||
default = ["default_fonts"]
|
||||
|
||||
# implement bytemuck on most types.
|
||||
convert_bytemuck = ["bytemuck", "emath/bytemuck"]
|
||||
|
@ -47,25 +47,26 @@ mint = ["emath/mint"]
|
|||
# implement serde on most types.
|
||||
serialize = ["serde", "ahash/serde", "emath/serde"]
|
||||
|
||||
single_threaded = ["atomic_refcell"]
|
||||
|
||||
# Only needed if you plan to use the same fonts from multiple threads.
|
||||
# It comes with a minor performance impact.
|
||||
multi_threaded = ["parking_lot"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
emath = { version = "0.17.0", path = "../emath" }
|
||||
|
||||
ab_glyph = "0.2.11"
|
||||
ahash = { version = "0.7", default-features = false, features = ["std"] }
|
||||
atomic_refcell = { version = "0.1", optional = true } # Used instead of parking_lot when you are always using epaint in a single thread. About as fast as parking_lot. Panics on multi-threaded use.
|
||||
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
|
||||
cint = { version = "^0.2.2", optional = true }
|
||||
nohash-hasher = "0.2"
|
||||
parking_lot = { version = "0.12", optional = true } # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.
|
||||
serde = { version = "1", optional = true, features = ["derive", "rc"] }
|
||||
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.
|
||||
|
||||
# web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
atomic_refcell = "0.1" # Used instead of parking_lot on on wasm. See https://github.com/emilk/egui/issues/1401
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3", default-features = false }
|
||||
|
||||
|
|
|
@ -5,85 +5,6 @@
|
|||
//! Create some [`Shape`]:s and pass them to [`tessellate_shapes`] to generate [`Mesh`]:es
|
||||
//! that you can then paint using some graphics API of your choice (e.g. OpenGL).
|
||||
|
||||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::await_holding_lock,
|
||||
clippy::char_lit_as_u8,
|
||||
clippy::checked_conversions,
|
||||
clippy::dbg_macro,
|
||||
clippy::debug_assert_with_mut_call,
|
||||
clippy::disallowed_method,
|
||||
clippy::doc_markdown,
|
||||
clippy::empty_enum,
|
||||
clippy::enum_glob_use,
|
||||
clippy::exit,
|
||||
clippy::expl_impl_clone_on_copy,
|
||||
clippy::explicit_deref_methods,
|
||||
clippy::explicit_into_iter_loop,
|
||||
clippy::fallible_impl_from,
|
||||
clippy::filter_map_next,
|
||||
clippy::flat_map_option,
|
||||
clippy::float_cmp_const,
|
||||
clippy::fn_params_excessive_bools,
|
||||
clippy::from_iter_instead_of_collect,
|
||||
clippy::if_let_mutex,
|
||||
clippy::implicit_clone,
|
||||
clippy::imprecise_flops,
|
||||
clippy::inefficient_to_string,
|
||||
clippy::invalid_upcast_comparisons,
|
||||
clippy::large_digit_groups,
|
||||
clippy::large_stack_arrays,
|
||||
clippy::large_types_passed_by_value,
|
||||
clippy::let_unit_value,
|
||||
clippy::linkedlist,
|
||||
clippy::lossy_float_literal,
|
||||
clippy::macro_use_imports,
|
||||
clippy::manual_ok_or,
|
||||
clippy::map_err_ignore,
|
||||
clippy::map_flatten,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::match_on_vec_items,
|
||||
clippy::match_same_arms,
|
||||
clippy::match_wild_err_arm,
|
||||
clippy::match_wildcard_for_single_variants,
|
||||
clippy::mem_forget,
|
||||
clippy::mismatched_target_os,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::mut_mut,
|
||||
clippy::mutex_integer,
|
||||
clippy::needless_borrow,
|
||||
clippy::needless_continue,
|
||||
clippy::needless_for_each,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::option_option,
|
||||
clippy::path_buf_push_overwrite,
|
||||
clippy::ptr_as_ptr,
|
||||
clippy::ref_option_ref,
|
||||
clippy::rest_pat_in_fully_bound_structs,
|
||||
clippy::same_functions_in_if_condition,
|
||||
clippy::semicolon_if_nothing_returned,
|
||||
clippy::single_match_else,
|
||||
clippy::string_add_assign,
|
||||
clippy::string_add,
|
||||
clippy::string_lit_as_bytes,
|
||||
clippy::string_to_string,
|
||||
clippy::todo,
|
||||
clippy::trait_duplication_in_bounds,
|
||||
clippy::unimplemented,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::unused_self,
|
||||
clippy::useless_transmute,
|
||||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
|
@ -110,7 +31,10 @@ pub use {
|
|||
image::{AlphaImage, ColorImage, ImageData, ImageDelta},
|
||||
mesh::{Mesh, Mesh16, Vertex},
|
||||
shadow::Shadow,
|
||||
shape::{CircleShape, PaintCallback, PathShape, RectShape, Rounding, Shape, TextShape},
|
||||
shape::{
|
||||
CircleShape, PaintCallback, PaintCallbackInfo, PathShape, RectShape, Rounding, Shape,
|
||||
TextShape,
|
||||
},
|
||||
stats::PaintStats,
|
||||
stroke::Stroke,
|
||||
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
|
||||
|
|
|
@ -91,17 +91,29 @@ impl Mesh {
|
|||
if self.is_empty() {
|
||||
*self = other;
|
||||
} else {
|
||||
self.append_ref(&other);
|
||||
}
|
||||
}
|
||||
|
||||
/// Append all the indices and vertices of `other` to `self` without
|
||||
/// taking ownership.
|
||||
pub fn append_ref(&mut self, other: &Mesh) {
|
||||
crate::epaint_assert!(other.is_valid());
|
||||
|
||||
if !self.is_empty() {
|
||||
assert_eq!(
|
||||
self.texture_id, other.texture_id,
|
||||
"Can't merge Mesh using different textures"
|
||||
);
|
||||
} else {
|
||||
self.texture_id = other.texture_id;
|
||||
}
|
||||
|
||||
let index_offset = self.vertices.len() as u32;
|
||||
self.indices
|
||||
.extend(other.indices.iter().map(|index| index + index_offset));
|
||||
self.vertices.extend(other.vertices.iter());
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn colored_vertex(&mut self, pos: Pos2, color: Color32) {
|
||||
|
@ -242,6 +254,15 @@ impl Mesh {
|
|||
v.pos += delta;
|
||||
}
|
||||
}
|
||||
|
||||
/// Rotate by some angle about an origin, in-place.
|
||||
///
|
||||
/// Origin is a position in screen space.
|
||||
pub fn rotate(&mut self, rot: Rot2, origin: Pos2) {
|
||||
for v in &mut self.vertices {
|
||||
v.pos = origin + rot * (v.pos - origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
//! Helper module that wraps some Mutex types with different implementations.
|
||||
//!
|
||||
//! When the `single_threaded` feature is on the mutexes will panic when locked from different threads.
|
||||
|
||||
#[cfg(not(any(feature = "single_threaded", feature = "multi_threaded")))]
|
||||
compile_error!("Either feature \"single_threaded\" or \"multi_threaded\" must be enabled.");
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(debug_assertions))]
|
||||
mod mutex_impl {
|
||||
/// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled.
|
||||
/// Provides interior mutability.
|
||||
///
|
||||
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||
#[derive(Default)]
|
||||
pub struct Mutex<T>(parking_lot::Mutex<T>);
|
||||
|
||||
|
@ -30,10 +27,12 @@ mod mutex_impl {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(debug_assertions)]
|
||||
mod mutex_impl {
|
||||
/// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled.
|
||||
/// Provides interior mutability.
|
||||
///
|
||||
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||
#[derive(Default)]
|
||||
pub struct Mutex<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 {
|
||||
/// The lock you get from [`RwLock::read`].
|
||||
pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard;
|
||||
|
@ -119,7 +118,9 @@ mod rw_lock_impl {
|
|||
/// The lock you get from [`RwLock::write`].
|
||||
pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard;
|
||||
|
||||
/// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled.
|
||||
/// Provides interior mutability.
|
||||
///
|
||||
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||
#[derive(Default)]
|
||||
pub struct RwLock<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 {
|
||||
pub use std::sync::Arc;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(not(feature = "multi_threaded"))]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod mutex_impl {
|
||||
// `atomic_refcell` will panic if multiple threads try to access the same value
|
||||
|
||||
/// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled.
|
||||
/// Provides interior mutability.
|
||||
///
|
||||
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||
#[derive(Default)]
|
||||
pub struct Mutex<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 {
|
||||
// `atomic_refcell` will panic if multiple threads try to access the same value
|
||||
|
||||
|
@ -183,7 +186,9 @@ mod rw_lock_impl {
|
|||
/// The lock you get from [`RwLock::write`].
|
||||
pub use atomic_refcell::AtomicRefMut as RwLockWriteGuard;
|
||||
|
||||
/// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled.
|
||||
/// Provides interior mutability.
|
||||
///
|
||||
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||
#[derive(Default)]
|
||||
pub struct RwLock<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 {
|
||||
// pub use std::rc::Rc as Arc; // TODO(emilk): optimize single threaded code by using `Rc` instead of `Arc`.
|
||||
pub use std::sync::Arc;
|
||||
|
|
|
@ -63,11 +63,15 @@ impl Shadow {
|
|||
|
||||
use crate::tessellator::*;
|
||||
let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color);
|
||||
let mut tessellator = Tessellator::from_options(TessellationOptions {
|
||||
aa_size: extrusion,
|
||||
anti_alias: true,
|
||||
let pixels_per_point = 1.0; // doesn't matter here
|
||||
let mut tessellator = Tessellator::new(
|
||||
pixels_per_point,
|
||||
TessellationOptions {
|
||||
feathering: true,
|
||||
feathering_size_in_pixels: extrusion * pixels_per_point,
|
||||
..Default::default()
|
||||
});
|
||||
},
|
||||
);
|
||||
let mut mesh = Mesh::default();
|
||||
tessellator.tessellate_rect(&rect, &mut mesh);
|
||||
mesh
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use crate::{
|
||||
text::{FontId, Fonts, Galley},
|
||||
Color32, Mesh, Stroke,
|
||||
Color32, Mesh, Stroke, TextureId,
|
||||
};
|
||||
use emath::*;
|
||||
|
||||
|
@ -184,6 +184,12 @@ impl Shape {
|
|||
Self::Mesh(mesh)
|
||||
}
|
||||
|
||||
pub fn image(texture_id: TextureId, rect: Rect, uv: Rect, tint: Color32) -> Self {
|
||||
let mut mesh = Mesh::with_texture(texture_id);
|
||||
mesh.add_rect_with_uv(rect, uv, tint);
|
||||
Shape::mesh(mesh)
|
||||
}
|
||||
|
||||
/// The visual bounding rectangle (includes stroke widths)
|
||||
pub fn visual_bounding_rect(&self) -> Rect {
|
||||
match self {
|
||||
|
@ -636,6 +642,52 @@ fn dashes_from_line(
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Information passed along with [`PaintCallback`] ([`Shape::Callback`]).
|
||||
pub struct PaintCallbackInfo {
|
||||
/// Viewport in points.
|
||||
pub rect: Rect,
|
||||
|
||||
/// Pixels per point.
|
||||
pub pixels_per_point: f32,
|
||||
|
||||
/// Full size of the screen, in pixels.
|
||||
pub screen_size_px: [u32; 2],
|
||||
}
|
||||
|
||||
impl PaintCallbackInfo {
|
||||
/// Physical pixel offset for left side of the viewport.
|
||||
#[inline]
|
||||
pub fn viewport_left_px(&self) -> f32 {
|
||||
self.rect.min.x * self.pixels_per_point
|
||||
}
|
||||
|
||||
/// Physical pixel offset for top side of the viewport.
|
||||
#[inline]
|
||||
pub fn viewport_top_px(&self) -> f32 {
|
||||
self.rect.min.y * self.pixels_per_point
|
||||
}
|
||||
|
||||
/// Physical pixel offset for bottom side of the viewport.
|
||||
///
|
||||
/// This is what `glViewport` etc expects for the y axis.
|
||||
#[inline]
|
||||
pub fn viewport_from_bottom_px(&self) -> f32 {
|
||||
self.screen_size_px[1] as f32 - self.rect.max.y * self.pixels_per_point
|
||||
}
|
||||
|
||||
/// Viewport width in physical pixels.
|
||||
#[inline]
|
||||
pub fn viewport_width_px(&self) -> f32 {
|
||||
self.rect.width() * self.pixels_per_point
|
||||
}
|
||||
|
||||
/// Viewport width in physical pixels.
|
||||
#[inline]
|
||||
pub fn viewport_height_px(&self) -> f32 {
|
||||
self.rect.height() * self.pixels_per_point
|
||||
}
|
||||
}
|
||||
|
||||
/// If you want to paint some 3D shapes inside an egui region, you can use this.
|
||||
///
|
||||
/// This is advanced usage, and is backend specific.
|
||||
|
@ -644,21 +696,22 @@ pub struct PaintCallback {
|
|||
/// Where to paint.
|
||||
pub rect: Rect,
|
||||
|
||||
/// Paint something custom using.
|
||||
/// Paint something custom (e.g. 3D stuff).
|
||||
///
|
||||
/// The argument is the render context, and what it contains depends on the backend.
|
||||
/// In `eframe` it will be `egui_glow::Painter`.
|
||||
///
|
||||
/// The rendering backend is responsible for first setting the active viewport to [`Self::rect`].
|
||||
/// The rendering backend is also responsible for restoring any state it needs,
|
||||
///
|
||||
/// The rendering backend is also responsible for restoring any state,
|
||||
/// such as the bound shader program and vertex array.
|
||||
pub callback: std::sync::Arc<dyn Fn(&dyn std::any::Any) + Send + Sync>,
|
||||
pub callback: std::sync::Arc<dyn Fn(&PaintCallbackInfo, &dyn std::any::Any) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl PaintCallback {
|
||||
#[inline]
|
||||
pub fn call(&self, render_ctx: &dyn std::any::Any) {
|
||||
(self.callback)(render_ctx);
|
||||
pub fn call(&self, info: &PaintCallbackInfo, render_ctx: &dyn std::any::Any) {
|
||||
(self.callback)(info, render_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -126,17 +126,17 @@ impl AllocInfo {
|
|||
|
||||
pub fn format(&self, what: &str) -> String {
|
||||
if self.num_allocs() == 0 {
|
||||
format!("{:6} {:14}", 0, what)
|
||||
format!("{:6} {:16}", 0, what)
|
||||
} else if self.num_allocs() == 1 {
|
||||
format!(
|
||||
"{:6} {:14} {} 1 allocation",
|
||||
"{:6} {:16} {} 1 allocation",
|
||||
self.num_elements,
|
||||
what,
|
||||
self.megabytes()
|
||||
)
|
||||
} else if self.element_size != ElementSize::Heterogenous {
|
||||
format!(
|
||||
"{:6} {:14} {} {:3} allocations",
|
||||
"{:6} {:16} {} {:3} allocations",
|
||||
self.num_elements(),
|
||||
what,
|
||||
self.megabytes(),
|
||||
|
@ -144,7 +144,7 @@ impl AllocInfo {
|
|||
)
|
||||
} else {
|
||||
format!(
|
||||
"{:6} {:14} {} {:3} allocations",
|
||||
"{:6} {:16} {} {:3} allocations",
|
||||
"",
|
||||
what,
|
||||
self.megabytes(),
|
||||
|
|
|
@ -163,23 +163,17 @@ impl Path {
|
|||
}
|
||||
|
||||
/// Open-ended.
|
||||
pub fn stroke_open(&self, stroke: Stroke, options: &TessellationOptions, out: &mut Mesh) {
|
||||
stroke_path(&self.0, PathType::Open, stroke, options, out);
|
||||
pub fn stroke_open(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) {
|
||||
stroke_path(feathering, &self.0, PathType::Open, stroke, out);
|
||||
}
|
||||
|
||||
/// A closed path (returning to the first point).
|
||||
pub fn stroke_closed(&self, stroke: Stroke, options: &TessellationOptions, out: &mut Mesh) {
|
||||
stroke_path(&self.0, PathType::Closed, stroke, options, out);
|
||||
pub fn stroke_closed(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) {
|
||||
stroke_path(feathering, &self.0, PathType::Closed, stroke, out);
|
||||
}
|
||||
|
||||
pub fn stroke(
|
||||
&self,
|
||||
path_type: PathType,
|
||||
stroke: Stroke,
|
||||
options: &TessellationOptions,
|
||||
out: &mut Mesh,
|
||||
) {
|
||||
stroke_path(&self.0, path_type, stroke, options, out);
|
||||
pub fn stroke(&self, feathering: f32, path_type: PathType, stroke: Stroke, out: &mut Mesh) {
|
||||
stroke_path(feathering, &self.0, path_type, stroke, out);
|
||||
}
|
||||
|
||||
/// The path is taken to be closed (i.e. returning to the start again).
|
||||
|
@ -187,8 +181,8 @@ impl Path {
|
|||
/// Calling this may reverse the vertices in the path if they are wrong winding order.
|
||||
///
|
||||
/// The preferred winding order is clockwise.
|
||||
pub fn fill(&mut self, color: Color32, options: &TessellationOptions, out: &mut Mesh) {
|
||||
fill_closed_path(&mut self.0, color, options, out);
|
||||
pub fn fill(&mut self, feathering: f32, color: Color32, out: &mut Mesh) {
|
||||
fill_closed_path(feathering, &mut self.0, color, out);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,18 +280,23 @@ pub enum PathType {
|
|||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct TessellationOptions {
|
||||
/// Size of a point in pixels (DPI scaling), e.g. 2.0. Used to snap text to pixel boundaries.
|
||||
pub pixels_per_point: f32,
|
||||
|
||||
/// The size of a pixel (in points), used for anti-aliasing (smoothing of edges).
|
||||
/// This is normally the inverse of [`Self::pixels_per_point`],
|
||||
/// but you can make it larger if you want more blurry edges.
|
||||
pub aa_size: f32,
|
||||
|
||||
/// Anti-aliasing makes shapes appear smoother, but requires more triangles and is therefore slower.
|
||||
/// Use "feathering" to smooth out the edges of shapes as a form of anti-aliasing.
|
||||
///
|
||||
/// Feathering works by making each edge into a thin gradient into transparency.
|
||||
/// The size of this edge is controlled by [`Self::feathering_size_in_pixels`].
|
||||
///
|
||||
/// This makes shapes appear smoother, but requires more triangles and is therefore slower.
|
||||
///
|
||||
/// This setting does not affect text.
|
||||
///
|
||||
/// Default: `true`.
|
||||
pub anti_alias: bool,
|
||||
pub feathering: bool,
|
||||
|
||||
/// The size of the the feathering, in physical pixels.
|
||||
///
|
||||
/// The default, and suggested, value for this is `1.0`.
|
||||
/// If you use a larger value, edges will appear blurry.
|
||||
pub feathering_size_in_pixels: f32,
|
||||
|
||||
/// If `true` (default) cull certain primitives before tessellating them.
|
||||
/// This likely makes
|
||||
|
@ -326,9 +325,8 @@ pub struct TessellationOptions {
|
|||
impl Default for TessellationOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pixels_per_point: 1.0,
|
||||
aa_size: 1.0,
|
||||
anti_alias: true,
|
||||
feathering: true,
|
||||
feathering_size_in_pixels: 1.0,
|
||||
coarse_tessellation_culling: true,
|
||||
round_text_to_pixels: true,
|
||||
debug_paint_text_rects: false,
|
||||
|
@ -340,27 +338,6 @@ impl Default for TessellationOptions {
|
|||
}
|
||||
}
|
||||
|
||||
impl TessellationOptions {
|
||||
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
|
||||
Self {
|
||||
pixels_per_point,
|
||||
aa_size: 1.0 / pixels_per_point,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TessellationOptions {
|
||||
#[inline(always)]
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
if self.round_text_to_pixels {
|
||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||
} else {
|
||||
point
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cw_signed_area(path: &[PathPoint]) -> f64 {
|
||||
if let Some(last) = path.last() {
|
||||
let mut previous = last.pos;
|
||||
|
@ -380,18 +357,13 @@ fn cw_signed_area(path: &[PathPoint]) -> f64 {
|
|||
/// Calling this may reverse the vertices in the path if they are wrong winding order.
|
||||
///
|
||||
/// The preferred winding order is clockwise.
|
||||
fn fill_closed_path(
|
||||
path: &mut [PathPoint],
|
||||
color: Color32,
|
||||
options: &TessellationOptions,
|
||||
out: &mut Mesh,
|
||||
) {
|
||||
fn fill_closed_path(feathering: f32, path: &mut [PathPoint], color: Color32, out: &mut Mesh) {
|
||||
if color == Color32::TRANSPARENT {
|
||||
return;
|
||||
}
|
||||
|
||||
let n = path.len() as u32;
|
||||
if options.anti_alias {
|
||||
if feathering > 0.0 {
|
||||
if cw_signed_area(path) < 0.0 {
|
||||
// Wrong winding order - fix:
|
||||
path.reverse();
|
||||
|
@ -415,7 +387,7 @@ fn fill_closed_path(
|
|||
let mut i0 = n - 1;
|
||||
for i1 in 0..n {
|
||||
let p1 = &path[i1 as usize];
|
||||
let dm = 0.5 * options.aa_size * p1.normal;
|
||||
let dm = 0.5 * feathering * p1.normal;
|
||||
out.colored_vertex(p1.pos - dm, color);
|
||||
out.colored_vertex(p1.pos + dm, color_outer);
|
||||
out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
|
||||
|
@ -438,10 +410,10 @@ fn fill_closed_path(
|
|||
|
||||
/// Tessellate the given path as a stroke with thickness.
|
||||
fn stroke_path(
|
||||
feathering: f32,
|
||||
path: &[PathPoint],
|
||||
path_type: PathType,
|
||||
stroke: Stroke,
|
||||
options: &TessellationOptions,
|
||||
out: &mut Mesh,
|
||||
) {
|
||||
let n = path.len() as u32;
|
||||
|
@ -452,21 +424,21 @@ fn stroke_path(
|
|||
|
||||
let idx = out.vertices.len() as u32;
|
||||
|
||||
if options.anti_alias {
|
||||
if feathering > 0.0 {
|
||||
let color_inner = stroke.color;
|
||||
let color_outer = Color32::TRANSPARENT;
|
||||
|
||||
let thin_line = stroke.width <= options.aa_size;
|
||||
let thin_line = stroke.width <= feathering;
|
||||
if thin_line {
|
||||
/*
|
||||
We paint the line using three edges: outer, inner, outer.
|
||||
|
||||
. o i o outer, inner, outer
|
||||
. |---| aa_size (pixel width)
|
||||
. |---| feathering (pixel width)
|
||||
*/
|
||||
|
||||
// Fade out as it gets thinner:
|
||||
let color_inner = mul_color(color_inner, stroke.width / options.aa_size);
|
||||
let color_inner = mul_color(color_inner, stroke.width / feathering);
|
||||
if color_inner == Color32::TRANSPARENT {
|
||||
return;
|
||||
}
|
||||
|
@ -480,9 +452,9 @@ fn stroke_path(
|
|||
let p1 = &path[i1 as usize];
|
||||
let p = p1.pos;
|
||||
let n = p1.normal;
|
||||
out.colored_vertex(p + n * options.aa_size, color_outer);
|
||||
out.colored_vertex(p + n * feathering, color_outer);
|
||||
out.colored_vertex(p, color_inner);
|
||||
out.colored_vertex(p - n * options.aa_size, color_outer);
|
||||
out.colored_vertex(p - n * feathering, color_outer);
|
||||
|
||||
if connect_with_previous {
|
||||
out.add_triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0);
|
||||
|
@ -500,14 +472,14 @@ fn stroke_path(
|
|||
We paint the line using four edges: outer, inner, inner, outer
|
||||
|
||||
. o i p i o outer, inner, point, inner, outer
|
||||
. |---| aa_size (pixel width)
|
||||
. |---| feathering (pixel width)
|
||||
. |--------------| width
|
||||
. |---------| outer_rad
|
||||
. |-----| inner_rad
|
||||
*/
|
||||
|
||||
let inner_rad = 0.5 * (stroke.width - options.aa_size);
|
||||
let outer_rad = 0.5 * (stroke.width + options.aa_size);
|
||||
let inner_rad = 0.5 * (stroke.width - feathering);
|
||||
let outer_rad = 0.5 * (stroke.width + feathering);
|
||||
|
||||
match path_type {
|
||||
PathType::Closed => {
|
||||
|
@ -542,7 +514,7 @@ fn stroke_path(
|
|||
|
||||
// | aa | | aa |
|
||||
// _________________ ___
|
||||
// | \ added / | aa_size
|
||||
// | \ added / | feathering
|
||||
// | \ ___p___ / | ___
|
||||
// | | | |
|
||||
// | | opa | |
|
||||
|
@ -558,7 +530,7 @@ fn stroke_path(
|
|||
let end = &path[0];
|
||||
let p = end.pos;
|
||||
let n = end.normal;
|
||||
let back_extrude = n.rot90() * options.aa_size;
|
||||
let back_extrude = n.rot90() * feathering;
|
||||
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
||||
out.colored_vertex(p + n * inner_rad, color_inner);
|
||||
out.colored_vertex(p - n * inner_rad, color_inner);
|
||||
|
@ -595,7 +567,7 @@ fn stroke_path(
|
|||
let end = &path[i1 as usize];
|
||||
let p = end.pos;
|
||||
let n = end.normal;
|
||||
let back_extrude = -n.rot90() * options.aa_size;
|
||||
let back_extrude = -n.rot90() * feathering;
|
||||
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
||||
out.colored_vertex(p + n * inner_rad, color_inner);
|
||||
out.colored_vertex(p - n * inner_rad, color_inner);
|
||||
|
@ -640,11 +612,11 @@ fn stroke_path(
|
|||
);
|
||||
}
|
||||
|
||||
let thin_line = stroke.width <= options.aa_size;
|
||||
let thin_line = stroke.width <= feathering;
|
||||
if thin_line {
|
||||
// Fade out thin lines rather than making them thinner
|
||||
let radius = options.aa_size / 2.0;
|
||||
let color = mul_color(stroke.color, stroke.width / options.aa_size);
|
||||
let radius = feathering / 2.0;
|
||||
let color = mul_color(stroke.color, stroke.width / feathering);
|
||||
if color == Color32::TRANSPARENT {
|
||||
return;
|
||||
}
|
||||
|
@ -677,7 +649,10 @@ fn mul_color(color: Color32, factor: f32) -> Color32 {
|
|||
///
|
||||
/// Se also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`].
|
||||
pub struct Tessellator {
|
||||
pixels_per_point: f32,
|
||||
options: TessellationOptions,
|
||||
/// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled
|
||||
feathering: f32,
|
||||
/// Only used for culling
|
||||
clip_rect: Rect,
|
||||
scratchpad_points: Vec<Pos2>,
|
||||
|
@ -686,24 +661,43 @@ pub struct Tessellator {
|
|||
|
||||
impl Tessellator {
|
||||
/// Create a new [`Tessellator`].
|
||||
pub fn from_options(options: TessellationOptions) -> Self {
|
||||
pub fn new(pixels_per_point: f32, options: TessellationOptions) -> Self {
|
||||
let feathering = if options.feathering {
|
||||
let pixel_size = 1.0 / pixels_per_point;
|
||||
options.feathering_size_in_pixels * pixel_size
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
Self {
|
||||
pixels_per_point,
|
||||
options,
|
||||
feathering,
|
||||
clip_rect: Rect::EVERYTHING,
|
||||
scratchpad_points: Default::default(),
|
||||
scratchpad_path: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the `Rect` to use for culling.
|
||||
pub fn set_clip_rect(&mut self, clip_rect: Rect) {
|
||||
self.clip_rect = clip_rect;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
if self.options.round_text_to_pixels {
|
||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||
} else {
|
||||
point
|
||||
}
|
||||
}
|
||||
|
||||
/// Tessellate a single [`Shape`] into a [`Mesh`].
|
||||
///
|
||||
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles).
|
||||
/// * `shape`: the shape to tessellate.
|
||||
/// * `out`: triangles are appended to this.
|
||||
pub fn tessellate_shape(&mut self, tex_size: [usize; 2], shape: Shape, out: &mut Mesh) {
|
||||
let clip_rect = self.clip_rect;
|
||||
let options = &self.options;
|
||||
|
||||
match shape {
|
||||
Shape::Noop => {}
|
||||
Shape::Vec(vec) => {
|
||||
|
@ -711,26 +705,8 @@ impl Tessellator {
|
|||
self.tessellate_shape(tex_size, shape, out);
|
||||
}
|
||||
}
|
||||
Shape::Circle(CircleShape {
|
||||
center,
|
||||
radius,
|
||||
fill,
|
||||
stroke,
|
||||
}) => {
|
||||
if radius <= 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
if options.coarse_tessellation_culling
|
||||
&& !clip_rect.expand(radius + stroke.width).contains(center)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self.scratchpad_path.clear();
|
||||
self.scratchpad_path.add_circle(center, radius);
|
||||
self.scratchpad_path.fill(fill, options, out);
|
||||
self.scratchpad_path.stroke_closed(stroke, options, out);
|
||||
Shape::Circle(circle) => {
|
||||
self.tessellate_circle(circle, out);
|
||||
}
|
||||
Shape::Mesh(mesh) => {
|
||||
if !mesh.is_valid() {
|
||||
|
@ -738,44 +714,29 @@ impl Tessellator {
|
|||
return;
|
||||
}
|
||||
|
||||
if options.coarse_tessellation_culling && !clip_rect.intersects(mesh.calc_bounds())
|
||||
if self.options.coarse_tessellation_culling
|
||||
&& !self.clip_rect.intersects(mesh.calc_bounds())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
out.append(mesh);
|
||||
}
|
||||
Shape::LineSegment { points, stroke } => {
|
||||
if stroke.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if options.coarse_tessellation_culling
|
||||
&& !clip_rect
|
||||
.intersects(Rect::from_two_pos(points[0], points[1]).expand(stroke.width))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self.scratchpad_path.clear();
|
||||
self.scratchpad_path.add_line_segment(points);
|
||||
self.scratchpad_path.stroke_open(stroke, options, out);
|
||||
}
|
||||
Shape::LineSegment { points, stroke } => self.tessellate_line(points, stroke, out),
|
||||
Shape::Path(path_shape) => {
|
||||
self.tessellate_path(path_shape, out);
|
||||
self.tessellate_path(&path_shape, out);
|
||||
}
|
||||
Shape::Rect(rect_shape) => {
|
||||
self.tessellate_rect(&rect_shape, out);
|
||||
}
|
||||
Shape::Text(text_shape) => {
|
||||
if options.debug_paint_text_rects {
|
||||
if self.options.debug_paint_text_rects {
|
||||
let rect = text_shape.galley.rect.translate(text_shape.pos.to_vec2());
|
||||
self.tessellate_rect(
|
||||
&RectShape::stroke(rect.expand(0.5), 2.0, (0.5, Color32::GREEN)),
|
||||
out,
|
||||
);
|
||||
}
|
||||
self.tessellate_text(tex_size, text_shape, out);
|
||||
self.tessellate_text(tex_size, &text_shape, out);
|
||||
}
|
||||
Shape::QuadraticBezier(quadratic_shape) => {
|
||||
self.tessellate_quadratic_bezier(quadratic_shape, out);
|
||||
|
@ -787,7 +748,267 @@ impl Tessellator {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn tessellate_quadratic_bezier(
|
||||
/// Tessellate a single [`CircleShape`] into a [`Mesh`].
|
||||
///
|
||||
/// * `shape`: the circle to tessellate.
|
||||
/// * `out`: triangles are appended to this.
|
||||
pub fn tessellate_circle(&mut self, shape: CircleShape, out: &mut Mesh) {
|
||||
let CircleShape {
|
||||
center,
|
||||
radius,
|
||||
fill,
|
||||
stroke,
|
||||
} = shape;
|
||||
|
||||
if radius <= 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.options.coarse_tessellation_culling
|
||||
&& !self
|
||||
.clip_rect
|
||||
.expand(radius + stroke.width)
|
||||
.contains(center)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self.scratchpad_path.clear();
|
||||
self.scratchpad_path.add_circle(center, radius);
|
||||
self.scratchpad_path.fill(self.feathering, fill, out);
|
||||
self.scratchpad_path
|
||||
.stroke_closed(self.feathering, stroke, out);
|
||||
}
|
||||
|
||||
/// Tessellate a single [`Mesh`] into a [`Mesh`].
|
||||
///
|
||||
/// * `mesh`: the mesh to tessellate.
|
||||
/// * `out`: triangles are appended to this.
|
||||
pub fn tessellate_mesh(&mut self, mesh: &Mesh, out: &mut Mesh) {
|
||||
if !mesh.is_valid() {
|
||||
crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh");
|
||||
return;
|
||||
}
|
||||
|
||||
if self.options.coarse_tessellation_culling
|
||||
&& !self.clip_rect.intersects(mesh.calc_bounds())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
out.append_ref(mesh);
|
||||
}
|
||||
|
||||
/// Tessellate a line segment between the two points with the given stoken into a [`Mesh`].
|
||||
///
|
||||
/// * `shape`: the mesh to tessellate.
|
||||
/// * `out`: triangles are appended to this.
|
||||
pub fn tessellate_line(&mut self, points: [Pos2; 2], stroke: Stroke, out: &mut Mesh) {
|
||||
if stroke.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.options.coarse_tessellation_culling
|
||||
&& !self
|
||||
.clip_rect
|
||||
.intersects(Rect::from_two_pos(points[0], points[1]).expand(stroke.width))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self.scratchpad_path.clear();
|
||||
self.scratchpad_path.add_line_segment(points);
|
||||
self.scratchpad_path
|
||||
.stroke_open(self.feathering, stroke, out);
|
||||
}
|
||||
|
||||
/// Tessellate a single [`PathShape`] into a [`Mesh`].
|
||||
///
|
||||
/// * `path_shape`: the path to tessellate.
|
||||
/// * `out`: triangles are appended to this.
|
||||
pub fn tessellate_path(&mut self, path_shape: &PathShape, out: &mut Mesh) {
|
||||
if path_shape.points.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.options.coarse_tessellation_culling
|
||||
&& !path_shape.visual_bounding_rect().intersects(self.clip_rect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let PathShape {
|
||||
points,
|
||||
closed,
|
||||
fill,
|
||||
stroke,
|
||||
} = path_shape;
|
||||
|
||||
self.scratchpad_path.clear();
|
||||
if *closed {
|
||||
self.scratchpad_path.add_line_loop(points);
|
||||
} else {
|
||||
self.scratchpad_path.add_open_points(points);
|
||||
}
|
||||
|
||||
if *fill != Color32::TRANSPARENT {
|
||||
crate::epaint_assert!(
|
||||
closed,
|
||||
"You asked to fill a path that is not closed. That makes no sense."
|
||||
);
|
||||
self.scratchpad_path.fill(self.feathering, *fill, out);
|
||||
}
|
||||
let typ = if *closed {
|
||||
PathType::Closed
|
||||
} else {
|
||||
PathType::Open
|
||||
};
|
||||
self.scratchpad_path
|
||||
.stroke(self.feathering, typ, *stroke, out);
|
||||
}
|
||||
|
||||
/// Tessellate a single [`Rect`] into a [`Mesh`].
|
||||
///
|
||||
/// * `rect`: the rectangle to tessellate.
|
||||
/// * `out`: triangles are appended to this.
|
||||
pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) {
|
||||
let RectShape {
|
||||
mut rect,
|
||||
rounding,
|
||||
fill,
|
||||
stroke,
|
||||
} = *rect;
|
||||
|
||||
if self.options.coarse_tessellation_culling
|
||||
&& !rect.expand(stroke.width).intersects(self.clip_rect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if rect.is_negative() {
|
||||
return;
|
||||
}
|
||||
|
||||
// It is common to (sometimes accidentally) create an infinitely sized rectangle.
|
||||
// Make sure we can handle that:
|
||||
rect.min = rect.min.at_least(pos2(-1e7, -1e7));
|
||||
rect.max = rect.max.at_most(pos2(1e7, 1e7));
|
||||
|
||||
let path = &mut self.scratchpad_path;
|
||||
path.clear();
|
||||
path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding);
|
||||
path.add_line_loop(&self.scratchpad_points);
|
||||
path.fill(self.feathering, fill, out);
|
||||
path.stroke_closed(self.feathering, stroke, out);
|
||||
}
|
||||
|
||||
/// Tessellate a single [`TextShape`] into a [`Mesh`].
|
||||
///
|
||||
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles).
|
||||
/// * `text_shape`: the text to tessellate.
|
||||
/// * `out`: triangles are appended to this.
|
||||
pub fn tessellate_text(
|
||||
&mut self,
|
||||
tex_size: [usize; 2],
|
||||
text_shape: &TextShape,
|
||||
out: &mut Mesh,
|
||||
) {
|
||||
let TextShape {
|
||||
pos: galley_pos,
|
||||
galley,
|
||||
underline,
|
||||
override_text_color,
|
||||
angle,
|
||||
} = text_shape;
|
||||
|
||||
if galley.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
out.vertices.reserve(galley.num_vertices);
|
||||
out.indices.reserve(galley.num_indices);
|
||||
|
||||
// The contents of the galley is already snapped to pixel coordinates,
|
||||
// but we need to make sure the galley ends up on the start of a physical pixel:
|
||||
let galley_pos = pos2(
|
||||
self.round_to_pixel(galley_pos.x),
|
||||
self.round_to_pixel(galley_pos.y),
|
||||
);
|
||||
|
||||
let uv_normalizer = vec2(1.0 / tex_size[0] as f32, 1.0 / tex_size[1] as f32);
|
||||
|
||||
let rotator = Rot2::from_angle(*angle);
|
||||
|
||||
for row in &galley.rows {
|
||||
if row.visuals.mesh.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut row_rect = row.visuals.mesh_bounds;
|
||||
if *angle != 0.0 {
|
||||
row_rect = row_rect.rotate_bb(rotator);
|
||||
}
|
||||
row_rect = row_rect.translate(galley_pos.to_vec2());
|
||||
|
||||
if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) {
|
||||
// culling individual lines of text is important, since a single `Shape::Text`
|
||||
// can span hundreds of lines.
|
||||
continue;
|
||||
}
|
||||
|
||||
let index_offset = out.vertices.len() as u32;
|
||||
|
||||
out.indices.extend(
|
||||
row.visuals
|
||||
.mesh
|
||||
.indices
|
||||
.iter()
|
||||
.map(|index| index + index_offset),
|
||||
);
|
||||
|
||||
out.vertices.extend(
|
||||
row.visuals
|
||||
.mesh
|
||||
.vertices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, vertex)| {
|
||||
let Vertex { pos, uv, mut color } = *vertex;
|
||||
|
||||
if let Some(override_text_color) = override_text_color {
|
||||
if row.visuals.glyph_vertex_range.contains(&i) {
|
||||
color = *override_text_color;
|
||||
}
|
||||
}
|
||||
|
||||
let offset = if *angle == 0.0 {
|
||||
pos.to_vec2()
|
||||
} else {
|
||||
rotator * pos.to_vec2()
|
||||
};
|
||||
|
||||
Vertex {
|
||||
pos: galley_pos + offset,
|
||||
uv: (uv.to_vec2() * uv_normalizer).to_pos2(),
|
||||
color,
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if *underline != Stroke::none() {
|
||||
self.scratchpad_path.clear();
|
||||
self.scratchpad_path
|
||||
.add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]);
|
||||
self.scratchpad_path
|
||||
.stroke_open(self.feathering, *underline, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tessellate a single [`QuadraticBezierShape`] into a [`Mesh`].
|
||||
///
|
||||
/// * `quadratic_shape`: the shape to tessellate.
|
||||
/// * `out`: triangles are appended to this.
|
||||
pub fn tessellate_quadratic_bezier(
|
||||
&mut self,
|
||||
quadratic_shape: QuadraticBezierShape,
|
||||
out: &mut Mesh,
|
||||
|
@ -812,11 +1033,11 @@ impl Tessellator {
|
|||
);
|
||||
}
|
||||
|
||||
pub(crate) fn tessellate_cubic_bezier(
|
||||
&mut self,
|
||||
cubic_shape: CubicBezierShape,
|
||||
out: &mut Mesh,
|
||||
) {
|
||||
/// Tessellate a single [`CubicBezierShape`] into a [`Mesh`].
|
||||
///
|
||||
/// * `cubic_shape`: the shape to tessellate.
|
||||
/// * `out`: triangles are appended to this.
|
||||
pub fn tessellate_cubic_bezier(&mut self, cubic_shape: CubicBezierShape, out: &mut Mesh) {
|
||||
let options = &self.options;
|
||||
let clip_rect = self.clip_rect;
|
||||
if options.coarse_tessellation_culling
|
||||
|
@ -858,177 +1079,15 @@ impl Tessellator {
|
|||
closed,
|
||||
"You asked to fill a path that is not closed. That makes no sense."
|
||||
);
|
||||
self.scratchpad_path.fill(fill, &self.options, out);
|
||||
self.scratchpad_path.fill(self.feathering, fill, out);
|
||||
}
|
||||
let typ = if closed {
|
||||
PathType::Closed
|
||||
} else {
|
||||
PathType::Open
|
||||
};
|
||||
self.scratchpad_path.stroke(typ, stroke, &self.options, out);
|
||||
}
|
||||
|
||||
pub(crate) fn tessellate_path(&mut self, path_shape: PathShape, out: &mut Mesh) {
|
||||
if path_shape.points.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.options.coarse_tessellation_culling
|
||||
&& !path_shape.visual_bounding_rect().intersects(self.clip_rect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let PathShape {
|
||||
points,
|
||||
closed,
|
||||
fill,
|
||||
stroke,
|
||||
} = path_shape;
|
||||
|
||||
self.scratchpad_path.clear();
|
||||
if closed {
|
||||
self.scratchpad_path.add_line_loop(&points);
|
||||
} else {
|
||||
self.scratchpad_path.add_open_points(&points);
|
||||
}
|
||||
|
||||
if fill != Color32::TRANSPARENT {
|
||||
crate::epaint_assert!(
|
||||
closed,
|
||||
"You asked to fill a path that is not closed. That makes no sense."
|
||||
);
|
||||
self.scratchpad_path.fill(fill, &self.options, out);
|
||||
}
|
||||
let typ = if closed {
|
||||
PathType::Closed
|
||||
} else {
|
||||
PathType::Open
|
||||
};
|
||||
self.scratchpad_path.stroke(typ, stroke, &self.options, out);
|
||||
}
|
||||
|
||||
pub(crate) fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) {
|
||||
let RectShape {
|
||||
mut rect,
|
||||
rounding,
|
||||
fill,
|
||||
stroke,
|
||||
} = *rect;
|
||||
|
||||
if self.options.coarse_tessellation_culling
|
||||
&& !rect.expand(stroke.width).intersects(self.clip_rect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if rect.is_negative() {
|
||||
return;
|
||||
}
|
||||
|
||||
// It is common to (sometimes accidentally) create an infinitely sized rectangle.
|
||||
// Make sure we can handle that:
|
||||
rect.min = rect.min.at_least(pos2(-1e7, -1e7));
|
||||
rect.max = rect.max.at_most(pos2(1e7, 1e7));
|
||||
|
||||
let path = &mut self.scratchpad_path;
|
||||
path.clear();
|
||||
path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding);
|
||||
path.add_line_loop(&self.scratchpad_points);
|
||||
path.fill(fill, &self.options, out);
|
||||
path.stroke_closed(stroke, &self.options, out);
|
||||
}
|
||||
|
||||
pub fn tessellate_text(&mut self, tex_size: [usize; 2], text_shape: TextShape, out: &mut Mesh) {
|
||||
let TextShape {
|
||||
pos: galley_pos,
|
||||
galley,
|
||||
underline,
|
||||
override_text_color,
|
||||
angle,
|
||||
} = text_shape;
|
||||
|
||||
if galley.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
out.vertices.reserve(galley.num_vertices);
|
||||
out.indices.reserve(galley.num_indices);
|
||||
|
||||
// The contents of the galley is already snapped to pixel coordinates,
|
||||
// but we need to make sure the galley ends up on the start of a physical pixel:
|
||||
let galley_pos = pos2(
|
||||
self.options.round_to_pixel(galley_pos.x),
|
||||
self.options.round_to_pixel(galley_pos.y),
|
||||
);
|
||||
|
||||
let uv_normalizer = vec2(1.0 / tex_size[0] as f32, 1.0 / tex_size[1] as f32);
|
||||
|
||||
let rotator = Rot2::from_angle(angle);
|
||||
|
||||
for row in &galley.rows {
|
||||
if row.visuals.mesh.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut row_rect = row.visuals.mesh_bounds;
|
||||
if angle != 0.0 {
|
||||
row_rect = row_rect.rotate_bb(rotator);
|
||||
}
|
||||
row_rect = row_rect.translate(galley_pos.to_vec2());
|
||||
|
||||
if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) {
|
||||
// culling individual lines of text is important, since a single `Shape::Text`
|
||||
// can span hundreds of lines.
|
||||
continue;
|
||||
}
|
||||
|
||||
let index_offset = out.vertices.len() as u32;
|
||||
|
||||
out.indices.extend(
|
||||
row.visuals
|
||||
.mesh
|
||||
.indices
|
||||
.iter()
|
||||
.map(|index| index + index_offset),
|
||||
);
|
||||
|
||||
out.vertices.extend(
|
||||
row.visuals
|
||||
.mesh
|
||||
.vertices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, vertex)| {
|
||||
let Vertex { pos, uv, mut color } = *vertex;
|
||||
|
||||
if let Some(override_text_color) = override_text_color {
|
||||
if row.visuals.glyph_vertex_range.contains(&i) {
|
||||
color = override_text_color;
|
||||
}
|
||||
}
|
||||
|
||||
let offset = if angle == 0.0 {
|
||||
pos.to_vec2()
|
||||
} else {
|
||||
rotator * pos.to_vec2()
|
||||
};
|
||||
|
||||
Vertex {
|
||||
pos: galley_pos + offset,
|
||||
uv: (uv.to_vec2() * uv_normalizer).to_pos2(),
|
||||
color,
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
if underline != Stroke::none() {
|
||||
self.scratchpad_path.clear();
|
||||
self.scratchpad_path
|
||||
.add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]);
|
||||
self.scratchpad_path
|
||||
.stroke_open(underline, &self.options, out);
|
||||
}
|
||||
}
|
||||
.stroke(self.feathering, typ, stroke, out);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1037,8 +1096,9 @@ impl Tessellator {
|
|||
/// The given shapes will tessellated in the same order as they are given.
|
||||
/// They will be batched together by clip rectangle.
|
||||
///
|
||||
/// * `shapes`: what to tessellate
|
||||
/// * `pixels_per_point`: number of physical pixels to each logical point
|
||||
/// * `options`: tessellation quality
|
||||
/// * `shapes`: what to tessellate
|
||||
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles)
|
||||
///
|
||||
/// The implementation uses a [`Tessellator`].
|
||||
|
@ -1046,11 +1106,12 @@ impl Tessellator {
|
|||
/// ## Returns
|
||||
/// A list of clip rectangles with matching [`Mesh`].
|
||||
pub fn tessellate_shapes(
|
||||
shapes: Vec<ClippedShape>,
|
||||
pixels_per_point: f32,
|
||||
options: TessellationOptions,
|
||||
shapes: Vec<ClippedShape>,
|
||||
tex_size: [usize; 2],
|
||||
) -> 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();
|
||||
|
||||
|
|
|
@ -599,10 +599,8 @@ fn add_hline(point_scale: PointScale, [start, stop]: [Pos2; 2], stroke: Stroke,
|
|||
if antialiased {
|
||||
let mut path = crate::tessellator::Path::default(); // TODO: reuse this to avoid re-allocations.
|
||||
path.add_line_segment([start, stop]);
|
||||
let options = crate::tessellator::TessellationOptions::from_pixels_per_point(
|
||||
point_scale.pixels_per_point(),
|
||||
);
|
||||
path.stroke_open(stroke, &options, mesh);
|
||||
let feathering = 1.0 / point_scale.pixels_per_point();
|
||||
path.stroke_open(feathering, stroke, mesh);
|
||||
} else {
|
||||
// Thin lines often lost, so this is a bad idea
|
||||
|
||||
|
|
|
@ -28,9 +28,7 @@ persistence = ["ron", "serde", "egui/persistence"]
|
|||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||
"single_threaded",
|
||||
] }
|
||||
egui = { version = "0.17.0", path = "../egui", default-features = false }
|
||||
glow = "0.11"
|
||||
tracing = "0.1"
|
||||
|
||||
|
|
198
epi/src/lib.rs
198
epi/src/lib.rs
|
@ -6,87 +6,6 @@
|
|||
//!
|
||||
//! Start by looking at the [`App`] trait, and implement [`App::update`].
|
||||
|
||||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::await_holding_lock,
|
||||
clippy::char_lit_as_u8,
|
||||
clippy::checked_conversions,
|
||||
clippy::dbg_macro,
|
||||
clippy::debug_assert_with_mut_call,
|
||||
clippy::disallowed_method,
|
||||
clippy::doc_markdown,
|
||||
clippy::empty_enum,
|
||||
clippy::enum_glob_use,
|
||||
clippy::exit,
|
||||
clippy::expl_impl_clone_on_copy,
|
||||
clippy::explicit_deref_methods,
|
||||
clippy::explicit_into_iter_loop,
|
||||
clippy::fallible_impl_from,
|
||||
clippy::filter_map_next,
|
||||
clippy::flat_map_option,
|
||||
clippy::float_cmp_const,
|
||||
clippy::fn_params_excessive_bools,
|
||||
clippy::from_iter_instead_of_collect,
|
||||
clippy::if_let_mutex,
|
||||
clippy::implicit_clone,
|
||||
clippy::imprecise_flops,
|
||||
clippy::inefficient_to_string,
|
||||
clippy::invalid_upcast_comparisons,
|
||||
clippy::large_digit_groups,
|
||||
clippy::large_stack_arrays,
|
||||
clippy::large_types_passed_by_value,
|
||||
clippy::let_unit_value,
|
||||
clippy::linkedlist,
|
||||
clippy::lossy_float_literal,
|
||||
clippy::macro_use_imports,
|
||||
clippy::manual_ok_or,
|
||||
clippy::map_err_ignore,
|
||||
clippy::map_flatten,
|
||||
clippy::map_unwrap_or,
|
||||
clippy::match_on_vec_items,
|
||||
clippy::match_same_arms,
|
||||
clippy::match_wild_err_arm,
|
||||
clippy::match_wildcard_for_single_variants,
|
||||
clippy::mem_forget,
|
||||
clippy::mismatched_target_os,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::mut_mut,
|
||||
clippy::mutex_integer,
|
||||
clippy::needless_borrow,
|
||||
clippy::needless_continue,
|
||||
clippy::needless_for_each,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::option_option,
|
||||
clippy::path_buf_push_overwrite,
|
||||
clippy::ptr_as_ptr,
|
||||
clippy::ref_option_ref,
|
||||
clippy::rest_pat_in_fully_bound_structs,
|
||||
clippy::same_functions_in_if_condition,
|
||||
clippy::semicolon_if_nothing_returned,
|
||||
clippy::single_match_else,
|
||||
clippy::string_add_assign,
|
||||
clippy::string_add,
|
||||
clippy::string_lit_as_bytes,
|
||||
clippy::string_to_string,
|
||||
clippy::todo,
|
||||
clippy::trait_duplication_in_bounds,
|
||||
clippy::unimplemented,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::unused_self,
|
||||
clippy::useless_transmute,
|
||||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
#![warn(missing_docs)] // Let's keep `epi` well-documented.
|
||||
|
||||
/// File storage which can be used by native backends.
|
||||
|
@ -98,6 +17,30 @@ pub use glow; // Re-export for user convenience
|
|||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// The is is how your app is created.
|
||||
///
|
||||
/// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc.
|
||||
pub type AppCreator = Box<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,
|
||||
|
@ -109,39 +52,20 @@ pub trait App {
|
|||
///
|
||||
/// The [`egui::Context`] and [`Frame`] can be cloned and saved if you like.
|
||||
///
|
||||
/// To force a repaint, call either [`egui::Context::request_repaint`] during the call to `update`,
|
||||
/// or call [`Frame::request_repaint`] at any time (e.g. from another thread).
|
||||
/// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &Frame);
|
||||
|
||||
/// Called exactly once at startup, before any call to [`Self::update`].
|
||||
///
|
||||
/// Allows you to do setup code, e.g to call [`egui::Context::set_fonts`],
|
||||
/// [`egui::Context::set_visuals`] etc.
|
||||
///
|
||||
/// Also allows you to restore state, if there is a storage (requires the "persistence" feature).
|
||||
///
|
||||
/// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that
|
||||
/// you might want to use later from a [`egui::PaintCallback`].
|
||||
fn setup(
|
||||
&mut self,
|
||||
_ctx: &egui::Context,
|
||||
_frame: &Frame,
|
||||
_storage: Option<&dyn Storage>,
|
||||
_gl: &std::rc::Rc<glow::Context>,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Called on shutdown, and perhaps at regular intervals. Allows you to save state.
|
||||
///
|
||||
/// Only called when the "persistence" feature is enabled.
|
||||
///
|
||||
/// On web the states is stored to "Local Storage".
|
||||
/// On web the state is stored to "Local Storage".
|
||||
/// On native the path is picked using [`directories_next::ProjectDirs::data_dir`](https://docs.rs/directories-next/2.0.0/directories_next/struct.ProjectDirs.html#method.data_dir) which is:
|
||||
/// * Linux: `/home/UserName/.local/share/APPNAME`
|
||||
/// * macOS: `/Users/UserName/Library/Application Support/APPNAME`
|
||||
/// * Windows: `C:\Users\UserName\AppData\Roaming\APPNAME`
|
||||
///
|
||||
/// where `APPNAME` is what is returned by [`Self::name()`].
|
||||
/// where `APPNAME` is what is given to `eframe::run_native`.
|
||||
fn save(&mut self, _storage: &mut dyn Storage) {}
|
||||
|
||||
/// Called before an exit that can be aborted.
|
||||
|
@ -156,17 +80,14 @@ pub trait App {
|
|||
true
|
||||
}
|
||||
|
||||
/// Called once on shutdown (before or after [`Self::save`]). If you need to abort an exit use
|
||||
/// [`Self::on_exit_event`]
|
||||
fn on_exit(&mut self) {}
|
||||
/// Called once on shutdown, after [`Self::save`].
|
||||
///
|
||||
/// If you need to abort an exit use [`Self::on_exit_event`].
|
||||
fn on_exit(&mut self, _gl: &glow::Context) {}
|
||||
|
||||
// ---------
|
||||
// Settings:
|
||||
|
||||
/// The name of your App, used for the title bar of native windows
|
||||
/// and the save location of persistence (see [`Self::save`]).
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// Time between automatic calls to [`Self::save`]
|
||||
fn auto_save_interval(&self) -> std::time::Duration {
|
||||
std::time::Duration::from_secs(30)
|
||||
|
@ -174,13 +95,12 @@ pub trait App {
|
|||
|
||||
/// The size limit of the web app canvas.
|
||||
///
|
||||
/// By default the size if limited to 1024x2048.
|
||||
/// By default the max size is [`egui::Vec2::INFINITY`], i.e. unlimited.
|
||||
///
|
||||
/// A larger canvas can lead to bad frame rates on some browsers on some platforms.
|
||||
/// In particular, Firefox on Mac and Linux is really bad at handling large WebGL canvases:
|
||||
/// <https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0> (unfixed since 2014).
|
||||
/// A large canvas can lead to bad frame rates on some older browsers on some platforms
|
||||
/// (see <https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0>).
|
||||
fn max_size_points(&self) -> egui::Vec2 {
|
||||
egui::Vec2::new(1024.0, 2048.0)
|
||||
egui::Vec2::INFINITY
|
||||
}
|
||||
|
||||
/// Background color for the app, e.g. what is sent to `gl.clearColor`.
|
||||
|
@ -263,6 +183,32 @@ pub struct NativeOptions {
|
|||
/// You control the transparency with [`App::clear_color()`].
|
||||
/// You should avoid having a [`egui::CentralPanel`], or make sure its frame is also transparent.
|
||||
pub transparent: bool,
|
||||
|
||||
/// Turn on vertical syncing, limiting the FPS to the display refresh rate.
|
||||
///
|
||||
/// The default is `true`.
|
||||
pub vsync: bool,
|
||||
|
||||
/// Set the level of the multisampling anti-aliasing (MSAA).
|
||||
///
|
||||
/// Must be a power-of-two. Higher = more smooth 3D.
|
||||
///
|
||||
/// A value of `0` turns it off (default).
|
||||
///
|
||||
/// `egui` already performs anti-aliasing via "feathering"
|
||||
/// (controlled by [`egui::epaint::TessellationOptions`]),
|
||||
/// but if you are embedding 3D in egui you may want to turn on multisampling.
|
||||
pub multisampling: u16,
|
||||
|
||||
/// Sets the number of bits in the depth buffer.
|
||||
///
|
||||
/// `egui` doesn't need the depth buffer, so the default value is 0.
|
||||
pub depth_buffer: u8,
|
||||
|
||||
/// Sets the number of bits in the stencil buffer.
|
||||
///
|
||||
/// `egui` doesn't need the stencil buffer, so the default value is 0.
|
||||
pub stencil_buffer: u8,
|
||||
}
|
||||
|
||||
impl Default for NativeOptions {
|
||||
|
@ -279,6 +225,10 @@ impl Default for NativeOptions {
|
|||
max_window_size: None,
|
||||
resizable: true,
|
||||
transparent: false,
|
||||
vsync: true,
|
||||
multisampling: 0,
|
||||
depth_buffer: 0,
|
||||
stencil_buffer: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -359,13 +309,6 @@ impl Frame {
|
|||
self.lock().output.drag_window = true;
|
||||
}
|
||||
|
||||
/// This signals the [`egui`] integration that a repaint is required.
|
||||
///
|
||||
/// Call this e.g. when a background process finishes in an async context and/or background thread.
|
||||
pub fn request_repaint(&self) {
|
||||
self.lock().repaint_signal.request_repaint();
|
||||
}
|
||||
|
||||
/// for integrations only: call once per frame
|
||||
pub fn take_app_output(&self) -> crate::backend::AppOutput {
|
||||
std::mem::take(&mut self.lock().output)
|
||||
|
@ -524,14 +467,6 @@ pub const APP_KEY: &str = "app";
|
|||
pub mod backend {
|
||||
use super::*;
|
||||
|
||||
/// How to signal the [`egui`] integration that a repaint is required.
|
||||
pub trait RepaintSignal: Send + Sync {
|
||||
/// This signals the [`egui`] integration that a repaint is required.
|
||||
///
|
||||
/// Call this e.g. when a background process finishes in an async context and/or background thread.
|
||||
fn request_repaint(&self);
|
||||
}
|
||||
|
||||
/// The data required by [`Frame`] each frame.
|
||||
pub struct FrameData {
|
||||
/// Information about the integration.
|
||||
|
@ -539,9 +474,6 @@ pub mod backend {
|
|||
|
||||
/// Where the app can issue commands back to the integration.
|
||||
pub output: AppOutput,
|
||||
|
||||
/// If you need to request a repaint from another thread, clone this and send it to that other thread.
|
||||
pub repaint_signal: std::sync::Arc<dyn RepaintSignal>,
|
||||
}
|
||||
|
||||
/// Action that can be taken by the user app.
|
||||
|
|
10
sh/check.sh
10
sh/check.sh
|
@ -20,17 +20,15 @@ cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps --all-feat
|
|||
cargo doc --document-private-items --no-deps --all-features
|
||||
|
||||
(cd emath && cargo check --no-default-features)
|
||||
(cd epaint && cargo check --no-default-features --features "single_threaded")
|
||||
(cd epaint && cargo check --no-default-features --features "multi_threaded")
|
||||
(cd epaint && cargo check --no-default-features --features "single_threaded" --release)
|
||||
(cd epaint && cargo check --no-default-features --features "multi_threaded" --release)
|
||||
(cd egui && cargo check --no-default-features --features "multi_threaded,serialize")
|
||||
(cd epaint && cargo check --no-default-features)
|
||||
(cd epaint && cargo check --no-default-features --release)
|
||||
(cd egui && cargo check --no-default-features --features "serialize")
|
||||
(cd eframe && cargo check --no-default-features)
|
||||
(cd epi && cargo check --no-default-features)
|
||||
(cd egui_demo_lib && cargo check --no-default-features)
|
||||
(cd egui_extras && cargo check --no-default-features)
|
||||
(cd egui_web && cargo check --no-default-features)
|
||||
# (cd egui-winit && cargo check --no-default-features) # we don't pick singlethreaded or multithreaded
|
||||
(cd egui-winit && cargo check --no-default-features)
|
||||
(cd egui_glium && cargo check --no-default-features)
|
||||
(cd egui_glow && cargo check --no-default-features)
|
||||
|
||||
|
|
Loading…
Reference in a new issue