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 }`
|
Example: `Shape::Circle { center, radius, fill, stroke }`
|
||||||
|
|
||||||
Depends on `emath`, [`ab_glyph`](https://crates.io/crates/ab_glyph), [`atomic_refcell`](https://crates.io/crates/atomic_refcell), [`ahash`](https://crates.io/crates/ahash).
|
Depends on `emath`.
|
||||||
|
|
||||||
### `egui_extras`
|
### `egui_extras`
|
||||||
This adds additional features on top of `egui`.
|
This adds additional features on top of `egui`.
|
||||||
|
|
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -7,14 +7,22 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Added ⭐
|
### Added ⭐
|
||||||
* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)).
|
* Added `Shape::Callback` for backend-specific painting, [with an example](https://github.com/emilk/egui/blob/master/eframe/examples/custom_3d_three-d.rs) ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||||
* Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)).
|
* Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)).
|
||||||
|
* `Context::request_repaint` will wake up UI thread, if integrations has called `Context::set_request_repaint_callback` ([#1366](https://github.com/emilk/egui/pull/1366)).
|
||||||
|
* Added `Ui::push_id` ([#1374](https://github.com/emilk/egui/pull/1374)).
|
||||||
|
* Added `Frame::outer_margin`.
|
||||||
|
|
||||||
### Changed 🔧
|
### Changed 🔧
|
||||||
* `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)).
|
* `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||||
|
* Renamed `Frame::margin` to `Frame::inner_margin`.
|
||||||
|
|
||||||
### Fixed 🐛
|
### Fixed 🐛
|
||||||
* Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)).
|
* Fixed ComboBoxes always being rendered left-aligned ([#1304](https://github.com/emilk/egui/pull/1304)).
|
||||||
|
* Fixed ui code that could lead to a deadlock ([#1380](https://github.com/emilk/egui/pull/1380)).
|
||||||
|
|
||||||
|
### Removed 🔥
|
||||||
|
* Removed the `single_threaded/multi_threaded` flags - egui is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)).
|
||||||
|
|
||||||
|
|
||||||
## 0.17.0 - 2022-02-22 - Improved font selection and image handling
|
## 0.17.0 - 2022-02-22 - Improved font selection and image handling
|
||||||
|
|
99
Cargo.lock
generated
99
Cargo.lock
generated
|
@ -81,6 +81,15 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "approx"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
@ -442,6 +451,16 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cgmath"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.19"
|
version = "0.4.19"
|
||||||
|
@ -993,9 +1012,9 @@ dependencies = [
|
||||||
"epi",
|
"epi",
|
||||||
"glow",
|
"glow",
|
||||||
"image",
|
"image",
|
||||||
"parking_lot 0.12.0",
|
|
||||||
"poll-promise",
|
"poll-promise",
|
||||||
"rfd",
|
"rfd",
|
||||||
|
"three-d",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1053,6 +1072,7 @@ dependencies = [
|
||||||
"poll-promise",
|
"poll-promise",
|
||||||
"serde",
|
"serde",
|
||||||
"syntect",
|
"syntect",
|
||||||
|
"tracing",
|
||||||
"unicode_names2",
|
"unicode_names2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1063,7 +1083,6 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"egui",
|
"egui",
|
||||||
"image",
|
"image",
|
||||||
"parking_lot 0.12.0",
|
|
||||||
"resvg",
|
"resvg",
|
||||||
"serde",
|
"serde",
|
||||||
"tiny-skia",
|
"tiny-skia",
|
||||||
|
@ -1489,6 +1508,16 @@ version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gloo-timers"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glow"
|
name = "glow"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
|
@ -1607,6 +1636,11 @@ name = "half"
|
||||||
version = "1.8.2"
|
version = "1.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
|
@ -1810,6 +1844,12 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "line-wrap"
|
name = "line-wrap"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -2125,6 +2165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
|
"libm",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3041,6 +3082,18 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "synstructure"
|
||||||
|
version = "0.12.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syntect"
|
name = "syntect"
|
||||||
version = "4.6.0"
|
version = "4.6.0"
|
||||||
|
@ -3129,6 +3182,25 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "three-d"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "git+https://github.com/asny/three-d?rev=fa475673e284e05b2f4e068769dce3ec5bcabc8d#fa475673e284e05b2f4e068769dce3ec5bcabc8d"
|
||||||
|
dependencies = [
|
||||||
|
"cgmath",
|
||||||
|
"gl_generator",
|
||||||
|
"gloo-timers",
|
||||||
|
"glow",
|
||||||
|
"half",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.43"
|
version = "0.1.43"
|
||||||
|
@ -3473,6 +3545,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3947,6 +4021,27 @@ dependencies = [
|
||||||
"zvariant",
|
"zvariant",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0fbc82b82efe24da867ee52e015e58178684bd9dd64c34e66bdf21da2582a9f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn",
|
||||||
|
"synstructure",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zvariant"
|
name = "zvariant"
|
||||||
version = "3.1.2"
|
version = "3.1.2"
|
||||||
|
|
24
README.md
24
README.md
|
@ -5,8 +5,8 @@
|
||||||
[](https://docs.rs/egui)
|
[](https://docs.rs/egui)
|
||||||
[](https://github.com/rust-secure-code/safety-dance/)
|
[](https://github.com/rust-secure-code/safety-dance/)
|
||||||
[](https://github.com/emilk/egui/actions?workflow=CI)
|
[](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)
|
[](https://discord.gg/JFcEma9bJq)
|
||||||
|
|
||||||
👉 [Click to run the web demo](https://www.egui.rs/#demo) 👈
|
👉 [Click to run the web demo](https://www.egui.rs/#demo) 👈
|
||||||
|
@ -85,7 +85,7 @@ On Fedora Rawhide you need to run:
|
||||||
* Extensible: [easy to write your own widgets for egui](https://github.com/emilk/egui/blob/master/egui_demo_lib/src/apps/demo/toggle_switch.rs)
|
* Extensible: [easy to write your own widgets for egui](https://github.com/emilk/egui/blob/master/egui_demo_lib/src/apps/demo/toggle_switch.rs)
|
||||||
* Modular: You should be able to use small parts of egui and combine them in new ways
|
* Modular: You should be able to use small parts of egui and combine them in new ways
|
||||||
* Safe: there is no `unsafe` code in egui
|
* Safe: there is no `unsafe` code in egui
|
||||||
* Minimal dependencies: [`ab_glyph`](https://crates.io/crates/ab_glyph) [`ahash`](https://crates.io/crates/ahash) [`atomic_refcell`](https://crates.io/crates/atomic_refcell), [`nohash-hasher`](https://crates.io/crates/nohash-hasher)
|
* Minimal dependencies: [`ab_glyph`](https://crates.io/crates/ab_glyph) [`ahash`](https://crates.io/crates/ahash) [`nohash-hasher`](https://crates.io/crates/nohash-hasher) [`parking_lot`](https://crates.io/crates/parking_lot)
|
||||||
|
|
||||||
egui is *not* a framework. egui is a library you call into, not an environment you program for.
|
egui is *not* a framework. egui is a library you call into, not an environment you program for.
|
||||||
|
|
||||||
|
@ -338,9 +338,23 @@ On Linux and Mac, Firefox will copy the WebGL render target from GPU, to CPU and
|
||||||
To alleviate the above mentioned performance issues the default max-width of an egui web app is 1024 points. You can change this by overriding the `fn max_size_points` of [`epi::App`](https://docs.rs/epi/latest/epi/trait.App.html).
|
To alleviate the above mentioned performance issues the default max-width of an egui web app is 1024 points. You can change this by overriding the `fn max_size_points` of [`epi::App`](https://docs.rs/epi/latest/epi/trait.App.html).
|
||||||
|
|
||||||
### How do I render 3D stuff in an egui area?
|
### How do I render 3D stuff in an egui area?
|
||||||
egui can't do 3D graphics itself, but if you use a 3D library (e.g. [`glium`](https://github.com/glium/glium) using [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium), or [`miniquad`](https://github.com/not-fl3/miniquad) using [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad)) you can render your 3D content to a texture, then display it using [`ui.image(…)`](https://docs.rs/egui/latest/egui/struct.Ui.html#method.image). You first need to convert the native texture to an [`egui::TextureId`](https://docs.rs/egui/latest/egui/enum.TextureId.html), and how to do this depends on the integration you use (e.g. [`register_glium_texture`](https://docs.rs/epi/latest/epi/trait.NativeTexture.html#tymethod.register_native_texture)).
|
There are multiple ways to combine egui with 3D. The simplest way is to use a 3D library and have egui sit on top of the 3D view. See for instance [`bevy_egui`](https://github.com/mvlabat/bevy_egui) or [`three-d`](https://github.com/asny/three-d).
|
||||||
|
|
||||||
There is an example for showing a native glium texture in an egui window at <https://github.com/emilk/egui/blob/master/egui_glium/examples/native_texture.rs>.
|
If you want to embed 3D into an egui view there are two options.
|
||||||
|
|
||||||
|
#### `Shape::Callback`
|
||||||
|
Examples:
|
||||||
|
* <https://github.com/emilk/egui/blob/master/eframe/examples/custom_3d_three-d.rs>
|
||||||
|
* <https://github.com/emilk/egui/blob/master/eframe/examples/custom_3d_glow.rs>
|
||||||
|
|
||||||
|
`Shape::Callback` will call your code when egui gets painted, to show anything using whatever the background rendering context is. When using [`eframe`](https://github.com/emilk/egui/tree/master/eframe) this will be [`glow`](https://github.com/grovesNL/glow). Other integrations will give you other rendering contexts, if they support `Shape::Callback` at all.
|
||||||
|
|
||||||
|
#### Render-to-texture
|
||||||
|
You can also render your 3D scene to a texture and display it using [`ui.image(…)`](https://docs.rs/egui/latest/egui/struct.Ui.html#method.image). You first need to convert the native texture to an [`egui::TextureId`](https://docs.rs/egui/latest/egui/enum.TextureId.html), and how to do this depends on the integration you use.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
* Using [`egui-miniquad`]( https://github.com/not-fl3/egui-miniquad): https://github.com/not-fl3/egui-miniquad/blob/master/examples/render_to_egui_image.rs
|
||||||
|
* Using [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium): <https://github.com/emilk/egui/blob/master/egui_glium/examples/native_texture.rs>.
|
||||||
|
|
||||||
|
|
||||||
## Other
|
## Other
|
||||||
|
|
|
@ -5,8 +5,11 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
* Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)).
|
|
||||||
* Change default for `NativeOptions::drag_and_drop_support` to `true` ([#1329](https://github.com/emilk/egui/pull/1329)).
|
* Change default for `NativeOptions::drag_and_drop_support` to `true` ([#1329](https://github.com/emilk/egui/pull/1329)).
|
||||||
|
* Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)).
|
||||||
|
* Removed `Frame::request_repaint` - just call `egui::Context::request_repaint` for the same effect ([#1366](https://github.com/emilk/egui/pull/1366)).
|
||||||
|
* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)).
|
||||||
|
* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
|
||||||
|
|
||||||
|
|
||||||
## 0.17.0 - 2022-02-22
|
## 0.17.0 - 2022-02-22
|
||||||
|
|
|
@ -71,6 +71,6 @@ image = { version = "0.24", default-features = false, features = [
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"png",
|
"png",
|
||||||
] }
|
] }
|
||||||
parking_lot = "0.12"
|
|
||||||
poll-promise = "0.1"
|
poll-promise = "0.1"
|
||||||
rfd = "0.8"
|
rfd = "0.8"
|
||||||
|
three-d = { git = "https://github.com/asny/three-d", rev = "fa475673e284e05b2f4e068769dce3ec5bcabc8d", default-features = false } # 2022-03-22
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
use eframe::{egui, epi};
|
use eframe::egui;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let options = eframe::NativeOptions::default();
|
||||||
|
eframe::run_native(
|
||||||
|
"Confirm exit",
|
||||||
|
options,
|
||||||
|
Box::new(|_cc| Box::new(MyApp::default())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct MyApp {
|
struct MyApp {
|
||||||
|
@ -8,17 +17,13 @@ struct MyApp {
|
||||||
is_exiting: bool,
|
is_exiting: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for MyApp {
|
impl eframe::App for MyApp {
|
||||||
fn name(&self) -> &str {
|
|
||||||
"Confirm exit"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_exit_event(&mut self) -> bool {
|
fn on_exit_event(&mut self) -> bool {
|
||||||
self.is_exiting = true;
|
self.is_exiting = true;
|
||||||
self.can_exit
|
self.can_exit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.heading("Try to close the window");
|
ui.heading("Try to close the window");
|
||||||
});
|
});
|
||||||
|
@ -42,8 +47,3 @@ impl epi::App for MyApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let options = eframe::NativeOptions::default();
|
|
||||||
eframe::run_native(Box::new(MyApp::default()), options);
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,68 +2,87 @@
|
||||||
//!
|
//!
|
||||||
//! This is very advanced usage, and you need to be careful.
|
//! This is very advanced usage, and you need to be careful.
|
||||||
//!
|
//!
|
||||||
//! If you want an easier way to show 3D graphics with egui, take a look at:
|
//! If you want an easier way to show 3D graphics with egui, take a look at the `custom_3d_three-d.rs` example.
|
||||||
|
//!
|
||||||
|
//! If you are content of having egui sit on top of a 3D background, take a look at:
|
||||||
|
//!
|
||||||
//! * [`bevy_egui`](https://github.com/mvlabat/bevy_egui)
|
//! * [`bevy_egui`](https://github.com/mvlabat/bevy_egui)
|
||||||
//! * [`three-d`](https://github.com/asny/three-d)
|
//! * [`three-d`](https://github.com/asny/three-d)
|
||||||
|
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
#![allow(unsafe_code)]
|
||||||
|
|
||||||
use eframe::{egui, epi};
|
use eframe::egui;
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
use egui::mutex::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Default)]
|
fn main() {
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
initial_window_size: Some(egui::vec2(350.0, 380.0)),
|
||||||
|
multisampling: 8,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
eframe::run_native(
|
||||||
|
"Custom 3D painting in eframe using glow",
|
||||||
|
options,
|
||||||
|
Box::new(|cc| Box::new(MyApp::new(cc))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
struct MyApp {
|
struct MyApp {
|
||||||
rotating_triangle: Arc<Mutex<Option<RotatingTriangle>>>,
|
/// Behind an `Arc<Mutex<…>>` so we can pass it to [`egui::PaintCallback`] and paint later.
|
||||||
|
rotating_triangle: Arc<Mutex<RotatingTriangle>>,
|
||||||
angle: f32,
|
angle: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for MyApp {
|
impl MyApp {
|
||||||
fn name(&self) -> &str {
|
fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||||
"Custom 3D painting inside an egui window"
|
Self {
|
||||||
|
rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(&cc.gl))),
|
||||||
|
angle: 0.0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
impl eframe::App for MyApp {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.heading("Here is some 3D stuff:");
|
ui.horizontal(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
egui::ScrollArea::both().show(ui, |ui| {
|
ui.label("The triangle is being painted using ");
|
||||||
egui::Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
|
||||||
self.custom_painting(ui);
|
ui.label(" (OpenGL).");
|
||||||
});
|
|
||||||
ui.label("Drag to rotate!");
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
let mut frame = egui::Frame::window(&*ctx.style());
|
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||||
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);
|
self.custom_painting(ui);
|
||||||
});
|
});
|
||||||
|
ui.label("Drag to rotate!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_exit(&mut self, gl: &glow::Context) {
|
||||||
|
self.rotating_triangle.lock().destroy(gl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MyApp {
|
impl MyApp {
|
||||||
fn custom_painting(&mut self, ui: &mut egui::Ui) {
|
fn custom_painting(&mut self, ui: &mut egui::Ui) {
|
||||||
let (rect, response) =
|
let (rect, response) =
|
||||||
ui.allocate_exact_size(egui::Vec2::splat(256.0), egui::Sense::drag());
|
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
|
||||||
|
|
||||||
self.angle += response.drag_delta().x * 0.01;
|
self.angle += response.drag_delta().x * 0.01;
|
||||||
|
|
||||||
|
// Clone locals so we can move them into the paint callback:
|
||||||
let angle = self.angle;
|
let angle = self.angle;
|
||||||
let rotating_triangle = self.rotating_triangle.clone();
|
let rotating_triangle = self.rotating_triangle.clone();
|
||||||
|
|
||||||
let callback = egui::epaint::PaintCallback {
|
let callback = egui::PaintCallback {
|
||||||
rect,
|
rect,
|
||||||
callback: std::sync::Arc::new(move |render_ctx| {
|
callback: std::sync::Arc::new(move |_info, render_ctx| {
|
||||||
if let Some(painter) = render_ctx.downcast_ref::<egui_glow::Painter>() {
|
if let Some(painter) = render_ctx.downcast_ref::<egui_glow::Painter>() {
|
||||||
let mut rotating_triangle = rotating_triangle.lock();
|
rotating_triangle.lock().paint(painter.gl(), angle);
|
||||||
let rotating_triangle = rotating_triangle
|
|
||||||
.get_or_insert_with(|| RotatingTriangle::new(painter.gl()));
|
|
||||||
rotating_triangle.paint(painter.gl(), angle);
|
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Can't do custom painting because we are not using a glow context");
|
eprintln!("Can't do custom painting because we are not using a glow context");
|
||||||
}
|
}
|
||||||
|
@ -163,9 +182,7 @@ impl RotatingTriangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: figure out how to call this in a nice way
|
fn destroy(&self, gl: &glow::Context) {
|
||||||
#[allow(unused)]
|
|
||||||
fn destroy(self, gl: &glow::Context) {
|
|
||||||
use glow::HasContext as _;
|
use glow::HasContext as _;
|
||||||
unsafe {
|
unsafe {
|
||||||
gl.delete_program(self.program);
|
gl.delete_program(self.program);
|
||||||
|
@ -186,8 +203,3 @@ impl RotatingTriangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let options = eframe::NativeOptions::default();
|
|
||||||
eframe::run_native(Box::new(MyApp::default()), options);
|
|
||||||
}
|
|
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,66 +1,63 @@
|
||||||
use eframe::{egui, epi};
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let options = eframe::NativeOptions::default();
|
||||||
|
eframe::run_native(
|
||||||
|
"egui example: custom font",
|
||||||
|
options,
|
||||||
|
Box::new(|cc| Box::new(MyApp::new(cc))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_custom_fonts(ctx: &egui::Context) {
|
||||||
|
// Start with the default fonts (we will be adding to them rather than replacing them).
|
||||||
|
let mut fonts = egui::FontDefinitions::default();
|
||||||
|
|
||||||
|
// Install my own font (maybe supporting non-latin characters).
|
||||||
|
// .ttf and .otf files supported.
|
||||||
|
fonts.font_data.insert(
|
||||||
|
"my_font".to_owned(),
|
||||||
|
egui::FontData::from_static(include_bytes!("../../epaint/fonts/Hack-Regular.ttf")),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Put my font first (highest priority) for proportional text:
|
||||||
|
fonts
|
||||||
|
.families
|
||||||
|
.entry(egui::FontFamily::Proportional)
|
||||||
|
.or_default()
|
||||||
|
.insert(0, "my_font".to_owned());
|
||||||
|
|
||||||
|
// Put my font as last fallback for monospace:
|
||||||
|
fonts
|
||||||
|
.families
|
||||||
|
.entry(egui::FontFamily::Monospace)
|
||||||
|
.or_default()
|
||||||
|
.push("my_font".to_owned());
|
||||||
|
|
||||||
|
// Tell egui to use these fonts:
|
||||||
|
ctx.set_fonts(fonts);
|
||||||
|
}
|
||||||
|
|
||||||
struct MyApp {
|
struct MyApp {
|
||||||
text: String,
|
text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MyApp {
|
impl MyApp {
|
||||||
fn default() -> Self {
|
fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||||
|
setup_custom_fonts(&cc.egui_ctx);
|
||||||
Self {
|
Self {
|
||||||
text: "Edit this text field if you want".to_owned(),
|
text: "Edit this text field if you want".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for MyApp {
|
impl eframe::App for MyApp {
|
||||||
fn name(&self) -> &str {
|
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||||
"egui example: custom font"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup(
|
|
||||||
&mut self,
|
|
||||||
ctx: &egui::Context,
|
|
||||||
_frame: &epi::Frame,
|
|
||||||
_storage: Option<&dyn epi::Storage>,
|
|
||||||
_gl: &std::rc::Rc<epi::glow::Context>,
|
|
||||||
) {
|
|
||||||
// Start with the default fonts (we will be adding to them rather than replacing them).
|
|
||||||
let mut fonts = egui::FontDefinitions::default();
|
|
||||||
|
|
||||||
// Install my own font (maybe supporting non-latin characters).
|
|
||||||
// .ttf and .otf files supported.
|
|
||||||
fonts.font_data.insert(
|
|
||||||
"my_font".to_owned(),
|
|
||||||
egui::FontData::from_static(include_bytes!("../../epaint/fonts/Hack-Regular.ttf")),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Put my font first (highest priority) for proportional text:
|
|
||||||
fonts
|
|
||||||
.families
|
|
||||||
.entry(egui::FontFamily::Proportional)
|
|
||||||
.or_default()
|
|
||||||
.insert(0, "my_font".to_owned());
|
|
||||||
|
|
||||||
// Put my font as last fallback for monospace:
|
|
||||||
fonts
|
|
||||||
.families
|
|
||||||
.entry(egui::FontFamily::Monospace)
|
|
||||||
.or_default()
|
|
||||||
.push("my_font".to_owned());
|
|
||||||
|
|
||||||
// Tell egui to use these fonts:
|
|
||||||
ctx.set_fonts(fonts);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.heading("egui using custom fonts");
|
ui.heading("egui using custom fonts");
|
||||||
ui.text_edit_multiline(&mut self.text);
|
ui.text_edit_multiline(&mut self.text);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let options = eframe::NativeOptions::default();
|
|
||||||
eframe::run_native(Box::new(MyApp::default()), options);
|
|
||||||
}
|
|
||||||
|
|
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
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
use eframe::{egui, epi};
|
use eframe::egui;
|
||||||
use egui_extras::RetainedImage;
|
use egui_extras::RetainedImage;
|
||||||
use poll_promise::Promise;
|
use poll_promise::Promise;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let options = eframe::NativeOptions::default();
|
let options = eframe::NativeOptions::default();
|
||||||
eframe::run_native(Box::new(MyApp::default()), options);
|
eframe::run_native(
|
||||||
|
"Download and show an image with eframe/egui",
|
||||||
|
options,
|
||||||
|
Box::new(|_cc| Box::new(MyApp::default())),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -15,23 +19,19 @@ struct MyApp {
|
||||||
promise: Option<Promise<ehttp::Result<RetainedImage>>>,
|
promise: Option<Promise<ehttp::Result<RetainedImage>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for MyApp {
|
impl eframe::App for MyApp {
|
||||||
fn name(&self) -> &str {
|
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||||
"Download and show an image with eframe/egui"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
|
||||||
let promise = self.promise.get_or_insert_with(|| {
|
let promise = self.promise.get_or_insert_with(|| {
|
||||||
// Begin download.
|
// Begin download.
|
||||||
// We download the image using `ehttp`, a library that works both in WASM and on native.
|
// We download the image using `ehttp`, a library that works both in WASM and on native.
|
||||||
// We use the `poll-promise` library to communicate with the UI thread.
|
// We use the `poll-promise` library to communicate with the UI thread.
|
||||||
let frame = frame.clone();
|
let ctx = ctx.clone();
|
||||||
let (sender, promise) = Promise::new();
|
let (sender, promise) = Promise::new();
|
||||||
let request = ehttp::Request::get("https://picsum.photos/seed/1.759706314/1024");
|
let request = ehttp::Request::get("https://picsum.photos/seed/1.759706314/1024");
|
||||||
ehttp::fetch(request, move |response| {
|
ehttp::fetch(request, move |response| {
|
||||||
let image = response.and_then(parse_response);
|
let image = response.and_then(parse_response);
|
||||||
sender.send(image); // send the results back to the UI thread.
|
sender.send(image); // send the results back to the UI thread.
|
||||||
frame.request_repaint(); // wake up UI thread
|
ctx.request_repaint(); // wake up UI thread
|
||||||
});
|
});
|
||||||
promise
|
promise
|
||||||
});
|
});
|
||||||
|
@ -50,6 +50,7 @@ impl epi::App for MyApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
fn parse_response(response: ehttp::Response) -> Result<RetainedImage, String> {
|
fn parse_response(response: ehttp::Response) -> Result<RetainedImage, String> {
|
||||||
let content_type = response.content_type().unwrap_or_default();
|
let content_type = response.content_type().unwrap_or_default();
|
||||||
if content_type.starts_with("image/") {
|
if content_type.starts_with("image/") {
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
use eframe::{egui, epi};
|
use eframe::egui;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
drag_and_drop_support: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
eframe::run_native(
|
||||||
|
"Native file dialogs and drag-and-drop files",
|
||||||
|
options,
|
||||||
|
Box::new(|_cc| Box::new(MyApp::default())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct MyApp {
|
struct MyApp {
|
||||||
|
@ -8,12 +20,8 @@ struct MyApp {
|
||||||
picked_path: Option<String>,
|
picked_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for MyApp {
|
impl eframe::App for MyApp {
|
||||||
fn name(&self) -> &str {
|
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||||
"Native file dialogs and drag-and-drop files"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.label("Drag-and-drop files onto the window!");
|
ui.label("Drag-and-drop files onto the window!");
|
||||||
|
|
||||||
|
@ -93,11 +101,3 @@ impl MyApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let options = eframe::NativeOptions {
|
|
||||||
drag_and_drop_support: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
eframe::run_native(Box::new(MyApp::default()), options);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
use eframe::{egui, epi};
|
use eframe::egui;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let options = eframe::NativeOptions::default();
|
||||||
|
eframe::run_native(
|
||||||
|
"My egui App",
|
||||||
|
options,
|
||||||
|
Box::new(|_cc| Box::new(MyApp::default())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
struct MyApp {
|
struct MyApp {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -16,12 +25,8 @@ impl Default for MyApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for MyApp {
|
impl eframe::App for MyApp {
|
||||||
fn name(&self) -> &str {
|
fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||||
"My egui App"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.heading("My egui Application");
|
ui.heading("My egui Application");
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
@ -39,8 +44,3 @@ impl epi::App for MyApp {
|
||||||
frame.set_window_size(ctx.used_size());
|
frame.set_window_size(ctx.used_size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let options = eframe::NativeOptions::default();
|
|
||||||
eframe::run_native(Box::new(MyApp::default()), options);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,20 @@
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
use eframe::{egui, epi};
|
use eframe::egui;
|
||||||
use egui_extras::RetainedImage;
|
use egui_extras::RetainedImage;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
initial_window_size: Some(egui::vec2(500.0, 900.0)),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
eframe::run_native(
|
||||||
|
"Show an image with eframe/egui",
|
||||||
|
options,
|
||||||
|
Box::new(|_cc| Box::new(MyApp::default())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
struct MyApp {
|
struct MyApp {
|
||||||
image: RetainedImage,
|
image: RetainedImage,
|
||||||
}
|
}
|
||||||
|
@ -19,16 +31,18 @@ impl Default for MyApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for MyApp {
|
impl eframe::App for MyApp {
|
||||||
fn name(&self) -> &str {
|
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||||
"Show an image with eframe/egui"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.heading("This is an image:");
|
ui.heading("This is an image:");
|
||||||
self.image.show(ui);
|
self.image.show(ui);
|
||||||
|
|
||||||
|
ui.heading("This is a rotated image:");
|
||||||
|
ui.add(
|
||||||
|
egui::Image::new(self.image.texture_id(ctx), self.image.size_vec2())
|
||||||
|
.rotate(45.0_f32.to_radians(), egui::Vec2::splat(0.5)),
|
||||||
|
);
|
||||||
|
|
||||||
ui.heading("This is an image you can click:");
|
ui.heading("This is an image you can click:");
|
||||||
ui.add(egui::ImageButton::new(
|
ui.add(egui::ImageButton::new(
|
||||||
self.image.texture_id(ctx),
|
self.image.texture_id(ctx),
|
||||||
|
@ -37,8 +51,3 @@ impl epi::App for MyApp {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let options = eframe::NativeOptions::default();
|
|
||||||
eframe::run_native(Box::new(MyApp::default()), options);
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,7 +4,19 @@
|
||||||
|
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
use eframe::{egui, epi};
|
use eframe::egui;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
initial_window_size: Some(egui::vec2(1000.0, 700.0)),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
eframe::run_native(
|
||||||
|
"svg example",
|
||||||
|
options,
|
||||||
|
Box::new(|_cc| Box::new(MyApp::default())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
struct MyApp {
|
struct MyApp {
|
||||||
svg_image: egui_extras::RetainedImage,
|
svg_image: egui_extras::RetainedImage,
|
||||||
|
@ -22,12 +34,8 @@ impl Default for MyApp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for MyApp {
|
impl eframe::App for MyApp {
|
||||||
fn name(&self) -> &str {
|
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
|
||||||
"svg example"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
ui.heading("SVG example");
|
ui.heading("SVG example");
|
||||||
ui.label("The SVG is rasterized and displayed as a texture.");
|
ui.label("The SVG is rasterized and displayed as a texture.");
|
||||||
|
@ -39,11 +47,3 @@ impl epi::App for MyApp {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let options = eframe::NativeOptions {
|
|
||||||
initial_window_size: Some(egui::vec2(1000.0, 700.0)),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
eframe::run_native(Box::new(MyApp::default()), options);
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,27 +16,32 @@
|
||||||
//!
|
//!
|
||||||
//! ## Usage, native:
|
//! ## Usage, native:
|
||||||
//! ``` no_run
|
//! ``` no_run
|
||||||
//! use eframe::{epi, egui};
|
//! use eframe::egui;
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! let native_options = eframe::NativeOptions::default();
|
||||||
|
//! eframe::run_native("My egui App", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))));
|
||||||
|
//! }
|
||||||
//!
|
//!
|
||||||
//! #[derive(Default)]
|
//! #[derive(Default)]
|
||||||
//! struct MyEguiApp {}
|
//! struct MyEguiApp {}
|
||||||
//!
|
//!
|
||||||
//! impl epi::App for MyEguiApp {
|
//! impl MyEguiApp {
|
||||||
//! fn name(&self) -> &str {
|
//! fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||||
//! "My egui App"
|
//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
|
||||||
//! }
|
//! // Restore app state using cc.storage (requires the "persistence" feature).
|
||||||
|
//! // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
|
||||||
|
//! // for e.g. egui::PaintCallback.
|
||||||
|
//! Self::default()
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
//!
|
//!
|
||||||
//! fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
//! impl eframe::App for MyEguiApp {
|
||||||
|
//! fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||||
//! egui::CentralPanel::default().show(ctx, |ui| {
|
//! egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
//! ui.heading("Hello World!");
|
//! ui.heading("Hello World!");
|
||||||
//! });
|
//! });
|
||||||
//! }
|
//! }
|
||||||
//!}
|
|
||||||
//!
|
|
||||||
//! fn main() {
|
|
||||||
//! let app = MyEguiApp::default();
|
|
||||||
//! let native_options = eframe::NativeOptions::default();
|
|
||||||
//! eframe::run_native(Box::new(app), native_options);
|
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
@ -49,26 +54,17 @@
|
||||||
//! #[cfg(target_arch = "wasm32")]
|
//! #[cfg(target_arch = "wasm32")]
|
||||||
//! #[wasm_bindgen]
|
//! #[wasm_bindgen]
|
||||||
//! pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
|
//! pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
|
||||||
//! let app = MyEguiApp::default();
|
//! eframe::start_web(canvas_id, Box::new(|cc| Box::new(MyApp::new(cc))))
|
||||||
//! eframe::start_web(canvas_id, Box::new(app))
|
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
// Forbid warnings in release builds:
|
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(
|
|
||||||
clippy::all,
|
|
||||||
missing_docs,
|
|
||||||
rust_2018_idioms,
|
|
||||||
rustdoc::missing_crate_level_docs
|
|
||||||
)]
|
|
||||||
#![allow(clippy::needless_doctest_main)]
|
#![allow(clippy::needless_doctest_main)]
|
||||||
|
|
||||||
|
// Re-export all useful libraries:
|
||||||
pub use {egui, egui::emath, egui::epaint, epi};
|
pub use {egui, egui::emath, egui::epaint, epi};
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
// Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is:
|
||||||
pub use epi::NativeOptions;
|
pub use epi::*;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// When compiling for web
|
// When compiling for web
|
||||||
|
@ -79,20 +75,6 @@ pub use egui_web::wasm_bindgen;
|
||||||
/// Install event listeners to register different input events
|
/// Install event listeners to register different input events
|
||||||
/// and start running the given app.
|
/// and start running the given app.
|
||||||
///
|
///
|
||||||
/// For performance reasons (on some browsers) the egui canvas does not, by default,
|
|
||||||
/// fill the whole width of the browser.
|
|
||||||
/// This can be changed by overriding [`epi::Frame::max_size_points`].
|
|
||||||
///
|
|
||||||
/// ### Usage, native:
|
|
||||||
/// ``` no_run
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = MyEguiApp::default();
|
|
||||||
/// let native_options = eframe::NativeOptions::default();
|
|
||||||
/// eframe::run_native(Box::new(app), native_options);
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// ### Web
|
|
||||||
/// ``` no_run
|
/// ``` no_run
|
||||||
/// #[cfg(target_arch = "wasm32")]
|
/// #[cfg(target_arch = "wasm32")]
|
||||||
/// use wasm_bindgen::prelude::*;
|
/// use wasm_bindgen::prelude::*;
|
||||||
|
@ -104,45 +86,55 @@ pub use egui_web::wasm_bindgen;
|
||||||
/// #[cfg(target_arch = "wasm32")]
|
/// #[cfg(target_arch = "wasm32")]
|
||||||
/// #[wasm_bindgen]
|
/// #[wasm_bindgen]
|
||||||
/// pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
|
/// pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
|
||||||
/// let app = MyEguiApp::default();
|
/// eframe::start_web(canvas_id, Box::new(|cc| Box::new(MyEguiApp::new(cc))))
|
||||||
/// eframe::start_web(canvas_id, Box::new(app))
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn start_web(canvas_id: &str, app: Box<dyn epi::App>) -> Result<(), wasm_bindgen::JsValue> {
|
pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bindgen::JsValue> {
|
||||||
egui_web::start(canvas_id, app)?;
|
egui_web::start(canvas_id, app_creator)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// When compiling natively
|
// When compiling natively
|
||||||
|
|
||||||
|
/// This is how you start a native (desktop) app.
|
||||||
|
///
|
||||||
|
/// The first argument is name of your app, used for the title bar of the native window
|
||||||
|
/// and the save location of persistence (see [`epi::App::save`]).
|
||||||
|
///
|
||||||
/// Call from `fn main` like this:
|
/// Call from `fn main` like this:
|
||||||
/// ``` no_run
|
/// ``` no_run
|
||||||
/// use eframe::{epi, egui};
|
/// use eframe::egui;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let native_options = eframe::NativeOptions::default();
|
||||||
|
/// eframe::run_native("MyApp", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))));
|
||||||
|
/// }
|
||||||
///
|
///
|
||||||
/// #[derive(Default)]
|
/// #[derive(Default)]
|
||||||
/// struct MyEguiApp {}
|
/// struct MyEguiApp {}
|
||||||
///
|
///
|
||||||
/// impl epi::App for MyEguiApp {
|
/// impl MyEguiApp {
|
||||||
/// fn name(&self) -> &str {
|
/// fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||||
/// "My egui App"
|
/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
|
||||||
/// }
|
/// // Restore app state using cc.storage (requires the "persistence" feature).
|
||||||
|
/// // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
|
||||||
|
/// // for e.g. egui::PaintCallback.
|
||||||
|
/// Self::default()
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
///
|
///
|
||||||
/// fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
/// impl eframe::App for MyEguiApp {
|
||||||
|
/// fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
|
||||||
/// egui::CentralPanel::default().show(ctx, |ui| {
|
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
/// ui.heading("Hello World!");
|
/// ui.heading("Hello World!");
|
||||||
/// });
|
/// });
|
||||||
/// }
|
/// }
|
||||||
///}
|
|
||||||
///
|
|
||||||
/// fn main() {
|
|
||||||
/// let app = MyEguiApp::default();
|
|
||||||
/// let native_options = eframe::NativeOptions::default();
|
|
||||||
/// eframe::run_native(Box::new(app), native_options);
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn run_native(app: Box<dyn epi::App>, native_options: epi::NativeOptions) -> ! {
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
egui_glow::run(app, &native_options)
|
pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! {
|
||||||
|
egui_glow::run(app_name, &native_options, app_creator)
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,6 @@ serialize = ["egui/serialize", "serde"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||||
"single_threaded",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
] }
|
] }
|
||||||
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
||||||
|
|
|
@ -21,6 +21,10 @@ pub fn window_builder(
|
||||||
max_window_size,
|
max_window_size,
|
||||||
resizable,
|
resizable,
|
||||||
transparent,
|
transparent,
|
||||||
|
vsync: _, // used in `fn create_display`
|
||||||
|
multisampling: _, // used in `fn create_display`
|
||||||
|
depth_buffer: _, // used in `fn create_display`
|
||||||
|
stencil_buffer: _, // used in `fn create_display`
|
||||||
} = native_options;
|
} = native_options;
|
||||||
|
|
||||||
let window_icon = icon_data.clone().and_then(load_icon);
|
let window_icon = icon_data.clone().and_then(load_icon);
|
||||||
|
@ -216,12 +220,11 @@ impl Persistence {
|
||||||
|
|
||||||
/// Everything needed to make a winit-based integration for [`epi`].
|
/// Everything needed to make a winit-based integration for [`epi`].
|
||||||
pub struct EpiIntegration {
|
pub struct EpiIntegration {
|
||||||
frame: epi::Frame,
|
pub frame: epi::Frame,
|
||||||
persistence: crate::epi::Persistence,
|
pub persistence: crate::epi::Persistence,
|
||||||
pub egui_ctx: egui::Context,
|
pub egui_ctx: egui::Context,
|
||||||
pending_full_output: egui::FullOutput,
|
pending_full_output: egui::FullOutput,
|
||||||
egui_winit: crate::State,
|
egui_winit: crate::State,
|
||||||
pub app: Box<dyn epi::App>,
|
|
||||||
/// When set, it is time to quit
|
/// When set, it is time to quit
|
||||||
quit: bool,
|
quit: bool,
|
||||||
can_drag_window: bool,
|
can_drag_window: bool,
|
||||||
|
@ -232,10 +235,7 @@ impl EpiIntegration {
|
||||||
integration_name: &'static str,
|
integration_name: &'static str,
|
||||||
max_texture_side: usize,
|
max_texture_side: usize,
|
||||||
window: &winit::window::Window,
|
window: &winit::window::Window,
|
||||||
gl: &std::rc::Rc<glow::Context>,
|
|
||||||
repaint_signal: std::sync::Arc<dyn epi::backend::RepaintSignal>,
|
|
||||||
persistence: crate::epi::Persistence,
|
persistence: crate::epi::Persistence,
|
||||||
app: Box<dyn epi::App>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let egui_ctx = egui::Context::default();
|
let egui_ctx = egui::Context::default();
|
||||||
|
|
||||||
|
@ -252,7 +252,6 @@ impl EpiIntegration {
|
||||||
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
|
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
|
||||||
},
|
},
|
||||||
output: Default::default(),
|
output: Default::default(),
|
||||||
repaint_signal,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if prefer_dark_mode == Some(true) {
|
if prefer_dark_mode == Some(true) {
|
||||||
|
@ -261,41 +260,21 @@ impl EpiIntegration {
|
||||||
egui_ctx.set_visuals(egui::Visuals::light());
|
egui_ctx.set_visuals(egui::Visuals::light());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut slf = Self {
|
Self {
|
||||||
frame,
|
frame,
|
||||||
persistence,
|
persistence,
|
||||||
egui_ctx,
|
egui_ctx,
|
||||||
egui_winit: crate::State::new(max_texture_side, window),
|
egui_winit: crate::State::new(max_texture_side, window),
|
||||||
pending_full_output: Default::default(),
|
pending_full_output: Default::default(),
|
||||||
app,
|
|
||||||
quit: false,
|
quit: false,
|
||||||
can_drag_window: false,
|
can_drag_window: false,
|
||||||
};
|
|
||||||
|
|
||||||
slf.setup(window, gl);
|
|
||||||
if slf.app.warm_up_enabled() {
|
|
||||||
slf.warm_up(window);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
slf
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, window: &winit::window::Window, gl: &std::rc::Rc<glow::Context>) {
|
pub fn warm_up(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
|
||||||
self.app
|
|
||||||
.setup(&self.egui_ctx, &self.frame, self.persistence.storage(), gl);
|
|
||||||
let app_output = self.frame.take_app_output();
|
|
||||||
|
|
||||||
if app_output.quit {
|
|
||||||
self.quit = self.app.on_exit_event();
|
|
||||||
}
|
|
||||||
|
|
||||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn warm_up(&mut self, window: &winit::window::Window) {
|
|
||||||
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
|
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
|
||||||
self.egui_ctx.memory().set_everything_is_visible(true);
|
self.egui_ctx.memory().set_everything_is_visible(true);
|
||||||
let full_output = self.update(window);
|
let full_output = self.update(app, window);
|
||||||
self.pending_full_output.append(full_output); // Handle it next frame
|
self.pending_full_output.append(full_output); // Handle it next frame
|
||||||
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
|
||||||
self.egui_ctx.clear_animations();
|
self.egui_ctx.clear_animations();
|
||||||
|
@ -306,11 +285,11 @@ impl EpiIntegration {
|
||||||
self.quit
|
self.quit
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) {
|
pub fn on_event(&mut self, app: &mut dyn epi::App, event: &winit::event::WindowEvent<'_>) {
|
||||||
use winit::event::{ElementState, MouseButton, WindowEvent};
|
use winit::event::{ElementState, MouseButton, WindowEvent};
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
WindowEvent::CloseRequested => self.quit = self.app.on_exit_event(),
|
WindowEvent::CloseRequested => self.quit = app.on_exit_event(),
|
||||||
WindowEvent::Destroyed => self.quit = true,
|
WindowEvent::Destroyed => self.quit = true,
|
||||||
WindowEvent::MouseInput {
|
WindowEvent::MouseInput {
|
||||||
button: MouseButton::Left,
|
button: MouseButton::Left,
|
||||||
|
@ -323,12 +302,16 @@ impl EpiIntegration {
|
||||||
self.egui_winit.on_event(&self.egui_ctx, event);
|
self.egui_winit.on_event(&self.egui_ctx, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, window: &winit::window::Window) -> egui::FullOutput {
|
pub fn update(
|
||||||
|
&mut self,
|
||||||
|
app: &mut dyn epi::App,
|
||||||
|
window: &winit::window::Window,
|
||||||
|
) -> egui::FullOutput {
|
||||||
let frame_start = instant::Instant::now();
|
let frame_start = instant::Instant::now();
|
||||||
|
|
||||||
let raw_input = self.egui_winit.take_egui_input(window);
|
let raw_input = self.egui_winit.take_egui_input(window);
|
||||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||||
self.app.update(egui_ctx, &self.frame);
|
app.update(egui_ctx, &self.frame);
|
||||||
});
|
});
|
||||||
self.pending_full_output.append(full_output);
|
self.pending_full_output.append(full_output);
|
||||||
let full_output = std::mem::take(&mut self.pending_full_output);
|
let full_output = std::mem::take(&mut self.pending_full_output);
|
||||||
|
@ -338,7 +321,7 @@ impl EpiIntegration {
|
||||||
app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
|
app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
|
||||||
self.can_drag_window = false;
|
self.can_drag_window = false;
|
||||||
if app_output.quit {
|
if app_output.quit {
|
||||||
self.quit = self.app.on_exit_event();
|
self.quit = app.on_exit_event();
|
||||||
}
|
}
|
||||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||||
}
|
}
|
||||||
|
@ -358,15 +341,9 @@ impl EpiIntegration {
|
||||||
.handle_platform_output(window, &self.egui_ctx, platform_output);
|
.handle_platform_output(window, &self.egui_ctx, platform_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
|
pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
|
||||||
self.persistence
|
self.persistence
|
||||||
.maybe_autosave(&mut *self.app, &self.egui_ctx, window);
|
.maybe_autosave(&mut *app, &self.egui_ctx, window);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_exit(&mut self, window: &winit::window::Window) {
|
|
||||||
self.app.on_exit();
|
|
||||||
self.persistence
|
|
||||||
.save(&mut *self.app, &self.egui_ctx, window);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,84 +3,6 @@
|
||||||
//! The library translates winit events to egui, handled copy/paste,
|
//! The library translates winit events to egui, handled copy/paste,
|
||||||
//! updates the cursor, open links clicked in egui, etc.
|
//! updates the cursor, open links clicked in egui, etc.
|
||||||
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(
|
|
||||||
clippy::all,
|
|
||||||
clippy::await_holding_lock,
|
|
||||||
clippy::char_lit_as_u8,
|
|
||||||
clippy::checked_conversions,
|
|
||||||
clippy::dbg_macro,
|
|
||||||
clippy::debug_assert_with_mut_call,
|
|
||||||
clippy::disallowed_method,
|
|
||||||
clippy::doc_markdown,
|
|
||||||
clippy::empty_enum,
|
|
||||||
clippy::enum_glob_use,
|
|
||||||
clippy::exit,
|
|
||||||
clippy::expl_impl_clone_on_copy,
|
|
||||||
clippy::explicit_deref_methods,
|
|
||||||
clippy::explicit_into_iter_loop,
|
|
||||||
clippy::fallible_impl_from,
|
|
||||||
clippy::filter_map_next,
|
|
||||||
clippy::flat_map_option,
|
|
||||||
clippy::float_cmp_const,
|
|
||||||
clippy::fn_params_excessive_bools,
|
|
||||||
clippy::from_iter_instead_of_collect,
|
|
||||||
clippy::if_let_mutex,
|
|
||||||
clippy::implicit_clone,
|
|
||||||
clippy::imprecise_flops,
|
|
||||||
clippy::inefficient_to_string,
|
|
||||||
clippy::invalid_upcast_comparisons,
|
|
||||||
clippy::large_digit_groups,
|
|
||||||
clippy::large_stack_arrays,
|
|
||||||
clippy::large_types_passed_by_value,
|
|
||||||
clippy::let_unit_value,
|
|
||||||
clippy::linkedlist,
|
|
||||||
clippy::lossy_float_literal,
|
|
||||||
clippy::macro_use_imports,
|
|
||||||
clippy::manual_ok_or,
|
|
||||||
clippy::map_err_ignore,
|
|
||||||
clippy::map_flatten,
|
|
||||||
clippy::map_unwrap_or,
|
|
||||||
clippy::match_on_vec_items,
|
|
||||||
clippy::match_same_arms,
|
|
||||||
clippy::match_wild_err_arm,
|
|
||||||
clippy::match_wildcard_for_single_variants,
|
|
||||||
clippy::mem_forget,
|
|
||||||
clippy::mismatched_target_os,
|
|
||||||
clippy::missing_errors_doc,
|
|
||||||
clippy::missing_safety_doc,
|
|
||||||
clippy::mut_mut,
|
|
||||||
clippy::mutex_integer,
|
|
||||||
clippy::needless_borrow,
|
|
||||||
clippy::needless_continue,
|
|
||||||
clippy::needless_for_each,
|
|
||||||
clippy::needless_pass_by_value,
|
|
||||||
clippy::option_option,
|
|
||||||
clippy::path_buf_push_overwrite,
|
|
||||||
clippy::ptr_as_ptr,
|
|
||||||
clippy::ref_option_ref,
|
|
||||||
clippy::rest_pat_in_fully_bound_structs,
|
|
||||||
clippy::same_functions_in_if_condition,
|
|
||||||
clippy::semicolon_if_nothing_returned,
|
|
||||||
clippy::single_match_else,
|
|
||||||
clippy::string_add_assign,
|
|
||||||
clippy::string_add,
|
|
||||||
clippy::string_lit_as_bytes,
|
|
||||||
clippy::string_to_string,
|
|
||||||
clippy::todo,
|
|
||||||
clippy::trait_duplication_in_bounds,
|
|
||||||
clippy::unimplemented,
|
|
||||||
clippy::unnested_or_patterns,
|
|
||||||
clippy::unused_self,
|
|
||||||
clippy::useless_transmute,
|
|
||||||
clippy::verbose_file_reads,
|
|
||||||
clippy::zero_sized_map_values,
|
|
||||||
future_incompatible,
|
|
||||||
nonstandard_style,
|
|
||||||
rust_2018_idioms,
|
|
||||||
rustdoc::missing_crate_level_docs
|
|
||||||
)]
|
|
||||||
#![allow(clippy::float_cmp)]
|
|
||||||
#![allow(clippy::manual_range_contains)]
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
pub use winit;
|
pub use winit;
|
||||||
|
|
|
@ -20,7 +20,7 @@ all-features = true
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["default_fonts", "single_threaded"]
|
default = ["default_fonts"]
|
||||||
|
|
||||||
# add compatibility with https://crates.io/crates/cint
|
# add compatibility with https://crates.io/crates/cint
|
||||||
cint = ["epaint/cint"]
|
cint = ["epaint/cint"]
|
||||||
|
@ -46,11 +46,6 @@ persistence = ["serde", "epaint/serialize", "ron"]
|
||||||
# implement serde on most types.
|
# implement serde on most types.
|
||||||
serialize = ["serde", "epaint/serialize"]
|
serialize = ["serde", "epaint/serialize"]
|
||||||
|
|
||||||
# multi_threaded is only needed if you plan to use the same egui::Context
|
|
||||||
# from multiple threads. It comes with a minor performance impact.
|
|
||||||
single_threaded = ["epaint/single_threaded"]
|
|
||||||
multi_threaded = ["epaint/multi_threaded"]
|
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
epaint = { version = "0.17.0", path = "../epaint", default-features = false }
|
epaint = { version = "0.17.0", path = "../epaint", default-features = false }
|
||||||
|
|
|
@ -7,8 +7,10 @@ use epaint::*;
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
#[must_use = "You should call .show()"]
|
#[must_use = "You should call .show()"]
|
||||||
pub struct Frame {
|
pub struct Frame {
|
||||||
/// On each side
|
/// Margin within the painted frame.
|
||||||
pub margin: Margin,
|
pub inner_margin: Margin,
|
||||||
|
/// Margin outside the painted frame.
|
||||||
|
pub outer_margin: Margin,
|
||||||
pub rounding: Rounding,
|
pub rounding: Rounding,
|
||||||
pub shadow: Shadow,
|
pub shadow: Shadow,
|
||||||
pub fill: Color32,
|
pub fill: Color32,
|
||||||
|
@ -23,7 +25,7 @@ impl Frame {
|
||||||
/// For when you want to group a few widgets together within a frame.
|
/// For when you want to group a few widgets together within a frame.
|
||||||
pub fn group(style: &Style) -> Self {
|
pub fn group(style: &Style) -> Self {
|
||||||
Self {
|
Self {
|
||||||
margin: Margin::same(6.0), // symmetric looks best in corners when nesting
|
inner_margin: Margin::same(6.0), // symmetric looks best in corners when nesting
|
||||||
rounding: style.visuals.widgets.noninteractive.rounding,
|
rounding: style.visuals.widgets.noninteractive.rounding,
|
||||||
stroke: style.visuals.widgets.noninteractive.bg_stroke,
|
stroke: style.visuals.widgets.noninteractive.bg_stroke,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -32,7 +34,7 @@ impl Frame {
|
||||||
|
|
||||||
pub(crate) fn side_top_panel(style: &Style) -> Self {
|
pub(crate) fn side_top_panel(style: &Style) -> Self {
|
||||||
Self {
|
Self {
|
||||||
margin: Margin::symmetric(8.0, 2.0),
|
inner_margin: Margin::symmetric(8.0, 2.0),
|
||||||
rounding: Rounding::none(),
|
rounding: Rounding::none(),
|
||||||
fill: style.visuals.window_fill(),
|
fill: style.visuals.window_fill(),
|
||||||
stroke: style.visuals.window_stroke(),
|
stroke: style.visuals.window_stroke(),
|
||||||
|
@ -42,7 +44,7 @@ impl Frame {
|
||||||
|
|
||||||
pub(crate) fn central_panel(style: &Style) -> Self {
|
pub(crate) fn central_panel(style: &Style) -> Self {
|
||||||
Self {
|
Self {
|
||||||
margin: Margin::symmetric(8.0, 8.0),
|
inner_margin: Margin::symmetric(8.0, 8.0),
|
||||||
rounding: Rounding::none(),
|
rounding: Rounding::none(),
|
||||||
fill: style.visuals.window_fill(),
|
fill: style.visuals.window_fill(),
|
||||||
stroke: Default::default(),
|
stroke: Default::default(),
|
||||||
|
@ -52,31 +54,34 @@ impl Frame {
|
||||||
|
|
||||||
pub fn window(style: &Style) -> Self {
|
pub fn window(style: &Style) -> Self {
|
||||||
Self {
|
Self {
|
||||||
margin: style.spacing.window_margin,
|
inner_margin: style.spacing.window_margin,
|
||||||
rounding: style.visuals.window_rounding,
|
rounding: style.visuals.window_rounding,
|
||||||
shadow: style.visuals.window_shadow,
|
shadow: style.visuals.window_shadow,
|
||||||
fill: style.visuals.window_fill(),
|
fill: style.visuals.window_fill(),
|
||||||
stroke: style.visuals.window_stroke(),
|
stroke: style.visuals.window_stroke(),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn menu(style: &Style) -> Self {
|
pub fn menu(style: &Style) -> Self {
|
||||||
Self {
|
Self {
|
||||||
margin: Margin::same(1.0),
|
inner_margin: Margin::same(1.0),
|
||||||
rounding: style.visuals.widgets.noninteractive.rounding,
|
rounding: style.visuals.widgets.noninteractive.rounding,
|
||||||
shadow: style.visuals.popup_shadow,
|
shadow: style.visuals.popup_shadow,
|
||||||
fill: style.visuals.window_fill(),
|
fill: style.visuals.window_fill(),
|
||||||
stroke: style.visuals.window_stroke(),
|
stroke: style.visuals.window_stroke(),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn popup(style: &Style) -> Self {
|
pub fn popup(style: &Style) -> Self {
|
||||||
Self {
|
Self {
|
||||||
margin: style.spacing.window_margin,
|
inner_margin: style.spacing.window_margin,
|
||||||
rounding: style.visuals.widgets.noninteractive.rounding,
|
rounding: style.visuals.widgets.noninteractive.rounding,
|
||||||
shadow: style.visuals.popup_shadow,
|
shadow: style.visuals.popup_shadow,
|
||||||
fill: style.visuals.window_fill(),
|
fill: style.visuals.window_fill(),
|
||||||
stroke: style.visuals.window_stroke(),
|
stroke: style.visuals.window_stroke(),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +91,7 @@ impl Frame {
|
||||||
/// and in dark mode this will be very dark.
|
/// and in dark mode this will be very dark.
|
||||||
pub fn canvas(style: &Style) -> Self {
|
pub fn canvas(style: &Style) -> Self {
|
||||||
Self {
|
Self {
|
||||||
margin: Margin::symmetric(10.0, 10.0),
|
inner_margin: Margin::symmetric(10.0, 10.0),
|
||||||
rounding: style.visuals.widgets.noninteractive.rounding,
|
rounding: style.visuals.widgets.noninteractive.rounding,
|
||||||
fill: style.visuals.extreme_bg_color,
|
fill: style.visuals.extreme_bg_color,
|
||||||
stroke: style.visuals.window_stroke(),
|
stroke: style.visuals.window_stroke(),
|
||||||
|
@ -119,12 +124,23 @@ impl Frame {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Margin on each side of the frame.
|
/// Margin within the painted frame.
|
||||||
pub fn margin(mut self, margin: impl Into<Margin>) -> Self {
|
pub fn inner_margin(mut self, inner_margin: impl Into<Margin>) -> Self {
|
||||||
self.margin = margin.into();
|
self.inner_margin = inner_margin.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Margin outside the painted frame.
|
||||||
|
pub fn outer_margin(mut self, outer_margin: impl Into<Margin>) -> Self {
|
||||||
|
self.outer_margin = outer_margin.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated = "Renamed inner_margin in egui 0.18"]
|
||||||
|
pub fn margin(self, margin: impl Into<Margin>) -> Self {
|
||||||
|
self.inner_margin(margin)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn shadow(mut self, shadow: Shadow) -> Self {
|
pub fn shadow(mut self, shadow: Shadow) -> Self {
|
||||||
self.shadow = shadow;
|
self.shadow = shadow;
|
||||||
self
|
self
|
||||||
|
@ -150,8 +166,8 @@ impl Frame {
|
||||||
let outer_rect_bounds = ui.available_rect_before_wrap();
|
let outer_rect_bounds = ui.available_rect_before_wrap();
|
||||||
|
|
||||||
let mut inner_rect = outer_rect_bounds;
|
let mut inner_rect = outer_rect_bounds;
|
||||||
inner_rect.min += Vec2::new(self.margin.left, self.margin.top);
|
inner_rect.min += self.outer_margin.left_top() + self.inner_margin.left_top();
|
||||||
inner_rect.max -= Vec2::new(self.margin.right, self.margin.bottom);
|
inner_rect.max -= self.outer_margin.right_bottom() + self.inner_margin.right_bottom();
|
||||||
|
|
||||||
// Make sure we don't shrink to the negative:
|
// Make sure we don't shrink to the negative:
|
||||||
inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x);
|
inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x);
|
||||||
|
@ -185,7 +201,8 @@ impl Frame {
|
||||||
|
|
||||||
pub fn paint(&self, outer_rect: Rect) -> Shape {
|
pub fn paint(&self, outer_rect: Rect) -> Shape {
|
||||||
let Self {
|
let Self {
|
||||||
margin: _,
|
inner_margin: _,
|
||||||
|
outer_margin: _,
|
||||||
rounding,
|
rounding,
|
||||||
shadow,
|
shadow,
|
||||||
fill,
|
fill,
|
||||||
|
@ -210,15 +227,15 @@ impl Frame {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Prepared {
|
impl Prepared {
|
||||||
pub fn outer_rect(&self) -> Rect {
|
fn paint_rect(&self) -> Rect {
|
||||||
let mut rect = self.content_ui.min_rect();
|
let mut rect = self.content_ui.min_rect();
|
||||||
rect.min -= Vec2::new(self.frame.margin.left, self.frame.margin.top);
|
rect.min -= self.frame.inner_margin.left_top();
|
||||||
rect.max += Vec2::new(self.frame.margin.right, self.frame.margin.bottom);
|
rect.max += self.frame.inner_margin.right_bottom();
|
||||||
rect
|
rect
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end(self, ui: &mut Ui) -> Response {
|
pub fn end(self, ui: &mut Ui) -> Response {
|
||||||
let outer_rect = self.outer_rect();
|
let paint_rect = self.paint_rect();
|
||||||
|
|
||||||
let Prepared {
|
let Prepared {
|
||||||
frame,
|
frame,
|
||||||
|
@ -226,11 +243,11 @@ impl Prepared {
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
if ui.is_rect_visible(outer_rect) {
|
if ui.is_rect_visible(paint_rect) {
|
||||||
let shape = frame.paint(outer_rect);
|
let shape = frame.paint(paint_rect);
|
||||||
ui.painter().set(where_to_put_background, shape);
|
ui.painter().set(where_to_put_background, shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.allocate_rect(outer_rect, Sense::hover())
|
ui.allocate_rect(paint_rect, Sense::hover())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -298,7 +298,7 @@ pub fn popup_below_widget<R>(
|
||||||
// Note: we use a separate clip-rect for this area, so the popup can be outside the parent.
|
// Note: we use a separate clip-rect for this area, so the popup can be outside the parent.
|
||||||
// See https://github.com/emilk/egui/issues/825
|
// See https://github.com/emilk/egui/issues/825
|
||||||
let frame = Frame::popup(ui.style());
|
let frame = Frame::popup(ui.style());
|
||||||
let frame_margin = frame.margin;
|
let frame_margin = frame.inner_margin + frame.outer_margin;
|
||||||
frame
|
frame
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
|
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
|
||||||
|
|
|
@ -301,7 +301,9 @@ impl<'open> Window<'open> {
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
let margins = frame.margin.sum() + vec2(0.0, title_bar_height);
|
let margins = frame.outer_margin.sum()
|
||||||
|
+ frame.inner_margin.sum()
|
||||||
|
+ vec2(0.0, title_bar_height);
|
||||||
|
|
||||||
interact(
|
interact(
|
||||||
window_interaction,
|
window_interaction,
|
||||||
|
|
|
@ -48,6 +48,7 @@ struct ContextImpl {
|
||||||
|
|
||||||
/// While positive, keep requesting repaints. Decrement at the end of each frame.
|
/// While positive, keep requesting repaints. Decrement at the end of each frame.
|
||||||
repaint_requests: u32,
|
repaint_requests: u32,
|
||||||
|
request_repaint_callbacks: Option<Box<dyn Fn() + Send + Sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextImpl {
|
impl ContextImpl {
|
||||||
|
@ -533,11 +534,28 @@ impl Context {
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
/// Call this if there is need to repaint the UI, i.e. if you are showing an animation.
|
/// Call this if there is need to repaint the UI, i.e. if you are showing an animation.
|
||||||
|
///
|
||||||
/// If this is called at least once in a frame, then there will be another frame right after this.
|
/// If this is called at least once in a frame, then there will be another frame right after this.
|
||||||
/// Call as many times as you wish, only one repaint will be issued.
|
/// Call as many times as you wish, only one repaint will be issued.
|
||||||
|
///
|
||||||
|
/// If called from outside the UI thread, the UI thread will wake up and run,
|
||||||
|
/// provided the egui integration has set that up via [`Self::set_request_repaint_callback`]
|
||||||
|
/// (this will work on `eframe`).
|
||||||
pub fn request_repaint(&self) {
|
pub fn request_repaint(&self) {
|
||||||
// request two frames of repaint, just to cover some corner cases (frame delays):
|
// request two frames of repaint, just to cover some corner cases (frame delays):
|
||||||
self.write().repaint_requests = 2;
|
let mut ctx = self.write();
|
||||||
|
ctx.repaint_requests = 2;
|
||||||
|
if let Some(callback) = &ctx.request_repaint_callbacks {
|
||||||
|
(callback)();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`].
|
||||||
|
///
|
||||||
|
/// This lets you wake up a sleeping UI thread.
|
||||||
|
pub fn set_request_repaint_callback(&self, callback: impl Fn() + Send + Sync + 'static) {
|
||||||
|
let callback = Box::new(callback);
|
||||||
|
self.write().request_repaint_callbacks = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell `egui` which fonts to use.
|
/// Tell `egui` which fonts to use.
|
||||||
|
@ -667,8 +685,19 @@ impl Context {
|
||||||
name: impl Into<String>,
|
name: impl Into<String>,
|
||||||
image: impl Into<ImageData>,
|
image: impl Into<ImageData>,
|
||||||
) -> TextureHandle {
|
) -> TextureHandle {
|
||||||
|
let name = name.into();
|
||||||
|
let image = image.into();
|
||||||
|
let max_texture_side = self.input().max_texture_side;
|
||||||
|
crate::egui_assert!(
|
||||||
|
image.width() <= max_texture_side && image.height() <= max_texture_side,
|
||||||
|
"Texture {:?} has size {}x{}, but the maximum texture side is {}",
|
||||||
|
name,
|
||||||
|
image.width(),
|
||||||
|
image.height(),
|
||||||
|
max_texture_side
|
||||||
|
);
|
||||||
let tex_mngr = self.tex_manager();
|
let tex_mngr = self.tex_manager();
|
||||||
let tex_id = tex_mngr.write().alloc(name.into(), image.into());
|
let tex_id = tex_mngr.write().alloc(name, image);
|
||||||
TextureHandle::new(tex_mngr, tex_id)
|
TextureHandle::new(tex_mngr, tex_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -778,14 +807,16 @@ impl Context {
|
||||||
// shapes are the same, but just comparing the shapes takes about 50% of the time
|
// shapes are the same, but just comparing the shapes takes about 50% of the time
|
||||||
// it takes to tessellate them, so it is not a worth optimization.
|
// it takes to tessellate them, so it is not a worth optimization.
|
||||||
|
|
||||||
let mut tessellation_options = *self.tessellation_options();
|
let pixels_per_point = self.pixels_per_point();
|
||||||
tessellation_options.pixels_per_point = self.pixels_per_point();
|
let tessellation_options = *self.tessellation_options();
|
||||||
tessellation_options.aa_size = 1.0 / self.pixels_per_point();
|
let font_image_size = self.fonts().font_image_size();
|
||||||
|
|
||||||
let paint_stats = PaintStats::from_shapes(&shapes);
|
let paint_stats = PaintStats::from_shapes(&shapes);
|
||||||
let clipped_primitives = tessellator::tessellate_shapes(
|
let clipped_primitives = tessellator::tessellate_shapes(
|
||||||
shapes,
|
pixels_per_point,
|
||||||
tessellation_options,
|
tessellation_options,
|
||||||
self.fonts().font_image_size(),
|
shapes,
|
||||||
|
font_image_size,
|
||||||
);
|
);
|
||||||
self.write().paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives);
|
self.write().paint_stats = paint_stats.with_clipped_primitives(&clipped_primitives);
|
||||||
clipped_primitives
|
clipped_primitives
|
||||||
|
|
|
@ -101,11 +101,14 @@ impl Widget for &epaint::stats::PaintStats {
|
||||||
|
|
||||||
ui.label("Intermediate:");
|
ui.label("Intermediate:");
|
||||||
label(ui, shapes, "shapes").on_hover_text("Boxes, circles, etc");
|
label(ui, shapes, "shapes").on_hover_text("Boxes, circles, etc");
|
||||||
label(ui, shape_text, "text (mostly cached)");
|
ui.horizontal(|ui| {
|
||||||
|
label(ui, shape_text, "text");
|
||||||
|
ui.small("(mostly cached)");
|
||||||
|
});
|
||||||
label(ui, shape_path, "paths");
|
label(ui, shape_path, "paths");
|
||||||
label(ui, shape_mesh, "nested meshes");
|
label(ui, shape_mesh, "nested meshes");
|
||||||
label(ui, shape_vec, "nested shapes");
|
label(ui, shape_vec, "nested shapes");
|
||||||
ui.label(format!("{} callbacks", num_callbacks));
|
ui.label(format!("{:6} callbacks", num_callbacks));
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
|
|
||||||
ui.label("Text shapes:");
|
ui.label("Text shapes:");
|
||||||
|
@ -115,7 +118,7 @@ impl Widget for &epaint::stats::PaintStats {
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
|
|
||||||
ui.label("Tessellated (and culled):");
|
ui.label("Tessellated (and culled):");
|
||||||
label(ui, clipped_primitives, "clipped_primitives")
|
label(ui, clipped_primitives, "primitives lists")
|
||||||
.on_hover_text("Number of separate clip rectangles");
|
.on_hover_text("Number of separate clip rectangles");
|
||||||
label(ui, vertices, "vertices");
|
label(ui, vertices, "vertices");
|
||||||
label(ui, indices, "indices").on_hover_text("Three 32-bit indices per triangles");
|
label(ui, indices, "indices").on_hover_text("Three 32-bit indices per triangles");
|
||||||
|
@ -136,9 +139,8 @@ impl Widget for &mut epaint::TessellationOptions {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
let epaint::TessellationOptions {
|
let epaint::TessellationOptions {
|
||||||
pixels_per_point: _,
|
feathering,
|
||||||
aa_size: _,
|
feathering_size_in_pixels,
|
||||||
anti_alias,
|
|
||||||
coarse_tessellation_culling,
|
coarse_tessellation_culling,
|
||||||
round_text_to_pixels,
|
round_text_to_pixels,
|
||||||
debug_paint_clip_rects,
|
debug_paint_clip_rects,
|
||||||
|
@ -147,8 +149,15 @@ impl Widget for &mut epaint::TessellationOptions {
|
||||||
bezier_tolerance,
|
bezier_tolerance,
|
||||||
epsilon: _,
|
epsilon: _,
|
||||||
} = self;
|
} = self;
|
||||||
ui.checkbox(anti_alias, "Antialias")
|
|
||||||
|
ui.checkbox(feathering, "Feathering (antialias)")
|
||||||
.on_hover_text("Apply feathering to smooth out the edges of shapes. Turn off for small performance gain.");
|
.on_hover_text("Apply feathering to smooth out the edges of shapes. Turn off for small performance gain.");
|
||||||
|
let feathering_slider = crate::Slider::new(feathering_size_in_pixels, 0.0..=10.0)
|
||||||
|
.smallest_positive(0.1)
|
||||||
|
.logarithmic(true)
|
||||||
|
.text("Feathering size in pixels");
|
||||||
|
ui.add_enabled(*feathering, feathering_slider);
|
||||||
|
|
||||||
ui.add(
|
ui.add(
|
||||||
crate::widgets::Slider::new(bezier_tolerance, 0.0001..=10.0)
|
crate::widgets::Slider::new(bezier_tolerance, 0.0001..=10.0)
|
||||||
.logarithmic(true)
|
.logarithmic(true)
|
||||||
|
|
|
@ -273,85 +273,6 @@
|
||||||
//! # });
|
//! # });
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
// Forbid warnings in release builds:
|
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(
|
|
||||||
clippy::all,
|
|
||||||
clippy::await_holding_lock,
|
|
||||||
clippy::char_lit_as_u8,
|
|
||||||
clippy::checked_conversions,
|
|
||||||
clippy::dbg_macro,
|
|
||||||
clippy::debug_assert_with_mut_call,
|
|
||||||
clippy::disallowed_method,
|
|
||||||
clippy::doc_markdown,
|
|
||||||
clippy::empty_enum,
|
|
||||||
clippy::enum_glob_use,
|
|
||||||
clippy::exit,
|
|
||||||
clippy::expl_impl_clone_on_copy,
|
|
||||||
clippy::explicit_deref_methods,
|
|
||||||
clippy::explicit_into_iter_loop,
|
|
||||||
clippy::fallible_impl_from,
|
|
||||||
clippy::filter_map_next,
|
|
||||||
clippy::flat_map_option,
|
|
||||||
clippy::float_cmp_const,
|
|
||||||
clippy::fn_params_excessive_bools,
|
|
||||||
clippy::from_iter_instead_of_collect,
|
|
||||||
clippy::if_let_mutex,
|
|
||||||
clippy::implicit_clone,
|
|
||||||
clippy::imprecise_flops,
|
|
||||||
clippy::inefficient_to_string,
|
|
||||||
clippy::invalid_upcast_comparisons,
|
|
||||||
clippy::large_digit_groups,
|
|
||||||
clippy::large_stack_arrays,
|
|
||||||
clippy::large_types_passed_by_value,
|
|
||||||
clippy::let_unit_value,
|
|
||||||
clippy::linkedlist,
|
|
||||||
clippy::lossy_float_literal,
|
|
||||||
clippy::macro_use_imports,
|
|
||||||
clippy::manual_ok_or,
|
|
||||||
clippy::map_err_ignore,
|
|
||||||
clippy::map_flatten,
|
|
||||||
clippy::map_unwrap_or,
|
|
||||||
clippy::match_on_vec_items,
|
|
||||||
clippy::match_same_arms,
|
|
||||||
clippy::match_wild_err_arm,
|
|
||||||
clippy::match_wildcard_for_single_variants,
|
|
||||||
clippy::mem_forget,
|
|
||||||
clippy::mismatched_target_os,
|
|
||||||
clippy::missing_errors_doc,
|
|
||||||
clippy::missing_safety_doc,
|
|
||||||
clippy::mut_mut,
|
|
||||||
clippy::mutex_integer,
|
|
||||||
clippy::needless_borrow,
|
|
||||||
clippy::needless_continue,
|
|
||||||
clippy::needless_for_each,
|
|
||||||
clippy::needless_pass_by_value,
|
|
||||||
clippy::option_option,
|
|
||||||
clippy::path_buf_push_overwrite,
|
|
||||||
clippy::ptr_as_ptr,
|
|
||||||
clippy::ref_option_ref,
|
|
||||||
clippy::rest_pat_in_fully_bound_structs,
|
|
||||||
clippy::same_functions_in_if_condition,
|
|
||||||
clippy::semicolon_if_nothing_returned,
|
|
||||||
clippy::single_match_else,
|
|
||||||
clippy::string_add_assign,
|
|
||||||
clippy::string_add,
|
|
||||||
clippy::string_lit_as_bytes,
|
|
||||||
clippy::string_to_string,
|
|
||||||
clippy::todo,
|
|
||||||
clippy::trait_duplication_in_bounds,
|
|
||||||
clippy::unimplemented,
|
|
||||||
clippy::unnested_or_patterns,
|
|
||||||
clippy::unused_self,
|
|
||||||
clippy::useless_transmute,
|
|
||||||
clippy::verbose_file_reads,
|
|
||||||
clippy::zero_sized_map_values,
|
|
||||||
future_incompatible,
|
|
||||||
nonstandard_style,
|
|
||||||
rust_2018_idioms,
|
|
||||||
rustdoc::missing_crate_level_docs
|
|
||||||
)]
|
|
||||||
#![allow(clippy::float_cmp)]
|
#![allow(clippy::float_cmp)]
|
||||||
#![allow(clippy::manual_range_contains)]
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
|
@ -386,14 +307,15 @@ pub use epaint::{
|
||||||
color, mutex,
|
color, mutex,
|
||||||
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
|
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
|
||||||
textures::TexturesDelta,
|
textures::TexturesDelta,
|
||||||
AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, Rgba, Rounding, Shape,
|
AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, PaintCallback,
|
||||||
Stroke, TextureHandle, TextureId,
|
PaintCallbackInfo, Rgba, Rounding, Shape, Stroke, TextureHandle, TextureId,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod text {
|
pub mod text {
|
||||||
|
pub use crate::text_edit::CCursorRange;
|
||||||
pub use epaint::text::{
|
pub use epaint::text::{
|
||||||
FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob, LayoutSection, TextFormat,
|
cursor::CCursor, FontData, FontDefinitions, FontFamily, Fonts, Galley, LayoutJob,
|
||||||
TAB_SIZE,
|
LayoutSection, TextFormat, TAB_SIZE,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -277,10 +277,10 @@ impl MenuRoot {
|
||||||
root: &mut MenuRootManager,
|
root: &mut MenuRootManager,
|
||||||
id: Id,
|
id: Id,
|
||||||
) -> MenuResponse {
|
) -> MenuResponse {
|
||||||
let pointer = &response.ctx.input().pointer;
|
// Lock the input once for the whole function call (see https://github.com/emilk/egui/pull/1380).
|
||||||
if (response.clicked() && root.is_menu_open(id))
|
let input = response.ctx.input();
|
||||||
|| response.ctx.input().key_pressed(Key::Escape)
|
|
||||||
{
|
if (response.clicked() && root.is_menu_open(id)) || input.key_pressed(Key::Escape) {
|
||||||
// menu open and button clicked or esc pressed
|
// menu open and button clicked or esc pressed
|
||||||
return MenuResponse::Close;
|
return MenuResponse::Close;
|
||||||
} else if (response.clicked() && !root.is_menu_open(id))
|
} else if (response.clicked() && !root.is_menu_open(id))
|
||||||
|
@ -290,8 +290,8 @@ impl MenuRoot {
|
||||||
// or button hovered while other menu is open
|
// or button hovered while other menu is open
|
||||||
let pos = response.rect.left_bottom();
|
let pos = response.rect.left_bottom();
|
||||||
return MenuResponse::Create(pos, id);
|
return MenuResponse::Create(pos, id);
|
||||||
} else if pointer.any_pressed() && pointer.primary_down() {
|
} else if input.pointer.any_pressed() && input.pointer.primary_down() {
|
||||||
if let Some(pos) = pointer.interact_pos() {
|
if let Some(pos) = input.pointer.interact_pos() {
|
||||||
if let Some(root) = root.inner.as_mut() {
|
if let Some(root) = root.inner.as_mut() {
|
||||||
if root.id == id {
|
if root.id == id {
|
||||||
// pressed somewhere while this menu is open
|
// pressed somewhere while this menu is open
|
||||||
|
|
|
@ -277,6 +277,8 @@ impl Spacing {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct Margin {
|
pub struct Margin {
|
||||||
|
@ -312,6 +314,20 @@ impl Margin {
|
||||||
pub fn sum(&self) -> Vec2 {
|
pub fn sum(&self) -> Vec2 {
|
||||||
Vec2::new(self.left + self.right, self.top + self.bottom)
|
Vec2::new(self.left + self.right, self.top + self.bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn left_top(&self) -> Vec2 {
|
||||||
|
Vec2::new(self.left, self.top)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn right_bottom(&self) -> Vec2 {
|
||||||
|
Vec2::new(self.right, self.bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f32> for Margin {
|
||||||
|
fn from(v: f32) -> Self {
|
||||||
|
Self::same(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Vec2> for Margin {
|
impl From<Vec2> for Margin {
|
||||||
|
@ -320,6 +336,20 @@ impl From<Vec2> for Margin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add for Margin {
|
||||||
|
type Output = Self;
|
||||||
|
fn add(self, other: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
left: self.left + other.left,
|
||||||
|
right: self.right + other.right,
|
||||||
|
top: self.top + other.top,
|
||||||
|
bottom: self.bottom + other.bottom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// How and when interaction happens.
|
/// How and when interaction happens.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
|
|
@ -1546,6 +1546,26 @@ impl Ui {
|
||||||
crate::Frame::group(self.style()).show(self, add_contents)
|
crate::Frame::group(self.style()).show(self, add_contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a child Ui with an explicit [`Id`].
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # egui::__run_test_ui(|ui| {
|
||||||
|
/// for i in 0..10 {
|
||||||
|
/// // `ui.make_persistent_id("foo")` here will produce the same id each loop.
|
||||||
|
/// ui.push_id(i, |ui| {
|
||||||
|
/// // `ui.make_persistent_id("foo")` here will produce different id:s
|
||||||
|
/// });
|
||||||
|
/// }
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
pub fn push_id<R>(
|
||||||
|
&mut self,
|
||||||
|
id_source: impl Hash,
|
||||||
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
|
) -> InnerResponse<R> {
|
||||||
|
self.scope_dyn(Box::new(add_contents), Id::new(id_source))
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a scoped child ui.
|
/// Create a scoped child ui.
|
||||||
///
|
///
|
||||||
/// You can use this to temporarily change the [`Style`] of a sub-region, for instance:
|
/// You can use this to temporarily change the [`Style`] of a sub-region, for instance:
|
||||||
|
@ -1559,16 +1579,17 @@ impl Ui {
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
pub fn scope<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
|
pub fn scope<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
|
||||||
self.scope_dyn(Box::new(add_contents))
|
self.scope_dyn(Box::new(add_contents), Id::new("child"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scope_dyn<'c, R>(
|
fn scope_dyn<'c, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||||
|
id_source: Id,
|
||||||
) -> InnerResponse<R> {
|
) -> InnerResponse<R> {
|
||||||
let child_rect = self.available_rect_before_wrap();
|
let child_rect = self.available_rect_before_wrap();
|
||||||
let next_auto_id_source = self.next_auto_id_source;
|
let next_auto_id_source = self.next_auto_id_source;
|
||||||
let mut child_ui = self.child_ui(child_rect, *self.layout());
|
let mut child_ui = self.child_ui_with_id_source(child_rect, *self.layout(), id_source);
|
||||||
self.next_auto_id_source = next_auto_id_source; // HACK: we want `scope` to only increment this once, so that `ui.scope` is equivalent to `ui.allocate_space`.
|
self.next_auto_id_source = next_auto_id_source; // HACK: we want `scope` to only increment this once, so that `ui.scope` is equivalent to `ui.allocate_space`.
|
||||||
let ret = add_contents(&mut child_ui);
|
let ret = add_contents(&mut child_ui);
|
||||||
let response = self.allocate_rect(child_ui.min_rect(), Sense::hover());
|
let response = self.allocate_rect(child_ui.min_rect(), Sense::hover());
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use emath::Rot2;
|
||||||
|
|
||||||
/// An widget to show an image of a given size.
|
/// An widget to show an image of a given size.
|
||||||
///
|
///
|
||||||
|
@ -36,6 +37,7 @@ pub struct Image {
|
||||||
bg_fill: Color32,
|
bg_fill: Color32,
|
||||||
tint: Color32,
|
tint: Color32,
|
||||||
sense: Sense,
|
sense: Sense,
|
||||||
|
rotation: Option<(Rot2, Vec2)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
|
@ -47,6 +49,7 @@ impl Image {
|
||||||
bg_fill: Default::default(),
|
bg_fill: Default::default(),
|
||||||
tint: Color32::WHITE,
|
tint: Color32::WHITE,
|
||||||
sense: Sense::hover(),
|
sense: Sense::hover(),
|
||||||
|
rotation: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +78,17 @@ impl Image {
|
||||||
self.sense = sense;
|
self.sense = sense;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rotate the image about an origin by some angle
|
||||||
|
///
|
||||||
|
/// Positive angle is clockwise.
|
||||||
|
/// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right).
|
||||||
|
///
|
||||||
|
/// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin.
|
||||||
|
pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self {
|
||||||
|
self.rotation = Some((Rot2::from_angle(angle), origin));
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
|
@ -88,10 +102,11 @@ impl Image {
|
||||||
let Self {
|
let Self {
|
||||||
texture_id,
|
texture_id,
|
||||||
uv,
|
uv,
|
||||||
size: _,
|
size,
|
||||||
bg_fill,
|
bg_fill,
|
||||||
tint,
|
tint,
|
||||||
sense: _,
|
sense: _,
|
||||||
|
rotation,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
if *bg_fill != Default::default() {
|
if *bg_fill != Default::default() {
|
||||||
|
@ -104,6 +119,9 @@ impl Image {
|
||||||
// TODO: builder pattern for Mesh
|
// TODO: builder pattern for Mesh
|
||||||
let mut mesh = Mesh::with_texture(*texture_id);
|
let mut mesh = Mesh::with_texture(*texture_id);
|
||||||
mesh.add_rect_with_uv(rect, *uv, *tint);
|
mesh.add_rect_with_uv(rect, *uv, *tint);
|
||||||
|
if let Some((rot, origin)) = rotation {
|
||||||
|
mesh.rotate(*rot, rect.min + *origin * *size);
|
||||||
|
}
|
||||||
ui.painter().add(Shape::mesh(mesh));
|
ui.painter().add(Shape::mesh(mesh));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,11 +239,12 @@ impl Widget for &mut LegendWidget {
|
||||||
legend_ui
|
legend_ui
|
||||||
.scope(|ui| {
|
.scope(|ui| {
|
||||||
let background_frame = Frame {
|
let background_frame = Frame {
|
||||||
margin: vec2(8.0, 4.0).into(),
|
inner_margin: vec2(8.0, 4.0).into(),
|
||||||
rounding: ui.style().visuals.window_rounding,
|
rounding: ui.style().visuals.window_rounding,
|
||||||
shadow: epaint::Shadow::default(),
|
shadow: epaint::Shadow::default(),
|
||||||
fill: ui.style().visuals.extreme_bg_color,
|
fill: ui.style().visuals.extreme_bg_color,
|
||||||
stroke: ui.style().visuals.window_stroke(),
|
stroke: ui.style().visuals.window_stroke(),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
.multiply_with_opacity(config.background_alpha);
|
.multiply_with_opacity(config.background_alpha);
|
||||||
background_frame
|
background_frame
|
||||||
|
|
|
@ -8,13 +8,14 @@ use epaint::color::Hsva;
|
||||||
use epaint::util::FloatOrd;
|
use epaint::util::FloatOrd;
|
||||||
use items::PlotItem;
|
use items::PlotItem;
|
||||||
use legend::LegendWidget;
|
use legend::LegendWidget;
|
||||||
use transform::{PlotBounds, ScreenTransform};
|
use transform::ScreenTransform;
|
||||||
|
|
||||||
pub use items::{
|
pub use items::{
|
||||||
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape,
|
Arrows, Bar, BarChart, BoxElem, BoxPlot, BoxSpread, HLine, Line, LineStyle, MarkerShape,
|
||||||
Orientation, PlotImage, Points, Polygon, Text, VLine, Value, Values,
|
Orientation, PlotImage, Points, Polygon, Text, VLine, Value, Values,
|
||||||
};
|
};
|
||||||
pub use legend::{Corner, Legend};
|
pub use legend::{Corner, Legend};
|
||||||
|
pub use transform::PlotBounds;
|
||||||
|
|
||||||
mod items;
|
mod items;
|
||||||
mod legend;
|
mod legend;
|
||||||
|
@ -767,6 +768,11 @@ impl PlotUi {
|
||||||
self.response.hovered()
|
self.response.hovered()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the plot was clicked by the primary button.
|
||||||
|
pub fn plot_clicked(&self) -> bool {
|
||||||
|
self.response.clicked()
|
||||||
|
}
|
||||||
|
|
||||||
/// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area.
|
/// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area.
|
||||||
pub fn pointer_coordinate(&self) -> Option<Value> {
|
pub fn pointer_coordinate(&self) -> Option<Value> {
|
||||||
// We need to subtract the drag delta to keep in sync with the frame-delayed screen transform:
|
// We need to subtract the drag delta to keep in sync with the frame-delayed screen transform:
|
||||||
|
|
|
@ -455,10 +455,14 @@ impl<'a> Slider<'a> {
|
||||||
|
|
||||||
fn value_ui(&mut self, ui: &mut Ui, position_range: RangeInclusive<f32>) -> Response {
|
fn value_ui(&mut self, ui: &mut Ui, position_range: RangeInclusive<f32>) -> Response {
|
||||||
// If `DragValue` is controlled from the keyboard and `step` is defined, set speed to `step`
|
// If `DragValue` is controlled from the keyboard and `step` is defined, set speed to `step`
|
||||||
let change = ui.input().num_presses(Key::ArrowUp) as i32
|
let change = {
|
||||||
+ ui.input().num_presses(Key::ArrowRight) as i32
|
// Hold one lock rather than 4 (see https://github.com/emilk/egui/pull/1380).
|
||||||
- ui.input().num_presses(Key::ArrowDown) as i32
|
let input = ui.input();
|
||||||
- ui.input().num_presses(Key::ArrowLeft) as i32;
|
|
||||||
|
input.num_presses(Key::ArrowUp) as i32 + input.num_presses(Key::ArrowRight) as i32
|
||||||
|
- input.num_presses(Key::ArrowDown) as i32
|
||||||
|
- input.num_presses(Key::ArrowLeft) as i32
|
||||||
|
};
|
||||||
let speed = match self.step {
|
let speed = match self.step {
|
||||||
Some(step) if change != 0 => step,
|
Some(step) if change != 0 => step,
|
||||||
_ => self.current_gradient(&position_range),
|
_ => self.current_gradient(&position_range),
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
// Forbid warnings in release builds:
|
//! Demo app for egui
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(clippy::all, rust_2018_idioms)]
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use eframe::wasm_bindgen::{self, prelude::*};
|
use eframe::wasm_bindgen::{self, prelude::*};
|
||||||
|
@ -19,6 +16,8 @@ pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> {
|
||||||
// Redirect tracing to console.log and friends:
|
// Redirect tracing to console.log and friends:
|
||||||
tracing_wasm::set_as_global_default();
|
tracing_wasm::set_as_global_default();
|
||||||
|
|
||||||
let app = egui_demo_lib::WrapApp::default();
|
eframe::start_web(
|
||||||
eframe::start_web(canvas_id, Box::new(app))
|
canvas_id,
|
||||||
|
Box::new(|cc| Box::new(egui_demo_lib::WrapApp::new(cc))),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
//! Demo app for egui
|
||||||
|
|
||||||
// Forbid warnings in release builds:
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(clippy::all, rust_2018_idioms)]
|
|
||||||
|
|
||||||
// When compiling natively:
|
// When compiling natively:
|
||||||
fn main() {
|
fn main() {
|
||||||
// Log to stdout (if you run with `RUST_LOG=debug`).
|
// Log to stdout (if you run with `RUST_LOG=debug`).
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let app = egui_demo_lib::WrapApp::default();
|
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
// Let's show off that we support transparent windows
|
// Let's show off that we support transparent windows
|
||||||
transparent: true,
|
transparent: true,
|
||||||
drag_and_drop_support: true,
|
drag_and_drop_support: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
eframe::run_native(Box::new(app), options);
|
eframe::run_native(
|
||||||
|
"egui demo app",
|
||||||
|
options,
|
||||||
|
Box::new(|cc| Box::new(egui_demo_lib::WrapApp::new(cc))),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ epi = { version = "0.17.0", path = "../epi" }
|
||||||
|
|
||||||
chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] }
|
chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] }
|
||||||
enum-map = { version = "2", features = ["serde"] }
|
enum-map = { version = "2", features = ["serde"] }
|
||||||
|
tracing = "0.1"
|
||||||
unicode_names2 = { version = "0.5.0", default-features = false }
|
unicode_names2 = { version = "0.5.0", default-features = false }
|
||||||
|
|
||||||
# feature "http":
|
# feature "http":
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
demo_windows.ui(ctx);
|
demo_windows.ui(ctx);
|
||||||
});
|
});
|
||||||
ctx.tessellate(full_output.shapes)
|
ctx.tessellate(full_output.shapes)
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
c.bench_function("demo_no_tessellate", |b| {
|
c.bench_function("demo_no_tessellate", |b| {
|
||||||
|
@ -25,14 +25,14 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
ctx.run(RawInput::default(), |ctx| {
|
ctx.run(RawInput::default(), |ctx| {
|
||||||
demo_windows.ui(ctx);
|
demo_windows.ui(ctx);
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let full_output = ctx.run(RawInput::default(), |ctx| {
|
let full_output = ctx.run(RawInput::default(), |ctx| {
|
||||||
demo_windows.ui(ctx);
|
demo_windows.ui(ctx);
|
||||||
});
|
});
|
||||||
c.bench_function("demo_only_tessellate", |b| {
|
c.bench_function("demo_only_tessellate", |b| {
|
||||||
b.iter(|| ctx.tessellate(full_output.shapes.clone()))
|
b.iter(|| ctx.tessellate(full_output.shapes.clone()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
ctx.run(RawInput::default(), |ctx| {
|
ctx.run(RawInput::default(), |ctx| {
|
||||||
demo_windows.ui(ctx);
|
demo_windows.ui(ctx);
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,12 +56,12 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
c.bench_function("label &str", |b| {
|
c.bench_function("label &str", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
ui.label("the quick brown fox jumps over the lazy dog");
|
ui.label("the quick brown fox jumps over the lazy dog");
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
c.bench_function("label format!", |b| {
|
c.bench_function("label format!", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
ui.label("the quick brown fox jumps over the lazy dog".to_owned());
|
ui.label("the quick brown fox jumps over the lazy dog".to_owned());
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -77,7 +77,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
let rect = ui.max_rect();
|
let rect = ui.max_rect();
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
painter.rect(rect, 2.0, egui::Color32::RED, (1.0, egui::Color32::WHITE));
|
painter.rect(rect, 2.0, egui::Color32::RED, (1.0, egui::Color32::WHITE));
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
wrap_width,
|
wrap_width,
|
||||||
);
|
);
|
||||||
layout(&mut locked_fonts.fonts, job.into())
|
layout(&mut locked_fonts.fonts, job.into())
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
c.bench_function("text_layout_cached", |b| {
|
c.bench_function("text_layout_cached", |b| {
|
||||||
|
@ -119,19 +119,19 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||||
color,
|
color,
|
||||||
wrap_width,
|
wrap_width,
|
||||||
)
|
)
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width);
|
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width);
|
||||||
let mut tessellator = egui::epaint::Tessellator::from_options(Default::default());
|
let mut tessellator = egui::epaint::Tessellator::new(1.0, Default::default());
|
||||||
let mut mesh = egui::epaint::Mesh::default();
|
let mut mesh = egui::epaint::Mesh::default();
|
||||||
let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
|
let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
|
||||||
let font_image_size = fonts.font_image_size();
|
let font_image_size = fonts.font_image_size();
|
||||||
c.bench_function("tessellate_text", |b| {
|
c.bench_function("tessellate_text", |b| {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
tessellator.tessellate_text(font_image_size, text_shape.clone(), &mut mesh);
|
tessellator.tessellate_text(font_image_size, &text_shape, &mut mesh);
|
||||||
mesh.clear();
|
mesh.clear();
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,10 +30,6 @@ impl Default for ColorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for ColorTest {
|
impl epi::App for ColorTest {
|
||||||
fn name(&self) -> &str {
|
|
||||||
"🎨 Color test"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
if frame.is_web() {
|
if frame.is_web() {
|
||||||
|
@ -132,9 +128,9 @@ impl ColorTest {
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
// TODO: another ground truth where we do the alpha-blending against the background also.
|
self.show_gradients(ui, BLACK, (BLACK, WHITE));
|
||||||
// TODO: exactly the same thing, but with vertex colors (no textures)
|
ui.separator();
|
||||||
self.show_gradients(ui, WHITE, (TRANSPARENT, BLACK));
|
self.show_gradients(ui, WHITE, (BLACK, TRANSPARENT));
|
||||||
ui.separator();
|
ui.separator();
|
||||||
self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE));
|
self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE));
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
@ -149,6 +145,10 @@ impl ColorTest {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
pixel_test(ui);
|
pixel_test(ui);
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
fine_line_test(ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_gradients(&mut self, ui: &mut Ui, bg_fill: Color32, (left, right): (Color32, Color32)) {
|
fn show_gradients(&mut self, ui: &mut Ui, bg_fill: Color32, (left, right): (Color32, Color32)) {
|
||||||
|
@ -357,6 +357,12 @@ impl TextureManager {
|
||||||
fn pixel_test(ui: &mut Ui) {
|
fn pixel_test(ui: &mut Ui) {
|
||||||
ui.label("Each subsequent square should be one physical pixel larger than the previous. They should be exactly one physical pixel apart. They should be perfectly aligned to the pixel grid.");
|
ui.label("Each subsequent square should be one physical pixel larger than the previous. They should be exactly one physical pixel apart. They should be perfectly aligned to the pixel grid.");
|
||||||
|
|
||||||
|
let color = if ui.style().visuals.dark_mode {
|
||||||
|
egui::Color32::WHITE
|
||||||
|
} else {
|
||||||
|
egui::Color32::BLACK
|
||||||
|
};
|
||||||
|
|
||||||
let pixels_per_point = ui.ctx().pixels_per_point();
|
let pixels_per_point = ui.ctx().pixels_per_point();
|
||||||
let num_squares: u32 = 8;
|
let num_squares: u32 = 8;
|
||||||
let size_pixels = Vec2::new(
|
let size_pixels = Vec2::new(
|
||||||
|
@ -379,7 +385,71 @@ fn pixel_test(ui: &mut Ui) {
|
||||||
),
|
),
|
||||||
Vec2::splat(size as f32) / pixels_per_point,
|
Vec2::splat(size as f32) / pixels_per_point,
|
||||||
);
|
);
|
||||||
painter.rect_filled(rect_points, 0.0, egui::Color32::WHITE);
|
painter.rect_filled(rect_points, 0.0, color);
|
||||||
cursor_pixel.x += (1 + size) as f32;
|
cursor_pixel.x += (1 + size) as f32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fine_line_test(ui: &mut Ui) {
|
||||||
|
ui.label("Some fine lines for testing anti-aliasing and blending:");
|
||||||
|
|
||||||
|
let size = Vec2::new(256.0, 512.0);
|
||||||
|
let (response, painter) = ui.allocate_painter(size, Sense::hover());
|
||||||
|
let rect = response.rect;
|
||||||
|
|
||||||
|
let mut top_half = rect;
|
||||||
|
top_half.set_bottom(top_half.center().y);
|
||||||
|
painter.rect_filled(top_half, 0.0, Color32::BLACK);
|
||||||
|
paint_fine_lines(&painter, top_half, Color32::WHITE);
|
||||||
|
|
||||||
|
let mut bottom_half = rect;
|
||||||
|
bottom_half.set_top(bottom_half.center().y);
|
||||||
|
painter.rect_filled(bottom_half, 0.0, Color32::WHITE);
|
||||||
|
paint_fine_lines(&painter, bottom_half, Color32::BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint_fine_lines(painter: &egui::Painter, mut rect: Rect, color: Color32) {
|
||||||
|
rect = rect.shrink(12.0);
|
||||||
|
for width in [0.5, 1.0, 2.0] {
|
||||||
|
painter.text(
|
||||||
|
rect.left_top(),
|
||||||
|
Align2::CENTER_CENTER,
|
||||||
|
width.to_string(),
|
||||||
|
FontId::monospace(14.0),
|
||||||
|
color,
|
||||||
|
);
|
||||||
|
|
||||||
|
painter.add(egui::epaint::CubicBezierShape::from_points_stroke(
|
||||||
|
[
|
||||||
|
rect.left_top() + Vec2::new(16.0, 0.0),
|
||||||
|
rect.right_top(),
|
||||||
|
rect.right_center(),
|
||||||
|
rect.right_bottom(),
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
Color32::TRANSPARENT,
|
||||||
|
Stroke::new(width, color),
|
||||||
|
));
|
||||||
|
|
||||||
|
rect.min.y += 32.0;
|
||||||
|
rect.max.x -= 32.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rect.min.y += 16.0;
|
||||||
|
painter.text(
|
||||||
|
rect.left_top(),
|
||||||
|
Align2::LEFT_CENTER,
|
||||||
|
"transparent --> opaque",
|
||||||
|
FontId::monospace(11.0),
|
||||||
|
color,
|
||||||
|
);
|
||||||
|
rect.min.y += 12.0;
|
||||||
|
let mut mesh = Mesh::default();
|
||||||
|
mesh.colored_vertex(rect.left_bottom(), Color32::TRANSPARENT);
|
||||||
|
mesh.colored_vertex(rect.left_top(), Color32::TRANSPARENT);
|
||||||
|
mesh.colored_vertex(rect.right_bottom(), color);
|
||||||
|
mesh.colored_vertex(rect.right_top(), color);
|
||||||
|
mesh.add_triangle(0, 1, 2);
|
||||||
|
mesh.add_triangle(1, 2, 3);
|
||||||
|
painter.add(mesh);
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
/// Demonstrates how to make an app using egui.
|
|
||||||
///
|
|
||||||
/// Implements `epi::App` so it can be used with
|
|
||||||
/// [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium), [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) and [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web).
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
@ -10,28 +6,6 @@ pub struct DemoApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for DemoApp {
|
impl epi::App for DemoApp {
|
||||||
fn name(&self) -> &str {
|
|
||||||
"✨ Demos"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup(
|
|
||||||
&mut self,
|
|
||||||
_ctx: &egui::Context,
|
|
||||||
_frame: &epi::Frame,
|
|
||||||
_storage: Option<&dyn epi::Storage>,
|
|
||||||
_gl: &std::rc::Rc<epi::glow::Context>,
|
|
||||||
) {
|
|
||||||
#[cfg(feature = "persistence")]
|
|
||||||
if let Some(storage) = _storage {
|
|
||||||
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "persistence")]
|
|
||||||
fn save(&mut self, storage: &mut dyn epi::Storage) {
|
|
||||||
epi::set_value(storage, epi::APP_KEY, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||||
self.demo_windows.ui(ctx);
|
self.demo_windows.ui(ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,7 @@ impl super::View for TextEdit {
|
||||||
anything_selected,
|
anything_selected,
|
||||||
egui::Label::new("Press ctrl+T to toggle the case of selected text (cmd+T on Mac)"),
|
egui::Label::new("Press ctrl+T to toggle the case of selected text (cmd+T on Mac)"),
|
||||||
);
|
);
|
||||||
|
|
||||||
if ui
|
if ui
|
||||||
.input_mut()
|
.input_mut()
|
||||||
.consume_key(egui::Modifiers::COMMAND, egui::Key::T)
|
.consume_key(egui::Modifiers::COMMAND, egui::Key::T)
|
||||||
|
@ -82,5 +83,29 @@ impl super::View for TextEdit {
|
||||||
text.insert_text(&new_text, selected_chars.start);
|
text.insert_text(&new_text, selected_chars.start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Move cursor to the:");
|
||||||
|
|
||||||
|
if ui.button("start").clicked() {
|
||||||
|
let text_edit_id = output.response.id;
|
||||||
|
if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) {
|
||||||
|
let ccursor = egui::text::CCursor::new(0);
|
||||||
|
state.set_ccursor_range(Some(egui::text::CCursorRange::one(ccursor)));
|
||||||
|
state.store(ui.ctx(), text_edit_id);
|
||||||
|
ui.ctx().memory().request_focus(text_edit_id); // give focus back to the `TextEdit`.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("end").clicked() {
|
||||||
|
let text_edit_id = output.response.id;
|
||||||
|
if let Some(mut state) = egui::TextEdit::load_state(ui.ctx(), text_edit_id) {
|
||||||
|
let ccursor = egui::text::CCursor::new(text.chars().count());
|
||||||
|
state.set_ccursor_range(Some(egui::text::CCursorRange::one(ccursor)));
|
||||||
|
state.store(ui.ctx(), text_edit_id);
|
||||||
|
ui.ctx().memory().request_focus(text_edit_id); // give focus back to the `TextEdit`.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,10 +33,6 @@ impl Default for FractalClock {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for FractalClock {
|
impl epi::App for FractalClock {
|
||||||
fn name(&self) -> &str {
|
|
||||||
"🕑 Fractal Clock"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||||
egui::CentralPanel::default()
|
egui::CentralPanel::default()
|
||||||
.frame(Frame::dark_canvas(&ctx.style()))
|
.frame(Frame::dark_canvas(&ctx.style()))
|
||||||
|
|
|
@ -54,10 +54,6 @@ impl Default for HttpApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for HttpApp {
|
impl epi::App for HttpApp {
|
||||||
fn name(&self) -> &str {
|
|
||||||
"⬇ HTTP"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||||
egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| {
|
egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| {
|
||||||
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
||||||
|
@ -78,11 +74,10 @@ impl epi::App for HttpApp {
|
||||||
|
|
||||||
if trigger_fetch {
|
if trigger_fetch {
|
||||||
let ctx = ctx.clone();
|
let ctx = ctx.clone();
|
||||||
let frame = frame.clone();
|
|
||||||
let (sender, promise) = Promise::new();
|
let (sender, promise) = Promise::new();
|
||||||
let request = ehttp::Request::get(&self.url);
|
let request = ehttp::Request::get(&self.url);
|
||||||
ehttp::fetch(request, move |response| {
|
ehttp::fetch(request, move |response| {
|
||||||
frame.request_repaint(); // wake up UI thread
|
ctx.request_repaint(); // wake up UI thread
|
||||||
let resource = response.map(|response| Resource::from_response(&ctx, response));
|
let resource = response.map(|response| Resource::from_response(&ctx, response));
|
||||||
sender.send(resource);
|
sender.send(resource);
|
||||||
});
|
});
|
||||||
|
|
|
@ -54,10 +54,6 @@ pub struct BackendPanel {
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
pixels_per_point: Option<f32>,
|
pixels_per_point: Option<f32>,
|
||||||
|
|
||||||
/// maximum size of the web browser canvas
|
|
||||||
max_size_points_ui: egui::Vec2,
|
|
||||||
pub max_size_points_active: egui::Vec2,
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
frame_history: crate::frame_history::FrameHistory,
|
frame_history: crate::frame_history::FrameHistory,
|
||||||
|
|
||||||
|
@ -70,8 +66,6 @@ impl Default for BackendPanel {
|
||||||
open: false,
|
open: false,
|
||||||
run_mode: Default::default(),
|
run_mode: Default::default(),
|
||||||
pixels_per_point: Default::default(),
|
pixels_per_point: Default::default(),
|
||||||
max_size_points_ui: egui::Vec2::new(1024.0, 2048.0),
|
|
||||||
max_size_points_active: egui::Vec2::new(1024.0, 2048.0),
|
|
||||||
frame_history: Default::default(),
|
frame_history: Default::default(),
|
||||||
egui_windows: Default::default(),
|
egui_windows: Default::default(),
|
||||||
}
|
}
|
||||||
|
@ -157,17 +151,6 @@ impl BackendPanel {
|
||||||
ui.hyperlink("https://github.com/emilk/egui");
|
ui.hyperlink("https://github.com/emilk/egui");
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.add(
|
|
||||||
egui::Slider::new(&mut self.max_size_points_ui.x, 512.0..=f32::INFINITY)
|
|
||||||
.logarithmic(true)
|
|
||||||
.largest_finite(8192.0)
|
|
||||||
.text("Max width"),
|
|
||||||
)
|
|
||||||
.on_hover_text("Maximum width of the egui region of the web page.");
|
|
||||||
if !ui.ctx().is_using_pointer() {
|
|
||||||
self.max_size_points_active = self.max_size_points_ui;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
show_integration_name(ui, &frame.info());
|
show_integration_name(ui, &frame.info());
|
||||||
|
|
|
@ -30,10 +30,6 @@ impl Default for EasyMarkEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for EasyMarkEditor {
|
impl epi::App for EasyMarkEditor {
|
||||||
fn name(&self) -> &str {
|
|
||||||
"🖹 EasyMark editor"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||||
egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {
|
egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {
|
||||||
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
||||||
|
|
|
@ -2,85 +2,6 @@
|
||||||
//!
|
//!
|
||||||
//! The demo-code is also used in benchmarks and tests.
|
//! The demo-code is also used in benchmarks and tests.
|
||||||
|
|
||||||
// Forbid warnings in release builds:
|
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(
|
|
||||||
clippy::all,
|
|
||||||
clippy::await_holding_lock,
|
|
||||||
clippy::char_lit_as_u8,
|
|
||||||
clippy::checked_conversions,
|
|
||||||
clippy::dbg_macro,
|
|
||||||
clippy::debug_assert_with_mut_call,
|
|
||||||
clippy::disallowed_method,
|
|
||||||
clippy::doc_markdown,
|
|
||||||
clippy::empty_enum,
|
|
||||||
clippy::enum_glob_use,
|
|
||||||
clippy::exit,
|
|
||||||
clippy::expl_impl_clone_on_copy,
|
|
||||||
clippy::explicit_deref_methods,
|
|
||||||
clippy::explicit_into_iter_loop,
|
|
||||||
clippy::fallible_impl_from,
|
|
||||||
clippy::filter_map_next,
|
|
||||||
clippy::flat_map_option,
|
|
||||||
clippy::float_cmp_const,
|
|
||||||
clippy::fn_params_excessive_bools,
|
|
||||||
clippy::from_iter_instead_of_collect,
|
|
||||||
clippy::if_let_mutex,
|
|
||||||
clippy::implicit_clone,
|
|
||||||
clippy::imprecise_flops,
|
|
||||||
clippy::inefficient_to_string,
|
|
||||||
clippy::invalid_upcast_comparisons,
|
|
||||||
clippy::large_digit_groups,
|
|
||||||
clippy::large_stack_arrays,
|
|
||||||
clippy::large_types_passed_by_value,
|
|
||||||
clippy::let_unit_value,
|
|
||||||
clippy::linkedlist,
|
|
||||||
clippy::lossy_float_literal,
|
|
||||||
clippy::macro_use_imports,
|
|
||||||
clippy::manual_ok_or,
|
|
||||||
clippy::map_err_ignore,
|
|
||||||
clippy::map_flatten,
|
|
||||||
clippy::map_unwrap_or,
|
|
||||||
clippy::match_on_vec_items,
|
|
||||||
clippy::match_same_arms,
|
|
||||||
clippy::match_wild_err_arm,
|
|
||||||
clippy::match_wildcard_for_single_variants,
|
|
||||||
clippy::mem_forget,
|
|
||||||
clippy::mismatched_target_os,
|
|
||||||
clippy::missing_errors_doc,
|
|
||||||
clippy::missing_safety_doc,
|
|
||||||
clippy::mut_mut,
|
|
||||||
clippy::mutex_integer,
|
|
||||||
clippy::needless_borrow,
|
|
||||||
clippy::needless_continue,
|
|
||||||
clippy::needless_for_each,
|
|
||||||
clippy::needless_pass_by_value,
|
|
||||||
clippy::option_option,
|
|
||||||
clippy::path_buf_push_overwrite,
|
|
||||||
clippy::ptr_as_ptr,
|
|
||||||
clippy::ref_option_ref,
|
|
||||||
clippy::rest_pat_in_fully_bound_structs,
|
|
||||||
clippy::same_functions_in_if_condition,
|
|
||||||
clippy::semicolon_if_nothing_returned,
|
|
||||||
clippy::single_match_else,
|
|
||||||
clippy::string_add_assign,
|
|
||||||
clippy::string_add,
|
|
||||||
clippy::string_lit_as_bytes,
|
|
||||||
clippy::string_to_string,
|
|
||||||
clippy::todo,
|
|
||||||
clippy::trait_duplication_in_bounds,
|
|
||||||
clippy::unimplemented,
|
|
||||||
clippy::unnested_or_patterns,
|
|
||||||
clippy::unused_self,
|
|
||||||
clippy::useless_transmute,
|
|
||||||
clippy::verbose_file_reads,
|
|
||||||
clippy::zero_sized_map_values,
|
|
||||||
future_incompatible,
|
|
||||||
nonstandard_style,
|
|
||||||
rust_2018_idioms,
|
|
||||||
rustdoc::missing_crate_level_docs
|
|
||||||
)]
|
|
||||||
#![allow(clippy::float_cmp)]
|
#![allow(clippy::float_cmp)]
|
||||||
#![allow(clippy::manual_range_contains)]
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
|
|
|
@ -277,7 +277,7 @@ impl CodeTheme {
|
||||||
ui.data().insert_persisted(selected_id, selected_tt);
|
ui.data().insert_persisted(selected_id, selected_tt);
|
||||||
|
|
||||||
egui::Frame::group(ui.style())
|
egui::Frame::group(ui.style())
|
||||||
.margin(egui::Vec2::splat(2.0))
|
.inner_margin(egui::Vec2::splat(2.0))
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
// ui.group(|ui| {
|
// ui.group(|ui| {
|
||||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
|
ui.style_mut().override_text_style = Some(egui::TextStyle::Small);
|
||||||
|
|
|
@ -12,14 +12,26 @@ pub struct Apps {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Apps {
|
impl Apps {
|
||||||
fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut dyn epi::App)> {
|
fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &str, &mut dyn epi::App)> {
|
||||||
vec![
|
vec![
|
||||||
("demo", &mut self.demo as &mut dyn epi::App),
|
("✨ Demos", "demo", &mut self.demo as &mut dyn epi::App),
|
||||||
("easymark", &mut self.easy_mark_editor as &mut dyn epi::App),
|
(
|
||||||
|
"🖹 EasyMark editor",
|
||||||
|
"easymark",
|
||||||
|
&mut self.easy_mark_editor as &mut dyn epi::App,
|
||||||
|
),
|
||||||
#[cfg(feature = "http")]
|
#[cfg(feature = "http")]
|
||||||
("http", &mut self.http as &mut dyn epi::App),
|
("⬇ HTTP", "http", &mut self.http as &mut dyn epi::App),
|
||||||
("clock", &mut self.clock as &mut dyn epi::App),
|
(
|
||||||
("colors", &mut self.color_test as &mut dyn epi::App),
|
"🕑 Fractal Clock",
|
||||||
|
"clock",
|
||||||
|
&mut self.clock as &mut dyn epi::App,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"🎨 Color test",
|
||||||
|
"colors",
|
||||||
|
&mut self.color_test as &mut dyn epi::App,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
@ -37,33 +49,22 @@ pub struct WrapApp {
|
||||||
dropped_files: Vec<egui::DroppedFile>,
|
dropped_files: Vec<egui::DroppedFile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for WrapApp {
|
impl WrapApp {
|
||||||
fn name(&self) -> &str {
|
pub fn new(_cc: &epi::CreationContext<'_>) -> Self {
|
||||||
"egui demo apps"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup(
|
|
||||||
&mut self,
|
|
||||||
_ctx: &egui::Context,
|
|
||||||
_frame: &epi::Frame,
|
|
||||||
_storage: Option<&dyn epi::Storage>,
|
|
||||||
_gl: &std::rc::Rc<epi::glow::Context>,
|
|
||||||
) {
|
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
if let Some(storage) = _storage {
|
if let Some(storage) = _cc.storage {
|
||||||
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default();
|
return epi::get_value(storage, epi::APP_KEY).unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
Self::default()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl epi::App for WrapApp {
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
fn save(&mut self, storage: &mut dyn epi::Storage) {
|
fn save(&mut self, storage: &mut dyn epi::Storage) {
|
||||||
epi::set_value(storage, epi::APP_KEY, self);
|
epi::set_value(storage, epi::APP_KEY, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn max_size_points(&self) -> egui::Vec2 {
|
|
||||||
self.backend_panel.max_size_points_active
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_color(&self) -> egui::Rgba {
|
fn clear_color(&self) -> egui::Rgba {
|
||||||
egui::Rgba::TRANSPARENT // we set a `CentralPanel` fill color in `demo_windows.rs`
|
egui::Rgba::TRANSPARENT // we set a `CentralPanel` fill color in `demo_windows.rs`
|
||||||
}
|
}
|
||||||
|
@ -111,7 +112,7 @@ impl epi::App for WrapApp {
|
||||||
|
|
||||||
let mut found_anchor = false;
|
let mut found_anchor = false;
|
||||||
|
|
||||||
for (anchor, app) in self.apps.iter_mut() {
|
for (_name, anchor, app) in self.apps.iter_mut() {
|
||||||
if anchor == self.selected_anchor || ctx.memory().everything_is_visible() {
|
if anchor == self.selected_anchor || ctx.memory().everything_is_visible() {
|
||||||
app.update(ctx, frame);
|
app.update(ctx, frame);
|
||||||
found_anchor = true;
|
found_anchor = true;
|
||||||
|
@ -138,9 +139,9 @@ impl WrapApp {
|
||||||
ui.checkbox(&mut self.backend_panel.open, "💻 Backend");
|
ui.checkbox(&mut self.backend_panel.open, "💻 Backend");
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
for (anchor, app) in self.apps.iter_mut() {
|
for (name, anchor, _app) in self.apps.iter_mut() {
|
||||||
if ui
|
if ui
|
||||||
.selectable_label(self.selected_anchor == anchor, app.name())
|
.selectable_label(self.selected_anchor == anchor, name)
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
self.selected_anchor = anchor.to_owned();
|
self.selected_anchor = anchor.to_owned();
|
||||||
|
|
|
@ -36,10 +36,7 @@ datepicker = ["chrono"]
|
||||||
persistence = ["serde"]
|
persistence = ["serde"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.17.0", path = "../egui", default-features = false }
|
||||||
"single_threaded",
|
|
||||||
] }
|
|
||||||
parking_lot = "0.12"
|
|
||||||
|
|
||||||
# Optional dependencies:
|
# Optional dependencies:
|
||||||
|
|
||||||
|
@ -49,7 +46,7 @@ chrono = { version = "0.4", optional = true }
|
||||||
# Add support for loading images with the `image` crate.
|
# Add support for loading images with the `image` crate.
|
||||||
# You also need to ALSO opt-in to the image formats you want to support, like so:
|
# You also need to ALSO opt-in to the image formats you want to support, like so:
|
||||||
# image = { version = "0.24", features = ["jpeg", "png"] }
|
# image = { version = "0.24", features = ["jpeg", "png"] }
|
||||||
image = { version = "0.24", optional = true, default-features = false, features = [] }
|
image = { version = "0.24", optional = true, default-features = false }
|
||||||
|
|
||||||
# svg feature
|
# svg feature
|
||||||
resvg = { version = "0.22", optional = true }
|
resvg = { version = "0.22", optional = true }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use parking_lot::Mutex;
|
use egui::mutex::Mutex;
|
||||||
|
|
||||||
/// An image to be shown in egui.
|
/// An image to be shown in egui.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,84 +1,5 @@
|
||||||
//! This is a crate that adds some features on top top of [`egui`](https://github.com/emilk/egui). This crate are for experimental features, and features that require big dependencies that does not belong in `egui`.
|
//! This is a crate that adds some features on top top of [`egui`](https://github.com/emilk/egui). This crate are for experimental features, and features that require big dependencies that does not belong in `egui`.
|
||||||
|
|
||||||
// Forbid warnings in release builds:
|
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(
|
|
||||||
clippy::all,
|
|
||||||
clippy::await_holding_lock,
|
|
||||||
clippy::char_lit_as_u8,
|
|
||||||
clippy::checked_conversions,
|
|
||||||
clippy::dbg_macro,
|
|
||||||
clippy::debug_assert_with_mut_call,
|
|
||||||
clippy::disallowed_method,
|
|
||||||
clippy::doc_markdown,
|
|
||||||
clippy::empty_enum,
|
|
||||||
clippy::enum_glob_use,
|
|
||||||
clippy::exit,
|
|
||||||
clippy::expl_impl_clone_on_copy,
|
|
||||||
clippy::explicit_deref_methods,
|
|
||||||
clippy::explicit_into_iter_loop,
|
|
||||||
clippy::fallible_impl_from,
|
|
||||||
clippy::filter_map_next,
|
|
||||||
clippy::flat_map_option,
|
|
||||||
clippy::float_cmp_const,
|
|
||||||
clippy::fn_params_excessive_bools,
|
|
||||||
clippy::from_iter_instead_of_collect,
|
|
||||||
clippy::if_let_mutex,
|
|
||||||
clippy::implicit_clone,
|
|
||||||
clippy::imprecise_flops,
|
|
||||||
clippy::inefficient_to_string,
|
|
||||||
clippy::invalid_upcast_comparisons,
|
|
||||||
clippy::large_digit_groups,
|
|
||||||
clippy::large_stack_arrays,
|
|
||||||
clippy::large_types_passed_by_value,
|
|
||||||
clippy::let_unit_value,
|
|
||||||
clippy::linkedlist,
|
|
||||||
clippy::lossy_float_literal,
|
|
||||||
clippy::macro_use_imports,
|
|
||||||
clippy::manual_ok_or,
|
|
||||||
clippy::map_err_ignore,
|
|
||||||
clippy::map_flatten,
|
|
||||||
clippy::map_unwrap_or,
|
|
||||||
clippy::match_on_vec_items,
|
|
||||||
clippy::match_same_arms,
|
|
||||||
clippy::match_wild_err_arm,
|
|
||||||
clippy::match_wildcard_for_single_variants,
|
|
||||||
clippy::mem_forget,
|
|
||||||
clippy::mismatched_target_os,
|
|
||||||
clippy::missing_errors_doc,
|
|
||||||
clippy::missing_safety_doc,
|
|
||||||
clippy::mut_mut,
|
|
||||||
clippy::mutex_integer,
|
|
||||||
clippy::needless_borrow,
|
|
||||||
clippy::needless_continue,
|
|
||||||
clippy::needless_for_each,
|
|
||||||
clippy::needless_pass_by_value,
|
|
||||||
clippy::option_option,
|
|
||||||
clippy::path_buf_push_overwrite,
|
|
||||||
clippy::ptr_as_ptr,
|
|
||||||
clippy::ref_option_ref,
|
|
||||||
clippy::rest_pat_in_fully_bound_structs,
|
|
||||||
clippy::same_functions_in_if_condition,
|
|
||||||
clippy::semicolon_if_nothing_returned,
|
|
||||||
clippy::single_match_else,
|
|
||||||
clippy::string_add_assign,
|
|
||||||
clippy::string_add,
|
|
||||||
clippy::string_lit_as_bytes,
|
|
||||||
clippy::string_to_string,
|
|
||||||
clippy::todo,
|
|
||||||
clippy::trait_duplication_in_bounds,
|
|
||||||
clippy::unimplemented,
|
|
||||||
clippy::unnested_or_patterns,
|
|
||||||
clippy::unused_self,
|
|
||||||
clippy::useless_transmute,
|
|
||||||
clippy::verbose_file_reads,
|
|
||||||
clippy::zero_sized_map_values,
|
|
||||||
future_incompatible,
|
|
||||||
nonstandard_style,
|
|
||||||
rust_2018_idioms,
|
|
||||||
rustdoc::missing_crate_level_docs
|
|
||||||
)]
|
|
||||||
#![allow(clippy::float_cmp)]
|
#![allow(clippy::float_cmp)]
|
||||||
#![allow(clippy::manual_range_contains)]
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,6 @@ screen_reader = ["egui-winit/screen_reader"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||||
"convert_bytemuck",
|
"convert_bytemuck",
|
||||||
"single_threaded",
|
|
||||||
] }
|
] }
|
||||||
egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false }
|
egui-winit = { version = "0.17.0", path = "../egui-winit", default-features = false }
|
||||||
|
|
||||||
|
|
|
@ -1,44 +1,9 @@
|
||||||
//! Example how to use [epi::NativeTexture] with glium.
|
//! Example how to use [`epi::NativeTexture`] with glium.
|
||||||
|
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
use glium::glutin;
|
use glium::glutin;
|
||||||
|
|
||||||
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
|
|
||||||
let window_builder = glutin::window::WindowBuilder::new()
|
|
||||||
.with_resizable(true)
|
|
||||||
.with_inner_size(glutin::dpi::LogicalSize {
|
|
||||||
width: 800.0,
|
|
||||||
height: 600.0,
|
|
||||||
})
|
|
||||||
.with_title("egui_glium example");
|
|
||||||
|
|
||||||
let context_builder = glutin::ContextBuilder::new()
|
|
||||||
.with_depth_buffer(0)
|
|
||||||
.with_srgb(true)
|
|
||||||
.with_stencil_buffer(0)
|
|
||||||
.with_vsync(true);
|
|
||||||
|
|
||||||
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_glium_image(png_data: &[u8]) -> glium::texture::RawImage2d<u8> {
|
|
||||||
// Load image using the image crate:
|
|
||||||
let image = image::load_from_memory(png_data).unwrap().to_rgba8();
|
|
||||||
let image_dimensions = image.dimensions();
|
|
||||||
|
|
||||||
// Premultiply alpha:
|
|
||||||
let pixels: Vec<_> = image
|
|
||||||
.into_vec()
|
|
||||||
.chunks_exact(4)
|
|
||||||
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
|
||||||
.flat_map(|color| color.to_array())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Convert to glium image:
|
|
||||||
glium::texture::RawImage2d::from_raw_rgba(pixels, image_dimensions)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let event_loop = glutin::event_loop::EventLoop::with_user_event();
|
let event_loop = glutin::event_loop::EventLoop::with_user_event();
|
||||||
let display = create_display(&event_loop);
|
let display = create_display(&event_loop);
|
||||||
|
@ -127,3 +92,38 @@ fn main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
|
||||||
|
let window_builder = glutin::window::WindowBuilder::new()
|
||||||
|
.with_resizable(true)
|
||||||
|
.with_inner_size(glutin::dpi::LogicalSize {
|
||||||
|
width: 800.0,
|
||||||
|
height: 600.0,
|
||||||
|
})
|
||||||
|
.with_title("egui_glium example");
|
||||||
|
|
||||||
|
let context_builder = glutin::ContextBuilder::new()
|
||||||
|
.with_depth_buffer(0)
|
||||||
|
.with_srgb(true)
|
||||||
|
.with_stencil_buffer(0)
|
||||||
|
.with_vsync(true);
|
||||||
|
|
||||||
|
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_glium_image(png_data: &[u8]) -> glium::texture::RawImage2d<'_, u8> {
|
||||||
|
// Load image using the image crate:
|
||||||
|
let image = image::load_from_memory(png_data).unwrap().to_rgba8();
|
||||||
|
let image_dimensions = image.dimensions();
|
||||||
|
|
||||||
|
// Premultiply alpha:
|
||||||
|
let pixels: Vec<_> = image
|
||||||
|
.into_vec()
|
||||||
|
.chunks_exact(4)
|
||||||
|
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||||
|
.flat_map(|color| color.to_array())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Convert to glium image:
|
||||||
|
glium::texture::RawImage2d::from_raw_rgba(pixels, image_dimensions)
|
||||||
|
}
|
||||||
|
|
|
@ -4,24 +4,6 @@
|
||||||
|
|
||||||
use glium::glutin;
|
use glium::glutin;
|
||||||
|
|
||||||
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
|
|
||||||
let window_builder = glutin::window::WindowBuilder::new()
|
|
||||||
.with_resizable(true)
|
|
||||||
.with_inner_size(glutin::dpi::LogicalSize {
|
|
||||||
width: 800.0,
|
|
||||||
height: 600.0,
|
|
||||||
})
|
|
||||||
.with_title("egui_glium example");
|
|
||||||
|
|
||||||
let context_builder = glutin::ContextBuilder::new()
|
|
||||||
.with_depth_buffer(0)
|
|
||||||
.with_srgb(true)
|
|
||||||
.with_stencil_buffer(0)
|
|
||||||
.with_vsync(true);
|
|
||||||
|
|
||||||
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let event_loop = glutin::event_loop::EventLoop::with_user_event();
|
let event_loop = glutin::event_loop::EventLoop::with_user_event();
|
||||||
let display = create_display(&event_loop);
|
let display = create_display(&event_loop);
|
||||||
|
@ -89,3 +71,21 @@ fn main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
|
||||||
|
let window_builder = glutin::window::WindowBuilder::new()
|
||||||
|
.with_resizable(true)
|
||||||
|
.with_inner_size(glutin::dpi::LogicalSize {
|
||||||
|
width: 800.0,
|
||||||
|
height: 600.0,
|
||||||
|
})
|
||||||
|
.with_title("egui_glium example");
|
||||||
|
|
||||||
|
let context_builder = glutin::ContextBuilder::new()
|
||||||
|
.with_depth_buffer(0)
|
||||||
|
.with_srgb(true)
|
||||||
|
.with_stencil_buffer(0)
|
||||||
|
.with_vsync(true);
|
||||||
|
|
||||||
|
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
|
||||||
|
}
|
||||||
|
|
|
@ -2,88 +2,8 @@
|
||||||
//!
|
//!
|
||||||
//! The main type you want to use is [`EguiGlium`].
|
//! The main type you want to use is [`EguiGlium`].
|
||||||
//!
|
//!
|
||||||
//! This library is an [`epi`] backend.
|
|
||||||
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
|
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
|
||||||
|
|
||||||
// Forbid warnings in release builds:
|
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(
|
|
||||||
clippy::all,
|
|
||||||
clippy::await_holding_lock,
|
|
||||||
clippy::char_lit_as_u8,
|
|
||||||
clippy::checked_conversions,
|
|
||||||
clippy::dbg_macro,
|
|
||||||
clippy::debug_assert_with_mut_call,
|
|
||||||
clippy::disallowed_method,
|
|
||||||
clippy::doc_markdown,
|
|
||||||
clippy::empty_enum,
|
|
||||||
clippy::enum_glob_use,
|
|
||||||
clippy::exit,
|
|
||||||
clippy::expl_impl_clone_on_copy,
|
|
||||||
clippy::explicit_deref_methods,
|
|
||||||
clippy::explicit_into_iter_loop,
|
|
||||||
clippy::fallible_impl_from,
|
|
||||||
clippy::filter_map_next,
|
|
||||||
clippy::flat_map_option,
|
|
||||||
clippy::float_cmp_const,
|
|
||||||
clippy::fn_params_excessive_bools,
|
|
||||||
clippy::from_iter_instead_of_collect,
|
|
||||||
clippy::if_let_mutex,
|
|
||||||
clippy::implicit_clone,
|
|
||||||
clippy::imprecise_flops,
|
|
||||||
clippy::inefficient_to_string,
|
|
||||||
clippy::invalid_upcast_comparisons,
|
|
||||||
clippy::large_digit_groups,
|
|
||||||
clippy::large_stack_arrays,
|
|
||||||
clippy::large_types_passed_by_value,
|
|
||||||
clippy::let_unit_value,
|
|
||||||
clippy::linkedlist,
|
|
||||||
clippy::lossy_float_literal,
|
|
||||||
clippy::macro_use_imports,
|
|
||||||
clippy::manual_ok_or,
|
|
||||||
clippy::map_err_ignore,
|
|
||||||
clippy::map_flatten,
|
|
||||||
clippy::map_unwrap_or,
|
|
||||||
clippy::match_on_vec_items,
|
|
||||||
clippy::match_same_arms,
|
|
||||||
clippy::match_wild_err_arm,
|
|
||||||
clippy::match_wildcard_for_single_variants,
|
|
||||||
clippy::mem_forget,
|
|
||||||
clippy::mismatched_target_os,
|
|
||||||
clippy::missing_errors_doc,
|
|
||||||
clippy::missing_safety_doc,
|
|
||||||
clippy::mut_mut,
|
|
||||||
clippy::mutex_integer,
|
|
||||||
clippy::needless_borrow,
|
|
||||||
clippy::needless_continue,
|
|
||||||
clippy::needless_for_each,
|
|
||||||
clippy::needless_pass_by_value,
|
|
||||||
clippy::option_option,
|
|
||||||
clippy::path_buf_push_overwrite,
|
|
||||||
clippy::ptr_as_ptr,
|
|
||||||
clippy::ref_option_ref,
|
|
||||||
clippy::rest_pat_in_fully_bound_structs,
|
|
||||||
clippy::same_functions_in_if_condition,
|
|
||||||
clippy::semicolon_if_nothing_returned,
|
|
||||||
clippy::single_match_else,
|
|
||||||
clippy::string_add_assign,
|
|
||||||
clippy::string_add,
|
|
||||||
clippy::string_lit_as_bytes,
|
|
||||||
clippy::string_to_string,
|
|
||||||
clippy::todo,
|
|
||||||
clippy::trait_duplication_in_bounds,
|
|
||||||
clippy::unimplemented,
|
|
||||||
clippy::unnested_or_patterns,
|
|
||||||
clippy::unused_self,
|
|
||||||
clippy::useless_transmute,
|
|
||||||
clippy::verbose_file_reads,
|
|
||||||
clippy::zero_sized_map_values,
|
|
||||||
future_incompatible,
|
|
||||||
nonstandard_style,
|
|
||||||
rust_2018_idioms,
|
|
||||||
rustdoc::missing_crate_level_docs
|
|
||||||
)]
|
|
||||||
#![allow(clippy::float_cmp)]
|
#![allow(clippy::float_cmp)]
|
||||||
#![allow(clippy::manual_range_contains)]
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ All notable changes to the `egui_glow` integration will be noted in this file.
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
* Improved logging on rendering failures.
|
||||||
|
* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
|
||||||
|
|
||||||
|
|
||||||
## 0.17.0 - 2022-02-22
|
## 0.17.0 - 2022-02-22
|
||||||
|
|
|
@ -57,7 +57,6 @@ winit = ["egui-winit", "glutin"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||||
"convert_bytemuck",
|
"convert_bytemuck",
|
||||||
"single_threaded",
|
|
||||||
] }
|
] }
|
||||||
epi = { version = "0.17.0", path = "../epi", optional = true }
|
epi = { version = "0.17.0", path = "../epi", optional = true }
|
||||||
|
|
||||||
|
|
|
@ -1,42 +1,7 @@
|
||||||
//! Example how to use pure `egui_glow` without [`epi`].
|
//! Example how to use pure `egui_glow` without [`epi`].
|
||||||
|
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
#![allow(unsafe_code)]
|
||||||
fn create_display(
|
|
||||||
event_loop: &glutin::event_loop::EventLoop<()>,
|
|
||||||
) -> (
|
|
||||||
glutin::WindowedContext<glutin::PossiblyCurrent>,
|
|
||||||
glow::Context,
|
|
||||||
) {
|
|
||||||
let window_builder = glutin::window::WindowBuilder::new()
|
|
||||||
.with_resizable(true)
|
|
||||||
.with_inner_size(glutin::dpi::LogicalSize {
|
|
||||||
width: 800.0,
|
|
||||||
height: 600.0,
|
|
||||||
})
|
|
||||||
.with_title("egui_glow example");
|
|
||||||
|
|
||||||
let gl_window = unsafe {
|
|
||||||
glutin::ContextBuilder::new()
|
|
||||||
.with_depth_buffer(0)
|
|
||||||
.with_srgb(true)
|
|
||||||
.with_stencil_buffer(0)
|
|
||||||
.with_vsync(true)
|
|
||||||
.build_windowed(window_builder, event_loop)
|
|
||||||
.unwrap()
|
|
||||||
.make_current()
|
|
||||||
.unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) };
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
use glow::HasContext as _;
|
|
||||||
gl.enable(glow::FRAMEBUFFER_SRGB);
|
|
||||||
}
|
|
||||||
|
|
||||||
(gl_window, gl)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut clear_color = [0.1, 0.1, 0.1];
|
let mut clear_color = [0.1, 0.1, 0.1];
|
||||||
|
@ -116,3 +81,34 @@ fn main() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_display(
|
||||||
|
event_loop: &glutin::event_loop::EventLoop<()>,
|
||||||
|
) -> (
|
||||||
|
glutin::WindowedContext<glutin::PossiblyCurrent>,
|
||||||
|
glow::Context,
|
||||||
|
) {
|
||||||
|
let window_builder = glutin::window::WindowBuilder::new()
|
||||||
|
.with_resizable(true)
|
||||||
|
.with_inner_size(glutin::dpi::LogicalSize {
|
||||||
|
width: 800.0,
|
||||||
|
height: 600.0,
|
||||||
|
})
|
||||||
|
.with_title("egui_glow example");
|
||||||
|
|
||||||
|
let gl_window = unsafe {
|
||||||
|
glutin::ContextBuilder::new()
|
||||||
|
.with_depth_buffer(0)
|
||||||
|
.with_srgb(true)
|
||||||
|
.with_stencil_buffer(0)
|
||||||
|
.with_vsync(true)
|
||||||
|
.build_windowed(window_builder, event_loop)
|
||||||
|
.unwrap()
|
||||||
|
.make_current()
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) };
|
||||||
|
|
||||||
|
(gl_window, gl)
|
||||||
|
}
|
||||||
|
|
|
@ -3,16 +3,9 @@ use egui_winit::winit;
|
||||||
|
|
||||||
struct RequestRepaintEvent;
|
struct RequestRepaintEvent;
|
||||||
|
|
||||||
struct GlowRepaintSignal(std::sync::Mutex<winit::event_loop::EventLoopProxy<RequestRepaintEvent>>);
|
|
||||||
|
|
||||||
impl epi::backend::RepaintSignal for GlowRepaintSignal {
|
|
||||||
fn request_repaint(&self) {
|
|
||||||
self.0.lock().unwrap().send_event(RequestRepaintEvent).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
fn create_display(
|
fn create_display(
|
||||||
|
native_options: &NativeOptions,
|
||||||
window_builder: winit::window::WindowBuilder,
|
window_builder: winit::window::WindowBuilder,
|
||||||
event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>,
|
event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>,
|
||||||
) -> (
|
) -> (
|
||||||
|
@ -21,10 +14,11 @@ fn create_display(
|
||||||
) {
|
) {
|
||||||
let gl_window = unsafe {
|
let gl_window = unsafe {
|
||||||
glutin::ContextBuilder::new()
|
glutin::ContextBuilder::new()
|
||||||
.with_depth_buffer(0)
|
.with_depth_buffer(native_options.depth_buffer)
|
||||||
|
.with_multisampling(native_options.multisampling)
|
||||||
.with_srgb(true)
|
.with_srgb(true)
|
||||||
.with_stencil_buffer(0)
|
.with_stencil_buffer(native_options.stencil_buffer)
|
||||||
.with_vsync(true)
|
.with_vsync(native_options.vsync)
|
||||||
.build_windowed(window_builder, event_loop)
|
.build_windowed(window_builder, event_loop)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.make_current()
|
.make_current()
|
||||||
|
@ -33,11 +27,6 @@ fn create_display(
|
||||||
|
|
||||||
let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) };
|
let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) };
|
||||||
|
|
||||||
unsafe {
|
|
||||||
use glow::HasContext as _;
|
|
||||||
gl.enable(glow::FRAMEBUFFER_SRGB);
|
|
||||||
}
|
|
||||||
|
|
||||||
(gl_window, gl)
|
(gl_window, gl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,31 +36,43 @@ pub use epi::NativeOptions;
|
||||||
|
|
||||||
/// Run an egui app
|
/// Run an egui app
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator) -> ! {
|
||||||
let persistence = egui_winit::epi::Persistence::from_app_name(app.name());
|
let persistence = egui_winit::epi::Persistence::from_app_name(app_name);
|
||||||
let window_settings = persistence.load_window_settings();
|
let window_settings = persistence.load_window_settings();
|
||||||
let window_builder =
|
let window_builder =
|
||||||
egui_winit::epi::window_builder(native_options, &window_settings).with_title(app.name());
|
egui_winit::epi::window_builder(native_options, &window_settings).with_title(app_name);
|
||||||
let event_loop = winit::event_loop::EventLoop::with_user_event();
|
let event_loop = winit::event_loop::EventLoop::with_user_event();
|
||||||
let (gl_window, gl) = create_display(window_builder, &event_loop);
|
let (gl_window, gl) = create_display(native_options, window_builder, &event_loop);
|
||||||
let gl = std::rc::Rc::new(gl);
|
let gl = std::rc::Rc::new(gl);
|
||||||
|
|
||||||
let repaint_signal = std::sync::Arc::new(GlowRepaintSignal(std::sync::Mutex::new(
|
|
||||||
event_loop.create_proxy(),
|
|
||||||
)));
|
|
||||||
|
|
||||||
let mut painter = crate::Painter::new(gl.clone(), None, "")
|
let mut painter = crate::Painter::new(gl.clone(), None, "")
|
||||||
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
|
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
|
||||||
|
|
||||||
let mut integration = egui_winit::epi::EpiIntegration::new(
|
let mut integration = egui_winit::epi::EpiIntegration::new(
|
||||||
"egui_glow",
|
"egui_glow",
|
||||||
painter.max_texture_side(),
|
painter.max_texture_side(),
|
||||||
gl_window.window(),
|
gl_window.window(),
|
||||||
&gl,
|
|
||||||
repaint_signal,
|
|
||||||
persistence,
|
persistence,
|
||||||
app,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy());
|
||||||
|
integration.egui_ctx.set_request_repaint_callback(move || {
|
||||||
|
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut app = app_creator(&epi::CreationContext {
|
||||||
|
egui_ctx: integration.egui_ctx.clone(),
|
||||||
|
integration_info: integration.frame.info(),
|
||||||
|
storage: integration.persistence.storage(),
|
||||||
|
gl: gl.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if app.warm_up_enabled() {
|
||||||
|
integration.warm_up(app.as_mut(), gl_window.window());
|
||||||
|
}
|
||||||
|
|
||||||
let mut is_focused = true;
|
let mut is_focused = true;
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
@ -90,7 +91,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
||||||
needs_repaint,
|
needs_repaint,
|
||||||
textures_delta,
|
textures_delta,
|
||||||
shapes,
|
shapes,
|
||||||
} = integration.update(gl_window.window());
|
} = integration.update(app.as_mut(), gl_window.window());
|
||||||
|
|
||||||
integration.handle_platform_output(gl_window.window(), platform_output);
|
integration.handle_platform_output(gl_window.window(), platform_output);
|
||||||
|
|
||||||
|
@ -98,7 +99,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
||||||
|
|
||||||
// paint:
|
// paint:
|
||||||
{
|
{
|
||||||
let color = integration.app.clear_color();
|
let color = app.clear_color();
|
||||||
unsafe {
|
unsafe {
|
||||||
use glow::HasContext as _;
|
use glow::HasContext as _;
|
||||||
gl.disable(glow::SCISSOR_TEST);
|
gl.disable(glow::SCISSOR_TEST);
|
||||||
|
@ -126,7 +127,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
integration.maybe_autosave(gl_window.window());
|
integration.maybe_autosave(app.as_mut(), gl_window.window());
|
||||||
};
|
};
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
@ -145,7 +146,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
||||||
gl_window.resize(physical_size);
|
gl_window.resize(physical_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
integration.on_event(&event);
|
integration.on_event(app.as_mut(), &event);
|
||||||
if integration.should_quit() {
|
if integration.should_quit() {
|
||||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
*control_flow = winit::event_loop::ControlFlow::Exit;
|
||||||
}
|
}
|
||||||
|
@ -153,7 +154,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
||||||
gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead
|
gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead
|
||||||
}
|
}
|
||||||
winit::event::Event::LoopDestroyed => {
|
winit::event::Event::LoopDestroyed => {
|
||||||
integration.on_exit(gl_window.window());
|
integration
|
||||||
|
.persistence
|
||||||
|
.save(&mut *app, &integration.egui_ctx, gl_window.window());
|
||||||
|
app.on_exit(&gl);
|
||||||
painter.destroy();
|
painter.destroy();
|
||||||
}
|
}
|
||||||
winit::event::Event::UserEvent(RequestRepaintEvent) => {
|
winit::event::Event::UserEvent(RequestRepaintEvent) => {
|
||||||
|
|
|
@ -5,85 +5,6 @@
|
||||||
//! This library is an [`epi`] backend.
|
//! This library is an [`epi`] backend.
|
||||||
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
|
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
|
||||||
|
|
||||||
// Forbid warnings in release builds:
|
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
||||||
#![deny(unsafe_code)]
|
|
||||||
#![warn(
|
|
||||||
clippy::all,
|
|
||||||
clippy::await_holding_lock,
|
|
||||||
clippy::char_lit_as_u8,
|
|
||||||
clippy::checked_conversions,
|
|
||||||
clippy::dbg_macro,
|
|
||||||
clippy::debug_assert_with_mut_call,
|
|
||||||
clippy::disallowed_method,
|
|
||||||
clippy::doc_markdown,
|
|
||||||
clippy::empty_enum,
|
|
||||||
clippy::enum_glob_use,
|
|
||||||
clippy::exit,
|
|
||||||
clippy::expl_impl_clone_on_copy,
|
|
||||||
clippy::explicit_deref_methods,
|
|
||||||
clippy::explicit_into_iter_loop,
|
|
||||||
clippy::fallible_impl_from,
|
|
||||||
clippy::filter_map_next,
|
|
||||||
clippy::flat_map_option,
|
|
||||||
clippy::float_cmp_const,
|
|
||||||
clippy::fn_params_excessive_bools,
|
|
||||||
clippy::from_iter_instead_of_collect,
|
|
||||||
clippy::if_let_mutex,
|
|
||||||
clippy::implicit_clone,
|
|
||||||
clippy::imprecise_flops,
|
|
||||||
clippy::inefficient_to_string,
|
|
||||||
clippy::invalid_upcast_comparisons,
|
|
||||||
clippy::large_digit_groups,
|
|
||||||
clippy::large_stack_arrays,
|
|
||||||
clippy::large_types_passed_by_value,
|
|
||||||
clippy::let_unit_value,
|
|
||||||
clippy::linkedlist,
|
|
||||||
clippy::lossy_float_literal,
|
|
||||||
clippy::macro_use_imports,
|
|
||||||
clippy::manual_ok_or,
|
|
||||||
clippy::map_err_ignore,
|
|
||||||
clippy::map_flatten,
|
|
||||||
clippy::map_unwrap_or,
|
|
||||||
clippy::match_on_vec_items,
|
|
||||||
clippy::match_same_arms,
|
|
||||||
clippy::match_wild_err_arm,
|
|
||||||
clippy::match_wildcard_for_single_variants,
|
|
||||||
clippy::mem_forget,
|
|
||||||
clippy::mismatched_target_os,
|
|
||||||
clippy::missing_errors_doc,
|
|
||||||
clippy::missing_safety_doc,
|
|
||||||
clippy::mut_mut,
|
|
||||||
clippy::mutex_integer,
|
|
||||||
clippy::needless_borrow,
|
|
||||||
clippy::needless_continue,
|
|
||||||
clippy::needless_for_each,
|
|
||||||
clippy::needless_pass_by_value,
|
|
||||||
clippy::option_option,
|
|
||||||
clippy::path_buf_push_overwrite,
|
|
||||||
clippy::ptr_as_ptr,
|
|
||||||
clippy::ref_option_ref,
|
|
||||||
clippy::rest_pat_in_fully_bound_structs,
|
|
||||||
clippy::same_functions_in_if_condition,
|
|
||||||
clippy::semicolon_if_nothing_returned,
|
|
||||||
clippy::single_match_else,
|
|
||||||
clippy::string_add_assign,
|
|
||||||
clippy::string_add,
|
|
||||||
clippy::string_lit_as_bytes,
|
|
||||||
clippy::string_to_string,
|
|
||||||
clippy::todo,
|
|
||||||
clippy::trait_duplication_in_bounds,
|
|
||||||
clippy::unimplemented,
|
|
||||||
clippy::unnested_or_patterns,
|
|
||||||
clippy::unused_self,
|
|
||||||
clippy::useless_transmute,
|
|
||||||
clippy::verbose_file_reads,
|
|
||||||
clippy::zero_sized_map_values,
|
|
||||||
future_incompatible,
|
|
||||||
nonstandard_style,
|
|
||||||
rust_2018_idioms,
|
|
||||||
rustdoc::missing_crate_level_docs
|
|
||||||
)]
|
|
||||||
#![allow(clippy::float_cmp)]
|
#![allow(clippy::float_cmp)]
|
||||||
#![allow(clippy::manual_range_contains)]
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
|
@ -93,7 +14,7 @@ pub use painter::Painter;
|
||||||
mod misc_util;
|
mod misc_util;
|
||||||
mod post_process;
|
mod post_process;
|
||||||
mod shader_version;
|
mod shader_version;
|
||||||
mod vao_emulate;
|
mod vao;
|
||||||
|
|
||||||
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
|
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
|
||||||
pub mod winit;
|
pub mod winit;
|
||||||
|
@ -105,3 +26,62 @@ mod epi_backend;
|
||||||
|
|
||||||
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
|
#[cfg(all(not(target_arch = "wasm32"), feature = "winit"))]
|
||||||
pub use epi_backend::{run, NativeOptions};
|
pub use epi_backend::{run, NativeOptions};
|
||||||
|
|
||||||
|
/// Check for OpenGL error and report it using `tracing::error`.
|
||||||
|
///
|
||||||
|
/// ``` no_run
|
||||||
|
/// # let glow_context = todo!();
|
||||||
|
/// use egui_glow::check_for_gl_error;
|
||||||
|
/// check_for_gl_error!(glow_context);
|
||||||
|
/// check_for_gl_error!(glow_context, "during painting");
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! check_for_gl_error {
|
||||||
|
($gl: expr) => {{
|
||||||
|
$crate::check_for_gl_error_impl($gl, file!(), line!(), "")
|
||||||
|
}};
|
||||||
|
($gl: expr, $context: literal) => {{
|
||||||
|
$crate::check_for_gl_error_impl($gl, file!(), line!(), $context)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn check_for_gl_error_impl(gl: &glow::Context, file: &str, line: u32, context: &str) {
|
||||||
|
use glow::HasContext as _;
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
let error_code = unsafe { gl.get_error() };
|
||||||
|
if error_code != glow::NO_ERROR {
|
||||||
|
let error_str = match error_code {
|
||||||
|
glow::INVALID_ENUM => "GL_INVALID_ENUM",
|
||||||
|
glow::INVALID_VALUE => "GL_INVALID_VALUE",
|
||||||
|
glow::INVALID_OPERATION => "GL_INVALID_OPERATION",
|
||||||
|
glow::STACK_OVERFLOW => "GL_STACK_OVERFLOW",
|
||||||
|
glow::STACK_UNDERFLOW => "GL_STACK_UNDERFLOW",
|
||||||
|
glow::OUT_OF_MEMORY => "GL_OUT_OF_MEMORY",
|
||||||
|
glow::INVALID_FRAMEBUFFER_OPERATION => "GL_INVALID_FRAMEBUFFER_OPERATION",
|
||||||
|
glow::CONTEXT_LOST => "GL_CONTEXT_LOST",
|
||||||
|
0x8031 => "GL_TABLE_TOO_LARGE1",
|
||||||
|
0x9242 => "CONTEXT_LOST_WEBGL",
|
||||||
|
_ => "<unknown>",
|
||||||
|
};
|
||||||
|
|
||||||
|
if context.is_empty() {
|
||||||
|
tracing::error!(
|
||||||
|
"GL error, at {}:{}: {} (0x{:X}). Please file a bug at https://github.com/emilk/egui/issues",
|
||||||
|
file,
|
||||||
|
line,
|
||||||
|
error_str,
|
||||||
|
error_code,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tracing::error!(
|
||||||
|
"GL error, at {}:{} ({}): {} (0x{:X}). Please file a bug at https://github.com/emilk/egui/issues",
|
||||||
|
file,
|
||||||
|
line,
|
||||||
|
context,
|
||||||
|
error_str,
|
||||||
|
error_code,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +1,6 @@
|
||||||
#![allow(unsafe_code)]
|
#![allow(unsafe_code)]
|
||||||
use glow::HasContext;
|
|
||||||
use std::option::Option::Some;
|
|
||||||
|
|
||||||
pub fn check_for_gl_error(gl: &glow::Context, context: &str) {
|
use glow::HasContext as _;
|
||||||
let error_code = unsafe { gl.get_error() };
|
|
||||||
if error_code != glow::NO_ERROR {
|
|
||||||
tracing::error!(
|
|
||||||
"GL error, at: '{}', code: {} (0x{:X})",
|
|
||||||
context,
|
|
||||||
error_code,
|
|
||||||
error_code
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe fn compile_shader(
|
pub(crate) unsafe fn compile_shader(
|
||||||
gl: &glow::Context,
|
gl: &glow::Context,
|
||||||
|
@ -50,105 +38,3 @@ pub(crate) unsafe fn link_program<'a, T: IntoIterator<Item = &'a glow::Shader>>(
|
||||||
Err(gl.get_program_info_log(program))
|
Err(gl.get_program_info_log(program))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
///Wrapper around Emulated VAO and GL's VAO
|
|
||||||
pub(crate) enum VAO {
|
|
||||||
Emulated(crate::vao_emulate::EmulatedVao),
|
|
||||||
Native(crate::glow::VertexArray),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VAO {
|
|
||||||
pub(crate) unsafe fn native(gl: &glow::Context) -> Self {
|
|
||||||
Self::Native(gl.create_vertex_array().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe fn emulated() -> Self {
|
|
||||||
Self::Emulated(crate::vao_emulate::EmulatedVao::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe fn bind_vertex_array(&self, gl: &glow::Context) {
|
|
||||||
match self {
|
|
||||||
VAO::Emulated(vao) => vao.bind_vertex_array(gl),
|
|
||||||
VAO::Native(vao) => gl.bind_vertex_array(Some(*vao)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe fn bind_buffer(&mut self, gl: &glow::Context, buffer: &glow::Buffer) {
|
|
||||||
match self {
|
|
||||||
VAO::Emulated(vao) => vao.bind_buffer(buffer),
|
|
||||||
VAO::Native(_) => gl.bind_buffer(glow::ARRAY_BUFFER, Some(*buffer)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe fn add_new_attribute(
|
|
||||||
&mut self,
|
|
||||||
gl: &glow::Context,
|
|
||||||
buffer_info: crate::vao_emulate::BufferInfo,
|
|
||||||
) {
|
|
||||||
match self {
|
|
||||||
VAO::Emulated(vao) => vao.add_new_attribute(buffer_info),
|
|
||||||
VAO::Native(_) => {
|
|
||||||
gl.vertex_attrib_pointer_f32(
|
|
||||||
buffer_info.location,
|
|
||||||
buffer_info.vector_size,
|
|
||||||
buffer_info.data_type,
|
|
||||||
buffer_info.normalized,
|
|
||||||
buffer_info.stride,
|
|
||||||
buffer_info.offset,
|
|
||||||
);
|
|
||||||
gl.enable_vertex_attrib_array(buffer_info.location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) unsafe fn unbind_vertex_array(&self, gl: &glow::Context) {
|
|
||||||
match self {
|
|
||||||
VAO::Emulated(vao) => vao.unbind_vertex_array(gl),
|
|
||||||
VAO::Native(_) => {
|
|
||||||
gl.bind_vertex_array(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If returned true no need to emulate vao
|
|
||||||
pub(crate) fn supports_vao(gl: &glow::Context) -> bool {
|
|
||||||
const WEBGL_PREFIX: &str = "WebGL ";
|
|
||||||
const OPENGL_ES_PREFIX: &str = "OpenGL ES ";
|
|
||||||
|
|
||||||
let version_string = unsafe { gl.get_parameter_string(glow::VERSION) };
|
|
||||||
tracing::debug!("GL version: {:?}.", version_string);
|
|
||||||
|
|
||||||
// Examples:
|
|
||||||
// * "WebGL 2.0 (OpenGL ES 3.0 Chromium)"
|
|
||||||
// * "WebGL 2.0"
|
|
||||||
|
|
||||||
if let Some(pos) = version_string.rfind(WEBGL_PREFIX) {
|
|
||||||
let version_str = &version_string[pos + WEBGL_PREFIX.len()..];
|
|
||||||
if version_str.contains("1.0") {
|
|
||||||
// need to test OES_vertex_array_object .
|
|
||||||
gl.supported_extensions()
|
|
||||||
.contains("OES_vertex_array_object")
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} else if version_string.contains(OPENGL_ES_PREFIX) {
|
|
||||||
// glow targets es2.0+ so we don't concern about OpenGL ES-CM,OpenGL ES-CL
|
|
||||||
if version_string.contains("2.0") {
|
|
||||||
// need to test OES_vertex_array_object .
|
|
||||||
gl.supported_extensions()
|
|
||||||
.contains("OES_vertex_array_object")
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// from OpenGL 3 vao into core
|
|
||||||
if version_string.starts_with('2') {
|
|
||||||
// I found APPLE_vertex_array_object , GL_ATI_vertex_array_object ,ARB_vertex_array_object
|
|
||||||
// but APPLE's and ATI's very old extension.
|
|
||||||
gl.supported_extensions()
|
|
||||||
.contains("ARB_vertex_array_object")
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,13 +6,14 @@ use egui::{
|
||||||
emath::Rect,
|
emath::Rect,
|
||||||
epaint::{Color32, Mesh, Primitive, Vertex},
|
epaint::{Color32, Mesh, Primitive, Vertex},
|
||||||
};
|
};
|
||||||
use glow::HasContext;
|
use glow::HasContext as _;
|
||||||
use memoffset::offset_of;
|
use memoffset::offset_of;
|
||||||
|
|
||||||
use crate::misc_util::{check_for_gl_error, compile_shader, link_program};
|
use crate::check_for_gl_error;
|
||||||
|
use crate::misc_util::{compile_shader, link_program};
|
||||||
use crate::post_process::PostProcess;
|
use crate::post_process::PostProcess;
|
||||||
use crate::shader_version::ShaderVersion;
|
use crate::shader_version::ShaderVersion;
|
||||||
use crate::vao_emulate;
|
use crate::vao;
|
||||||
|
|
||||||
pub use glow::Context;
|
pub use glow::Context;
|
||||||
|
|
||||||
|
@ -36,12 +37,12 @@ pub struct Painter {
|
||||||
u_sampler: glow::UniformLocation,
|
u_sampler: glow::UniformLocation,
|
||||||
is_webgl_1: bool,
|
is_webgl_1: bool,
|
||||||
is_embedded: bool,
|
is_embedded: bool,
|
||||||
vertex_array: crate::misc_util::VAO,
|
vao: crate::vao::VertexArrayObject,
|
||||||
srgb_support: bool,
|
srgb_support: bool,
|
||||||
/// The filter used for subsequent textures.
|
/// The filter used for subsequent textures.
|
||||||
texture_filter: TextureFilter,
|
texture_filter: TextureFilter,
|
||||||
post_process: Option<PostProcess>,
|
post_process: Option<PostProcess>,
|
||||||
vertex_buffer: glow::Buffer,
|
vbo: glow::Buffer,
|
||||||
element_array_buffer: glow::Buffer,
|
element_array_buffer: glow::Buffer,
|
||||||
|
|
||||||
textures: HashMap<egui::TextureId, glow::Texture>,
|
textures: HashMap<egui::TextureId, glow::Texture>,
|
||||||
|
@ -95,11 +96,10 @@ impl Painter {
|
||||||
pp_fb_extent: Option<[i32; 2]>,
|
pp_fb_extent: Option<[i32; 2]>,
|
||||||
shader_prefix: &str,
|
shader_prefix: &str,
|
||||||
) -> Result<Painter, String> {
|
) -> Result<Painter, String> {
|
||||||
check_for_gl_error(&gl, "before Painter::new");
|
check_for_gl_error!(&gl, "before Painter::new");
|
||||||
|
|
||||||
let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize;
|
let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize;
|
||||||
|
|
||||||
let support_vao = crate::misc_util::supports_vao(&gl);
|
|
||||||
let shader_version = ShaderVersion::get(&gl);
|
let shader_version = ShaderVersion::get(&gl);
|
||||||
let is_webgl_1 = shader_version == ShaderVersion::Es100;
|
let is_webgl_1 = shader_version == ShaderVersion::Es100;
|
||||||
let header = shader_version.version();
|
let header = shader_version.version();
|
||||||
|
@ -117,7 +117,6 @@ impl Painter {
|
||||||
Some(PostProcess::new(
|
Some(PostProcess::new(
|
||||||
gl.clone(),
|
gl.clone(),
|
||||||
shader_prefix,
|
shader_prefix,
|
||||||
support_vao,
|
|
||||||
is_webgl_1,
|
is_webgl_1,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
@ -168,48 +167,45 @@ impl Painter {
|
||||||
gl.delete_shader(frag);
|
gl.delete_shader(frag);
|
||||||
let u_screen_size = gl.get_uniform_location(program, "u_screen_size").unwrap();
|
let u_screen_size = gl.get_uniform_location(program, "u_screen_size").unwrap();
|
||||||
let u_sampler = gl.get_uniform_location(program, "u_sampler").unwrap();
|
let u_sampler = gl.get_uniform_location(program, "u_sampler").unwrap();
|
||||||
let vertex_buffer = gl.create_buffer()?;
|
|
||||||
let element_array_buffer = gl.create_buffer()?;
|
let vbo = gl.create_buffer()?;
|
||||||
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
|
|
||||||
let a_pos_loc = gl.get_attrib_location(program, "a_pos").unwrap();
|
let a_pos_loc = gl.get_attrib_location(program, "a_pos").unwrap();
|
||||||
let a_tc_loc = gl.get_attrib_location(program, "a_tc").unwrap();
|
let a_tc_loc = gl.get_attrib_location(program, "a_tc").unwrap();
|
||||||
let a_srgba_loc = gl.get_attrib_location(program, "a_srgba").unwrap();
|
let a_srgba_loc = gl.get_attrib_location(program, "a_srgba").unwrap();
|
||||||
let mut vertex_array = if support_vao {
|
|
||||||
crate::misc_util::VAO::native(&gl)
|
|
||||||
} else {
|
|
||||||
crate::misc_util::VAO::emulated()
|
|
||||||
};
|
|
||||||
vertex_array.bind_vertex_array(&gl);
|
|
||||||
vertex_array.bind_buffer(&gl, &vertex_buffer);
|
|
||||||
let stride = std::mem::size_of::<Vertex>() as i32;
|
let stride = std::mem::size_of::<Vertex>() as i32;
|
||||||
let position_buffer_info = vao_emulate::BufferInfo {
|
let buffer_infos = vec![
|
||||||
location: a_pos_loc,
|
vao::BufferInfo {
|
||||||
vector_size: 2,
|
location: a_pos_loc,
|
||||||
data_type: glow::FLOAT,
|
vector_size: 2,
|
||||||
normalized: false,
|
data_type: glow::FLOAT,
|
||||||
stride,
|
normalized: false,
|
||||||
offset: offset_of!(Vertex, pos) as i32,
|
stride,
|
||||||
};
|
offset: offset_of!(Vertex, pos) as i32,
|
||||||
let tex_coord_buffer_info = vao_emulate::BufferInfo {
|
},
|
||||||
location: a_tc_loc,
|
vao::BufferInfo {
|
||||||
vector_size: 2,
|
location: a_tc_loc,
|
||||||
data_type: glow::FLOAT,
|
vector_size: 2,
|
||||||
normalized: false,
|
data_type: glow::FLOAT,
|
||||||
stride,
|
normalized: false,
|
||||||
offset: offset_of!(Vertex, uv) as i32,
|
stride,
|
||||||
};
|
offset: offset_of!(Vertex, uv) as i32,
|
||||||
let color_buffer_info = vao_emulate::BufferInfo {
|
},
|
||||||
location: a_srgba_loc,
|
vao::BufferInfo {
|
||||||
vector_size: 4,
|
location: a_srgba_loc,
|
||||||
data_type: glow::UNSIGNED_BYTE,
|
vector_size: 4,
|
||||||
normalized: false,
|
data_type: glow::UNSIGNED_BYTE,
|
||||||
stride,
|
normalized: false,
|
||||||
offset: offset_of!(Vertex, color) as i32,
|
stride,
|
||||||
};
|
offset: offset_of!(Vertex, color) as i32,
|
||||||
vertex_array.add_new_attribute(&gl, position_buffer_info);
|
},
|
||||||
vertex_array.add_new_attribute(&gl, tex_coord_buffer_info);
|
];
|
||||||
vertex_array.add_new_attribute(&gl, color_buffer_info);
|
let vao = crate::vao::VertexArrayObject::new(&gl, vbo, buffer_infos);
|
||||||
check_for_gl_error(&gl, "after Painter::new");
|
|
||||||
|
let element_array_buffer = gl.create_buffer()?;
|
||||||
|
|
||||||
|
check_for_gl_error!(&gl, "after Painter::new");
|
||||||
|
|
||||||
Ok(Painter {
|
Ok(Painter {
|
||||||
gl,
|
gl,
|
||||||
|
@ -219,11 +215,11 @@ impl Painter {
|
||||||
u_sampler,
|
u_sampler,
|
||||||
is_webgl_1,
|
is_webgl_1,
|
||||||
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
|
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
|
||||||
vertex_array,
|
vao,
|
||||||
srgb_support,
|
srgb_support,
|
||||||
texture_filter: Default::default(),
|
texture_filter: Default::default(),
|
||||||
post_process,
|
post_process,
|
||||||
vertex_buffer,
|
vbo,
|
||||||
element_array_buffer,
|
element_array_buffer,
|
||||||
textures: Default::default(),
|
textures: Default::default(),
|
||||||
#[cfg(feature = "epi")]
|
#[cfg(feature = "epi")]
|
||||||
|
@ -251,9 +247,13 @@ impl Painter {
|
||||||
self.gl.enable(glow::SCISSOR_TEST);
|
self.gl.enable(glow::SCISSOR_TEST);
|
||||||
// egui outputs mesh in both winding orders
|
// egui outputs mesh in both winding orders
|
||||||
self.gl.disable(glow::CULL_FACE);
|
self.gl.disable(glow::CULL_FACE);
|
||||||
|
self.gl.disable(glow::DEPTH_TEST);
|
||||||
|
|
||||||
|
self.gl.color_mask(true, true, true, true);
|
||||||
|
|
||||||
self.gl.enable(glow::BLEND);
|
self.gl.enable(glow::BLEND);
|
||||||
self.gl.blend_equation(glow::FUNC_ADD);
|
self.gl
|
||||||
|
.blend_equation_separate(glow::FUNC_ADD, glow::FUNC_ADD);
|
||||||
self.gl.blend_func_separate(
|
self.gl.blend_func_separate(
|
||||||
// egui outputs colors with premultiplied alpha:
|
// egui outputs colors with premultiplied alpha:
|
||||||
glow::ONE,
|
glow::ONE,
|
||||||
|
@ -264,6 +264,11 @@ impl Painter {
|
||||||
glow::ONE,
|
glow::ONE,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if !cfg!(target_arch = "wasm32") {
|
||||||
|
self.gl.enable(glow::FRAMEBUFFER_SRGB);
|
||||||
|
check_for_gl_error!(&self.gl, "FRAMEBUFFER_SRGB");
|
||||||
|
}
|
||||||
|
|
||||||
let width_in_points = width_in_pixels as f32 / pixels_per_point;
|
let width_in_points = width_in_pixels as f32 / pixels_per_point;
|
||||||
let height_in_points = height_in_pixels as f32 / pixels_per_point;
|
let height_in_points = height_in_pixels as f32 / pixels_per_point;
|
||||||
|
|
||||||
|
@ -275,11 +280,13 @@ impl Painter {
|
||||||
.uniform_2_f32(Some(&self.u_screen_size), width_in_points, height_in_points);
|
.uniform_2_f32(Some(&self.u_screen_size), width_in_points, height_in_points);
|
||||||
self.gl.uniform_1_i32(Some(&self.u_sampler), 0);
|
self.gl.uniform_1_i32(Some(&self.u_sampler), 0);
|
||||||
self.gl.active_texture(glow::TEXTURE0);
|
self.gl.active_texture(glow::TEXTURE0);
|
||||||
self.vertex_array.bind_vertex_array(&self.gl);
|
|
||||||
|
|
||||||
|
self.vao.bind(&self.gl);
|
||||||
self.gl
|
self.gl
|
||||||
.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
|
.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_array_buffer));
|
||||||
|
|
||||||
|
check_for_gl_error!(&self.gl, "prepare_painting");
|
||||||
|
|
||||||
(width_in_pixels, height_in_pixels)
|
(width_in_pixels, height_in_pixels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,7 +375,15 @@ impl Painter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
callback.call(self);
|
let info = egui::PaintCallbackInfo {
|
||||||
|
rect: callback.rect,
|
||||||
|
pixels_per_point,
|
||||||
|
screen_size_px: inner_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
callback.call(&info, self);
|
||||||
|
|
||||||
|
check_for_gl_error!(&self.gl, "callback");
|
||||||
|
|
||||||
// Restore state:
|
// Restore state:
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -381,8 +396,9 @@ impl Painter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.vertex_array.unbind_vertex_array(&self.gl);
|
self.vao.unbind(&self.gl);
|
||||||
self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
||||||
|
|
||||||
if let Some(ref post_process) = self.post_process {
|
if let Some(ref post_process) = self.post_process {
|
||||||
|
@ -391,7 +407,7 @@ impl Painter {
|
||||||
|
|
||||||
self.gl.disable(glow::SCISSOR_TEST);
|
self.gl.disable(glow::SCISSOR_TEST);
|
||||||
|
|
||||||
check_for_gl_error(&self.gl, "painting");
|
check_for_gl_error!(&self.gl, "painting");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,8 +416,7 @@ impl Painter {
|
||||||
debug_assert!(mesh.is_valid());
|
debug_assert!(mesh.is_valid());
|
||||||
if let Some(texture) = self.get_texture(mesh.texture_id) {
|
if let Some(texture) = self.get_texture(mesh.texture_id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.gl
|
self.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
|
||||||
.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
|
|
||||||
self.gl.buffer_data_u8_slice(
|
self.gl.buffer_data_u8_slice(
|
||||||
glow::ARRAY_BUFFER,
|
glow::ARRAY_BUFFER,
|
||||||
bytemuck::cast_slice(&mesh.vertices),
|
bytemuck::cast_slice(&mesh.vertices),
|
||||||
|
@ -427,6 +442,8 @@ impl Painter {
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_for_gl_error!(&self.gl, "paint_mesh");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,7 +502,20 @@ impl Painter {
|
||||||
|
|
||||||
fn upload_texture_srgb(&mut self, pos: Option<[usize; 2]>, [w, h]: [usize; 2], data: &[u8]) {
|
fn upload_texture_srgb(&mut self, pos: Option<[usize; 2]>, [w, h]: [usize; 2], data: &[u8]) {
|
||||||
assert_eq!(data.len(), w * h * 4);
|
assert_eq!(data.len(), w * h * 4);
|
||||||
assert!(w >= 1 && h >= 1);
|
assert!(
|
||||||
|
w >= 1 && h >= 1,
|
||||||
|
"Got a texture image of size {}x{}. A texture must at least be one texel wide.",
|
||||||
|
w,
|
||||||
|
h
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
w <= self.max_texture_side && h <= self.max_texture_side,
|
||||||
|
"Got a texture image of size {}x{}, but the maximum supported texture side is only {}",
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
self.max_texture_side
|
||||||
|
);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
self.gl.tex_parameter_i32(
|
self.gl.tex_parameter_i32(
|
||||||
glow::TEXTURE_2D,
|
glow::TEXTURE_2D,
|
||||||
|
@ -508,7 +538,7 @@ impl Painter {
|
||||||
glow::TEXTURE_WRAP_T,
|
glow::TEXTURE_WRAP_T,
|
||||||
glow::CLAMP_TO_EDGE as i32,
|
glow::CLAMP_TO_EDGE as i32,
|
||||||
);
|
);
|
||||||
check_for_gl_error(&self.gl, "tex_parameter");
|
check_for_gl_error!(&self.gl, "tex_parameter");
|
||||||
|
|
||||||
let (internal_format, src_format) = if self.is_webgl_1 {
|
let (internal_format, src_format) = if self.is_webgl_1 {
|
||||||
let format = if self.srgb_support {
|
let format = if self.srgb_support {
|
||||||
|
@ -536,7 +566,7 @@ impl Painter {
|
||||||
glow::UNSIGNED_BYTE,
|
glow::UNSIGNED_BYTE,
|
||||||
glow::PixelUnpackData::Slice(data),
|
glow::PixelUnpackData::Slice(data),
|
||||||
);
|
);
|
||||||
check_for_gl_error(&self.gl, "tex_sub_image_2d");
|
check_for_gl_error!(&self.gl, "tex_sub_image_2d");
|
||||||
} else {
|
} else {
|
||||||
let border = 0;
|
let border = 0;
|
||||||
self.gl.tex_image_2d(
|
self.gl.tex_image_2d(
|
||||||
|
@ -550,7 +580,7 @@ impl Painter {
|
||||||
glow::UNSIGNED_BYTE,
|
glow::UNSIGNED_BYTE,
|
||||||
Some(data),
|
Some(data),
|
||||||
);
|
);
|
||||||
check_for_gl_error(&self.gl, "tex_image_2d");
|
check_for_gl_error!(&self.gl, "tex_image_2d");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -571,7 +601,7 @@ impl Painter {
|
||||||
for tex in self.textures.values() {
|
for tex in self.textures.values() {
|
||||||
self.gl.delete_texture(*tex);
|
self.gl.delete_texture(*tex);
|
||||||
}
|
}
|
||||||
self.gl.delete_buffer(self.vertex_buffer);
|
self.gl.delete_buffer(self.vbo);
|
||||||
self.gl.delete_buffer(self.element_array_buffer);
|
self.gl.delete_buffer(self.element_array_buffer);
|
||||||
for t in &self.textures_to_destroy {
|
for t in &self.textures_to_destroy {
|
||||||
self.gl.delete_texture(*t);
|
self.gl.delete_texture(*t);
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
#![allow(unsafe_code)]
|
#![allow(unsafe_code)]
|
||||||
use crate::misc_util::{check_for_gl_error, compile_shader, link_program};
|
use crate::check_for_gl_error;
|
||||||
use crate::vao_emulate::BufferInfo;
|
use crate::misc_util::{compile_shader, link_program};
|
||||||
use glow::HasContext;
|
use crate::vao::BufferInfo;
|
||||||
|
use glow::HasContext as _;
|
||||||
|
|
||||||
/// Uses a framebuffer to render everything in linear color space and convert it back to `sRGB`
|
/// Uses a framebuffer to render everything in linear color space and convert it back to `sRGB`
|
||||||
/// in a separate "post processing" step
|
/// in a separate "post processing" step
|
||||||
|
@ -9,7 +10,7 @@ pub(crate) struct PostProcess {
|
||||||
gl: std::rc::Rc<glow::Context>,
|
gl: std::rc::Rc<glow::Context>,
|
||||||
pos_buffer: glow::Buffer,
|
pos_buffer: glow::Buffer,
|
||||||
index_buffer: glow::Buffer,
|
index_buffer: glow::Buffer,
|
||||||
vertex_array: crate::misc_util::VAO,
|
vao: crate::vao::VertexArrayObject,
|
||||||
is_webgl_1: bool,
|
is_webgl_1: bool,
|
||||||
texture: glow::Texture,
|
texture: glow::Texture,
|
||||||
texture_size: (i32, i32),
|
texture_size: (i32, i32),
|
||||||
|
@ -21,7 +22,6 @@ impl PostProcess {
|
||||||
pub(crate) unsafe fn new(
|
pub(crate) unsafe fn new(
|
||||||
gl: std::rc::Rc<glow::Context>,
|
gl: std::rc::Rc<glow::Context>,
|
||||||
shader_prefix: &str,
|
shader_prefix: &str,
|
||||||
need_to_emulate_vao: bool,
|
|
||||||
is_webgl_1: bool,
|
is_webgl_1: bool,
|
||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
|
@ -77,7 +77,7 @@ impl PostProcess {
|
||||||
glow::UNSIGNED_BYTE,
|
glow::UNSIGNED_BYTE,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
check_for_gl_error(&gl, "post process texture initialization");
|
check_for_gl_error!(&gl, "post process texture initialization");
|
||||||
|
|
||||||
gl.framebuffer_texture_2d(
|
gl.framebuffer_texture_2d(
|
||||||
glow::FRAMEBUFFER,
|
glow::FRAMEBUFFER,
|
||||||
|
@ -124,35 +124,31 @@ impl PostProcess {
|
||||||
let a_pos_loc = gl
|
let a_pos_loc = gl
|
||||||
.get_attrib_location(program, "a_pos")
|
.get_attrib_location(program, "a_pos")
|
||||||
.ok_or_else(|| "failed to get location of a_pos".to_string())?;
|
.ok_or_else(|| "failed to get location of a_pos".to_string())?;
|
||||||
let mut vertex_array = if need_to_emulate_vao {
|
let vao = crate::vao::VertexArrayObject::new(
|
||||||
crate::misc_util::VAO::emulated()
|
&gl,
|
||||||
} else {
|
pos_buffer,
|
||||||
crate::misc_util::VAO::native(&gl)
|
vec![BufferInfo {
|
||||||
};
|
location: a_pos_loc,
|
||||||
vertex_array.bind_vertex_array(&gl);
|
vector_size: 2,
|
||||||
vertex_array.bind_buffer(&gl, &pos_buffer);
|
data_type: glow::FLOAT,
|
||||||
let buffer_info_a_pos = BufferInfo {
|
normalized: false,
|
||||||
location: a_pos_loc,
|
stride: 0,
|
||||||
vector_size: 2,
|
offset: 0,
|
||||||
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()?;
|
let index_buffer = gl.create_buffer()?;
|
||||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer));
|
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer));
|
||||||
gl.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, &indices, glow::STATIC_DRAW);
|
gl.buffer_data_u8_slice(glow::ELEMENT_ARRAY_BUFFER, &indices, glow::STATIC_DRAW);
|
||||||
|
|
||||||
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
||||||
check_for_gl_error(&gl, "post process initialization");
|
check_for_gl_error!(&gl, "post process initialization");
|
||||||
|
|
||||||
Ok(PostProcess {
|
Ok(PostProcess {
|
||||||
gl,
|
gl,
|
||||||
pos_buffer,
|
pos_buffer,
|
||||||
index_buffer,
|
index_buffer,
|
||||||
vertex_array,
|
vao,
|
||||||
is_webgl_1,
|
is_webgl_1,
|
||||||
texture,
|
texture,
|
||||||
texture_size: (width, height),
|
texture_size: (width, height),
|
||||||
|
@ -190,6 +186,8 @@ impl PostProcess {
|
||||||
self.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo));
|
self.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo));
|
||||||
self.gl.clear_color(0.0, 0.0, 0.0, 0.0);
|
self.gl.clear_color(0.0, 0.0, 0.0, 0.0);
|
||||||
self.gl.clear(glow::COLOR_BUFFER_BIT);
|
self.gl.clear(glow::COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
check_for_gl_error!(&self.gl, "PostProcess::begin");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) unsafe fn bind(&self) {
|
pub(crate) unsafe fn bind(&self) {
|
||||||
|
@ -209,16 +207,18 @@ impl PostProcess {
|
||||||
.get_uniform_location(self.program, "u_sampler")
|
.get_uniform_location(self.program, "u_sampler")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
self.gl.uniform_1_i32(Some(&u_sampler_loc), 0);
|
self.gl.uniform_1_i32(Some(&u_sampler_loc), 0);
|
||||||
self.vertex_array.bind_vertex_array(&self.gl);
|
self.vao.bind(&self.gl);
|
||||||
|
|
||||||
self.gl
|
self.gl
|
||||||
.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer));
|
.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buffer));
|
||||||
self.gl
|
self.gl
|
||||||
.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0);
|
.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_BYTE, 0);
|
||||||
self.vertex_array.unbind_vertex_array(&self.gl);
|
self.vao.unbind(&self.gl);
|
||||||
self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
self.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, None);
|
||||||
self.gl.bind_texture(glow::TEXTURE_2D, None);
|
self.gl.bind_texture(glow::TEXTURE_2D, None);
|
||||||
self.gl.use_program(None);
|
self.gl.use_program(None);
|
||||||
|
|
||||||
|
check_for_gl_error!(&self.gl, "PostProcess::end");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) unsafe fn destroy(&self) {
|
pub(crate) unsafe fn destroy(&self) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#![allow(unsafe_code)]
|
#![allow(unsafe_code)]
|
||||||
|
|
||||||
use glow::HasContext;
|
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -14,6 +13,7 @@ pub(crate) enum ShaderVersion {
|
||||||
|
|
||||||
impl ShaderVersion {
|
impl ShaderVersion {
|
||||||
pub(crate) fn get(gl: &glow::Context) -> Self {
|
pub(crate) fn get(gl: &glow::Context) -> Self {
|
||||||
|
use glow::HasContext as _;
|
||||||
let shading_lang_string =
|
let shading_lang_string =
|
||||||
unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) };
|
unsafe { gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION) };
|
||||||
let shader_version = Self::parse(&shading_lang_string);
|
let shader_version = Self::parse(&shading_lang_string);
|
||||||
|
|
154
egui_glow/src/vao.rs
Normal file
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
|
## Unreleased
|
||||||
* egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306))
|
* egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306)).
|
||||||
* Remove the "webgl" feature. `egui_web` now always use `glow` (which in turn wraps WebGL) ([#1356](https://github.com/emilk/egui/pull/1356)).
|
* Remove the "webgl" feature. `egui_web` now always use `glow` (which in turn wraps WebGL) ([#1356](https://github.com/emilk/egui/pull/1356)).
|
||||||
|
* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)).
|
||||||
|
|
||||||
|
|
||||||
## 0.17.0 - 2022-02-22
|
## 0.17.0 - 2022-02-22
|
||||||
|
|
|
@ -43,7 +43,6 @@ screen_reader = ["tts"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
||||||
"convert_bytemuck",
|
"convert_bytemuck",
|
||||||
"single_threaded",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
] }
|
] }
|
||||||
egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false }
|
egui_glow = { version = "0.17.0", path = "../egui_glow", default-features = false }
|
||||||
|
|
|
@ -50,12 +50,6 @@ impl NeedRepaint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::backend::RepaintSignal for NeedRepaint {
|
|
||||||
fn request_repaint(&self) {
|
|
||||||
self.0.store(true, SeqCst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
fn web_location() -> epi::Location {
|
fn web_location() -> epi::Location {
|
||||||
|
@ -72,7 +66,7 @@ fn web_location() -> epi::Location {
|
||||||
|
|
||||||
let query_map = parse_query_map(&query)
|
let query_map = parse_query_map(&query)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
epi::Location {
|
epi::Location {
|
||||||
|
@ -145,13 +139,11 @@ pub struct AppRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppRunner {
|
impl AppRunner {
|
||||||
pub fn new(canvas_id: &str, app: Box<dyn epi::App>) -> Result<Self, JsValue> {
|
pub fn new(canvas_id: &str, app_creator: epi::AppCreator) -> Result<Self, JsValue> {
|
||||||
let painter = WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?;
|
let painter = WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?;
|
||||||
|
|
||||||
let prefer_dark_mode = crate::prefer_dark_mode();
|
let prefer_dark_mode = crate::prefer_dark_mode();
|
||||||
|
|
||||||
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
|
||||||
|
|
||||||
let frame = epi::Frame::new(epi::backend::FrameData {
|
let frame = epi::Frame::new(epi::backend::FrameData {
|
||||||
info: epi::IntegrationInfo {
|
info: epi::IntegrationInfo {
|
||||||
name: "egui_web",
|
name: "egui_web",
|
||||||
|
@ -163,10 +155,19 @@ impl AppRunner {
|
||||||
native_pixels_per_point: Some(native_pixels_per_point()),
|
native_pixels_per_point: Some(native_pixels_per_point()),
|
||||||
},
|
},
|
||||||
output: Default::default(),
|
output: Default::default(),
|
||||||
repaint_signal: needs_repaint.clone(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
||||||
|
|
||||||
let egui_ctx = egui::Context::default();
|
let egui_ctx = egui::Context::default();
|
||||||
|
|
||||||
|
{
|
||||||
|
let needs_repaint = needs_repaint.clone();
|
||||||
|
egui_ctx.set_request_repaint_callback(move || {
|
||||||
|
needs_repaint.0.store(true, SeqCst);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
load_memory(&egui_ctx);
|
load_memory(&egui_ctx);
|
||||||
if prefer_dark_mode == Some(true) {
|
if prefer_dark_mode == Some(true) {
|
||||||
egui_ctx.set_visuals(egui::Visuals::dark());
|
egui_ctx.set_visuals(egui::Visuals::dark());
|
||||||
|
@ -176,6 +177,13 @@ impl AppRunner {
|
||||||
|
|
||||||
let storage = LocalStorage::default();
|
let storage = LocalStorage::default();
|
||||||
|
|
||||||
|
let app = app_creator(&epi::CreationContext {
|
||||||
|
egui_ctx: egui_ctx.clone(),
|
||||||
|
integration_info: frame.info(),
|
||||||
|
storage: Some(&storage),
|
||||||
|
gl: painter.painter.gl().clone(),
|
||||||
|
});
|
||||||
|
|
||||||
let mut runner = Self {
|
let mut runner = Self {
|
||||||
frame,
|
frame,
|
||||||
egui_ctx,
|
egui_ctx,
|
||||||
|
@ -193,11 +201,6 @@ impl AppRunner {
|
||||||
|
|
||||||
runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side());
|
runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side());
|
||||||
|
|
||||||
let gl = runner.painter.painter.gl();
|
|
||||||
runner
|
|
||||||
.app
|
|
||||||
.setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage), gl);
|
|
||||||
|
|
||||||
Ok(runner)
|
Ok(runner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,8 +329,8 @@ impl AppRunner {
|
||||||
|
|
||||||
/// Install event listeners to register different input events
|
/// Install event listeners to register different input events
|
||||||
/// and start running the given app.
|
/// and start running the given app.
|
||||||
pub fn start(canvas_id: &str, app: Box<dyn epi::App>) -> Result<AppRunnerRef, JsValue> {
|
pub fn start(canvas_id: &str, app_creator: epi::AppCreator) -> Result<AppRunnerRef, JsValue> {
|
||||||
let mut runner = AppRunner::new(canvas_id, app)?;
|
let mut runner = AppRunner::new(canvas_id, app_creator)?;
|
||||||
runner.warm_up()?;
|
runner.warm_up()?;
|
||||||
start_runner(runner)
|
start_runner(runner)
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ impl WrappedGlowPainter {
|
||||||
|
|
||||||
pub fn clear(&mut self, clear_color: Rgba) {
|
pub fn clear(&mut self, clear_color: Rgba) {
|
||||||
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
||||||
egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color)
|
egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint_primitives(
|
pub fn paint_primitives(
|
||||||
|
|
|
@ -3,16 +3,8 @@
|
||||||
//! This library is an [`epi`] backend.
|
//! This library is an [`epi`] backend.
|
||||||
//!
|
//!
|
||||||
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
|
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
|
||||||
//!
|
|
||||||
//! ## Specifying the size of the egui canvas
|
|
||||||
//! For performance reasons (on some browsers) the egui canvas does not, by default,
|
|
||||||
//! fill the whole width of the browser.
|
|
||||||
//! This can be changed by overriding [`epi::App::max_size_points`].
|
|
||||||
|
|
||||||
// Forbid warnings in release builds:
|
#![allow(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>`
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(clippy::all, rustdoc::missing_crate_level_docs, rust_2018_idioms)]
|
|
||||||
|
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
mod glow_wrapping;
|
mod glow_wrapping;
|
||||||
|
@ -896,11 +888,11 @@ pub(crate) fn webgl1_requires_brightening(gl: &web_sys::WebGlRenderingContext) -
|
||||||
!user_agent.contains("Mac OS X") && crate::is_safari_and_webkit_gtk(gl)
|
!user_agent.contains("Mac OS X") && crate::is_safari_and_webkit_gtk(gl)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// detecting Safari and webkitGTK.
|
/// detecting Safari and `webkitGTK`.
|
||||||
///
|
///
|
||||||
/// Safari and webkitGTK use unmasked renderer :Apple GPU
|
/// Safari and `webkitGTK` use unmasked renderer :Apple GPU
|
||||||
///
|
///
|
||||||
/// If we detect safari or webkitGTK returns true.
|
/// If we detect safari or `webkitGTKs` returns true.
|
||||||
///
|
///
|
||||||
/// This function used to avoid displaying linear color with `sRGB` supported systems.
|
/// This function used to avoid displaying linear color with `sRGB` supported systems.
|
||||||
fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool {
|
fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool {
|
||||||
|
|
|
@ -29,6 +29,7 @@ impl Default for ScreenReader {
|
||||||
|
|
||||||
impl ScreenReader {
|
impl ScreenReader {
|
||||||
#[cfg(not(feature = "screen_reader"))]
|
#[cfg(not(feature = "screen_reader"))]
|
||||||
|
#[allow(clippy::unused_self)]
|
||||||
pub fn speak(&mut self, _text: &str) {}
|
pub fn speak(&mut self, _text: &str) {}
|
||||||
|
|
||||||
#[cfg(feature = "screen_reader")]
|
#[cfg(feature = "screen_reader")]
|
||||||
|
|
|
@ -161,7 +161,7 @@ pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> {
|
||||||
|
|
||||||
let delta = delta.max(-keyboard_fraction); // Don't move it crazy much
|
let delta = delta.max(-keyboard_fraction); // Don't move it crazy much
|
||||||
|
|
||||||
let new_pos_percent = (delta * 100.0).round().to_string() + "%";
|
let new_pos_percent = format!("{}%", (delta * 100.0).round());
|
||||||
|
|
||||||
canvas_style.set_property("position", "absolute").ok()?;
|
canvas_style.set_property("position", "absolute").ok()?;
|
||||||
canvas_style.set_property("top", &new_pos_percent).ok()?;
|
canvas_style.set_property("top", &new_pos_percent).ok()?;
|
||||||
|
@ -217,8 +217,8 @@ pub fn move_text_cursor(cursor: Option<egui::Pos2>, canvas_id: &str) -> Option<(
|
||||||
let x = (x - canvas.offset_width() as f32 / 2.0)
|
let x = (x - canvas.offset_width() as f32 / 2.0)
|
||||||
.min(canvas.client_width() as f32 - bounding_rect.width() as f32);
|
.min(canvas.client_width() as f32 - bounding_rect.width() as f32);
|
||||||
style.set_property("position", "absolute").ok()?;
|
style.set_property("position", "absolute").ok()?;
|
||||||
style.set_property("top", &(y.to_string() + "px")).ok()?;
|
style.set_property("top", &format!("{}px", y)).ok()?;
|
||||||
style.set_property("left", &(x.to_string() + "px")).ok()
|
style.set_property("left", &format!("{}px", x)).ok()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
style.set_property("position", "absolute").ok()?;
|
style.set_property("position", "absolute").ok()?;
|
||||||
|
|
|
@ -15,85 +15,6 @@
|
||||||
//! For that, use something else ([`glam`](https://docs.rs/glam), [`nalgebra`](https://docs.rs/nalgebra), …)
|
//! For that, use something else ([`glam`](https://docs.rs/glam), [`nalgebra`](https://docs.rs/nalgebra), …)
|
||||||
//! and enable the `mint` feature flag in `emath` to enable implicit conversion to/from `emath`.
|
//! and enable the `mint` feature flag in `emath` to enable implicit conversion to/from `emath`.
|
||||||
|
|
||||||
// Forbid warnings in release builds:
|
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(
|
|
||||||
clippy::all,
|
|
||||||
clippy::await_holding_lock,
|
|
||||||
clippy::char_lit_as_u8,
|
|
||||||
clippy::checked_conversions,
|
|
||||||
clippy::dbg_macro,
|
|
||||||
clippy::debug_assert_with_mut_call,
|
|
||||||
clippy::disallowed_method,
|
|
||||||
clippy::doc_markdown,
|
|
||||||
clippy::empty_enum,
|
|
||||||
clippy::enum_glob_use,
|
|
||||||
clippy::exit,
|
|
||||||
clippy::expl_impl_clone_on_copy,
|
|
||||||
clippy::explicit_deref_methods,
|
|
||||||
clippy::explicit_into_iter_loop,
|
|
||||||
clippy::fallible_impl_from,
|
|
||||||
clippy::filter_map_next,
|
|
||||||
clippy::flat_map_option,
|
|
||||||
clippy::float_cmp_const,
|
|
||||||
clippy::fn_params_excessive_bools,
|
|
||||||
clippy::from_iter_instead_of_collect,
|
|
||||||
clippy::if_let_mutex,
|
|
||||||
clippy::implicit_clone,
|
|
||||||
clippy::imprecise_flops,
|
|
||||||
clippy::inefficient_to_string,
|
|
||||||
clippy::invalid_upcast_comparisons,
|
|
||||||
clippy::large_digit_groups,
|
|
||||||
clippy::large_stack_arrays,
|
|
||||||
clippy::large_types_passed_by_value,
|
|
||||||
clippy::let_unit_value,
|
|
||||||
clippy::linkedlist,
|
|
||||||
clippy::lossy_float_literal,
|
|
||||||
clippy::macro_use_imports,
|
|
||||||
clippy::manual_ok_or,
|
|
||||||
clippy::map_err_ignore,
|
|
||||||
clippy::map_flatten,
|
|
||||||
clippy::map_unwrap_or,
|
|
||||||
clippy::match_on_vec_items,
|
|
||||||
clippy::match_same_arms,
|
|
||||||
clippy::match_wild_err_arm,
|
|
||||||
clippy::match_wildcard_for_single_variants,
|
|
||||||
clippy::mem_forget,
|
|
||||||
clippy::mismatched_target_os,
|
|
||||||
clippy::missing_errors_doc,
|
|
||||||
clippy::missing_safety_doc,
|
|
||||||
clippy::mut_mut,
|
|
||||||
clippy::mutex_integer,
|
|
||||||
clippy::needless_borrow,
|
|
||||||
clippy::needless_continue,
|
|
||||||
clippy::needless_for_each,
|
|
||||||
clippy::needless_pass_by_value,
|
|
||||||
clippy::option_option,
|
|
||||||
clippy::path_buf_push_overwrite,
|
|
||||||
clippy::ptr_as_ptr,
|
|
||||||
clippy::ref_option_ref,
|
|
||||||
clippy::rest_pat_in_fully_bound_structs,
|
|
||||||
clippy::same_functions_in_if_condition,
|
|
||||||
clippy::semicolon_if_nothing_returned,
|
|
||||||
clippy::single_match_else,
|
|
||||||
clippy::string_add_assign,
|
|
||||||
clippy::string_add,
|
|
||||||
clippy::string_lit_as_bytes,
|
|
||||||
clippy::string_to_string,
|
|
||||||
clippy::todo,
|
|
||||||
clippy::trait_duplication_in_bounds,
|
|
||||||
clippy::unimplemented,
|
|
||||||
clippy::unnested_or_patterns,
|
|
||||||
clippy::unused_self,
|
|
||||||
clippy::useless_transmute,
|
|
||||||
clippy::verbose_file_reads,
|
|
||||||
clippy::zero_sized_map_values,
|
|
||||||
future_incompatible,
|
|
||||||
nonstandard_style,
|
|
||||||
rust_2018_idioms,
|
|
||||||
rustdoc::missing_crate_level_docs
|
|
||||||
)]
|
|
||||||
#![allow(clippy::float_cmp)]
|
#![allow(clippy::float_cmp)]
|
||||||
#![allow(clippy::manual_range_contains)]
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@ All notable changes to the epaint crate will be documented in this file.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)).
|
* Add `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||||
|
* Removed the `single_threaded/multi_threaded` flags - epaint is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)).
|
||||||
|
* `Tessellator::from_options` is now `Tessellator::new` ([#1408](https://github.com/emilk/egui/pull/1408)).
|
||||||
|
* Renamed `TessellationOptions::anti_alias` to `feathering` ([#1408](https://github.com/emilk/egui/pull/1408)).
|
||||||
|
|
||||||
|
|
||||||
## 0.17.0 - 2022-02-22
|
## 0.17.0 - 2022-02-22
|
||||||
|
|
|
@ -27,7 +27,7 @@ all-features = true
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["default_fonts", "multi_threaded"]
|
default = ["default_fonts"]
|
||||||
|
|
||||||
# implement bytemuck on most types.
|
# implement bytemuck on most types.
|
||||||
convert_bytemuck = ["bytemuck", "emath/bytemuck"]
|
convert_bytemuck = ["bytemuck", "emath/bytemuck"]
|
||||||
|
@ -47,25 +47,26 @@ mint = ["emath/mint"]
|
||||||
# implement serde on most types.
|
# implement serde on most types.
|
||||||
serialize = ["serde", "ahash/serde", "emath/serde"]
|
serialize = ["serde", "ahash/serde", "emath/serde"]
|
||||||
|
|
||||||
single_threaded = ["atomic_refcell"]
|
|
||||||
|
|
||||||
# Only needed if you plan to use the same fonts from multiple threads.
|
|
||||||
# It comes with a minor performance impact.
|
|
||||||
multi_threaded = ["parking_lot"]
|
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
emath = { version = "0.17.0", path = "../emath" }
|
emath = { version = "0.17.0", path = "../emath" }
|
||||||
|
|
||||||
ab_glyph = "0.2.11"
|
ab_glyph = "0.2.11"
|
||||||
ahash = { version = "0.7", default-features = false, features = ["std"] }
|
ahash = { version = "0.7", default-features = false, features = ["std"] }
|
||||||
atomic_refcell = { version = "0.1", optional = true } # Used instead of parking_lot when you are always using epaint in a single thread. About as fast as parking_lot. Panics on multi-threaded use.
|
|
||||||
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
|
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
|
||||||
cint = { version = "^0.2.2", optional = true }
|
cint = { version = "^0.2.2", optional = true }
|
||||||
nohash-hasher = "0.2"
|
nohash-hasher = "0.2"
|
||||||
parking_lot = { version = "0.12", optional = true } # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.
|
|
||||||
serde = { version = "1", optional = true, features = ["derive", "rc"] }
|
serde = { version = "1", optional = true, features = ["derive", "rc"] }
|
||||||
|
|
||||||
|
# native:
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.
|
||||||
|
|
||||||
|
# web:
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
atomic_refcell = "0.1" # Used instead of parking_lot on on wasm. See https://github.com/emilk/egui/issues/1401
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.3", default-features = false }
|
criterion = { version = "0.3", default-features = false }
|
||||||
|
|
||||||
|
|
|
@ -5,85 +5,6 @@
|
||||||
//! Create some [`Shape`]:s and pass them to [`tessellate_shapes`] to generate [`Mesh`]:es
|
//! Create some [`Shape`]:s and pass them to [`tessellate_shapes`] to generate [`Mesh`]:es
|
||||||
//! that you can then paint using some graphics API of your choice (e.g. OpenGL).
|
//! that you can then paint using some graphics API of your choice (e.g. OpenGL).
|
||||||
|
|
||||||
// Forbid warnings in release builds:
|
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(
|
|
||||||
clippy::all,
|
|
||||||
clippy::await_holding_lock,
|
|
||||||
clippy::char_lit_as_u8,
|
|
||||||
clippy::checked_conversions,
|
|
||||||
clippy::dbg_macro,
|
|
||||||
clippy::debug_assert_with_mut_call,
|
|
||||||
clippy::disallowed_method,
|
|
||||||
clippy::doc_markdown,
|
|
||||||
clippy::empty_enum,
|
|
||||||
clippy::enum_glob_use,
|
|
||||||
clippy::exit,
|
|
||||||
clippy::expl_impl_clone_on_copy,
|
|
||||||
clippy::explicit_deref_methods,
|
|
||||||
clippy::explicit_into_iter_loop,
|
|
||||||
clippy::fallible_impl_from,
|
|
||||||
clippy::filter_map_next,
|
|
||||||
clippy::flat_map_option,
|
|
||||||
clippy::float_cmp_const,
|
|
||||||
clippy::fn_params_excessive_bools,
|
|
||||||
clippy::from_iter_instead_of_collect,
|
|
||||||
clippy::if_let_mutex,
|
|
||||||
clippy::implicit_clone,
|
|
||||||
clippy::imprecise_flops,
|
|
||||||
clippy::inefficient_to_string,
|
|
||||||
clippy::invalid_upcast_comparisons,
|
|
||||||
clippy::large_digit_groups,
|
|
||||||
clippy::large_stack_arrays,
|
|
||||||
clippy::large_types_passed_by_value,
|
|
||||||
clippy::let_unit_value,
|
|
||||||
clippy::linkedlist,
|
|
||||||
clippy::lossy_float_literal,
|
|
||||||
clippy::macro_use_imports,
|
|
||||||
clippy::manual_ok_or,
|
|
||||||
clippy::map_err_ignore,
|
|
||||||
clippy::map_flatten,
|
|
||||||
clippy::map_unwrap_or,
|
|
||||||
clippy::match_on_vec_items,
|
|
||||||
clippy::match_same_arms,
|
|
||||||
clippy::match_wild_err_arm,
|
|
||||||
clippy::match_wildcard_for_single_variants,
|
|
||||||
clippy::mem_forget,
|
|
||||||
clippy::mismatched_target_os,
|
|
||||||
clippy::missing_errors_doc,
|
|
||||||
clippy::missing_safety_doc,
|
|
||||||
clippy::mut_mut,
|
|
||||||
clippy::mutex_integer,
|
|
||||||
clippy::needless_borrow,
|
|
||||||
clippy::needless_continue,
|
|
||||||
clippy::needless_for_each,
|
|
||||||
clippy::needless_pass_by_value,
|
|
||||||
clippy::option_option,
|
|
||||||
clippy::path_buf_push_overwrite,
|
|
||||||
clippy::ptr_as_ptr,
|
|
||||||
clippy::ref_option_ref,
|
|
||||||
clippy::rest_pat_in_fully_bound_structs,
|
|
||||||
clippy::same_functions_in_if_condition,
|
|
||||||
clippy::semicolon_if_nothing_returned,
|
|
||||||
clippy::single_match_else,
|
|
||||||
clippy::string_add_assign,
|
|
||||||
clippy::string_add,
|
|
||||||
clippy::string_lit_as_bytes,
|
|
||||||
clippy::string_to_string,
|
|
||||||
clippy::todo,
|
|
||||||
clippy::trait_duplication_in_bounds,
|
|
||||||
clippy::unimplemented,
|
|
||||||
clippy::unnested_or_patterns,
|
|
||||||
clippy::unused_self,
|
|
||||||
clippy::useless_transmute,
|
|
||||||
clippy::verbose_file_reads,
|
|
||||||
clippy::zero_sized_map_values,
|
|
||||||
future_incompatible,
|
|
||||||
nonstandard_style,
|
|
||||||
rust_2018_idioms,
|
|
||||||
rustdoc::missing_crate_level_docs
|
|
||||||
)]
|
|
||||||
#![allow(clippy::float_cmp)]
|
#![allow(clippy::float_cmp)]
|
||||||
#![allow(clippy::manual_range_contains)]
|
#![allow(clippy::manual_range_contains)]
|
||||||
|
|
||||||
|
@ -110,7 +31,10 @@ pub use {
|
||||||
image::{AlphaImage, ColorImage, ImageData, ImageDelta},
|
image::{AlphaImage, ColorImage, ImageData, ImageDelta},
|
||||||
mesh::{Mesh, Mesh16, Vertex},
|
mesh::{Mesh, Mesh16, Vertex},
|
||||||
shadow::Shadow,
|
shadow::Shadow,
|
||||||
shape::{CircleShape, PaintCallback, PathShape, RectShape, Rounding, Shape, TextShape},
|
shape::{
|
||||||
|
CircleShape, PaintCallback, PaintCallbackInfo, PathShape, RectShape, Rounding, Shape,
|
||||||
|
TextShape,
|
||||||
|
},
|
||||||
stats::PaintStats,
|
stats::PaintStats,
|
||||||
stroke::Stroke,
|
stroke::Stroke,
|
||||||
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
|
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
|
||||||
|
|
|
@ -91,16 +91,28 @@ impl Mesh {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
*self = other;
|
*self = other;
|
||||||
} else {
|
} else {
|
||||||
|
self.append_ref(&other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Append all the indices and vertices of `other` to `self` without
|
||||||
|
/// taking ownership.
|
||||||
|
pub fn append_ref(&mut self, other: &Mesh) {
|
||||||
|
crate::epaint_assert!(other.is_valid());
|
||||||
|
|
||||||
|
if !self.is_empty() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
self.texture_id, other.texture_id,
|
self.texture_id, other.texture_id,
|
||||||
"Can't merge Mesh using different textures"
|
"Can't merge Mesh using different textures"
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
let index_offset = self.vertices.len() as u32;
|
self.texture_id = other.texture_id;
|
||||||
self.indices
|
|
||||||
.extend(other.indices.iter().map(|index| index + index_offset));
|
|
||||||
self.vertices.extend(other.vertices.iter());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
#[inline(always)]
|
||||||
|
@ -242,6 +254,15 @@ impl Mesh {
|
||||||
v.pos += delta;
|
v.pos += delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rotate by some angle about an origin, in-place.
|
||||||
|
///
|
||||||
|
/// Origin is a position in screen space.
|
||||||
|
pub fn rotate(&mut self, rot: Rot2, origin: Pos2) {
|
||||||
|
for v in &mut self.vertices {
|
||||||
|
v.pos = origin + rot * (v.pos - origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
//! Helper module that wraps some Mutex types with different implementations.
|
//! Helper module that wraps some Mutex types with different implementations.
|
||||||
//!
|
|
||||||
//! When the `single_threaded` feature is on the mutexes will panic when locked from different threads.
|
|
||||||
|
|
||||||
#[cfg(not(any(feature = "single_threaded", feature = "multi_threaded")))]
|
|
||||||
compile_error!("Either feature \"single_threaded\" or \"multi_threaded\" must be enabled.");
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
#[cfg(feature = "multi_threaded")]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
mod mutex_impl {
|
mod mutex_impl {
|
||||||
/// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled.
|
/// Provides interior mutability.
|
||||||
|
///
|
||||||
|
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Mutex<T>(parking_lot::Mutex<T>);
|
pub struct Mutex<T>(parking_lot::Mutex<T>);
|
||||||
|
|
||||||
|
@ -30,10 +27,12 @@ mod mutex_impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "multi_threaded")]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
mod mutex_impl {
|
mod mutex_impl {
|
||||||
/// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled.
|
/// Provides interior mutability.
|
||||||
|
///
|
||||||
|
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Mutex<T>(parking_lot::Mutex<T>);
|
pub struct Mutex<T>(parking_lot::Mutex<T>);
|
||||||
|
|
||||||
|
@ -111,7 +110,7 @@ mod mutex_impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "multi_threaded")]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
mod rw_lock_impl {
|
mod rw_lock_impl {
|
||||||
/// The lock you get from [`RwLock::read`].
|
/// The lock you get from [`RwLock::read`].
|
||||||
pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard;
|
pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard;
|
||||||
|
@ -119,7 +118,9 @@ mod rw_lock_impl {
|
||||||
/// The lock you get from [`RwLock::write`].
|
/// The lock you get from [`RwLock::write`].
|
||||||
pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard;
|
pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard;
|
||||||
|
|
||||||
/// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled.
|
/// Provides interior mutability.
|
||||||
|
///
|
||||||
|
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct RwLock<T>(parking_lot::RwLock<T>);
|
pub struct RwLock<T>(parking_lot::RwLock<T>);
|
||||||
|
|
||||||
|
@ -141,18 +142,20 @@ mod rw_lock_impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "multi_threaded")]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
mod arc_impl {
|
mod arc_impl {
|
||||||
pub use std::sync::Arc;
|
pub use std::sync::Arc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
#[cfg(not(feature = "multi_threaded"))]
|
#[cfg(target_arch = "wasm32")]
|
||||||
mod mutex_impl {
|
mod mutex_impl {
|
||||||
// `atomic_refcell` will panic if multiple threads try to access the same value
|
// `atomic_refcell` will panic if multiple threads try to access the same value
|
||||||
|
|
||||||
/// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled.
|
/// Provides interior mutability.
|
||||||
|
///
|
||||||
|
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Mutex<T>(atomic_refcell::AtomicRefCell<T>);
|
pub struct Mutex<T>(atomic_refcell::AtomicRefCell<T>);
|
||||||
|
|
||||||
|
@ -173,7 +176,7 @@ mod mutex_impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "multi_threaded"))]
|
#[cfg(target_arch = "wasm32")]
|
||||||
mod rw_lock_impl {
|
mod rw_lock_impl {
|
||||||
// `atomic_refcell` will panic if multiple threads try to access the same value
|
// `atomic_refcell` will panic if multiple threads try to access the same value
|
||||||
|
|
||||||
|
@ -183,7 +186,9 @@ mod rw_lock_impl {
|
||||||
/// The lock you get from [`RwLock::write`].
|
/// The lock you get from [`RwLock::write`].
|
||||||
pub use atomic_refcell::AtomicRefMut as RwLockWriteGuard;
|
pub use atomic_refcell::AtomicRefMut as RwLockWriteGuard;
|
||||||
|
|
||||||
/// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled.
|
/// Provides interior mutability.
|
||||||
|
///
|
||||||
|
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct RwLock<T>(atomic_refcell::AtomicRefCell<T>);
|
pub struct RwLock<T>(atomic_refcell::AtomicRefCell<T>);
|
||||||
|
|
||||||
|
@ -206,7 +211,7 @@ mod rw_lock_impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "multi_threaded"))]
|
#[cfg(target_arch = "wasm32")]
|
||||||
mod arc_impl {
|
mod arc_impl {
|
||||||
// pub use std::rc::Rc as Arc; // TODO(emilk): optimize single threaded code by using `Rc` instead of `Arc`.
|
// pub use std::rc::Rc as Arc; // TODO(emilk): optimize single threaded code by using `Rc` instead of `Arc`.
|
||||||
pub use std::sync::Arc;
|
pub use std::sync::Arc;
|
||||||
|
|
|
@ -63,11 +63,15 @@ impl Shadow {
|
||||||
|
|
||||||
use crate::tessellator::*;
|
use crate::tessellator::*;
|
||||||
let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color);
|
let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color);
|
||||||
let mut tessellator = Tessellator::from_options(TessellationOptions {
|
let pixels_per_point = 1.0; // doesn't matter here
|
||||||
aa_size: extrusion,
|
let mut tessellator = Tessellator::new(
|
||||||
anti_alias: true,
|
pixels_per_point,
|
||||||
..Default::default()
|
TessellationOptions {
|
||||||
});
|
feathering: true,
|
||||||
|
feathering_size_in_pixels: extrusion * pixels_per_point,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
let mut mesh = Mesh::default();
|
let mut mesh = Mesh::default();
|
||||||
tessellator.tessellate_rect(&rect, &mut mesh);
|
tessellator.tessellate_rect(&rect, &mut mesh);
|
||||||
mesh
|
mesh
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
text::{FontId, Fonts, Galley},
|
text::{FontId, Fonts, Galley},
|
||||||
Color32, Mesh, Stroke,
|
Color32, Mesh, Stroke, TextureId,
|
||||||
};
|
};
|
||||||
use emath::*;
|
use emath::*;
|
||||||
|
|
||||||
|
@ -184,6 +184,12 @@ impl Shape {
|
||||||
Self::Mesh(mesh)
|
Self::Mesh(mesh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn image(texture_id: TextureId, rect: Rect, uv: Rect, tint: Color32) -> Self {
|
||||||
|
let mut mesh = Mesh::with_texture(texture_id);
|
||||||
|
mesh.add_rect_with_uv(rect, uv, tint);
|
||||||
|
Shape::mesh(mesh)
|
||||||
|
}
|
||||||
|
|
||||||
/// The visual bounding rectangle (includes stroke widths)
|
/// The visual bounding rectangle (includes stroke widths)
|
||||||
pub fn visual_bounding_rect(&self) -> Rect {
|
pub fn visual_bounding_rect(&self) -> Rect {
|
||||||
match self {
|
match self {
|
||||||
|
@ -636,6 +642,52 @@ fn dashes_from_line(
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Information passed along with [`PaintCallback`] ([`Shape::Callback`]).
|
||||||
|
pub struct PaintCallbackInfo {
|
||||||
|
/// Viewport in points.
|
||||||
|
pub rect: Rect,
|
||||||
|
|
||||||
|
/// Pixels per point.
|
||||||
|
pub pixels_per_point: f32,
|
||||||
|
|
||||||
|
/// Full size of the screen, in pixels.
|
||||||
|
pub screen_size_px: [u32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaintCallbackInfo {
|
||||||
|
/// Physical pixel offset for left side of the viewport.
|
||||||
|
#[inline]
|
||||||
|
pub fn viewport_left_px(&self) -> f32 {
|
||||||
|
self.rect.min.x * self.pixels_per_point
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Physical pixel offset for top side of the viewport.
|
||||||
|
#[inline]
|
||||||
|
pub fn viewport_top_px(&self) -> f32 {
|
||||||
|
self.rect.min.y * self.pixels_per_point
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Physical pixel offset for bottom side of the viewport.
|
||||||
|
///
|
||||||
|
/// This is what `glViewport` etc expects for the y axis.
|
||||||
|
#[inline]
|
||||||
|
pub fn viewport_from_bottom_px(&self) -> f32 {
|
||||||
|
self.screen_size_px[1] as f32 - self.rect.max.y * self.pixels_per_point
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Viewport width in physical pixels.
|
||||||
|
#[inline]
|
||||||
|
pub fn viewport_width_px(&self) -> f32 {
|
||||||
|
self.rect.width() * self.pixels_per_point
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Viewport width in physical pixels.
|
||||||
|
#[inline]
|
||||||
|
pub fn viewport_height_px(&self) -> f32 {
|
||||||
|
self.rect.height() * self.pixels_per_point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// If you want to paint some 3D shapes inside an egui region, you can use this.
|
/// If you want to paint some 3D shapes inside an egui region, you can use this.
|
||||||
///
|
///
|
||||||
/// This is advanced usage, and is backend specific.
|
/// This is advanced usage, and is backend specific.
|
||||||
|
@ -644,21 +696,22 @@ pub struct PaintCallback {
|
||||||
/// Where to paint.
|
/// Where to paint.
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
|
|
||||||
/// Paint something custom using.
|
/// Paint something custom (e.g. 3D stuff).
|
||||||
///
|
///
|
||||||
/// The argument is the render context, and what it contains depends on the backend.
|
/// The argument is the render context, and what it contains depends on the backend.
|
||||||
/// In `eframe` it will be `egui_glow::Painter`.
|
/// In `eframe` it will be `egui_glow::Painter`.
|
||||||
///
|
///
|
||||||
/// The rendering backend is responsible for first setting the active viewport to [`Self::rect`].
|
/// The rendering backend is responsible for first setting the active viewport to [`Self::rect`].
|
||||||
/// The rendering backend is also responsible for restoring any state it needs,
|
///
|
||||||
|
/// The rendering backend is also responsible for restoring any state,
|
||||||
/// such as the bound shader program and vertex array.
|
/// such as the bound shader program and vertex array.
|
||||||
pub callback: std::sync::Arc<dyn Fn(&dyn std::any::Any) + Send + Sync>,
|
pub callback: std::sync::Arc<dyn Fn(&PaintCallbackInfo, &dyn std::any::Any) + Send + Sync>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PaintCallback {
|
impl PaintCallback {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn call(&self, render_ctx: &dyn std::any::Any) {
|
pub fn call(&self, info: &PaintCallbackInfo, render_ctx: &dyn std::any::Any) {
|
||||||
(self.callback)(render_ctx);
|
(self.callback)(info, render_ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -126,17 +126,17 @@ impl AllocInfo {
|
||||||
|
|
||||||
pub fn format(&self, what: &str) -> String {
|
pub fn format(&self, what: &str) -> String {
|
||||||
if self.num_allocs() == 0 {
|
if self.num_allocs() == 0 {
|
||||||
format!("{:6} {:14}", 0, what)
|
format!("{:6} {:16}", 0, what)
|
||||||
} else if self.num_allocs() == 1 {
|
} else if self.num_allocs() == 1 {
|
||||||
format!(
|
format!(
|
||||||
"{:6} {:14} {} 1 allocation",
|
"{:6} {:16} {} 1 allocation",
|
||||||
self.num_elements,
|
self.num_elements,
|
||||||
what,
|
what,
|
||||||
self.megabytes()
|
self.megabytes()
|
||||||
)
|
)
|
||||||
} else if self.element_size != ElementSize::Heterogenous {
|
} else if self.element_size != ElementSize::Heterogenous {
|
||||||
format!(
|
format!(
|
||||||
"{:6} {:14} {} {:3} allocations",
|
"{:6} {:16} {} {:3} allocations",
|
||||||
self.num_elements(),
|
self.num_elements(),
|
||||||
what,
|
what,
|
||||||
self.megabytes(),
|
self.megabytes(),
|
||||||
|
@ -144,7 +144,7 @@ impl AllocInfo {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
"{:6} {:14} {} {:3} allocations",
|
"{:6} {:16} {} {:3} allocations",
|
||||||
"",
|
"",
|
||||||
what,
|
what,
|
||||||
self.megabytes(),
|
self.megabytes(),
|
||||||
|
|
|
@ -163,23 +163,17 @@ impl Path {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open-ended.
|
/// Open-ended.
|
||||||
pub fn stroke_open(&self, stroke: Stroke, options: &TessellationOptions, out: &mut Mesh) {
|
pub fn stroke_open(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) {
|
||||||
stroke_path(&self.0, PathType::Open, stroke, options, out);
|
stroke_path(feathering, &self.0, PathType::Open, stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A closed path (returning to the first point).
|
/// A closed path (returning to the first point).
|
||||||
pub fn stroke_closed(&self, stroke: Stroke, options: &TessellationOptions, out: &mut Mesh) {
|
pub fn stroke_closed(&self, feathering: f32, stroke: Stroke, out: &mut Mesh) {
|
||||||
stroke_path(&self.0, PathType::Closed, stroke, options, out);
|
stroke_path(feathering, &self.0, PathType::Closed, stroke, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stroke(
|
pub fn stroke(&self, feathering: f32, path_type: PathType, stroke: Stroke, out: &mut Mesh) {
|
||||||
&self,
|
stroke_path(feathering, &self.0, path_type, stroke, out);
|
||||||
path_type: PathType,
|
|
||||||
stroke: Stroke,
|
|
||||||
options: &TessellationOptions,
|
|
||||||
out: &mut Mesh,
|
|
||||||
) {
|
|
||||||
stroke_path(&self.0, path_type, stroke, options, out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The path is taken to be closed (i.e. returning to the start again).
|
/// The path is taken to be closed (i.e. returning to the start again).
|
||||||
|
@ -187,8 +181,8 @@ impl Path {
|
||||||
/// Calling this may reverse the vertices in the path if they are wrong winding order.
|
/// Calling this may reverse the vertices in the path if they are wrong winding order.
|
||||||
///
|
///
|
||||||
/// The preferred winding order is clockwise.
|
/// The preferred winding order is clockwise.
|
||||||
pub fn fill(&mut self, color: Color32, options: &TessellationOptions, out: &mut Mesh) {
|
pub fn fill(&mut self, feathering: f32, color: Color32, out: &mut Mesh) {
|
||||||
fill_closed_path(&mut self.0, color, options, out);
|
fill_closed_path(feathering, &mut self.0, color, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,18 +280,23 @@ pub enum PathType {
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
pub struct TessellationOptions {
|
pub struct TessellationOptions {
|
||||||
/// Size of a point in pixels (DPI scaling), e.g. 2.0. Used to snap text to pixel boundaries.
|
/// Use "feathering" to smooth out the edges of shapes as a form of anti-aliasing.
|
||||||
pub pixels_per_point: f32,
|
///
|
||||||
|
/// Feathering works by making each edge into a thin gradient into transparency.
|
||||||
/// The size of a pixel (in points), used for anti-aliasing (smoothing of edges).
|
/// The size of this edge is controlled by [`Self::feathering_size_in_pixels`].
|
||||||
/// This is normally the inverse of [`Self::pixels_per_point`],
|
///
|
||||||
/// but you can make it larger if you want more blurry edges.
|
/// This makes shapes appear smoother, but requires more triangles and is therefore slower.
|
||||||
pub aa_size: f32,
|
///
|
||||||
|
|
||||||
/// Anti-aliasing makes shapes appear smoother, but requires more triangles and is therefore slower.
|
|
||||||
/// This setting does not affect text.
|
/// This setting does not affect text.
|
||||||
|
///
|
||||||
/// Default: `true`.
|
/// Default: `true`.
|
||||||
pub anti_alias: bool,
|
pub feathering: bool,
|
||||||
|
|
||||||
|
/// The size of the the feathering, in physical pixels.
|
||||||
|
///
|
||||||
|
/// The default, and suggested, value for this is `1.0`.
|
||||||
|
/// If you use a larger value, edges will appear blurry.
|
||||||
|
pub feathering_size_in_pixels: f32,
|
||||||
|
|
||||||
/// If `true` (default) cull certain primitives before tessellating them.
|
/// If `true` (default) cull certain primitives before tessellating them.
|
||||||
/// This likely makes
|
/// This likely makes
|
||||||
|
@ -326,9 +325,8 @@ pub struct TessellationOptions {
|
||||||
impl Default for TessellationOptions {
|
impl Default for TessellationOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
pixels_per_point: 1.0,
|
feathering: true,
|
||||||
aa_size: 1.0,
|
feathering_size_in_pixels: 1.0,
|
||||||
anti_alias: true,
|
|
||||||
coarse_tessellation_culling: true,
|
coarse_tessellation_culling: true,
|
||||||
round_text_to_pixels: true,
|
round_text_to_pixels: true,
|
||||||
debug_paint_text_rects: false,
|
debug_paint_text_rects: false,
|
||||||
|
@ -340,27 +338,6 @@ impl Default for TessellationOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TessellationOptions {
|
|
||||||
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
pixels_per_point,
|
|
||||||
aa_size: 1.0 / pixels_per_point,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TessellationOptions {
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
|
||||||
if self.round_text_to_pixels {
|
|
||||||
(point * self.pixels_per_point).round() / self.pixels_per_point
|
|
||||||
} else {
|
|
||||||
point
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cw_signed_area(path: &[PathPoint]) -> f64 {
|
fn cw_signed_area(path: &[PathPoint]) -> f64 {
|
||||||
if let Some(last) = path.last() {
|
if let Some(last) = path.last() {
|
||||||
let mut previous = last.pos;
|
let mut previous = last.pos;
|
||||||
|
@ -380,18 +357,13 @@ fn cw_signed_area(path: &[PathPoint]) -> f64 {
|
||||||
/// Calling this may reverse the vertices in the path if they are wrong winding order.
|
/// Calling this may reverse the vertices in the path if they are wrong winding order.
|
||||||
///
|
///
|
||||||
/// The preferred winding order is clockwise.
|
/// The preferred winding order is clockwise.
|
||||||
fn fill_closed_path(
|
fn fill_closed_path(feathering: f32, path: &mut [PathPoint], color: Color32, out: &mut Mesh) {
|
||||||
path: &mut [PathPoint],
|
|
||||||
color: Color32,
|
|
||||||
options: &TessellationOptions,
|
|
||||||
out: &mut Mesh,
|
|
||||||
) {
|
|
||||||
if color == Color32::TRANSPARENT {
|
if color == Color32::TRANSPARENT {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = path.len() as u32;
|
let n = path.len() as u32;
|
||||||
if options.anti_alias {
|
if feathering > 0.0 {
|
||||||
if cw_signed_area(path) < 0.0 {
|
if cw_signed_area(path) < 0.0 {
|
||||||
// Wrong winding order - fix:
|
// Wrong winding order - fix:
|
||||||
path.reverse();
|
path.reverse();
|
||||||
|
@ -415,7 +387,7 @@ fn fill_closed_path(
|
||||||
let mut i0 = n - 1;
|
let mut i0 = n - 1;
|
||||||
for i1 in 0..n {
|
for i1 in 0..n {
|
||||||
let p1 = &path[i1 as usize];
|
let p1 = &path[i1 as usize];
|
||||||
let dm = 0.5 * options.aa_size * p1.normal;
|
let dm = 0.5 * feathering * p1.normal;
|
||||||
out.colored_vertex(p1.pos - dm, color);
|
out.colored_vertex(p1.pos - dm, color);
|
||||||
out.colored_vertex(p1.pos + dm, color_outer);
|
out.colored_vertex(p1.pos + dm, color_outer);
|
||||||
out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
|
out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
|
||||||
|
@ -438,10 +410,10 @@ fn fill_closed_path(
|
||||||
|
|
||||||
/// Tessellate the given path as a stroke with thickness.
|
/// Tessellate the given path as a stroke with thickness.
|
||||||
fn stroke_path(
|
fn stroke_path(
|
||||||
|
feathering: f32,
|
||||||
path: &[PathPoint],
|
path: &[PathPoint],
|
||||||
path_type: PathType,
|
path_type: PathType,
|
||||||
stroke: Stroke,
|
stroke: Stroke,
|
||||||
options: &TessellationOptions,
|
|
||||||
out: &mut Mesh,
|
out: &mut Mesh,
|
||||||
) {
|
) {
|
||||||
let n = path.len() as u32;
|
let n = path.len() as u32;
|
||||||
|
@ -452,21 +424,21 @@ fn stroke_path(
|
||||||
|
|
||||||
let idx = out.vertices.len() as u32;
|
let idx = out.vertices.len() as u32;
|
||||||
|
|
||||||
if options.anti_alias {
|
if feathering > 0.0 {
|
||||||
let color_inner = stroke.color;
|
let color_inner = stroke.color;
|
||||||
let color_outer = Color32::TRANSPARENT;
|
let color_outer = Color32::TRANSPARENT;
|
||||||
|
|
||||||
let thin_line = stroke.width <= options.aa_size;
|
let thin_line = stroke.width <= feathering;
|
||||||
if thin_line {
|
if thin_line {
|
||||||
/*
|
/*
|
||||||
We paint the line using three edges: outer, inner, outer.
|
We paint the line using three edges: outer, inner, outer.
|
||||||
|
|
||||||
. o i o outer, inner, outer
|
. o i o outer, inner, outer
|
||||||
. |---| aa_size (pixel width)
|
. |---| feathering (pixel width)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Fade out as it gets thinner:
|
// Fade out as it gets thinner:
|
||||||
let color_inner = mul_color(color_inner, stroke.width / options.aa_size);
|
let color_inner = mul_color(color_inner, stroke.width / feathering);
|
||||||
if color_inner == Color32::TRANSPARENT {
|
if color_inner == Color32::TRANSPARENT {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -480,9 +452,9 @@ fn stroke_path(
|
||||||
let p1 = &path[i1 as usize];
|
let p1 = &path[i1 as usize];
|
||||||
let p = p1.pos;
|
let p = p1.pos;
|
||||||
let n = p1.normal;
|
let n = p1.normal;
|
||||||
out.colored_vertex(p + n * options.aa_size, color_outer);
|
out.colored_vertex(p + n * feathering, color_outer);
|
||||||
out.colored_vertex(p, color_inner);
|
out.colored_vertex(p, color_inner);
|
||||||
out.colored_vertex(p - n * options.aa_size, color_outer);
|
out.colored_vertex(p - n * feathering, color_outer);
|
||||||
|
|
||||||
if connect_with_previous {
|
if connect_with_previous {
|
||||||
out.add_triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0);
|
out.add_triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0);
|
||||||
|
@ -500,14 +472,14 @@ fn stroke_path(
|
||||||
We paint the line using four edges: outer, inner, inner, outer
|
We paint the line using four edges: outer, inner, inner, outer
|
||||||
|
|
||||||
. o i p i o outer, inner, point, inner, outer
|
. o i p i o outer, inner, point, inner, outer
|
||||||
. |---| aa_size (pixel width)
|
. |---| feathering (pixel width)
|
||||||
. |--------------| width
|
. |--------------| width
|
||||||
. |---------| outer_rad
|
. |---------| outer_rad
|
||||||
. |-----| inner_rad
|
. |-----| inner_rad
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let inner_rad = 0.5 * (stroke.width - options.aa_size);
|
let inner_rad = 0.5 * (stroke.width - feathering);
|
||||||
let outer_rad = 0.5 * (stroke.width + options.aa_size);
|
let outer_rad = 0.5 * (stroke.width + feathering);
|
||||||
|
|
||||||
match path_type {
|
match path_type {
|
||||||
PathType::Closed => {
|
PathType::Closed => {
|
||||||
|
@ -542,7 +514,7 @@ fn stroke_path(
|
||||||
|
|
||||||
// | aa | | aa |
|
// | aa | | aa |
|
||||||
// _________________ ___
|
// _________________ ___
|
||||||
// | \ added / | aa_size
|
// | \ added / | feathering
|
||||||
// | \ ___p___ / | ___
|
// | \ ___p___ / | ___
|
||||||
// | | | |
|
// | | | |
|
||||||
// | | opa | |
|
// | | opa | |
|
||||||
|
@ -558,7 +530,7 @@ fn stroke_path(
|
||||||
let end = &path[0];
|
let end = &path[0];
|
||||||
let p = end.pos;
|
let p = end.pos;
|
||||||
let n = end.normal;
|
let n = end.normal;
|
||||||
let back_extrude = n.rot90() * options.aa_size;
|
let back_extrude = n.rot90() * feathering;
|
||||||
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
||||||
out.colored_vertex(p + n * inner_rad, color_inner);
|
out.colored_vertex(p + n * inner_rad, color_inner);
|
||||||
out.colored_vertex(p - n * inner_rad, color_inner);
|
out.colored_vertex(p - n * inner_rad, color_inner);
|
||||||
|
@ -595,7 +567,7 @@ fn stroke_path(
|
||||||
let end = &path[i1 as usize];
|
let end = &path[i1 as usize];
|
||||||
let p = end.pos;
|
let p = end.pos;
|
||||||
let n = end.normal;
|
let n = end.normal;
|
||||||
let back_extrude = -n.rot90() * options.aa_size;
|
let back_extrude = -n.rot90() * feathering;
|
||||||
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
out.colored_vertex(p + n * outer_rad + back_extrude, color_outer);
|
||||||
out.colored_vertex(p + n * inner_rad, color_inner);
|
out.colored_vertex(p + n * inner_rad, color_inner);
|
||||||
out.colored_vertex(p - n * inner_rad, color_inner);
|
out.colored_vertex(p - n * inner_rad, color_inner);
|
||||||
|
@ -640,11 +612,11 @@ fn stroke_path(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let thin_line = stroke.width <= options.aa_size;
|
let thin_line = stroke.width <= feathering;
|
||||||
if thin_line {
|
if thin_line {
|
||||||
// Fade out thin lines rather than making them thinner
|
// Fade out thin lines rather than making them thinner
|
||||||
let radius = options.aa_size / 2.0;
|
let radius = feathering / 2.0;
|
||||||
let color = mul_color(stroke.color, stroke.width / options.aa_size);
|
let color = mul_color(stroke.color, stroke.width / feathering);
|
||||||
if color == Color32::TRANSPARENT {
|
if color == Color32::TRANSPARENT {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -677,7 +649,10 @@ fn mul_color(color: Color32, factor: f32) -> Color32 {
|
||||||
///
|
///
|
||||||
/// Se also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`].
|
/// Se also [`tessellate_shapes`], a convenient wrapper around [`Tessellator`].
|
||||||
pub struct Tessellator {
|
pub struct Tessellator {
|
||||||
|
pixels_per_point: f32,
|
||||||
options: TessellationOptions,
|
options: TessellationOptions,
|
||||||
|
/// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled
|
||||||
|
feathering: f32,
|
||||||
/// Only used for culling
|
/// Only used for culling
|
||||||
clip_rect: Rect,
|
clip_rect: Rect,
|
||||||
scratchpad_points: Vec<Pos2>,
|
scratchpad_points: Vec<Pos2>,
|
||||||
|
@ -686,24 +661,43 @@ pub struct Tessellator {
|
||||||
|
|
||||||
impl Tessellator {
|
impl Tessellator {
|
||||||
/// Create a new [`Tessellator`].
|
/// Create a new [`Tessellator`].
|
||||||
pub fn from_options(options: TessellationOptions) -> Self {
|
pub fn new(pixels_per_point: f32, options: TessellationOptions) -> Self {
|
||||||
|
let feathering = if options.feathering {
|
||||||
|
let pixel_size = 1.0 / pixels_per_point;
|
||||||
|
options.feathering_size_in_pixels * pixel_size
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
|
pixels_per_point,
|
||||||
options,
|
options,
|
||||||
|
feathering,
|
||||||
clip_rect: Rect::EVERYTHING,
|
clip_rect: Rect::EVERYTHING,
|
||||||
scratchpad_points: Default::default(),
|
scratchpad_points: Default::default(),
|
||||||
scratchpad_path: Default::default(),
|
scratchpad_path: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the `Rect` to use for culling.
|
||||||
|
pub fn set_clip_rect(&mut self, clip_rect: Rect) {
|
||||||
|
self.clip_rect = clip_rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||||
|
if self.options.round_text_to_pixels {
|
||||||
|
(point * self.pixels_per_point).round() / self.pixels_per_point
|
||||||
|
} else {
|
||||||
|
point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Tessellate a single [`Shape`] into a [`Mesh`].
|
/// Tessellate a single [`Shape`] into a [`Mesh`].
|
||||||
///
|
///
|
||||||
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles).
|
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles).
|
||||||
/// * `shape`: the shape to tessellate.
|
/// * `shape`: the shape to tessellate.
|
||||||
/// * `out`: triangles are appended to this.
|
/// * `out`: triangles are appended to this.
|
||||||
pub fn tessellate_shape(&mut self, tex_size: [usize; 2], shape: Shape, out: &mut Mesh) {
|
pub fn tessellate_shape(&mut self, tex_size: [usize; 2], shape: Shape, out: &mut Mesh) {
|
||||||
let clip_rect = self.clip_rect;
|
|
||||||
let options = &self.options;
|
|
||||||
|
|
||||||
match shape {
|
match shape {
|
||||||
Shape::Noop => {}
|
Shape::Noop => {}
|
||||||
Shape::Vec(vec) => {
|
Shape::Vec(vec) => {
|
||||||
|
@ -711,26 +705,8 @@ impl Tessellator {
|
||||||
self.tessellate_shape(tex_size, shape, out);
|
self.tessellate_shape(tex_size, shape, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Shape::Circle(CircleShape {
|
Shape::Circle(circle) => {
|
||||||
center,
|
self.tessellate_circle(circle, out);
|
||||||
radius,
|
|
||||||
fill,
|
|
||||||
stroke,
|
|
||||||
}) => {
|
|
||||||
if radius <= 0.0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.coarse_tessellation_culling
|
|
||||||
&& !clip_rect.expand(radius + stroke.width).contains(center)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scratchpad_path.clear();
|
|
||||||
self.scratchpad_path.add_circle(center, radius);
|
|
||||||
self.scratchpad_path.fill(fill, options, out);
|
|
||||||
self.scratchpad_path.stroke_closed(stroke, options, out);
|
|
||||||
}
|
}
|
||||||
Shape::Mesh(mesh) => {
|
Shape::Mesh(mesh) => {
|
||||||
if !mesh.is_valid() {
|
if !mesh.is_valid() {
|
||||||
|
@ -738,44 +714,29 @@ impl Tessellator {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.coarse_tessellation_culling && !clip_rect.intersects(mesh.calc_bounds())
|
if self.options.coarse_tessellation_culling
|
||||||
|
&& !self.clip_rect.intersects(mesh.calc_bounds())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
out.append(mesh);
|
out.append(mesh);
|
||||||
}
|
}
|
||||||
Shape::LineSegment { points, stroke } => {
|
Shape::LineSegment { points, stroke } => self.tessellate_line(points, stroke, out),
|
||||||
if stroke.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.coarse_tessellation_culling
|
|
||||||
&& !clip_rect
|
|
||||||
.intersects(Rect::from_two_pos(points[0], points[1]).expand(stroke.width))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scratchpad_path.clear();
|
|
||||||
self.scratchpad_path.add_line_segment(points);
|
|
||||||
self.scratchpad_path.stroke_open(stroke, options, out);
|
|
||||||
}
|
|
||||||
Shape::Path(path_shape) => {
|
Shape::Path(path_shape) => {
|
||||||
self.tessellate_path(path_shape, out);
|
self.tessellate_path(&path_shape, out);
|
||||||
}
|
}
|
||||||
Shape::Rect(rect_shape) => {
|
Shape::Rect(rect_shape) => {
|
||||||
self.tessellate_rect(&rect_shape, out);
|
self.tessellate_rect(&rect_shape, out);
|
||||||
}
|
}
|
||||||
Shape::Text(text_shape) => {
|
Shape::Text(text_shape) => {
|
||||||
if options.debug_paint_text_rects {
|
if self.options.debug_paint_text_rects {
|
||||||
let rect = text_shape.galley.rect.translate(text_shape.pos.to_vec2());
|
let rect = text_shape.galley.rect.translate(text_shape.pos.to_vec2());
|
||||||
self.tessellate_rect(
|
self.tessellate_rect(
|
||||||
&RectShape::stroke(rect.expand(0.5), 2.0, (0.5, Color32::GREEN)),
|
&RectShape::stroke(rect.expand(0.5), 2.0, (0.5, Color32::GREEN)),
|
||||||
out,
|
out,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.tessellate_text(tex_size, text_shape, out);
|
self.tessellate_text(tex_size, &text_shape, out);
|
||||||
}
|
}
|
||||||
Shape::QuadraticBezier(quadratic_shape) => {
|
Shape::QuadraticBezier(quadratic_shape) => {
|
||||||
self.tessellate_quadratic_bezier(quadratic_shape, out);
|
self.tessellate_quadratic_bezier(quadratic_shape, out);
|
||||||
|
@ -787,7 +748,267 @@ impl Tessellator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn tessellate_quadratic_bezier(
|
/// Tessellate a single [`CircleShape`] into a [`Mesh`].
|
||||||
|
///
|
||||||
|
/// * `shape`: the circle to tessellate.
|
||||||
|
/// * `out`: triangles are appended to this.
|
||||||
|
pub fn tessellate_circle(&mut self, shape: CircleShape, out: &mut Mesh) {
|
||||||
|
let CircleShape {
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
fill,
|
||||||
|
stroke,
|
||||||
|
} = shape;
|
||||||
|
|
||||||
|
if radius <= 0.0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.options.coarse_tessellation_culling
|
||||||
|
&& !self
|
||||||
|
.clip_rect
|
||||||
|
.expand(radius + stroke.width)
|
||||||
|
.contains(center)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scratchpad_path.clear();
|
||||||
|
self.scratchpad_path.add_circle(center, radius);
|
||||||
|
self.scratchpad_path.fill(self.feathering, fill, out);
|
||||||
|
self.scratchpad_path
|
||||||
|
.stroke_closed(self.feathering, stroke, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tessellate a single [`Mesh`] into a [`Mesh`].
|
||||||
|
///
|
||||||
|
/// * `mesh`: the mesh to tessellate.
|
||||||
|
/// * `out`: triangles are appended to this.
|
||||||
|
pub fn tessellate_mesh(&mut self, mesh: &Mesh, out: &mut Mesh) {
|
||||||
|
if !mesh.is_valid() {
|
||||||
|
crate::epaint_assert!(false, "Invalid Mesh in Shape::Mesh");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.options.coarse_tessellation_culling
|
||||||
|
&& !self.clip_rect.intersects(mesh.calc_bounds())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.append_ref(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tessellate a line segment between the two points with the given stoken into a [`Mesh`].
|
||||||
|
///
|
||||||
|
/// * `shape`: the mesh to tessellate.
|
||||||
|
/// * `out`: triangles are appended to this.
|
||||||
|
pub fn tessellate_line(&mut self, points: [Pos2; 2], stroke: Stroke, out: &mut Mesh) {
|
||||||
|
if stroke.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.options.coarse_tessellation_culling
|
||||||
|
&& !self
|
||||||
|
.clip_rect
|
||||||
|
.intersects(Rect::from_two_pos(points[0], points[1]).expand(stroke.width))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scratchpad_path.clear();
|
||||||
|
self.scratchpad_path.add_line_segment(points);
|
||||||
|
self.scratchpad_path
|
||||||
|
.stroke_open(self.feathering, stroke, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tessellate a single [`PathShape`] into a [`Mesh`].
|
||||||
|
///
|
||||||
|
/// * `path_shape`: the path to tessellate.
|
||||||
|
/// * `out`: triangles are appended to this.
|
||||||
|
pub fn tessellate_path(&mut self, path_shape: &PathShape, out: &mut Mesh) {
|
||||||
|
if path_shape.points.len() < 2 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.options.coarse_tessellation_culling
|
||||||
|
&& !path_shape.visual_bounding_rect().intersects(self.clip_rect)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let PathShape {
|
||||||
|
points,
|
||||||
|
closed,
|
||||||
|
fill,
|
||||||
|
stroke,
|
||||||
|
} = path_shape;
|
||||||
|
|
||||||
|
self.scratchpad_path.clear();
|
||||||
|
if *closed {
|
||||||
|
self.scratchpad_path.add_line_loop(points);
|
||||||
|
} else {
|
||||||
|
self.scratchpad_path.add_open_points(points);
|
||||||
|
}
|
||||||
|
|
||||||
|
if *fill != Color32::TRANSPARENT {
|
||||||
|
crate::epaint_assert!(
|
||||||
|
closed,
|
||||||
|
"You asked to fill a path that is not closed. That makes no sense."
|
||||||
|
);
|
||||||
|
self.scratchpad_path.fill(self.feathering, *fill, out);
|
||||||
|
}
|
||||||
|
let typ = if *closed {
|
||||||
|
PathType::Closed
|
||||||
|
} else {
|
||||||
|
PathType::Open
|
||||||
|
};
|
||||||
|
self.scratchpad_path
|
||||||
|
.stroke(self.feathering, typ, *stroke, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tessellate a single [`Rect`] into a [`Mesh`].
|
||||||
|
///
|
||||||
|
/// * `rect`: the rectangle to tessellate.
|
||||||
|
/// * `out`: triangles are appended to this.
|
||||||
|
pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) {
|
||||||
|
let RectShape {
|
||||||
|
mut rect,
|
||||||
|
rounding,
|
||||||
|
fill,
|
||||||
|
stroke,
|
||||||
|
} = *rect;
|
||||||
|
|
||||||
|
if self.options.coarse_tessellation_culling
|
||||||
|
&& !rect.expand(stroke.width).intersects(self.clip_rect)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if rect.is_negative() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is common to (sometimes accidentally) create an infinitely sized rectangle.
|
||||||
|
// Make sure we can handle that:
|
||||||
|
rect.min = rect.min.at_least(pos2(-1e7, -1e7));
|
||||||
|
rect.max = rect.max.at_most(pos2(1e7, 1e7));
|
||||||
|
|
||||||
|
let path = &mut self.scratchpad_path;
|
||||||
|
path.clear();
|
||||||
|
path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding);
|
||||||
|
path.add_line_loop(&self.scratchpad_points);
|
||||||
|
path.fill(self.feathering, fill, out);
|
||||||
|
path.stroke_closed(self.feathering, stroke, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tessellate a single [`TextShape`] into a [`Mesh`].
|
||||||
|
///
|
||||||
|
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles).
|
||||||
|
/// * `text_shape`: the text to tessellate.
|
||||||
|
/// * `out`: triangles are appended to this.
|
||||||
|
pub fn tessellate_text(
|
||||||
|
&mut self,
|
||||||
|
tex_size: [usize; 2],
|
||||||
|
text_shape: &TextShape,
|
||||||
|
out: &mut Mesh,
|
||||||
|
) {
|
||||||
|
let TextShape {
|
||||||
|
pos: galley_pos,
|
||||||
|
galley,
|
||||||
|
underline,
|
||||||
|
override_text_color,
|
||||||
|
angle,
|
||||||
|
} = text_shape;
|
||||||
|
|
||||||
|
if galley.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.vertices.reserve(galley.num_vertices);
|
||||||
|
out.indices.reserve(galley.num_indices);
|
||||||
|
|
||||||
|
// The contents of the galley is already snapped to pixel coordinates,
|
||||||
|
// but we need to make sure the galley ends up on the start of a physical pixel:
|
||||||
|
let galley_pos = pos2(
|
||||||
|
self.round_to_pixel(galley_pos.x),
|
||||||
|
self.round_to_pixel(galley_pos.y),
|
||||||
|
);
|
||||||
|
|
||||||
|
let uv_normalizer = vec2(1.0 / tex_size[0] as f32, 1.0 / tex_size[1] as f32);
|
||||||
|
|
||||||
|
let rotator = Rot2::from_angle(*angle);
|
||||||
|
|
||||||
|
for row in &galley.rows {
|
||||||
|
if row.visuals.mesh.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut row_rect = row.visuals.mesh_bounds;
|
||||||
|
if *angle != 0.0 {
|
||||||
|
row_rect = row_rect.rotate_bb(rotator);
|
||||||
|
}
|
||||||
|
row_rect = row_rect.translate(galley_pos.to_vec2());
|
||||||
|
|
||||||
|
if self.options.coarse_tessellation_culling && !self.clip_rect.intersects(row_rect) {
|
||||||
|
// culling individual lines of text is important, since a single `Shape::Text`
|
||||||
|
// can span hundreds of lines.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index_offset = out.vertices.len() as u32;
|
||||||
|
|
||||||
|
out.indices.extend(
|
||||||
|
row.visuals
|
||||||
|
.mesh
|
||||||
|
.indices
|
||||||
|
.iter()
|
||||||
|
.map(|index| index + index_offset),
|
||||||
|
);
|
||||||
|
|
||||||
|
out.vertices.extend(
|
||||||
|
row.visuals
|
||||||
|
.mesh
|
||||||
|
.vertices
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, vertex)| {
|
||||||
|
let Vertex { pos, uv, mut color } = *vertex;
|
||||||
|
|
||||||
|
if let Some(override_text_color) = override_text_color {
|
||||||
|
if row.visuals.glyph_vertex_range.contains(&i) {
|
||||||
|
color = *override_text_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = if *angle == 0.0 {
|
||||||
|
pos.to_vec2()
|
||||||
|
} else {
|
||||||
|
rotator * pos.to_vec2()
|
||||||
|
};
|
||||||
|
|
||||||
|
Vertex {
|
||||||
|
pos: galley_pos + offset,
|
||||||
|
uv: (uv.to_vec2() * uv_normalizer).to_pos2(),
|
||||||
|
color,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if *underline != Stroke::none() {
|
||||||
|
self.scratchpad_path.clear();
|
||||||
|
self.scratchpad_path
|
||||||
|
.add_line_segment([row_rect.left_bottom(), row_rect.right_bottom()]);
|
||||||
|
self.scratchpad_path
|
||||||
|
.stroke_open(self.feathering, *underline, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tessellate a single [`QuadraticBezierShape`] into a [`Mesh`].
|
||||||
|
///
|
||||||
|
/// * `quadratic_shape`: the shape to tessellate.
|
||||||
|
/// * `out`: triangles are appended to this.
|
||||||
|
pub fn tessellate_quadratic_bezier(
|
||||||
&mut self,
|
&mut self,
|
||||||
quadratic_shape: QuadraticBezierShape,
|
quadratic_shape: QuadraticBezierShape,
|
||||||
out: &mut Mesh,
|
out: &mut Mesh,
|
||||||
|
@ -812,11 +1033,11 @@ impl Tessellator {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn tessellate_cubic_bezier(
|
/// Tessellate a single [`CubicBezierShape`] into a [`Mesh`].
|
||||||
&mut self,
|
///
|
||||||
cubic_shape: CubicBezierShape,
|
/// * `cubic_shape`: the shape to tessellate.
|
||||||
out: &mut Mesh,
|
/// * `out`: triangles are appended to this.
|
||||||
) {
|
pub fn tessellate_cubic_bezier(&mut self, cubic_shape: CubicBezierShape, out: &mut Mesh) {
|
||||||
let options = &self.options;
|
let options = &self.options;
|
||||||
let clip_rect = self.clip_rect;
|
let clip_rect = self.clip_rect;
|
||||||
if options.coarse_tessellation_culling
|
if options.coarse_tessellation_culling
|
||||||
|
@ -858,177 +1079,15 @@ impl Tessellator {
|
||||||
closed,
|
closed,
|
||||||
"You asked to fill a path that is not closed. That makes no sense."
|
"You asked to fill a path that is not closed. That makes no sense."
|
||||||
);
|
);
|
||||||
self.scratchpad_path.fill(fill, &self.options, out);
|
self.scratchpad_path.fill(self.feathering, fill, out);
|
||||||
}
|
}
|
||||||
let typ = if closed {
|
let typ = if closed {
|
||||||
PathType::Closed
|
PathType::Closed
|
||||||
} else {
|
} else {
|
||||||
PathType::Open
|
PathType::Open
|
||||||
};
|
};
|
||||||
self.scratchpad_path.stroke(typ, stroke, &self.options, out);
|
self.scratchpad_path
|
||||||
}
|
.stroke(self.feathering, typ, stroke, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1037,8 +1096,9 @@ impl Tessellator {
|
||||||
/// The given shapes will tessellated in the same order as they are given.
|
/// The given shapes will tessellated in the same order as they are given.
|
||||||
/// They will be batched together by clip rectangle.
|
/// They will be batched together by clip rectangle.
|
||||||
///
|
///
|
||||||
/// * `shapes`: what to tessellate
|
/// * `pixels_per_point`: number of physical pixels to each logical point
|
||||||
/// * `options`: tessellation quality
|
/// * `options`: tessellation quality
|
||||||
|
/// * `shapes`: what to tessellate
|
||||||
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles)
|
/// * `tex_size`: size of the font texture (required to normalize glyph uv rectangles)
|
||||||
///
|
///
|
||||||
/// The implementation uses a [`Tessellator`].
|
/// The implementation uses a [`Tessellator`].
|
||||||
|
@ -1046,11 +1106,12 @@ impl Tessellator {
|
||||||
/// ## Returns
|
/// ## Returns
|
||||||
/// A list of clip rectangles with matching [`Mesh`].
|
/// A list of clip rectangles with matching [`Mesh`].
|
||||||
pub fn tessellate_shapes(
|
pub fn tessellate_shapes(
|
||||||
shapes: Vec<ClippedShape>,
|
pixels_per_point: f32,
|
||||||
options: TessellationOptions,
|
options: TessellationOptions,
|
||||||
|
shapes: Vec<ClippedShape>,
|
||||||
tex_size: [usize; 2],
|
tex_size: [usize; 2],
|
||||||
) -> Vec<ClippedPrimitive> {
|
) -> Vec<ClippedPrimitive> {
|
||||||
let mut tessellator = Tessellator::from_options(options);
|
let mut tessellator = Tessellator::new(pixels_per_point, options);
|
||||||
|
|
||||||
let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();
|
let mut clipped_primitives: Vec<ClippedPrimitive> = Vec::default();
|
||||||
|
|
||||||
|
|
|
@ -599,10 +599,8 @@ fn add_hline(point_scale: PointScale, [start, stop]: [Pos2; 2], stroke: Stroke,
|
||||||
if antialiased {
|
if antialiased {
|
||||||
let mut path = crate::tessellator::Path::default(); // TODO: reuse this to avoid re-allocations.
|
let mut path = crate::tessellator::Path::default(); // TODO: reuse this to avoid re-allocations.
|
||||||
path.add_line_segment([start, stop]);
|
path.add_line_segment([start, stop]);
|
||||||
let options = crate::tessellator::TessellationOptions::from_pixels_per_point(
|
let feathering = 1.0 / point_scale.pixels_per_point();
|
||||||
point_scale.pixels_per_point(),
|
path.stroke_open(feathering, stroke, mesh);
|
||||||
);
|
|
||||||
path.stroke_open(stroke, &options, mesh);
|
|
||||||
} else {
|
} else {
|
||||||
// Thin lines often lost, so this is a bad idea
|
// Thin lines often lost, so this is a bad idea
|
||||||
|
|
||||||
|
|
|
@ -28,9 +28,7 @@ persistence = ["ron", "serde", "egui/persistence"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.17.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.17.0", path = "../egui", default-features = false }
|
||||||
"single_threaded",
|
|
||||||
] }
|
|
||||||
glow = "0.11"
|
glow = "0.11"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|
||||||
|
|
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`].
|
//! Start by looking at the [`App`] trait, and implement [`App::update`].
|
||||||
|
|
||||||
// Forbid warnings in release builds:
|
|
||||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
|
||||||
#![forbid(unsafe_code)]
|
|
||||||
#![warn(
|
|
||||||
clippy::all,
|
|
||||||
clippy::await_holding_lock,
|
|
||||||
clippy::char_lit_as_u8,
|
|
||||||
clippy::checked_conversions,
|
|
||||||
clippy::dbg_macro,
|
|
||||||
clippy::debug_assert_with_mut_call,
|
|
||||||
clippy::disallowed_method,
|
|
||||||
clippy::doc_markdown,
|
|
||||||
clippy::empty_enum,
|
|
||||||
clippy::enum_glob_use,
|
|
||||||
clippy::exit,
|
|
||||||
clippy::expl_impl_clone_on_copy,
|
|
||||||
clippy::explicit_deref_methods,
|
|
||||||
clippy::explicit_into_iter_loop,
|
|
||||||
clippy::fallible_impl_from,
|
|
||||||
clippy::filter_map_next,
|
|
||||||
clippy::flat_map_option,
|
|
||||||
clippy::float_cmp_const,
|
|
||||||
clippy::fn_params_excessive_bools,
|
|
||||||
clippy::from_iter_instead_of_collect,
|
|
||||||
clippy::if_let_mutex,
|
|
||||||
clippy::implicit_clone,
|
|
||||||
clippy::imprecise_flops,
|
|
||||||
clippy::inefficient_to_string,
|
|
||||||
clippy::invalid_upcast_comparisons,
|
|
||||||
clippy::large_digit_groups,
|
|
||||||
clippy::large_stack_arrays,
|
|
||||||
clippy::large_types_passed_by_value,
|
|
||||||
clippy::let_unit_value,
|
|
||||||
clippy::linkedlist,
|
|
||||||
clippy::lossy_float_literal,
|
|
||||||
clippy::macro_use_imports,
|
|
||||||
clippy::manual_ok_or,
|
|
||||||
clippy::map_err_ignore,
|
|
||||||
clippy::map_flatten,
|
|
||||||
clippy::map_unwrap_or,
|
|
||||||
clippy::match_on_vec_items,
|
|
||||||
clippy::match_same_arms,
|
|
||||||
clippy::match_wild_err_arm,
|
|
||||||
clippy::match_wildcard_for_single_variants,
|
|
||||||
clippy::mem_forget,
|
|
||||||
clippy::mismatched_target_os,
|
|
||||||
clippy::missing_errors_doc,
|
|
||||||
clippy::missing_safety_doc,
|
|
||||||
clippy::mut_mut,
|
|
||||||
clippy::mutex_integer,
|
|
||||||
clippy::needless_borrow,
|
|
||||||
clippy::needless_continue,
|
|
||||||
clippy::needless_for_each,
|
|
||||||
clippy::needless_pass_by_value,
|
|
||||||
clippy::option_option,
|
|
||||||
clippy::path_buf_push_overwrite,
|
|
||||||
clippy::ptr_as_ptr,
|
|
||||||
clippy::ref_option_ref,
|
|
||||||
clippy::rest_pat_in_fully_bound_structs,
|
|
||||||
clippy::same_functions_in_if_condition,
|
|
||||||
clippy::semicolon_if_nothing_returned,
|
|
||||||
clippy::single_match_else,
|
|
||||||
clippy::string_add_assign,
|
|
||||||
clippy::string_add,
|
|
||||||
clippy::string_lit_as_bytes,
|
|
||||||
clippy::string_to_string,
|
|
||||||
clippy::todo,
|
|
||||||
clippy::trait_duplication_in_bounds,
|
|
||||||
clippy::unimplemented,
|
|
||||||
clippy::unnested_or_patterns,
|
|
||||||
clippy::unused_self,
|
|
||||||
clippy::useless_transmute,
|
|
||||||
clippy::verbose_file_reads,
|
|
||||||
clippy::zero_sized_map_values,
|
|
||||||
future_incompatible,
|
|
||||||
nonstandard_style,
|
|
||||||
rust_2018_idioms,
|
|
||||||
rustdoc::missing_crate_level_docs
|
|
||||||
)]
|
|
||||||
#![allow(clippy::float_cmp)]
|
|
||||||
#![allow(clippy::manual_range_contains)]
|
|
||||||
#![warn(missing_docs)] // Let's keep `epi` well-documented.
|
#![warn(missing_docs)] // Let's keep `epi` well-documented.
|
||||||
|
|
||||||
/// File storage which can be used by native backends.
|
/// File storage which can be used by native backends.
|
||||||
|
@ -98,6 +17,30 @@ pub use glow; // Re-export for user convenience
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
/// The is is how your app is created.
|
||||||
|
///
|
||||||
|
/// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc.
|
||||||
|
pub type AppCreator = Box<dyn FnOnce(&CreationContext<'_>) -> Box<dyn App>>;
|
||||||
|
|
||||||
|
/// Data that is passed to [`AppCreator`] that can be used to setup and initialize your app.
|
||||||
|
pub struct CreationContext<'s> {
|
||||||
|
/// The egui Context.
|
||||||
|
///
|
||||||
|
/// You can use this to customize the look of egui, e.g to call [`egui::Context::set_fonts`],
|
||||||
|
/// [`egui::Context::set_visuals`] etc.
|
||||||
|
pub egui_ctx: egui::Context,
|
||||||
|
|
||||||
|
/// Information about the surrounding environment.
|
||||||
|
pub integration_info: IntegrationInfo,
|
||||||
|
|
||||||
|
/// You can use the storage to restore app state(requires the "persistence" feature).
|
||||||
|
pub storage: Option<&'s dyn Storage>,
|
||||||
|
|
||||||
|
/// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that
|
||||||
|
/// you might want to use later from a [`egui::PaintCallback`].
|
||||||
|
pub gl: std::rc::Rc<glow::Context>,
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Implement this trait to write apps that can be compiled both natively using the [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) crate,
|
/// Implement this trait to write apps that can be compiled both natively using the [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) crate,
|
||||||
|
@ -109,39 +52,20 @@ pub trait App {
|
||||||
///
|
///
|
||||||
/// The [`egui::Context`] and [`Frame`] can be cloned and saved if you like.
|
/// The [`egui::Context`] and [`Frame`] can be cloned and saved if you like.
|
||||||
///
|
///
|
||||||
/// To force a repaint, call either [`egui::Context::request_repaint`] during the call to `update`,
|
/// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
|
||||||
/// or call [`Frame::request_repaint`] at any time (e.g. from another thread).
|
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &Frame);
|
fn update(&mut self, ctx: &egui::Context, frame: &Frame);
|
||||||
|
|
||||||
/// Called exactly once at startup, before any call to [`Self::update`].
|
|
||||||
///
|
|
||||||
/// Allows you to do setup code, e.g to call [`egui::Context::set_fonts`],
|
|
||||||
/// [`egui::Context::set_visuals`] etc.
|
|
||||||
///
|
|
||||||
/// Also allows you to restore state, if there is a storage (requires the "persistence" feature).
|
|
||||||
///
|
|
||||||
/// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that
|
|
||||||
/// you might want to use later from a [`egui::PaintCallback`].
|
|
||||||
fn setup(
|
|
||||||
&mut self,
|
|
||||||
_ctx: &egui::Context,
|
|
||||||
_frame: &Frame,
|
|
||||||
_storage: Option<&dyn Storage>,
|
|
||||||
_gl: &std::rc::Rc<glow::Context>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called on shutdown, and perhaps at regular intervals. Allows you to save state.
|
/// Called on shutdown, and perhaps at regular intervals. Allows you to save state.
|
||||||
///
|
///
|
||||||
/// Only called when the "persistence" feature is enabled.
|
/// Only called when the "persistence" feature is enabled.
|
||||||
///
|
///
|
||||||
/// On web the states is stored to "Local Storage".
|
/// On web the state is stored to "Local Storage".
|
||||||
/// On native the path is picked using [`directories_next::ProjectDirs::data_dir`](https://docs.rs/directories-next/2.0.0/directories_next/struct.ProjectDirs.html#method.data_dir) which is:
|
/// On native the path is picked using [`directories_next::ProjectDirs::data_dir`](https://docs.rs/directories-next/2.0.0/directories_next/struct.ProjectDirs.html#method.data_dir) which is:
|
||||||
/// * Linux: `/home/UserName/.local/share/APPNAME`
|
/// * Linux: `/home/UserName/.local/share/APPNAME`
|
||||||
/// * macOS: `/Users/UserName/Library/Application Support/APPNAME`
|
/// * macOS: `/Users/UserName/Library/Application Support/APPNAME`
|
||||||
/// * Windows: `C:\Users\UserName\AppData\Roaming\APPNAME`
|
/// * Windows: `C:\Users\UserName\AppData\Roaming\APPNAME`
|
||||||
///
|
///
|
||||||
/// where `APPNAME` is what is returned by [`Self::name()`].
|
/// where `APPNAME` is what is given to `eframe::run_native`.
|
||||||
fn save(&mut self, _storage: &mut dyn Storage) {}
|
fn save(&mut self, _storage: &mut dyn Storage) {}
|
||||||
|
|
||||||
/// Called before an exit that can be aborted.
|
/// Called before an exit that can be aborted.
|
||||||
|
@ -156,17 +80,14 @@ pub trait App {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called once on shutdown (before or after [`Self::save`]). If you need to abort an exit use
|
/// Called once on shutdown, after [`Self::save`].
|
||||||
/// [`Self::on_exit_event`]
|
///
|
||||||
fn on_exit(&mut self) {}
|
/// If you need to abort an exit use [`Self::on_exit_event`].
|
||||||
|
fn on_exit(&mut self, _gl: &glow::Context) {}
|
||||||
|
|
||||||
// ---------
|
// ---------
|
||||||
// Settings:
|
// Settings:
|
||||||
|
|
||||||
/// The name of your App, used for the title bar of native windows
|
|
||||||
/// and the save location of persistence (see [`Self::save`]).
|
|
||||||
fn name(&self) -> &str;
|
|
||||||
|
|
||||||
/// Time between automatic calls to [`Self::save`]
|
/// Time between automatic calls to [`Self::save`]
|
||||||
fn auto_save_interval(&self) -> std::time::Duration {
|
fn auto_save_interval(&self) -> std::time::Duration {
|
||||||
std::time::Duration::from_secs(30)
|
std::time::Duration::from_secs(30)
|
||||||
|
@ -174,13 +95,12 @@ pub trait App {
|
||||||
|
|
||||||
/// The size limit of the web app canvas.
|
/// The size limit of the web app canvas.
|
||||||
///
|
///
|
||||||
/// By default the size if limited to 1024x2048.
|
/// By default the max size is [`egui::Vec2::INFINITY`], i.e. unlimited.
|
||||||
///
|
///
|
||||||
/// A larger canvas can lead to bad frame rates on some browsers on some platforms.
|
/// A large canvas can lead to bad frame rates on some older browsers on some platforms
|
||||||
/// In particular, Firefox on Mac and Linux is really bad at handling large WebGL canvases:
|
/// (see <https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0>).
|
||||||
/// <https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0> (unfixed since 2014).
|
|
||||||
fn max_size_points(&self) -> egui::Vec2 {
|
fn max_size_points(&self) -> egui::Vec2 {
|
||||||
egui::Vec2::new(1024.0, 2048.0)
|
egui::Vec2::INFINITY
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Background color for the app, e.g. what is sent to `gl.clearColor`.
|
/// Background color for the app, e.g. what is sent to `gl.clearColor`.
|
||||||
|
@ -263,6 +183,32 @@ pub struct NativeOptions {
|
||||||
/// You control the transparency with [`App::clear_color()`].
|
/// You control the transparency with [`App::clear_color()`].
|
||||||
/// You should avoid having a [`egui::CentralPanel`], or make sure its frame is also transparent.
|
/// You should avoid having a [`egui::CentralPanel`], or make sure its frame is also transparent.
|
||||||
pub transparent: bool,
|
pub transparent: bool,
|
||||||
|
|
||||||
|
/// Turn on vertical syncing, limiting the FPS to the display refresh rate.
|
||||||
|
///
|
||||||
|
/// The default is `true`.
|
||||||
|
pub vsync: bool,
|
||||||
|
|
||||||
|
/// Set the level of the multisampling anti-aliasing (MSAA).
|
||||||
|
///
|
||||||
|
/// Must be a power-of-two. Higher = more smooth 3D.
|
||||||
|
///
|
||||||
|
/// A value of `0` turns it off (default).
|
||||||
|
///
|
||||||
|
/// `egui` already performs anti-aliasing via "feathering"
|
||||||
|
/// (controlled by [`egui::epaint::TessellationOptions`]),
|
||||||
|
/// but if you are embedding 3D in egui you may want to turn on multisampling.
|
||||||
|
pub multisampling: u16,
|
||||||
|
|
||||||
|
/// Sets the number of bits in the depth buffer.
|
||||||
|
///
|
||||||
|
/// `egui` doesn't need the depth buffer, so the default value is 0.
|
||||||
|
pub depth_buffer: u8,
|
||||||
|
|
||||||
|
/// Sets the number of bits in the stencil buffer.
|
||||||
|
///
|
||||||
|
/// `egui` doesn't need the stencil buffer, so the default value is 0.
|
||||||
|
pub stencil_buffer: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NativeOptions {
|
impl Default for NativeOptions {
|
||||||
|
@ -279,6 +225,10 @@ impl Default for NativeOptions {
|
||||||
max_window_size: None,
|
max_window_size: None,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
transparent: false,
|
transparent: false,
|
||||||
|
vsync: true,
|
||||||
|
multisampling: 0,
|
||||||
|
depth_buffer: 0,
|
||||||
|
stencil_buffer: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -359,13 +309,6 @@ impl Frame {
|
||||||
self.lock().output.drag_window = true;
|
self.lock().output.drag_window = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This signals the [`egui`] integration that a repaint is required.
|
|
||||||
///
|
|
||||||
/// Call this e.g. when a background process finishes in an async context and/or background thread.
|
|
||||||
pub fn request_repaint(&self) {
|
|
||||||
self.lock().repaint_signal.request_repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// for integrations only: call once per frame
|
/// for integrations only: call once per frame
|
||||||
pub fn take_app_output(&self) -> crate::backend::AppOutput {
|
pub fn take_app_output(&self) -> crate::backend::AppOutput {
|
||||||
std::mem::take(&mut self.lock().output)
|
std::mem::take(&mut self.lock().output)
|
||||||
|
@ -524,14 +467,6 @@ pub const APP_KEY: &str = "app";
|
||||||
pub mod backend {
|
pub mod backend {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// How to signal the [`egui`] integration that a repaint is required.
|
|
||||||
pub trait RepaintSignal: Send + Sync {
|
|
||||||
/// This signals the [`egui`] integration that a repaint is required.
|
|
||||||
///
|
|
||||||
/// Call this e.g. when a background process finishes in an async context and/or background thread.
|
|
||||||
fn request_repaint(&self);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The data required by [`Frame`] each frame.
|
/// The data required by [`Frame`] each frame.
|
||||||
pub struct FrameData {
|
pub struct FrameData {
|
||||||
/// Information about the integration.
|
/// Information about the integration.
|
||||||
|
@ -539,9 +474,6 @@ pub mod backend {
|
||||||
|
|
||||||
/// Where the app can issue commands back to the integration.
|
/// Where the app can issue commands back to the integration.
|
||||||
pub output: AppOutput,
|
pub output: AppOutput,
|
||||||
|
|
||||||
/// If you need to request a repaint from another thread, clone this and send it to that other thread.
|
|
||||||
pub repaint_signal: std::sync::Arc<dyn RepaintSignal>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Action that can be taken by the user app.
|
/// Action that can be taken by the user app.
|
||||||
|
|
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
|
cargo doc --document-private-items --no-deps --all-features
|
||||||
|
|
||||||
(cd emath && cargo check --no-default-features)
|
(cd emath && cargo check --no-default-features)
|
||||||
(cd epaint && cargo check --no-default-features --features "single_threaded")
|
(cd epaint && cargo check --no-default-features)
|
||||||
(cd epaint && cargo check --no-default-features --features "multi_threaded")
|
(cd epaint && cargo check --no-default-features --release)
|
||||||
(cd epaint && cargo check --no-default-features --features "single_threaded" --release)
|
(cd egui && cargo check --no-default-features --features "serialize")
|
||||||
(cd epaint && cargo check --no-default-features --features "multi_threaded" --release)
|
|
||||||
(cd egui && cargo check --no-default-features --features "multi_threaded,serialize")
|
|
||||||
(cd eframe && cargo check --no-default-features)
|
(cd eframe && cargo check --no-default-features)
|
||||||
(cd epi && cargo check --no-default-features)
|
(cd epi && cargo check --no-default-features)
|
||||||
(cd egui_demo_lib && cargo check --no-default-features)
|
(cd egui_demo_lib && cargo check --no-default-features)
|
||||||
(cd egui_extras && cargo check --no-default-features)
|
(cd egui_extras && cargo check --no-default-features)
|
||||||
(cd egui_web && cargo check --no-default-features)
|
(cd egui_web && cargo check --no-default-features)
|
||||||
# (cd egui-winit && cargo check --no-default-features) # we don't pick singlethreaded or multithreaded
|
(cd egui-winit && cargo check --no-default-features)
|
||||||
(cd egui_glium && cargo check --no-default-features)
|
(cd egui_glium && cargo check --no-default-features)
|
||||||
(cd egui_glow && cargo check --no-default-features)
|
(cd egui_glow && cargo check --no-default-features)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue