Compare commits
86 commits
master
...
dynamic-gr
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8681ec5cc9 | ||
![]() |
8718ae6c59 | ||
![]() |
04135aaa9c | ||
![]() |
73444a7116 | ||
![]() |
40f62b1d17 | ||
![]() |
c2a0f64c81 | ||
![]() |
dc8c5aaa2b | ||
![]() |
d72cfe5359 | ||
![]() |
153e2cf54f | ||
![]() |
d2492c7d70 | ||
![]() |
b2124f4cd1 | ||
![]() |
d9eac765dc | ||
![]() |
2ec346e7f5 | ||
![]() |
275fa8c276 | ||
![]() |
e5aa546b98 | ||
![]() |
6d4e10cf91 | ||
![]() |
42b6ed6100 | ||
![]() |
67ebb6bde6 | ||
![]() |
3da93beab7 | ||
![]() |
1cd2a3c984 | ||
![]() |
294eca5b7f | ||
![]() |
671cbfe58e | ||
![]() |
738bb0c6fa | ||
![]() |
e252a71598 | ||
![]() |
5adb99cfc2 | ||
![]() |
5ccfa1117e | ||
![]() |
35c8e97b75 | ||
![]() |
787b1ad8be | ||
![]() |
dfab28fe6d | ||
![]() |
c55c8dfa1e | ||
![]() |
63a70ab00d | ||
![]() |
6261380180 | ||
![]() |
a42006ef14 | ||
![]() |
8ce59e703f | ||
![]() |
cef3c743cf | ||
![]() |
28a91fba1f | ||
![]() |
8495242f21 | ||
![]() |
5de7ac0c60 | ||
![]() |
4920f48ab4 | ||
![]() |
6194b5d5fb | ||
![]() |
bbc3fabcab | ||
![]() |
6db4b929eb | ||
![]() |
3c5d04a041 | ||
![]() |
f95755768e | ||
![]() |
2eae720ee6 | ||
![]() |
abe6daf880 | ||
![]() |
eeb79c0f88 | ||
![]() |
8a78302bd0 | ||
![]() |
504c791198 | ||
![]() |
afe583034a | ||
![]() |
63491e247f | ||
![]() |
b91855ca94 | ||
![]() |
f1e1cf0e8b | ||
![]() |
27c72efdc7 | ||
![]() |
562f3d2c28 | ||
![]() |
6e06a2097b | ||
![]() |
0cd74376a7 | ||
![]() |
9d19a07a55 | ||
![]() |
0396eed9bf | ||
![]() |
ae71f91f8c | ||
![]() |
10c51364c5 | ||
![]() |
417cb1dd02 | ||
![]() |
0e4ab1bda1 | ||
![]() |
d56dba5c7d | ||
![]() |
1c7df684a3 | ||
![]() |
0f385f6beb | ||
![]() |
99fdbb7918 | ||
![]() |
bb5da25a9b | ||
![]() |
7dec7054fb | ||
![]() |
a346bcf8a3 | ||
![]() |
4f6f871f29 | ||
![]() |
a944208b19 | ||
![]() |
e373961e21 | ||
![]() |
767357c468 | ||
![]() |
a1ba1ec079 | ||
![]() |
fc79988737 | ||
![]() |
31d8cbb2c0 | ||
![]() |
e00c726bff | ||
![]() |
84a3c1813f | ||
![]() |
23bcb81eed | ||
![]() |
6fce5a4c5d | ||
![]() |
c097b38558 | ||
![]() |
5f4525e001 | ||
![]() |
eb8e1c4303 | ||
![]() |
5c87d987cf | ||
![]() |
4e16d48dd6 |
411 changed files with 16583 additions and 33580 deletions
|
@ -1,6 +1,85 @@
|
|||
# clipboard api is still unstable, so web-sys requires the below flag to be passed for copy (ctrl + c) to work
|
||||
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
|
||||
# check status at https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility
|
||||
# we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = ["--cfg=web_sys_unstable_apis"]
|
||||
[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",
|
||||
]
|
||||
|
|
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -8,7 +8,7 @@ assignees: ''
|
|||
---
|
||||
|
||||
<!--
|
||||
First look if there is already a similar bug report. If there is, upvote the issue with 👍
|
||||
First look if there is already a similar bug report. If there is, add a comment to it instead!
|
||||
|
||||
Please also check if the bug is still present in latest master! Do so by adding the following lines to your Cargo.toml:
|
||||
|
||||
|
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -8,7 +8,7 @@ assignees: ''
|
|||
---
|
||||
|
||||
<!--
|
||||
First look if there is already a similar feature request. If there is, upvote the issue with 👍
|
||||
First look if there is already a similar feature request. If there is, add a comment to it instead!
|
||||
-->
|
||||
|
||||
|
||||
|
|
3
.github/pull_request_template.md
vendored
3
.github/pull_request_template.md
vendored
|
@ -6,10 +6,11 @@ Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/
|
|||
* Unless this is a trivial change, add a line to the relevant `CHANGELOG.md` under "Unreleased".
|
||||
* If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`.
|
||||
* Remember to run `cargo fmt` and `cargo clippy`.
|
||||
* Open the PR as a draft until you have self-reviewed it and run `./sh/check.sh`.
|
||||
* Open the PR as a draft until you have self-reviewed it and it is green.
|
||||
* When you have addressed a PR comment, mark it as resolved.
|
||||
|
||||
Please be patient! I will review you PR, but my time is limited!
|
||||
-->
|
||||
|
||||
Closes <https://github.com/emilk/egui/issues/THE_RELEVANT_ISSUE>.
|
||||
|
||||
|
|
323
.github/workflows/rust.yml
vendored
323
.github/workflows/rust.yml
vendored
|
@ -3,209 +3,194 @@ on: [push, pull_request]
|
|||
name: CI
|
||||
|
||||
env:
|
||||
# web_sys_unstable_apis is required to enable the web_sys clipboard API which eframe web uses,
|
||||
# as well as by the wasm32-backend of the wgpu crate.
|
||||
# This is required to enable the web_sys clipboard API which egui_web uses
|
||||
# https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html
|
||||
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
|
||||
RUSTFLAGS: --cfg=web_sys_unstable_apis -D warnings
|
||||
RUSTDOCFLAGS: -D warnings
|
||||
RUSTFLAGS: --cfg=web_sys_unstable_apis
|
||||
|
||||
jobs:
|
||||
fmt-crank-check-test:
|
||||
name: Format + check + test
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: default
|
||||
toolchain: 1.65.0
|
||||
override: true
|
||||
|
||||
- name: Install packages (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
#uses: awalsh128/cache-apt-pkgs-action@v1.2.2
|
||||
#TODO(emilk) use upstream when https://github.com/awalsh128/cache-apt-pkgs-action/pull/90 is merged
|
||||
uses: rerun-io/cache-apt-pkgs-action@59534850182063abf1b2c11bb3686722a12a8397
|
||||
with:
|
||||
packages: libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev libgtk-3-dev # libgtk-3-dev is used by rfd
|
||||
version: 1.0
|
||||
execute_install_scripts: true
|
||||
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Rustfmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Install cargo-cranky
|
||||
uses: baptiste0928/cargo-install@v1
|
||||
with:
|
||||
crate: cargo-cranky
|
||||
|
||||
- name: check --all-features
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked --all-features --all-targets
|
||||
|
||||
- name: check default features
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked --all-targets
|
||||
|
||||
- name: check --no-default-features
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked --no-default-features --lib --all-targets
|
||||
|
||||
- name: check eframe --no-default-features
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked --no-default-features --lib --all-targets -p eframe
|
||||
|
||||
- name: Test doc-tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --doc --all-features
|
||||
|
||||
- name: cargo doc --lib
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: --lib --no-deps --all-features
|
||||
|
||||
- name: cargo doc --document-private-items
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: --document-private-items --no-deps --all-features
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all-features
|
||||
|
||||
- name: Cranky
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: cranky
|
||||
args: --all-targets --all-features -- -D warnings
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
check_wasm:
|
||||
name: Check wasm32 + wasm-bindgen
|
||||
runs-on: ubuntu-22.04
|
||||
check_default:
|
||||
name: cargo check (default features)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.65.0
|
||||
target: wasm32-unknown-unknown
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
|
||||
- run: sudo apt-get update && sudo apt-get install libgtk-3-dev
|
||||
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install cargo-cranky
|
||||
uses: baptiste0928/cargo-install@v1
|
||||
- run: sudo apt-get update && sudo apt-get install libspeechd-dev
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
crate: cargo-cranky
|
||||
command: check
|
||||
|
||||
- name: Check wasm32 egui_demo_app
|
||||
uses: actions-rs/cargo@v1
|
||||
check_all_features:
|
||||
name: cargo check --all-features
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: sudo apt-get update && sudo apt-get install libspeechd-dev
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all-features
|
||||
|
||||
check_web_default:
|
||||
name: cargo check web (default features)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: -p egui_demo_app --lib --target wasm32-unknown-unknown
|
||||
|
||||
- name: Check wasm32 egui_demo_app --all-features
|
||||
uses: actions-rs/cargo@v1
|
||||
check_egui_demo_app:
|
||||
name: cargo check -p egui_demo_app
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- name: check
|
||||
run: cargo check -p egui_demo_app
|
||||
|
||||
check_wasm_eframe_with_features:
|
||||
name: cargo check wasm eframe
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- name: check
|
||||
run: cargo check -p eframe --lib --no-default-features --features persistence --target wasm32-unknown-unknown
|
||||
|
||||
check_web_all_features:
|
||||
name: cargo check web --all-features
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features
|
||||
|
||||
- name: Check wasm32 eframe
|
||||
uses: actions-rs/cargo@v1
|
||||
test:
|
||||
name: cargo test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
command: check
|
||||
args: -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown
|
||||
|
||||
- name: wasm-bindgen
|
||||
uses: jetli/wasm-bindgen-action@v0.1.0
|
||||
profile: minimal
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: sudo apt-get update && sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libgtk-3-dev # libgtk-3-dev is used by rfd
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
version: "0.2.84"
|
||||
command: test
|
||||
args: --all-features
|
||||
|
||||
- run: ./sh/wasm_bindgen_check.sh --skip-setup
|
||||
|
||||
- name: Cranky wasm32
|
||||
uses: actions-rs/cargo@v1
|
||||
fmt:
|
||||
name: cargo fmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
command: cranky
|
||||
args: --target wasm32-unknown-unknown --all-features -p egui_demo_app --lib -- -D warnings
|
||||
profile: minimal
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
clippy:
|
||||
name: cargo clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: rustup component add clippy
|
||||
- run: sudo apt-get update && sudo apt-get install libspeechd-dev libgtk-3-dev # libgtk-3-dev is used by rfd
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets --all-features -- -D warnings -W clippy::all
|
||||
|
||||
doc:
|
||||
name: cargo doc
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: sudo apt-get update && sudo apt-get install libspeechd-dev
|
||||
- run: cargo doc -p emath -p epaint -p egui -p eframe -p epi -p egui_web -p egui-winit -p egui_extras -p egui_glium -p egui_glow --lib --no-deps --all-features
|
||||
|
||||
doc_web:
|
||||
name: cargo doc web
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: sudo apt-get update && sudo apt-get install libspeechd-dev
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- run: cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps --all-features
|
||||
|
||||
cargo-deny:
|
||||
name: cargo deny
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
with:
|
||||
rust-version: "1.65.0"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
android:
|
||||
name: android
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.65.0
|
||||
target: aarch64-linux-android
|
||||
override: true
|
||||
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- run: cargo check --features wgpu --target aarch64-linux-android
|
||||
working-directory: crates/eframe
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
windows:
|
||||
name: Check Windows
|
||||
runs-on: windows-latest
|
||||
wasm_bindgen:
|
||||
name: wasm-bindgen
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.65.0
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all-targets --all-features
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- run: cargo install wasm-bindgen-cli
|
||||
- run: ./sh/wasm_bindgen_check.sh
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,7 +1,4 @@
|
|||
.DS_Store
|
||||
**/target
|
||||
**/target_ra
|
||||
**/target_wasm
|
||||
/.*.json
|
||||
/.vscode
|
||||
/media/*
|
||||
|
|
29
.vscode/settings.json
vendored
29
.vscode/settings.json
vendored
|
@ -1,32 +1,5 @@
|
|||
{
|
||||
"files.insertFinalNewline": true,
|
||||
"editor.formatOnSave": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"editor.semanticTokenColorCustomizations": {
|
||||
"rules": {
|
||||
"*.unsafe:rust": "#eb5046"
|
||||
}
|
||||
},
|
||||
"files.exclude": {
|
||||
"target/**": true,
|
||||
"target_ra/**": true,
|
||||
},
|
||||
// Tell Rust Analyzer to use its own target directory, so we don't need to wait for it to finish wen we want to `cargo run`
|
||||
"rust-analyzer.checkOnSave.overrideCommand": [
|
||||
"cargo",
|
||||
"cranky",
|
||||
"--target-dir=target_ra",
|
||||
"--workspace",
|
||||
"--message-format=json",
|
||||
"--all-targets"
|
||||
],
|
||||
"rust-analyzer.cargo.buildScripts.overrideCommand": [
|
||||
"cargo",
|
||||
"check",
|
||||
"--quiet",
|
||||
"--target-dir=target_ra",
|
||||
"--workspace",
|
||||
"--message-format=json",
|
||||
"--all-targets"
|
||||
],
|
||||
"files.trimTrailingWhitespace": true
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# Architecture
|
||||
# Arcitecture
|
||||
This document describes how the crates that make up egui are all connected.
|
||||
|
||||
Also see [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md) for what to do before opening a PR.
|
||||
|
||||
|
||||
## Crate overview
|
||||
The crates in this repository are: `egui, emath, epaint, egui_extras, egui-winit, egui_glium, egui_glow, egui_demo_lib, egui_demo_app`.
|
||||
The crates in this repository are: `egui, emath, epaint, egui_extras, epi, egui-winit, egui_web, egui_glium, egui_glow, egui_demo_lib, egui_demo_app`.
|
||||
|
||||
### `egui`: The main GUI library.
|
||||
Example code: `if ui.button("Click me").clicked() { … }`
|
||||
|
@ -24,11 +24,18 @@ Depends on `emath`.
|
|||
### `egui_extras`
|
||||
This adds additional features on top of `egui`.
|
||||
|
||||
### `epi`
|
||||
Depends only on `egui`.
|
||||
Adds a thin application level wrapper around `egui` for hosting an `egui` app inside of `eframe`.
|
||||
|
||||
### `egui-winit`
|
||||
This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [winit](https://crates.io/crates/winit).
|
||||
|
||||
The library translates winit events to egui, handled copy/paste, updates the cursor, open links clicked in egui, etc.
|
||||
|
||||
### `egui_web`
|
||||
Puts an egui app inside the web browser by compiling to WASM and binding to the web browser with [`js-sys`](https://crates.io/crates/js-sys) and [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen). Paints the triangles that egui outputs using WebGL.
|
||||
|
||||
### `egui_glium`
|
||||
Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glium](https://github.com/glium/glium).
|
||||
|
||||
|
@ -36,12 +43,12 @@ Puts an egui app inside a native window on your laptop. Paints the triangles tha
|
|||
Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glow](https://github.com/grovesNL/glow).
|
||||
|
||||
### `eframe`
|
||||
`eframe` is the official `egui` framework, built so you can compile the same app for either web or native.
|
||||
A wrapper around `egui_web` + `egui_glium`, so you can compile the same app for either web or native.
|
||||
|
||||
The demo that you can see at <https://www.egui.rs> is using `eframe` to host the `egui`. The demo code is found in:
|
||||
|
||||
### `egui_demo_lib`
|
||||
Depends on `egui`.
|
||||
Depends on `egui` + `epi`.
|
||||
This contains a bunch of uses of `egui` and looks like the ui code you would write for an `egui` app.
|
||||
|
||||
### `egui_demo_app`
|
||||
|
|
372
CHANGELOG.md
372
CHANGELOG.md
|
@ -1,235 +1,31 @@
|
|||
# egui changelog
|
||||
All notable changes to the `egui` crate will be documented in this file.
|
||||
|
||||
NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG.md), [`egui-winit`](crates/egui-winit/CHANGELOG.md), [`egui_glium`](crates/egui_glium/CHANGELOG.md), [`egui_glow`](crates/egui_glow/CHANGELOG.md) and [`egui-wgpu`](crates/egui-wgpu/CHANGELOG.md) have their own changelogs!
|
||||
NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.md), [`egui_glium`](egui_glium/CHANGELOG.md), and [`egui_glow`](egui_glow/CHANGELOG.md) have their own changelogs!
|
||||
|
||||
|
||||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08 - Deadlock fix and style customizability
|
||||
* ⚠️ BREAKING: `egui::Context` now use closures for locking ([#2625](https://github.com/emilk/egui/pull/2625)):
|
||||
* `ctx.input().key_pressed(Key::A)` -> `ctx.input(|i| i.key_pressed(Key::A))`
|
||||
* `ui.memory().toggle_popup(popup_id)` -> `ui.memory_mut(|mem| mem.toggle_popup(popup_id))`
|
||||
|
||||
### Added ⭐
|
||||
* Add `Response::drag_started_by` and `Response::drag_released_by` for convenience, similar to `dragged` and `dragged_by` ([#2507](https://github.com/emilk/egui/pull/2507)).
|
||||
* Add `PointerState::*_pressed` to check if the given button was pressed in this frame ([#2507](https://github.com/emilk/egui/pull/2507)).
|
||||
* `Event::Key` now has a `repeat` field that is set to `true` if the event was the result of a key-repeat ([#2435](https://github.com/emilk/egui/pull/2435)).
|
||||
* Add `Slider::drag_value_speed`, which lets you ask for finer precision when dragging the slider value rather than the actual slider.
|
||||
* Add `Memory::any_popup_open`, which returns true if any popup is currently open ([#2464](https://github.com/emilk/egui/pull/2464)).
|
||||
* Add `Plot::clamp_grid` to only show grid where there is data ([#2480](https://github.com/emilk/egui/pull/2480)).
|
||||
* Add `ScrollArea::drag_to_scroll` if you want to turn off that feature.
|
||||
* Add `Response::on_hover_and_drag_cursor`.
|
||||
* Add `Window::default_open` ([#2539](https://github.com/emilk/egui/pull/2539)).
|
||||
* Add `ProgressBar::fill` if you want to set the fill color manually. ([#2618](https://github.com/emilk/egui/pull/2618)).
|
||||
* Add `Button::rounding` to enable round buttons ([#2616](https://github.com/emilk/egui/pull/2616)).
|
||||
* Add `WidgetVisuals::optional_bg_color` - set it to `Color32::TRANSPARENT` to hide button backgrounds ([#2621](https://github.com/emilk/egui/pull/2621)).
|
||||
* Add `Context::screen_rect` and `Context::set_cursor_icon` ([#2625](https://github.com/emilk/egui/pull/2625)).
|
||||
* You can turn off the vertical line left of indented regions with `Visuals::indent_has_left_vline` ([#2636](https://github.com/emilk/egui/pull/2636)).
|
||||
* Add `Response.highlight` to highlight a widget ([#2632](https://github.com/emilk/egui/pull/2632)).
|
||||
* Add `Separator::grow` and `Separator::shrink` ([#2665](https://github.com/emilk/egui/pull/2665)).
|
||||
* Add `Slider::trailing_fill` for trailing color behind the circle like a `ProgressBar` ([#2660](https://github.com/emilk/egui/pull/2660)).
|
||||
|
||||
### Changed 🔧
|
||||
* Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)).
|
||||
* Improved the algorithm for picking the number of decimals to show when hovering values in the `Plot`.
|
||||
* Default `ComboBox` is now controlled with `Spacing::combo_width` ([#2621](https://github.com/emilk/egui/pull/2621)).
|
||||
* `DragValue` and `Slider` now use the proportional font ([#2638](https://github.com/emilk/egui/pull/2638)).
|
||||
* `ScrollArea` is less aggressive about clipping its contents ([#2665](https://github.com/emilk/egui/pull/2665)).
|
||||
* Updated to be compatible with a major breaking change in AccessKit that drastically reduces memory usage when accessibility is enabled ([#2678](https://github.com/emilk/egui/pull/2678)).
|
||||
* Improve `DragValue` behavior ([#2649](https://github.com/emilk/egui/pull/2649), [#2650](https://github.com/emilk/egui/pull/2650), [#2688](https://github.com/emilk/egui/pull/2688), [#2638](https://github.com/emilk/egui/pull/2638)).
|
||||
|
||||
### Fixed 🐛
|
||||
* Trigger `PointerEvent::Released` for drags ([#2507](https://github.com/emilk/egui/pull/2507)).
|
||||
* Expose `TextEdit`'s multiline flag to AccessKit ([#2448](https://github.com/emilk/egui/pull/2448)).
|
||||
* Don't render `\r` (Carriage Return) ([#2452](https://github.com/emilk/egui/pull/2452)).
|
||||
* The `button_padding` style option works closer as expected with image+text buttons now ([#2510](https://github.com/emilk/egui/pull/2510)).
|
||||
* Menus are now moved to fit on the screen.
|
||||
* Fix `Window::pivot` causing windows to move around ([#2694](https://github.com/emilk/egui/pull/2694)).
|
||||
|
||||
|
||||
## 0.20.1 - 2022-12-11 - Fix key-repeat
|
||||
### Changed 🔧
|
||||
* `InputState`: all press functions again include key repeats (like in egui 0.19) ([#2429](https://github.com/emilk/egui/pull/2429)).
|
||||
* Improve the look of thin white lines ([#2437](https://github.com/emilk/egui/pull/2437)).
|
||||
|
||||
### Fixed 🐛
|
||||
* Fix key-repeats for `TextEdit`, `Slider`s, etc ([#2429](https://github.com/emilk/egui/pull/2429)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08 - AccessKit, prettier text, overlapping widgets
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.65.0` ([#2314](https://github.com/emilk/egui/pull/2314)).
|
||||
* ⚠️ BREAKING: egui now expects integrations to do all color blending in gamma space ([#2071](https://github.com/emilk/egui/pull/2071)).
|
||||
* ⚠️ BREAKING: if you have overlapping interactive widgets, only the top widget (last added) will be interactive ([#2244](https://github.com/emilk/egui/pull/2244)).
|
||||
|
||||
### Added ⭐
|
||||
* Added helper functions for animating panels that collapse/expand ([#2190](https://github.com/emilk/egui/pull/2190)).
|
||||
* Added `Context::os/Context::set_os` to query/set what operating system egui believes it is running on ([#2202](https://github.com/emilk/egui/pull/2202)).
|
||||
* Added `Button::shortcut_text` for showing keyboard shortcuts in menu buttons ([#2202](https://github.com/emilk/egui/pull/2202)).
|
||||
* Added `egui::KeyboardShortcut` for showing keyboard shortcuts in menu buttons ([#2202](https://github.com/emilk/egui/pull/2202)).
|
||||
* Texture loading now takes a `TexureOptions` with minification and magnification filters ([#2224](https://github.com/emilk/egui/pull/2224)).
|
||||
* Added `Key::Minus` and `Key::Equals` ([#2239](https://github.com/emilk/egui/pull/2239)).
|
||||
* Added `egui::gui_zoom` module with helpers for scaling the whole GUI of an app ([#2239](https://github.com/emilk/egui/pull/2239)).
|
||||
* You can now put one interactive widget on top of another, and only one will get interaction at a time ([#2244](https://github.com/emilk/egui/pull/2244)).
|
||||
* Added `spacing.menu_margin` for customizing menu spacing ([#2036](https://github.com/emilk/egui/pull/2036))
|
||||
* Added possibility to enable text wrap for the selected text of `egui::ComboBox` ([#2272](https://github.com/emilk/egui/pull/2272))
|
||||
* Added `Area::constrain` and `Window::constrain` which constrains area to the screen bounds ([#2270](https://github.com/emilk/egui/pull/2270)).
|
||||
* Added `Area::pivot` and `Window::pivot` which controls what part of the window to position ([#2303](https://github.com/emilk/egui/pull/2303)).
|
||||
* Added support for [thin space](https://en.wikipedia.org/wiki/Thin_space).
|
||||
* Added optional integration with [AccessKit](https://accesskit.dev/) for implementing platform accessibility APIs ([#2294](https://github.com/emilk/egui/pull/2294)).
|
||||
* Added `panel_fill`, `window_fill` and `window_stroke` to `Visuals` for your theming pleasure ([#2406](https://github.com/emilk/egui/pull/2406)).
|
||||
* Plots:
|
||||
* Allow linking plot cursors ([#1722](https://github.com/emilk/egui/pull/1722)).
|
||||
* Added `Plot::auto_bounds_x/y` and `Plot::reset` ([#2029](https://github.com/emilk/egui/pull/2029)).
|
||||
* Added `PlotUi::translate_bounds` ([#2145](https://github.com/emilk/egui/pull/2145)).
|
||||
* Added `PlotUi::set_plot_bounds` ([#2320](https://github.com/emilk/egui/pull/2320)).
|
||||
* Added `PlotUi::plot_secondary_clicked` ([#2318](https://github.com/emilk/egui/pull/2318)).
|
||||
|
||||
### Changed 🔧
|
||||
* Panels always have a separator line, but no stroke on other sides. Their spacing has also changed slightly ([#2261](https://github.com/emilk/egui/pull/2261)).
|
||||
* Tooltips are only shown when mouse pointer is still ([#2263](https://github.com/emilk/egui/pull/2263)).
|
||||
* Make it slightly easier to click buttons ([#2304](https://github.com/emilk/egui/pull/2304)).
|
||||
* `egui::color` has been renamed `egui::ecolor` ([#2399](https://github.com/emilk/egui/pull/2399)).
|
||||
|
||||
### Fixed 🐛
|
||||
* ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)).
|
||||
* Improve mixed CJK/Latin line-breaking ([#1986](https://github.com/emilk/egui/pull/1986)).
|
||||
* Improved text rendering ([#2071](https://github.com/emilk/egui/pull/2071)).
|
||||
* Constrain menu popups to the screen ([#2191](https://github.com/emilk/egui/pull/2191)).
|
||||
* Less jitter when calling `Context::set_pixels_per_point` ([#2239](https://github.com/emilk/egui/pull/2239)).
|
||||
* Fixed popups and color edit going outside the screen.
|
||||
* Fixed keyboard support in `DragValue` ([#2342](https://github.com/emilk/egui/pull/2342)).
|
||||
* If you nest `ScrollAreas` inside each other, the inner area will now move its scroll bar so it is always visible ([#2371](https://github.com/emilk/egui/pull/2371)).
|
||||
* Ignore key-repeats for `input.key_pressed` ([#2334](https://github.com/emilk/egui/pull/2334), [#2389](https://github.com/emilk/egui/pull/2389)).
|
||||
* Fixed issue with calling `set_pixels_per_point` each frame ([#2352](https://github.com/emilk/egui/pull/2352)).
|
||||
* Fix bug in `ScrollArea::show_rows` ([#2258](https://github.com/emilk/egui/pull/2258)).
|
||||
* Fix bug in `plot::Line::fill` ([#2275](https://github.com/emilk/egui/pull/2275)).
|
||||
* Only emit `changed` events in `radio_value` and `selectable_value` if the value actually changed ([#2343](https://github.com/emilk/egui/pull/2343)).
|
||||
* Fixed sizing bug in `Grid` ([#2384](https://github.com/emilk/egui/pull/2384)).
|
||||
* `ComboBox::width` now correctly sets the outer width ([#2406](https://github.com/emilk/egui/pull/2406)).
|
||||
|
||||
|
||||
## 0.19.0 - 2022-08-20
|
||||
### Added ⭐
|
||||
* Added `*_released` & `*_clicked` methods for `PointerState` ([#1582](https://github.com/emilk/egui/pull/1582)).
|
||||
* Added `PointerButton::Extra1` and `PointerButton::Extra2` ([#1592](https://github.com/emilk/egui/pull/1592)).
|
||||
* Added `egui::hex_color!` to create `Color32`'s from hex strings under the `color-hex` feature ([#1596](https://github.com/emilk/egui/pull/1596)).
|
||||
* Optimized painting of filled circles (e.g. for scatter plots) by 10x or more ([#1616](https://github.com/emilk/egui/pull/1616)).
|
||||
* Added opt-in feature `deadlock_detection` to detect double-lock of mutexes on the same thread ([#1619](https://github.com/emilk/egui/pull/1619)).
|
||||
* Added `InputState::stable_dt`: a more stable estimate for the delta-time in reactive mode ([#1625](https://github.com/emilk/egui/pull/1625)).
|
||||
* You can now specify a texture filter for your textures ([#1636](https://github.com/emilk/egui/pull/1636)).
|
||||
* Added functions keys in `egui::Key` ([#1665](https://github.com/emilk/egui/pull/1665)).
|
||||
* Added support for using `PaintCallback` shapes with the WGPU backend ([#1684](https://github.com/emilk/egui/pull/1684)).
|
||||
* Added `Context::request_repaint_after` ([#1694](https://github.com/emilk/egui/pull/1694)).
|
||||
* `ctrl-h` now acts like backspace in `TextEdit` ([#1812](https://github.com/emilk/egui/pull/1812)).
|
||||
* Added `custom_formatter` method for `Slider` and `DragValue` ([#1851](https://github.com/emilk/egui/issues/1851)).
|
||||
* Added `RawInput::has_focus` which backends can set to indicate whether the UI as a whole has the keyboard focus ([#1859](https://github.com/emilk/egui/pull/1859)).
|
||||
* Added `PointerState::button_double_clicked()` and `PointerState::button_triple_clicked()` ([#1906](https://github.com/emilk/egui/issues/1906)).
|
||||
* Added `custom_formatter`, `binary`, `octal`, and `hexadecimal` to `DragValue` and `Slider` ([#1953](https://github.com/emilk/egui/issues/1953))
|
||||
|
||||
### Changed 🔧
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
|
||||
* `PaintCallback` shapes now require the whole callback to be put in an `Arc<dyn Any>` with the value being a backend-specific callback type ([#1684](https://github.com/emilk/egui/pull/1684)).
|
||||
* Replaced `needs_repaint` in `FullOutput` with `repaint_after`. Used to force repaint after the set duration in reactive mode ([#1694](https://github.com/emilk/egui/pull/1694)).
|
||||
* `Layout::left_to_right` and `Layout::right_to_left` now takes the vertical align as an argument. Previous default was `Align::Center`.
|
||||
* Improved ergonomics of adding plot items. All plot items that take a series of 2D coordinates can now be created directly from `Vec<[f64; 2]>`. The `Value` and `Values` types were removed in favor of `PlotPoint` and `PlotPoints` respectively ([#1816](https://github.com/emilk/egui/pull/1816)).
|
||||
* `TextBuffer` no longer needs to implement `AsRef<str>` ([#1824](https://github.com/emilk/egui/pull/1824)).
|
||||
|
||||
### Fixed 🐛
|
||||
* Fixed `Response::changed` for `ui.toggle_value` ([#1573](https://github.com/emilk/egui/pull/1573)).
|
||||
* Fixed `ImageButton`'s changing background padding on hover ([#1595](https://github.com/emilk/egui/pull/1595)).
|
||||
* Fixed `Plot` auto-bounds bug ([#1599](https://github.com/emilk/egui/pull/1599)).
|
||||
* Fixed dead-lock when alt-tabbing while also showing a tooltip ([#1618](https://github.com/emilk/egui/pull/1618)).
|
||||
* Fixed `ScrollArea` scrolling when editing an unrelated `TextEdit` ([#1779](https://github.com/emilk/egui/pull/1779)).
|
||||
* Fixed `Slider` not always generating events on change ([#1854](https://github.com/emilk/egui/pull/1854)).
|
||||
* Fixed jitter of anchored windows for the first frame ([#1856](https://github.com/emilk/egui/pull/1856)).
|
||||
* Fixed focus behavior when pressing Tab in a UI with no focused widget ([#1861](https://github.com/emilk/egui/pull/1861)).
|
||||
* Fixed automatic plot bounds ([#1865](https://github.com/emilk/egui/pull/1865)).
|
||||
|
||||
|
||||
## 0.18.1 - 2022-05-01
|
||||
* Change `Shape::Callback` from `&dyn Any` to `&mut dyn Any` to support more backends.
|
||||
|
||||
|
||||
## 0.18.0 - 2022-04-30
|
||||
|
||||
### Added ⭐
|
||||
* Added `Shape::Callback` for backend-specific painting, [with an example](https://github.com/emilk/egui/tree/master/examples/custom_3d_glow) ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||
* Added `Shape::Callback` for backend-specific painting, [with an example](https://github.com/emilk/egui/blob/master/eframe/examples/custom_3d_three-d.rs) ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||
* Added `Frame::canvas` ([#1362](https://github.com/emilk/egui/pull/1362)).
|
||||
* `Context::request_repaint` will now wake up UI thread, if integrations has called `Context::set_request_repaint_callback` ([#1366](https://github.com/emilk/egui/pull/1366)).
|
||||
* Added `Plot::allow_scroll`, `Plot::allow_zoom` no longer affects scrolling ([#1382](https://github.com/emilk/egui/pull/1382)).
|
||||
* Added `Ui::push_id` to resolve id clashes ([#1374](https://github.com/emilk/egui/pull/1374)).
|
||||
* Added `ComboBox::icon` ([#1405](https://github.com/emilk/egui/pull/1405)).
|
||||
* Added `Ui::scroll_with_delta`.
|
||||
* `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`.
|
||||
* Added `Painter::hline` and `Painter::vline`.
|
||||
* Added `Link` and `ui.link` ([#1506](https://github.com/emilk/egui/pull/1506)).
|
||||
* Added triple-click support; triple-clicking a TextEdit field will select the whole paragraph ([#1512](https://github.com/emilk/egui/pull/1512)).
|
||||
* Added `Plot::x_grid_spacer` and `Plot::y_grid_spacer` for custom grid spacing ([#1180](https://github.com/emilk/egui/pull/1180)).
|
||||
* Added `Ui::spinner()` shortcut method ([#1494](https://github.com/emilk/egui/pull/1494)).
|
||||
* Added `CursorIcon`s for resizing columns, rows, and the eight cardinal directions.
|
||||
* Added `Ui::toggle_value`.
|
||||
* Added ability to add any widgets to the header of a collapsing region ([#1538](https://github.com/emilk/egui/pull/1538)).
|
||||
|
||||
### Changed 🔧
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* `ClippedMesh` has been replaced with `ClippedPrimitive` ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||
* Renamed `Frame::margin` to `Frame::inner_margin`.
|
||||
* Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)).
|
||||
* Warnings will be painted on screen when there is an `Id` clash for `Grid`, `Plot` or `ScrollArea` ([#1452](https://github.com/emilk/egui/pull/1452)).
|
||||
* `Checkbox` and `RadioButton` with an empty label (`""`) will now take up much less space ([#1456](https://github.com/emilk/egui/pull/1456)).
|
||||
* Replaced `Memory::top_most_layer` with more flexible `Memory::layer_ids`.
|
||||
* Renamed the feature `convert_bytemuck` to `bytemuck` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* Renamed the feature `serialize` to `serde` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* Renamed `Painter::sub_region` to `Painter::with_clip_rect`.
|
||||
|
||||
### Fixed 🐛
|
||||
* Fixed `ComboBox`es 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)).
|
||||
* Text is darker and more readable in bright mode ([#1412](https://github.com/emilk/egui/pull/1412)).
|
||||
* Fixed a lot of broken/missing doclinks ([#1419](https://github.com/emilk/egui/pull/1419)).
|
||||
* Fixed `Ui::add_visible` sometimes leaving the `Ui` in a disabled state ([#1436](https://github.com/emilk/egui/issues/1436)).
|
||||
* Added line breaking rules for Japanese text ([#1498](https://github.com/emilk/egui/pull/1498)).
|
||||
|
||||
### Deprecated ☢️
|
||||
* Deprecated `CollapsingHeader::selectable` ([#1538](https://github.com/emilk/egui/pull/1538)).
|
||||
|
||||
### Removed 🔥
|
||||
* Removed the `single_threaded/multi_threaded` flags - egui is now always thread-safe ([#1390](https://github.com/emilk/egui/pull/1390)).
|
||||
|
||||
### Contributors 🙏
|
||||
* [4JX](https://github.com/4JX)
|
||||
* [AlexxxRu](https://github.com/AlexxxRu)
|
||||
* [ascclemens](https://github.com/ascclemens)
|
||||
* [awaken1ng](https://github.com/awaken1ng)
|
||||
* [bigfarts](https://github.com/bigfarts)
|
||||
* [bobyclaws](https://github.com/bobyclaws)
|
||||
* [Bromeon](https://github.com/Bromeon)
|
||||
* [cloudhead](https://github.com/cloudhead)
|
||||
* [collin-kemper](https://github.com/collin-kemper)
|
||||
* [cpterry](https://github.com/cpterry)
|
||||
* [dbuch](https://github.com/dbuch)
|
||||
* [DusterTheFirst](https://github.com/DusterTheFirst)
|
||||
* [Edgeworth ](https://github.com/Edgeworth )
|
||||
* [elwerene](https://github.com/elwerene)
|
||||
* [follower](https://github.com/follower)
|
||||
* [Friz64](https://github.com/Friz64)
|
||||
* [Hunter522 ](https://github.com/Hunter522 )
|
||||
* [Jake-Shadle](https://github.com/Jake-Shadle)
|
||||
* [jean-airoldie ](https://github.com/jean-airoldie )
|
||||
* [JelNiSlaw](https://github.com/JelNiSlaw)
|
||||
* [juancampa](https://github.com/juancampa)
|
||||
* [LU15W1R7H](https://github.com/LU15W1R7H)
|
||||
* [mbillingr](https://github.com/mbillingr)
|
||||
* [nicklasmoeller](https://github.com/nicklasmoeller)
|
||||
* [rukai](https://github.com/rukai)
|
||||
* [tami5](https://github.com/tami5)
|
||||
* [Titaniumtown](https://github.com/Titaniumtown)
|
||||
* [trevyn](https://github.com/trevyn)
|
||||
* [waynr](https://github.com/waynr)
|
||||
* [zam-5 ](https://github.com/zam-5 )
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22 - Improved font selection and image handling
|
||||
|
||||
|
@ -243,7 +39,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
* Plot:
|
||||
* Added `Plot::x_axis_formatter` and `Plot::y_axis_formatter` for custom axis labels ([#1130](https://github.com/emilk/egui/pull/1130)).
|
||||
* Added `Plot::allow_boxed_zoom()`, `Plot::boxed_zoom_pointer()` for boxed zooming on plots ([#1188](https://github.com/emilk/egui/pull/1188)).
|
||||
* Added plot pointer coordinates with `Plot::coordinates_formatter` ([#1235](https://github.com/emilk/egui/pull/1235)).
|
||||
* Added plot pointer coordinates with `Plot::coordinates_formatter`. ([#1235](https://github.com/emilk/egui/pull/1235)).
|
||||
* Added linked axis support for plots via `plot::LinkedAxisGroup` ([#1184](https://github.com/emilk/egui/pull/1184)).
|
||||
* `Context::load_texture` to convert an image into a texture which can be displayed using e.g. `ui.image(texture, size)` ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
* `Ui::input_mut` to modify how subsequent widgets see the `InputState` and a convenience method `InputState::consume_key` for shortcuts or hotkeys ([#1212](https://github.com/emilk/egui/pull/1212)).
|
||||
|
@ -277,7 +73,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
* Replaced Frame's `margin: Vec2` with `margin: Margin`, allowing for different margins on opposing sides ([#1219](https://github.com/emilk/egui/pull/1219)).
|
||||
* Renamed `Plot::custom_label_func` to `Plot::label_formatter` ([#1235](https://github.com/emilk/egui/pull/1235)).
|
||||
* `Areas::layer_id_at` ignores non-interatable layers (i.e. Tooltips) ([#1240](https://github.com/emilk/egui/pull/1240)).
|
||||
* `ScrollArea`s will not shrink below a certain minimum size, set by `min_scrolled_width/min_scrolled_height` ([1255](https://github.com/emilk/egui/pull/1255)).
|
||||
* `ScrollArea`:s will not shrink below a certain minimum size, set by `min_scrolled_width/min_scrolled_height` ([1255](https://github.com/emilk/egui/pull/1255)).
|
||||
* For integrations:
|
||||
* `Output` has now been renamed `PlatformOutput` and `Context::run` now returns the new `FullOutput` ([#1292](https://github.com/emilk/egui/pull/1292)).
|
||||
* `FontImage` has been replaced by `TexturesDelta` (found in `FullOutput`), describing what textures were loaded and freed each frame ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
|
@ -326,7 +122,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
## 0.16.1 - 2021-12-31 - Add back `CtxRef::begin_frame,end_frame`
|
||||
|
||||
### Added ⭐
|
||||
* Added back `CtxRef::begin_frame,end_frame` as an alternative to `CtxRef::run`.
|
||||
* Add back `CtxRef::begin_frame,end_frame` as an alternative to `CtxRef::run`.
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29 - Context menus and rich text
|
||||
|
@ -385,36 +181,36 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
<img src="media/egui-0.15-code-editor.gif">
|
||||
|
||||
### Added ⭐
|
||||
* Added horizontal scrolling support to `ScrollArea` and `Window` (opt-in).
|
||||
* Add horizontal scrolling support to `ScrollArea` and `Window` (opt-in).
|
||||
* `TextEdit::layouter`: Add custom text layout for e.g. syntax highlighting or WYSIWYG.
|
||||
* `Fonts::layout_job`: New text layout engine allowing mixing fonts, colors and styles, with underlining and strikethrough.
|
||||
* Added `ui.add_enabled(bool, widget)` to easily add a possibly disabled widget.
|
||||
* Added `ui.add_enabled_ui(bool, |ui| …)` to create a possibly disabled UI section.
|
||||
* Added feature `"serialize"` separatedly from `"persistence"`.
|
||||
* Added `egui::widgets::global_dark_light_mode_buttons` to easily add buttons for switching the egui theme.
|
||||
* Add `ui.add_enabled(bool, widget)` to easily add a possibly disabled widget.
|
||||
* Add `ui.add_enabled_ui(bool, |ui| …)` to create a possibly disabled UI section.
|
||||
* Add feature `"serialize"` separatedly from `"persistence"`.
|
||||
* Add `egui::widgets::global_dark_light_mode_buttons` to easily add buttons for switching the egui theme.
|
||||
* `TextEdit` can now be used to show text which can be selected and copied, but not edited.
|
||||
* Added `Memory::caches` for caching things from one frame to the next.
|
||||
* Add `Memory::caches` for caching things from one frame to the next.
|
||||
|
||||
### Changed 🔧
|
||||
* Change the default monospace font to [Hack](https://github.com/source-foundry/Hack).
|
||||
* Label text will now be centered, right-aligned and/or justified based on the layout of the `Ui` it is in.
|
||||
* `Hyperlink` will now word-wrap just like a `Label`.
|
||||
* All `Ui`s must now have a finite `max_rect`.
|
||||
* All `Ui`:s must now have a finite `max_rect`.
|
||||
* Deprecated: `max_rect_finite`, `available_size_before_wrap_finite` and `available_rect_before_wrap_finite`.
|
||||
* `Painter`/`Fonts`: text layout now expect a color when creating a `Galley`. You may override that color with `Painter::galley_with_color`.
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.54.0`.
|
||||
* By default, `DragValue`s no longer show a tooltip when hovered. Change with `Style::explanation_tooltips`.
|
||||
* By default, `DragValue`:s no longer show a tooltip when hovered. Change with `Style::explanation_tooltips`.
|
||||
* Smaller and nicer color picker.
|
||||
* `ScrollArea` will auto-shrink to content size unless told otherwise using `ScollArea::auto_shrink`.
|
||||
* By default, `Slider`'s `clamp_to_range` is set to true.
|
||||
* Renamed `TextEdit::enabled` to `TextEdit::interactive`.
|
||||
* Rename `TextEdit::enabled` to `TextEdit::interactive`.
|
||||
* `ui.label` (and friends) now take `impl ToString` as argument instead of `impl Into<Label>`.
|
||||
|
||||
### Fixed 🐛
|
||||
* Fixed wrongly sized multiline `TextEdit` in justified layouts.
|
||||
* Fixed clip rectangle of windows that don't fit the central area.
|
||||
* Fix wrongly sized multiline `TextEdit` in justified layouts.
|
||||
* Fix clip rectangle of windows that don't fit the central area.
|
||||
* Show tooltips above widgets on touch screens.
|
||||
* Fixed popups sometimes getting clipped by panels.
|
||||
* Fix popups sometimes getting clipped by panels.
|
||||
|
||||
### Removed 🔥
|
||||
* Replace `Button::enabled` with `ui.add_enabled`.
|
||||
|
@ -439,20 +235,20 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
## 0.14.2 - 2021-08-28 - Window resize fix
|
||||
|
||||
### Fixed 🐛
|
||||
* Fixed window resize bug introduced in `0.14.1`.
|
||||
* Fix window resize bug introduced in `0.14.1`.
|
||||
|
||||
|
||||
## 0.14.1 - 2021-08-28 - Layout bug fixes
|
||||
|
||||
### Added ⭐
|
||||
* Added `Ui::horizontal_top`.
|
||||
* Add `Ui::horizontal_top`.
|
||||
|
||||
### Fixed 🐛
|
||||
* Fixed `set_width/set_min_width/set_height/set_min_height/expand_to_include_x/expand_to_include_y`.
|
||||
* Fix `set_width/set_min_width/set_height/set_min_height/expand_to_include_x/expand_to_include_y`.
|
||||
* Make minimum grid column width propagate properly.
|
||||
* Make sure `TextEdit` contents expand to fill width if applicable.
|
||||
* `ProgressBar`: add a minimum width and fix for having it in an infinite layout.
|
||||
* Fixed sometimes not being able to click inside a combo box or popup menu.
|
||||
* Fix sometimes not being able to click inside a combo box or popup menu.
|
||||
|
||||
|
||||
## 0.14.0 - 2021-08-24 - Ui panels and bug fixes
|
||||
|
@ -461,10 +257,10 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
* Panels can now be added to any `Ui`.
|
||||
* Plot:
|
||||
* [Line styles](https://github.com/emilk/egui/pull/482).
|
||||
* Added `show_background` and `show_axes` methods to `Plot`.
|
||||
* Add `show_background` and `show_axes` methods to `Plot`.
|
||||
* [Progress bar](https://github.com/emilk/egui/pull/519).
|
||||
* `Grid::num_columns`: allow the last column to take up the rest of the space of the parent `Ui`.
|
||||
* Added an API for dropping files into egui (see `RawInput`).
|
||||
* Add an API for dropping files into egui (see `RawInput`).
|
||||
* `CollapsingHeader` can now optionally be selectable.
|
||||
|
||||
### Changed 🔧
|
||||
|
@ -474,12 +270,12 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
* Tooltips are now moved to not cover the widget they are attached to.
|
||||
|
||||
### Fixed 🐛
|
||||
* Fixed custom font definitions getting replaced when `pixels_per_point` is changed.
|
||||
* Fixed `lost_focus` for `TextEdit`.
|
||||
* Fix custom font definitions getting replaced when `pixels_per_point` is changed.
|
||||
* Fix `lost_focus` for `TextEdit`.
|
||||
* Clicking the edge of a menu button will now properly open the menu.
|
||||
* Fixed hover detection close to an `Area`.
|
||||
* Fixed case where `Plot`'s `min_auto_bounds` could be ignored after the first call to `Plot::ui`.
|
||||
* Fixed slow startup when using large font files.
|
||||
* Fix hover detection close to an `Area`.
|
||||
* Fix case where `Plot`'s `min_auto_bounds` could be ignored after the first call to `Plot::ui`.
|
||||
* Fix slow startup when using large font files.
|
||||
|
||||
### Contributors 🙏
|
||||
* [barrowsys](https://github.com/barrowsys)
|
||||
|
@ -511,19 +307,19 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
* [Plot legend improvements](https://github.com/emilk/egui/pull/410).
|
||||
* [Line markers for plots](https://github.com/emilk/egui/pull/363).
|
||||
* Panels:
|
||||
* Added right and bottom panels (`SidePanel::right` and `Panel::bottom`).
|
||||
* Add right and bottom panels (`SidePanel::right` and `Panel::bottom`).
|
||||
* Panels can now be resized.
|
||||
* Added an option to overwrite frame of a `Panel`.
|
||||
* Add an option to overwrite frame of a `Panel`.
|
||||
* [Improve accessibility / screen reader](https://github.com/emilk/egui/pull/412).
|
||||
* Added `ScrollArea::show_rows` for efficient scrolling of huge UI:s.
|
||||
* Added `ScrollArea::enable_scrolling` to allow freezing scrolling when editing TextEdit widgets within it
|
||||
* Added `Ui::set_visible` as a way to hide widgets.
|
||||
* Added `Style::override_text_style` to easily change the text style of everything in a `Ui` (or globally).
|
||||
* Add `ScrollArea::show_rows` for efficient scrolling of huge UI:s.
|
||||
* Add `ScrollArea::enable_scrolling` to allow freezing scrolling when editing TextEdit widgets within it
|
||||
* Add `Ui::set_visible` as a way to hide widgets.
|
||||
* Add `Style::override_text_style` to easily change the text style of everything in a `Ui` (or globally).
|
||||
* You can now change `TextStyle` on checkboxes, radio buttons and `SelectableLabel`.
|
||||
* Added support for [cint](https://crates.io/crates/cint) under `cint` feature.
|
||||
* Added features `extra_asserts` and `extra_debug_asserts` to enable additional checks.
|
||||
* Add support for [cint](https://crates.io/crates/cint) under `cint` feature.
|
||||
* Add features `extra_asserts` and `extra_debug_asserts` to enable additional checks.
|
||||
* `TextEdit` now supports edits on a generic buffer using `TextBuffer`.
|
||||
* Added `Context::set_debug_on_hover` and `egui::trace!(ui)`
|
||||
* Add `Context::set_debug_on_hover` and `egui::trace!(ui)`
|
||||
|
||||
### Changed 🔧
|
||||
* Minimum Rust version is now 1.51 (used to be 1.52)
|
||||
|
@ -534,36 +330,36 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
* `SidePanel::left` is resizable by default.
|
||||
|
||||
### Fixed 🐛
|
||||
* Fixed uneven lettering on non-integral device scales ("extortion lettering").
|
||||
* Fixed invisible scroll bar when native window is too narrow for egui.
|
||||
* Fix uneven lettering on non-integral device scales ("extortion lettering").
|
||||
* Fix invisible scroll bar when native window is too narrow for egui.
|
||||
|
||||
|
||||
## 0.12.0 - 2021-05-10 - Multitouch, user memory, window pivots, and improved plots
|
||||
|
||||
### Added ⭐
|
||||
* Added anchors to windows and areas so you can put a window in e.g. the top right corner.
|
||||
* Add anchors to windows and areas so you can put a window in e.g. the top right corner.
|
||||
* Make labels interactive with `Label::sense(Sense::click())`.
|
||||
* Added `Response::request_focus` and `Response::surrender_focus`.
|
||||
* Added `TextEdit::code_editor` (VERY basic).
|
||||
* Add `Response::request_focus` and `Response::surrender_focus`.
|
||||
* Add `TextEdit::code_editor` (VERY basic).
|
||||
* [Pan and zoom plots](https://github.com/emilk/egui/pull/317).
|
||||
* [Add plot legends](https://github.com/emilk/egui/pull/349).
|
||||
* [Users can now store custom state in `egui::Memory`](https://github.com/emilk/egui/pull/257).
|
||||
* Added `Response::on_disabled_hover_text` to show tooltip for disabled widgets.
|
||||
* Zoom input: ctrl-scroll and (on `eframe` web) trackpad-pinch gesture.
|
||||
* Add `Response::on_disabled_hover_text` to show tooltip for disabled widgets.
|
||||
* Zoom input: ctrl-scroll and (on `egui_web`) trackpad-pinch gesture.
|
||||
* Support for raw [multi touch](https://github.com/emilk/egui/pull/306) events,
|
||||
enabling zoom, rotate, and more. Works with `eframe` web on mobile devices,
|
||||
enabling zoom, rotate, and more. Works with `egui_web` on mobile devices,
|
||||
and should work with `egui_glium` for certain touch devices/screens.
|
||||
* Added (optional) compatibility with [mint](https://docs.rs/mint).
|
||||
* Add (optional) compatibility with [mint](https://docs.rs/mint).
|
||||
|
||||
### Changed 🔧
|
||||
* Make `Memory::has_focus` public (again).
|
||||
* `Plot` must now be given a name that is unique within its scope.
|
||||
* Tab only selects labels if the `screen_reader` option is turned on.
|
||||
* Renamed `ui.wrap` to `ui.scope`.
|
||||
* Rename `ui.wrap` to `ui.scope`.
|
||||
|
||||
### Fixed 🐛
|
||||
* Fixed [defocus-bug on touch screens](https://github.com/emilk/egui/issues/288).
|
||||
* Fixed bug with the layout of wide `DragValue`s.
|
||||
* Fix [defocus-bug on touch screens](https://github.com/emilk/egui/issues/288).
|
||||
* Fix bug with the layout of wide `DragValue`:s.
|
||||
|
||||
### Removed 🔥
|
||||
* Moved experimental markup language to `egui_demo_lib`
|
||||
|
@ -577,24 +373,24 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
* Use arrow keys to adjust sliders and `DragValue`s.
|
||||
* egui will now output events when widgets gain keyboard focus.
|
||||
* This can be hooked up to a screen reader to aid the visually impaired
|
||||
* Added the option to restrict the dragging bounds of `Window` and `Area` to a specified area using `drag_bounds(rect)`.
|
||||
* Added support for small and raised text.
|
||||
* Added `ui.set_row_height`.
|
||||
* Added `DebugOptions::show_widgets` to debug layouting by hovering widgets.
|
||||
* Added `ComboBox` to more easily customize combo boxes.
|
||||
* Added `Slider::new` and `DragValue::new` to replace old type-specific constructors.
|
||||
* Added `TextEdit::password` to hide input characters.
|
||||
* Add the option to restrict the dragging bounds of `Window` and `Area` to a specified area using `drag_bounds(rect)`.
|
||||
* Add support for small and raised text.
|
||||
* Add `ui.set_row_height`.
|
||||
* Add `DebugOptions::show_widgets` to debug layouting by hovering widgets.
|
||||
* Add `ComboBox` to more easily customize combo boxes.
|
||||
* Add `Slider::new` and `DragValue::new` to replace old type-specific constructors.
|
||||
* Add `TextEdit::password` to hide input characters.
|
||||
|
||||
### Changed 🔧
|
||||
* `ui.advance_cursor` is now called `ui.add_space`.
|
||||
* `kb_focus` is now just called `focus`.
|
||||
|
||||
### Fixed 🐛
|
||||
* Fixed some bugs related to centered layouts.
|
||||
* Fix some bugs related to centered layouts.
|
||||
* Fixed secondary-click to open a menu.
|
||||
* [Fix panic for zero-range sliders and zero-speed drag values](https://github.com/emilk/egui/pull/216).
|
||||
* Fixed false id clash error for wrapping text.
|
||||
* Fixed bug that would close a popup (e.g. the color picker) when clicking inside of it.
|
||||
* Fix false id clash error for wrapping text.
|
||||
* Fix bug that would close a popup (e.g. the color picker) when clicking inside of it.
|
||||
|
||||
### Deprecated ☢️
|
||||
* Deprectated `combo_box_with_label` in favor of new `ComboBox`.
|
||||
|
@ -606,12 +402,12 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
<img src="media/egui-0.10-plot.gif" width="50%">
|
||||
|
||||
### Added ⭐
|
||||
* Added `egui::plot::Plot` to plot some 2D data.
|
||||
* Added `Ui::hyperlink_to(label, url)`.
|
||||
* Add `egui::plot::Plot` to plot some 2D data.
|
||||
* Add `Ui::hyperlink_to(label, url)`.
|
||||
* Sliders can now have a value prefix and suffix (e.g. the suffix `"°"` works like a unit).
|
||||
* `Context::set_pixels_per_point` to control the scale of the UI.
|
||||
* Added `Response::changed()` to query if e.g. a slider was dragged, text was entered or a checkbox was clicked.
|
||||
* Added support for all integers in `DragValue` and `Slider` (except 128-bit).
|
||||
* Add `Response::changed()` to query if e.g. a slider was dragged, text was entered or a checkbox was clicked.
|
||||
* Add support for all integers in `DragValue` and `Slider` (except 128-bit).
|
||||
|
||||
### Changed 🔧
|
||||
* Improve the positioning of tooltips.
|
||||
|
@ -625,18 +421,18 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
<img src="media/0.9.0-disabled.gif" width="50%">
|
||||
|
||||
### Added ⭐
|
||||
* Added support for secondary and middle mouse buttons.
|
||||
* Added `Label` methods for code, strong, strikethrough, underline and italics.
|
||||
* Added `ui.group(|ui| { … })` to visually group some widgets within a frame.
|
||||
* Added `Ui` helpers for doing manual layout (`ui.put`, `ui.allocate_ui_at_rect` and more).
|
||||
* Added `ui.set_enabled(false)` to disable all widgets in a `Ui` (grayed out and non-interactive).
|
||||
* Added `TextEdit::hint_text` for showing a weak hint text when empty.
|
||||
* Add support for secondary and middle mouse buttons.
|
||||
* Add `Label` methods for code, strong, strikethrough, underline and italics.
|
||||
* Add `ui.group(|ui| { … })` to visually group some widgets within a frame.
|
||||
* Add `Ui` helpers for doing manual layout (`ui.put`, `ui.allocate_ui_at_rect` and more).
|
||||
* Add `ui.set_enabled(false)` to disable all widgets in a `Ui` (grayed out and non-interactive).
|
||||
* Add `TextEdit::hint_text` for showing a weak hint text when empty.
|
||||
* `egui::popup::popup_below_widget`: show a popup area below another widget.
|
||||
* Added `Slider::clamp_to_range(bool)`: if set, clamp the incoming and outgoing values to the slider range.
|
||||
* Add `Slider::clamp_to_range(bool)`: if set, clamp the incoming and outgoing values to the slider range.
|
||||
* Add: `ui.spacing()`, `ui.spacing_mut()`, `ui.visuals()`, `ui.visuals_mut()`.
|
||||
* Add: `ctx.set_visuals()`.
|
||||
* You can now control text wrapping with `Style::wrap`.
|
||||
* Added `Grid::max_col_width`.
|
||||
* Add `Grid::max_col_width`.
|
||||
|
||||
### Changed 🔧
|
||||
* Text will now wrap at newlines, spaces, dashes, punctuation or in the middle of a words if necessary, in that order of priority.
|
||||
|
@ -668,9 +464,9 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
|
||||
### Changed 🔧
|
||||
* New simpler and sleeker look!
|
||||
* Renamed `PaintCmd` to `Shape`.
|
||||
* Rename `PaintCmd` to `Shape`.
|
||||
* Replace tuple `(Rect, Shape)` with tuple-struct `ClippedShape`.
|
||||
* Renamed feature `"serde"` to `"persistence"`.
|
||||
* Rename feature `"serde"` to `"persistence"`.
|
||||
* Break out the modules `math` and `paint` into separate crates `emath` and `epaint`.
|
||||
|
||||
### Fixed 🐛
|
||||
|
@ -681,8 +477,8 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
## 0.7.0 - 2021-01-04
|
||||
|
||||
### Added ⭐
|
||||
* Added `ui.scroll_to_cursor` and `response.scroll_to_me` ([#81](https://github.com/emilk/egui/pull/81) by [lucaspoffo](https://github.com/lucaspoffo)).
|
||||
* Added `window.id(…)` and `area.id(…)` for overriding the default `Id`.
|
||||
* Add `ui.scroll_to_cursor` and `response.scroll_to_me` ([#81](https://github.com/emilk/egui/pull/81) by [lucaspoffo](https://github.com/lucaspoffo)).
|
||||
* Add `window.id(…)` and `area.id(…)` for overriding the default `Id`.
|
||||
|
||||
### Changed 🔧
|
||||
* Renamed `Srgba` to `Color32`.
|
||||
|
@ -708,10 +504,10 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
* Mouse-over explanation to duplicate ID warning.
|
||||
* You can now easily constrain egui to a portion of the screen using `RawInput::screen_rect`.
|
||||
* You can now control the minimum and maixumum number of decimals to show in a `Slider` or `DragValue`.
|
||||
* Added `egui::math::Rot2`: rotation helper.
|
||||
* Add `egui::math::Rot2`: rotation helper.
|
||||
* `Response` now contains the `Id` of the widget it pertains to.
|
||||
* `ui.allocate_response` that allocates space and checks for interactions.
|
||||
* Added `response.interact(sense)`, e.g. to check for clicks on labels.
|
||||
* Add `response.interact(sense)`, e.g. to check for clicks on labels.
|
||||
|
||||
### Changed 🔧
|
||||
* `ui.allocate_space` now returns an `(Id, Rect)` tuple.
|
||||
|
@ -726,7 +522,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
* Combo boxes has scroll bars when needed.
|
||||
* Expand `Window` + `Resize` containers to be large enough for last frames content
|
||||
* `ui.columns`: Columns now defaults to justified top-to-down layouts.
|
||||
* Renamed `Sense::nothing()` to `Sense::hover()`.
|
||||
* Rename `Sense::nothing()` to `Sense::hover()`.
|
||||
* Replaced `parking_lot` dependency with `atomic_refcell` by default.
|
||||
|
||||
### Fixed 🐛
|
||||
|
@ -752,7 +548,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
* `SelectableLabel` (`ui.selectable_label` and `ui.selectable_value`): A text-button that can be selected.
|
||||
* `ui.small_button`: A smaller button that looks good embedded in text.
|
||||
* `ui.drag_angle_tau`: For those who want to specify angles as fractions of τ (a full turn).
|
||||
* Added `Resize::id_source` and `ScrollArea::id_source` to let the user avoid Id clashes.
|
||||
* Add `Resize::id_source` and `ScrollArea::id_source` to let the user avoid Id clashes.
|
||||
|
||||
### Changed 🔧
|
||||
* New default font: [Ubuntu-Light](https://fonts.google.com/specimen/Ubuntu).
|
||||
|
@ -795,7 +591,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
### Added ⭐
|
||||
* Panels: you can now create panels using `SidePanel`, `TopPanel` and `CentralPanel`.
|
||||
* You can now override the default egui fonts.
|
||||
* Added ability to override text color with `visuals.override_text_color`.
|
||||
* Add ability to override text color with `visuals.override_text_color`.
|
||||
* The demo now includes a simple drag-and-drop example.
|
||||
* The demo app now has a slider to scale all of egui.
|
||||
|
||||
|
@ -812,7 +608,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
* You can no longer throw windows.
|
||||
|
||||
### Fixed 🐛
|
||||
* Fixed a bug where some regions would slowly grow for non-integral scales (`pixels_per_point`).
|
||||
* Fix a bug where some regions would slowly grow for non-integral scales (`pixels_per_point`).
|
||||
|
||||
|
||||
## 0.2.0 - 2020-10-10
|
||||
|
|
|
@ -59,44 +59,15 @@ Conventions unless otherwise specified:
|
|||
|
||||
While using an immediate mode gui is simple, implementing one is a lot more tricky. There are many subtle corner-case you need to think through. The `egui` source code is a bit messy, partially because it is still evolving.
|
||||
|
||||
* Read some code before writing your own.
|
||||
* Follow the `egui` code style.
|
||||
* Add blank lines around all `fn`, `struct`, `enum`, etc.
|
||||
* `// Comment like this.` and not `//like this`.
|
||||
* Use `TODO` instead of `FIXME`.
|
||||
* Add your github handle to the `TODO`:s you write, e.g: `TODO(emilk): clean this up`.
|
||||
* Write idiomatic rust.
|
||||
* Avoid `unsafe`.
|
||||
* Avoid code that can cause panics.
|
||||
* Use good names for everything.
|
||||
* Add docstrings to types, `struct` fields and all `pub fn`.
|
||||
* Add some example code (doc-tests).
|
||||
* Before making a function longer, consider adding a helper function.
|
||||
* If you are only using it in one function, put the `use` statement in that function. This improves locality, making it easier to read and move the code.
|
||||
* When importing a `trait` to use it's trait methods, do this: `use Trait as _;`. That lets the reader know why you imported it, even though it seems unused.
|
||||
* Follow the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/).
|
||||
* Break the above rules when it makes sense.
|
||||
|
||||
|
||||
### Good:
|
||||
``` rust
|
||||
/// The name of the thing.
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn foo(&self) {
|
||||
// TODO(emilk): implement
|
||||
}
|
||||
```
|
||||
|
||||
### Bad:
|
||||
``` rust
|
||||
//some function
|
||||
fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
fn foo(&self) {
|
||||
//FIXME: implement
|
||||
}
|
||||
```
|
||||
* read some code before writing your own
|
||||
* follow the `egui` code style
|
||||
* add blank lines around all `fn`, `struct`, `enum`, etc.
|
||||
* `// Comment like this`, not `//like this`
|
||||
* write idiomatic rust
|
||||
* avoid `unsafe`
|
||||
* avoid code that can cause panics
|
||||
* use good names for everything
|
||||
* add docstrings to types, `struct` fields and all `pub fn`.
|
||||
* add some example code (doc-tests)
|
||||
* before making a function longer, consider adding a helper function
|
||||
* break the above rules when it makes sense
|
||||
|
|
3205
Cargo.lock
generated
3205
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
36
Cargo.toml
36
Cargo.toml
|
@ -1,20 +1,22 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"crates/ecolor",
|
||||
"crates/egui_demo_app",
|
||||
"crates/egui_demo_lib",
|
||||
"crates/egui_extras",
|
||||
"crates/egui_glow",
|
||||
"crates/egui-wgpu",
|
||||
"crates/egui-winit",
|
||||
"crates/egui",
|
||||
"crates/emath",
|
||||
"crates/epaint",
|
||||
|
||||
"examples/*",
|
||||
"egui_demo_app",
|
||||
"egui_demo_lib",
|
||||
"egui_extras",
|
||||
"egui_glium",
|
||||
"egui_glow",
|
||||
"egui_web",
|
||||
"egui-winit",
|
||||
"egui",
|
||||
"emath",
|
||||
"epaint",
|
||||
"epi",
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked" # faster debug builds on mac
|
||||
|
||||
[profile.release]
|
||||
# lto = true # VERY slightly smaller wasm
|
||||
# opt-level = 's' # 10-20% smaller wasm compared to `opt-level = 3`
|
||||
|
@ -23,13 +25,3 @@ opt-level = 2 # fast and small wasm, basically same as `opt-level = 's'`
|
|||
# opt-level = 3 # unecessarily large wasm for no performance gain
|
||||
|
||||
# debug = true # include debug symbols, useful when profiling wasm
|
||||
|
||||
|
||||
[profile.dev]
|
||||
# Can't leave this on by default, because it breaks the Windows build. Related: https://github.com/rust-lang/cargo/issues/4897
|
||||
# split-debuginfo = "unpacked" # faster debug builds on mac
|
||||
# opt-level = 1 # Make debug builds run faster
|
||||
|
||||
# Optimize all dependencies even in debug builds (does not affect workspace packages):
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 2
|
||||
|
|
124
Cranky.toml
124
Cranky.toml
|
@ -1,124 +0,0 @@
|
|||
# https://github.com/ericseppanen/cargo-cranky
|
||||
# cargo install cargo-cranky && cargo cranky
|
||||
|
||||
deny = ["unsafe_code"]
|
||||
|
||||
warn = [
|
||||
"clippy::all",
|
||||
"clippy::await_holding_lock",
|
||||
"clippy::bool_to_int_with_if",
|
||||
"clippy::char_lit_as_u8",
|
||||
"clippy::checked_conversions",
|
||||
"clippy::cloned_instead_of_copied",
|
||||
"clippy::dbg_macro",
|
||||
"clippy::debug_assert_with_mut_call",
|
||||
"clippy::derive_partial_eq_without_eq",
|
||||
"clippy::disallowed_methods",
|
||||
"clippy::disallowed_script_idents",
|
||||
"clippy::doc_link_with_quotes",
|
||||
"clippy::doc_markdown",
|
||||
"clippy::empty_enum",
|
||||
"clippy::enum_glob_use",
|
||||
"clippy::equatable_if_let",
|
||||
"clippy::exit",
|
||||
"clippy::expl_impl_clone_on_copy",
|
||||
"clippy::explicit_deref_methods",
|
||||
"clippy::explicit_into_iter_loop",
|
||||
"clippy::explicit_iter_loop",
|
||||
"clippy::fallible_impl_from",
|
||||
"clippy::filter_map_next",
|
||||
"clippy::flat_map_option",
|
||||
"clippy::float_cmp_const",
|
||||
"clippy::fn_params_excessive_bools",
|
||||
"clippy::fn_to_numeric_cast_any",
|
||||
"clippy::from_iter_instead_of_collect",
|
||||
"clippy::if_let_mutex",
|
||||
"clippy::implicit_clone",
|
||||
"clippy::imprecise_flops",
|
||||
"clippy::index_refutable_slice",
|
||||
"clippy::inefficient_to_string",
|
||||
"clippy::invalid_upcast_comparisons",
|
||||
"clippy::iter_not_returning_iterator",
|
||||
"clippy::iter_on_empty_collections",
|
||||
"clippy::iter_on_single_items",
|
||||
"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_assert",
|
||||
"clippy::manual_instant_elapsed",
|
||||
"clippy::manual_ok_or",
|
||||
"clippy::manual_string_new",
|
||||
"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::mismatching_type_param_order",
|
||||
"clippy::missing_enforced_import_renames",
|
||||
"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::negative_feature_names",
|
||||
"clippy::nonstandard_macro_braces",
|
||||
"clippy::option_option",
|
||||
"clippy::path_buf_push_overwrite",
|
||||
"clippy::ptr_as_ptr",
|
||||
"clippy::rc_mutex",
|
||||
"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::str_to_string",
|
||||
"clippy::string_add_assign",
|
||||
"clippy::string_add",
|
||||
"clippy::string_lit_as_bytes",
|
||||
"clippy::string_to_string",
|
||||
"clippy::todo",
|
||||
"clippy::trailing_empty_array",
|
||||
"clippy::trait_duplication_in_bounds",
|
||||
"clippy::unimplemented",
|
||||
"clippy::unnecessary_wraps",
|
||||
"clippy::unnested_or_patterns",
|
||||
"clippy::unused_peekable",
|
||||
"clippy::unused_rounding",
|
||||
"clippy::unused_self",
|
||||
"clippy::useless_transmute",
|
||||
"clippy::verbose_file_reads",
|
||||
"clippy::zero_sized_map_values",
|
||||
"elided_lifetimes_in_paths",
|
||||
"future_incompatible",
|
||||
"nonstandard_style",
|
||||
"rust_2018_idioms",
|
||||
"rust_2021_prelude_collisions",
|
||||
"rustdoc::missing_crate_level_docs",
|
||||
"semicolon_in_expressions_from_macros",
|
||||
"trivial_numeric_casts",
|
||||
"unused_extern_crates",
|
||||
"unused_import_braces",
|
||||
"unused_lifetimes",
|
||||
]
|
||||
|
||||
allow = [
|
||||
"clippy::manual_range_contains", # This one is just annoying
|
||||
|
||||
# Some of these we should try to put in "warn":
|
||||
"clippy::type_complexity",
|
||||
"clippy::undocumented_unsafe_blocks",
|
||||
"trivial_casts",
|
||||
"unsafe_op_in_unsafe_fn", # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668
|
||||
"unused_qualifications",
|
||||
]
|
77
README.md
77
README.md
|
@ -11,7 +11,7 @@
|
|||
|
||||
👉 [Click to run the web demo](https://www.egui.rs/#demo) 👈
|
||||
|
||||
egui (pronounced "e-gooey") is a simple, fast, and highly portable immediate mode GUI library for Rust. egui runs on the web, natively, and [in your favorite game engine](#integrations) (or will soon).
|
||||
egui is a simple, fast, and highly portable immediate mode GUI library for Rust. egui runs on the web, natively, and [in your favorite game engine](#integrations) (or will soon).
|
||||
|
||||
egui aims to be the easiest-to-use Rust GUI library, and the simplest way to make a web app in Rust.
|
||||
|
||||
|
@ -31,8 +31,6 @@ Sections:
|
|||
* [Other](#other)
|
||||
* [Credits](#credits)
|
||||
|
||||
([egui 的中文翻译文档 / chinese translation](https://github.com/Re-Ch-Love/egui-doc-cn/blob/main/README_zh-hans.md))
|
||||
|
||||
## Example
|
||||
|
||||
``` rust
|
||||
|
@ -52,7 +50,7 @@ ui.label(format!("Hello '{}', age {}", name, age));
|
|||
|
||||
## Quick start
|
||||
|
||||
There are simple examples in [the `examples/` folder](https://github.com/emilk/egui/blob/master/examples/). If you want to write a web app, then go to <https://github.com/emilk/eframe_template/> and follow the instructions. The official docs are at <https://docs.rs/egui>. For inspiration and more examples, check out the [the egui web demo](https://www.egui.rs/#demo) and follow the links in it to its source code.
|
||||
If you just want to write a GUI application in Rust (for the web or for native), go to <https://github.com/emilk/eframe_template/> and follow the instructions there! The official docs are at <https://docs.rs/egui>. For inspiration, check out the [the egui web demo](https://www.egui.rs/#demo) and follow the links in it to its source code. There is also an excellent tutorial video at <https://www.youtube.com/watch?v=NtUkr_z7l84>.
|
||||
|
||||
If you want to integrate egui into an existing engine, go to the [Integrations](#integrations) section.
|
||||
|
||||
|
@ -60,17 +58,17 @@ If you have questions, use [GitHub Discussions](https://github.com/emilk/egui/di
|
|||
|
||||
## Demo
|
||||
|
||||
[Click to run egui web demo](https://www.egui.rs/#demo) (works in any browser with WASM and WebGL support). Uses [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe).
|
||||
[Click to run egui web demo](https://www.egui.rs/#demo) (works in any browser with WASM and WebGL support). Uses [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web).
|
||||
|
||||
To test the demo app locally, run `cargo run --release -p egui_demo_app`.
|
||||
|
||||
The native backend is [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) (using [`glow`](https://crates.io/crates/glow)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run:
|
||||
The native backend is [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) (using [`glow`](https://crates.io/crates/glow)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run:
|
||||
|
||||
`sudo apt-get install -y libclang-dev libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev`
|
||||
`sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev`
|
||||
|
||||
On Fedora Rawhide you need to run:
|
||||
|
||||
`dnf install clang clang-devel clang-tools-extra libxkbcommon-devel pkg-config openssl-devel libxcb-devel`
|
||||
`dnf install clang clang-devel clang-tools-extra speech-dispatcher-devel libxkbcommon-devel pkg-config openssl-devel libxcb-devel`
|
||||
|
||||
**NOTE**: This is just for the demo app - egui itself is completely platform agnostic!
|
||||
|
||||
|
@ -84,7 +82,7 @@ On Fedora Rawhide you need to run:
|
|||
* A simple 2D graphics API for custom painting ([`epaint`](https://docs.rs/epaint)).
|
||||
* No callbacks
|
||||
* Pure immediate mode
|
||||
* Extensible: [easy to write your own widgets for egui](https://github.com/emilk/egui/blob/master/crates/egui_demo_lib/src/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
|
||||
* Safe: there is no `unsafe` code in egui
|
||||
* 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)
|
||||
|
@ -114,7 +112,7 @@ The obvious alternative to egui is [`imgui-rs`](https://github.com/Gekkio/imgui-
|
|||
* egui is pure Rust
|
||||
* egui is easily compiled to WASM
|
||||
* egui lets you use native Rust string types (`imgui-rs` forces you to use annoying macros and wrappers for zero-terminated strings)
|
||||
* [Writing your own widgets in egui is simple](https://github.com/emilk/egui/blob/master/crates/egui_demo_lib/src/demo/toggle_switch.rs)
|
||||
* [Writing your own widgets in egui is simple](https://github.com/emilk/egui/blob/master/egui_demo_lib/src/apps/demo/toggle_switch.rs)
|
||||
|
||||
egui also tries to improve your experience in other small ways:
|
||||
|
||||
|
@ -158,42 +156,37 @@ An integration needs to do the following each frame:
|
|||
* **Input**: Gather input (mouse, touches, keyboard, screen size, etc) and give it to egui
|
||||
* Run the application code
|
||||
* **Output**: Handle egui output (cursor changes, paste, texture allocations, …)
|
||||
* **Painting**: Render the triangle mesh egui produces (see [OpenGL example](https://github.com/emilk/egui/blob/master/crates/egui_glow/src/painter.rs))
|
||||
* **Painting**: Render the triangle mesh egui produces (see [OpenGL example](https://github.com/emilk/egui/blob/master/egui_glium/src/painter.rs))
|
||||
|
||||
### Official integrations
|
||||
|
||||
If you're making an app, your best bet is using [`eframe`](https://github.com/emilk/egui/tree/master/eframe), the official egui framework. It lets you write apps that work on both the web and native. `eframe` is just a thin wrapper over `egui_web` and `egui_glow` (see below).
|
||||
|
||||
These are the official egui integrations:
|
||||
|
||||
* [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) for compiling the same app to web/wasm and desktop/native. Uses `egui-winit` and `egui_glow` or `egui-wgpu`.
|
||||
* [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) for rendering egui with [glow](https://github.com/grovesNL/glow) on native and web, and for making native apps.
|
||||
* [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu) for [wgpu](https://crates.io/crates/wgpu) (WebGPU API).
|
||||
* [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit) for integrating with [winit](https://github.com/rust-windowing/winit).
|
||||
* [`egui_glium`](https://github.com/emilk/egui/tree/master/crates/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium) (DEPRECATED - looking for new maintainer).
|
||||
* [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium).
|
||||
* [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) for compiling native apps with [glow](https://github.com/grovesNL/glow).
|
||||
* [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web) for making a web app. Compiles to WASM, renders with WebGL. [Click to run the egui demo](https://www.egui.rs/#demo). Uses `egui_glow`.
|
||||
* [`egui-winit`](https://github.com/emilk/egui/tree/master/egui-winit) for integrating with [winit](https://github.com/rust-windowing/winit). `egui-winit` is used by `egui_glium` and `egui_glow`.
|
||||
|
||||
### 3rd party integrations
|
||||
|
||||
* [`amethyst_egui`](https://github.com/jgraef/amethyst_egui) for [the Amethyst game engine](https://amethyst.rs/).
|
||||
* [`bevy_egui`](https://github.com/mvlabat/bevy_egui) for [the Bevy game engine](https://bevyengine.org/).
|
||||
* [`egui_glfw_gl`](https://github.com/cohaereo/egui_glfw_gl) for [GLFW](https://crates.io/crates/glfw).
|
||||
* [`egui-glutin-gl`](https://github.com/h3r2tic/egui-glutin-gl/) for [glutin](https://crates.io/crates/glutin).
|
||||
* [`egui_sdl2_gl`](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2).
|
||||
* [`egui_sdl2_platform`](https://github.com/ComLarsic/egui_sdl2_platform) for [SDL2](https://crates.io/crates/sdl2).
|
||||
* [`egui_vulkano`](https://github.com/derivator/egui_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).
|
||||
* [`egui_wgpu_backend`](https://crates.io/crates/egui_wgpu_backend) for [wgpu](https://crates.io/crates/wgpu) (WebGPU API).
|
||||
* [`egui_winit_vulkano`](https://github.com/hakolao/egui_winit_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).
|
||||
* [`egui-macroquad`](https://github.com/optozorax/egui-macroquad) for [macroquad](https://github.com/not-fl3/macroquad).
|
||||
* [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad) for [Miniquad](https://github.com/not-fl3/miniquad).
|
||||
* [`egui_speedy2d`](https://github.com/heretik31/egui_speedy2d) for [Speedy2d](https://github.com/QuantumBadger/Speedy2D).
|
||||
* [`egui-tetra`](https://crates.io/crates/egui-tetra) for [Tetra](https://crates.io/crates/tetra), a 2D game framework.
|
||||
* [`egui-winit-ash-integration`](https://github.com/MatchaChoco010/egui-winit-ash-integration) for [winit](https://github.com/rust-windowing/winit) and [ash](https://github.com/MaikKlein/ash).
|
||||
* [`fltk-egui`](https://crates.io/crates/fltk-egui) for [fltk-rs](https://github.com/fltk-rs/fltk-rs).
|
||||
* [`ggegui`](https://github.com/NemuiSen/ggegui) for the [ggez](https://ggez.rs/) game framework.
|
||||
* [`ggez-egui`](https://github.com/NemuiSen/ggez-egui) for the [ggez](https://ggez.rs/) game framework.
|
||||
* [`godot-egui`](https://github.com/setzer22/godot-egui) for [godot-rust](https://github.com/godot-rust/godot-rust).
|
||||
* [`nannou_egui`](https://github.com/nannou-org/nannou/tree/master/nannou_egui) for [nannou](https://nannou.cc).
|
||||
* [`notan_egui`](https://github.com/Nazariglez/notan/tree/main/crates/notan_egui) for [notan](https://github.com/Nazariglez/notan).
|
||||
* [`screen-13-egui`](https://github.com/attackgoat/screen-13/tree/master/contrib/screen-13-egui) for [Screen 13](https://github.com/attackgoat/screen-13).
|
||||
* [`egui_skia`](https://github.com/lucasmerlin/egui_skia) for [skia](https://github.com/rust-skia/rust-skia/tree/master/skia-safe).
|
||||
* [`nannou_egui`](https://github.com/AlexEne/nannou_egui) for [nannou](https://nannou.cc).
|
||||
* [`smithay-egui`](https://github.com/Smithay/smithay-egui) for [smithay](https://github.com/Smithay/smithay/).
|
||||
* [`tauri-egui`](https://github.com/tauri-apps/tauri-egui) for [tauri](https://github.com/tauri-apps/tauri).
|
||||
|
||||
Missing an integration for the thing you're working on? Create one, it's easy!
|
||||
|
||||
|
@ -224,7 +217,7 @@ loop {
|
|||
}
|
||||
```
|
||||
|
||||
For a reference OpenGL backend, see [the `egui_glium` painter](https://github.com/emilk/egui/blob/master/crates/egui_glium/src/painter.rs) or [the `egui_glow` painter](https://github.com/emilk/egui/blob/master/crates/egui_glow/src/painter.rs).
|
||||
For a reference OpenGL backend, see [the `egui_glium` painter](https://github.com/emilk/egui/blob/master/egui_glium/src/painter.rs), [the `egui_glow` painter](https://github.com/emilk/egui/blob/master/egui_glow/src/painter.rs), or [the `egui_web` `WebGL` painter](https://github.com/emilk/egui/blob/master/egui_web/src/webgl2.rs).
|
||||
|
||||
### Debugging your integration
|
||||
|
||||
|
@ -316,19 +309,19 @@ Here is an example (from https://github.com/AlexxxRu/TinyPomodoro):
|
|||
<img src="media/pompodoro-skin.png" width="50%">
|
||||
|
||||
### How do I use egui with `async`?
|
||||
If you call `.await` in your GUI code, the UI will freeze, which is very bad UX. Instead, keep the GUI thread non-blocking and communicate with any concurrent tasks (`async` tasks or other threads) with something like:
|
||||
If you call `.await` in your GUI code, the UI will freeze, with is very bad UX. Instead, keep the GUI thread non-blocking and communicate with any concurrent tasks (`async` tasks or other threads) with something like:
|
||||
* Channels (e.g. [`std::sync::mpsc::channel`](https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html)). Make sure to use [`try_recv`](https://doc.rust-lang.org/std/sync/mpsc/struct.Receiver.html#method.try_recv) so you don't block the gui thread!
|
||||
* `Arc<Mutex<Value>>` (background thread sets a value; GUI thread reads it)
|
||||
* [`poll_promise::Promise`](https://docs.rs/poll-promise) (example: [`examples/download_image/`](https://github.com/emilk/egui/blob/master/examples/download_image/))
|
||||
* [`poll_promise::Promise`](https://docs.rs/poll-promise) (example: [`eframe/examples/download_image.rs`](https://github.com/emilk/egui/blob/master/eframe/examples/download_image.rs))
|
||||
* [`eventuals::Eventual`](https://docs.rs/eventuals/latest/eventuals/struct.Eventual.html)
|
||||
* [`tokio::sync::watch::channel`](https://docs.rs/tokio/latest/tokio/sync/watch/fn.channel.html)
|
||||
|
||||
### What about accessibility, such as screen readers?
|
||||
egui includes optional support for [AccessKit](https://accesskit.dev/), which currently implements the native accessibility APIs on Windows and macOS. This feature is enabled by default in eframe. For platforms that AccessKit doesn't yet support, including web, there is an experimental built-in screen reader; in [the web demo](https://www.egui.rs/#demo) you can enable it in the "Backend" tab.
|
||||
There is experimental support for a screen reader. In [the web demo](https://www.egui.rs/#demo) you can enable it in the "Backend" tab.
|
||||
|
||||
The original discussion of accessibility in egui is at <https://github.com/emilk/egui/issues/167>. Now that AccessKit support is merged, providing a strong foundation for future accessibility work, please open new issues on specific accessibility problems.
|
||||
Read more at <https://github.com/emilk/egui/issues/167>.
|
||||
|
||||
### What is the difference between [egui](https://docs.rs/egui) and [eframe](https://github.com/emilk/egui/tree/master/crates/eframe)?
|
||||
### What is the difference between [egui](https://docs.rs/egui) and [eframe](https://github.com/emilk/egui/tree/master/eframe)?
|
||||
|
||||
`egui` is a 2D user interface library for laying out and interacting with buttons, sliders, etc.
|
||||
`egui` has no idea if it is running on the web or natively, and does not know how to collect input or show things on screen.
|
||||
|
@ -345,17 +338,17 @@ If you want to embed 3D into an egui view there are two options.
|
|||
|
||||
#### `Shape::Callback`
|
||||
Examples:
|
||||
* <https://github.com/emilk/egui/blob/master/examples/custom_3d_three-d.rs>
|
||||
* <https://github.com/emilk/egui/blob/master/examples/custom_3d_glow.rs>
|
||||
* <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/crates/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.
|
||||
`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/crates/egui_glium): <https://github.com/emilk/egui/blob/master/crates/egui_glium/examples/native_texture.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
|
||||
|
@ -372,8 +365,6 @@ egui uses the builder pattern for construction widgets. For instance: `ui.add(La
|
|||
|
||||
Instead of using matching `begin/end` style function calls (which can be error prone) egui prefers to use `FnOnce` closures passed to a wrapping function. Lambdas are a bit ugly though, so I'd like to find a nicer solution to this. More discussion of this at <https://github.com/emilk/egui/issues/1004#issuecomment-1001650754>.
|
||||
|
||||
egui uses a single `RwLock` for short-time locks on each access of `Context` data. This is to leave implementation simple and transactional and allow users to run their UI logic in parallel. Instead of creating mutex guards, egui uses closures passed to a wrapping function, e.g. `ctx.input(|i| i.key_down(Key::A))`. This is to make it less likely that a user would accidentally double-lock the `Context`, which would lead to a deadlock.
|
||||
|
||||
### Inspiration
|
||||
|
||||
The one and only [Dear ImGui](https://github.com/ocornut/imgui) is a great Immediate Mode GUI for C++ which works with many backends. That library revolutionized how I think about GUI code and turned GUI programming from something I hated to do to something I now enjoy.
|
||||
|
@ -394,12 +385,11 @@ Notable contributions by:
|
|||
* [@optozorax](https://github.com/optozorax): [Arbitrary widget data storage](https://github.com/emilk/egui/pull/257).
|
||||
* [@quadruple-output](https://github.com/quadruple-output): [Multitouch](https://github.com/emilk/egui/pull/306).
|
||||
* [@EmbersArc](https://github.com/EmbersArc): [Plots](https://github.com/emilk/egui/pulls?q=+is%3Apr+author%3AEmbersArc).
|
||||
* [@AsmPrgmC3](https://github.com/AsmPrgmC3): [Proper sRGBA blending for web](https://github.com/emilk/egui/pull/650).
|
||||
* [@AsmPrgmC3](https://github.com/AsmPrgmC3): [Proper sRGBA blending in `egui_web`](https://github.com/emilk/egui/pull/650).
|
||||
* [@AlexApps99](https://github.com/AlexApps99): [`egui_glow`](https://github.com/emilk/egui/pull/685).
|
||||
* [@mankinskin](https://github.com/mankinskin): [Context menus](https://github.com/emilk/egui/pull/543).
|
||||
* [@t18b219k](https://github.com/t18b219k): [Port glow painter to web](https://github.com/emilk/egui/pull/868).
|
||||
* [@danielkeller](https://github.com/danielkeller): [`Context` refactor](https://github.com/emilk/egui/pull/1050).
|
||||
* [@MaximOsipenko](https://github.com/MaximOsipenko): [`Context` lock refactor](https://github.com/emilk/egui/pull/2625).
|
||||
* And [many more](https://github.com/emilk/egui/graphs/contributors?type=a).
|
||||
|
||||
egui is licensed under [MIT](LICENSE-MIT) OR [Apache-2.0](LICENSE-APACHE).
|
||||
|
@ -412,12 +402,3 @@ Default fonts:
|
|||
* `Hack-Regular.ttf`: <https://github.com/source-foundry/Hack>, [MIT Licence](https://github.com/source-foundry/Hack/blob/master/LICENSE.md)
|
||||
* `NotoEmoji-Regular.ttf`: [google.com/get/noto](https://google.com/get/noto), [SIL Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL)
|
||||
* `Ubuntu-Light.ttf` by [Dalton Maag](http://www.daltonmaag.com/): [Ubuntu font licence](https://ubuntu.com/legal/font-licence)
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<img src="media/rerun_io_logo.png" width="50%">
|
||||
|
||||
egui development is sponsored by [Rerun](https://www.rerun.io/), a startup doing<br>
|
||||
visualizations for computer vision and robotics.
|
||||
</div>
|
||||
|
|
46
bacon.toml
46
bacon.toml
|
@ -1,46 +0,0 @@
|
|||
# This is a configuration file for the bacon tool
|
||||
# More info at https://github.com/Canop/bacon
|
||||
|
||||
default_job = "cranky"
|
||||
|
||||
[jobs]
|
||||
|
||||
[jobs.cranky]
|
||||
command = ["cargo", "cranky", "--all-targets", "--all-features", "--color", "always"]
|
||||
need_stdout = false
|
||||
watch = ["tests", "benches", "examples"]
|
||||
|
||||
[jobs.test]
|
||||
command = ["cargo", "test", "--color", "always"]
|
||||
need_stdout = true
|
||||
watch = ["tests"]
|
||||
|
||||
[jobs.doc]
|
||||
command = ["cargo", "doc", "--color", "always", "--all-features", "--no-deps"]
|
||||
need_stdout = false
|
||||
|
||||
# if the doc compiles, then it opens in your browser and bacon switches
|
||||
# to the previous job
|
||||
[jobs.doc-open]
|
||||
command = ["cargo", "doc", "--color", "always", "--all-features", "--no-deps", "--open"]
|
||||
need_stdout = false
|
||||
on_success = "back" # so that we don't open the browser at each change
|
||||
|
||||
# You can run your application and have the result displayed in bacon,
|
||||
# *if* it makes sense for this crate. You can run an example the same
|
||||
# way. Don't forget the `--color always` part or the errors won't be
|
||||
# properly parsed.
|
||||
[jobs.run]
|
||||
command = ["cargo", "run", "--color", "always"]
|
||||
need_stdout = true
|
||||
|
||||
# You may define here keybindings that would be specific to
|
||||
# a project, for example a shortcut to launch a specific job.
|
||||
# Shortcuts to internal functions (scrolling, toggling, etc.)
|
||||
# should go in your personal prefs.toml file instead.
|
||||
[keybindings]
|
||||
i = "job:initial"
|
||||
c = "job:cranky"
|
||||
d = "job:doc-open"
|
||||
t = "job:test"
|
||||
r = "job:run"
|
|
@ -1 +0,0 @@
|
|||
doc-valid-idents = ["AccessKit", ".."]
|
|
@ -1,13 +0,0 @@
|
|||
# Changelog for ecolor
|
||||
All notable changes to the `ecolor` crate will be noted in this file.
|
||||
|
||||
|
||||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08
|
||||
* Add `Color32::gamma_multiply` ([#2437](https://github.com/emilk/egui/pull/2437)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08
|
||||
* Split out `ecolor` crate from `epaint`
|
|
@ -1,50 +0,0 @@
|
|||
[package]
|
||||
name = "ecolor"
|
||||
version = "0.21.0"
|
||||
authors = [
|
||||
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
|
||||
"Andreas Reich <reichandreas@gmx.de>",
|
||||
]
|
||||
description = "Color structs and color conversion utilities"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui"
|
||||
categories = ["mathematics", "encoding"]
|
||||
keywords = ["gui", "color", "conversion", "gamedev", "images"]
|
||||
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
## Enable additional checks if debug assertions are enabled (debug builds).
|
||||
extra_debug_asserts = []
|
||||
## Always enable additional checks.
|
||||
extra_asserts = []
|
||||
|
||||
|
||||
[dependencies]
|
||||
#! ### Optional dependencies
|
||||
|
||||
## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast `ecolor` types to `&[u8]`.
|
||||
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
|
||||
|
||||
## [`cint`](https://docs.rs/cint) enables interopability with other color libraries.
|
||||
cint = { version = "0.3.1", optional = true }
|
||||
|
||||
## Enable the [`hex_color`] macro.
|
||||
color-hex = { version = "0.2.0", optional = true }
|
||||
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
|
@ -1,11 +0,0 @@
|
|||
# ecolor - egui color library
|
||||
|
||||
[](https://crates.io/crates/ecolor)
|
||||
[](https://docs.rs/ecolor)
|
||||
[](https://github.com/rust-secure-code/safety-dance/)
|
||||

|
||||

|
||||
|
||||
A simple color storage and conversion library.
|
||||
|
||||
Made for [`egui`](https://github.com/emilk/egui/).
|
|
@ -1,161 +0,0 @@
|
|||
use super::*;
|
||||
use cint::{Alpha, ColorInterop, EncodedSrgb, Hsv, LinearSrgb, PremultipliedAlpha};
|
||||
|
||||
// ---- Color32 ----
|
||||
|
||||
impl From<Alpha<EncodedSrgb<u8>>> for Color32 {
|
||||
fn from(srgba: Alpha<EncodedSrgb<u8>>) -> Self {
|
||||
let Alpha {
|
||||
color: EncodedSrgb { r, g, b },
|
||||
alpha: a,
|
||||
} = srgba;
|
||||
|
||||
Color32::from_rgba_unmultiplied(r, g, b, a)
|
||||
}
|
||||
}
|
||||
|
||||
// No From<Color32> for Alpha<_> because Color32 is premultiplied
|
||||
|
||||
impl From<PremultipliedAlpha<EncodedSrgb<u8>>> for Color32 {
|
||||
fn from(srgba: PremultipliedAlpha<EncodedSrgb<u8>>) -> Self {
|
||||
let PremultipliedAlpha {
|
||||
color: EncodedSrgb { r, g, b },
|
||||
alpha: a,
|
||||
} = srgba;
|
||||
|
||||
Color32::from_rgba_premultiplied(r, g, b, a)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color32> for PremultipliedAlpha<EncodedSrgb<u8>> {
|
||||
fn from(col: Color32) -> Self {
|
||||
let (r, g, b, a) = col.to_tuple();
|
||||
|
||||
PremultipliedAlpha {
|
||||
color: EncodedSrgb { r, g, b },
|
||||
alpha: a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PremultipliedAlpha<EncodedSrgb<f32>>> for Color32 {
|
||||
fn from(srgba: PremultipliedAlpha<EncodedSrgb<f32>>) -> Self {
|
||||
let PremultipliedAlpha {
|
||||
color: EncodedSrgb { r, g, b },
|
||||
alpha: a,
|
||||
} = srgba;
|
||||
|
||||
// This is a bit of an abuse of the function name but it does what we want.
|
||||
let r = linear_u8_from_linear_f32(r);
|
||||
let g = linear_u8_from_linear_f32(g);
|
||||
let b = linear_u8_from_linear_f32(b);
|
||||
let a = linear_u8_from_linear_f32(a);
|
||||
|
||||
Color32::from_rgba_premultiplied(r, g, b, a)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color32> for PremultipliedAlpha<EncodedSrgb<f32>> {
|
||||
fn from(col: Color32) -> Self {
|
||||
let (r, g, b, a) = col.to_tuple();
|
||||
|
||||
// This is a bit of an abuse of the function name but it does what we want.
|
||||
let r = linear_f32_from_linear_u8(r);
|
||||
let g = linear_f32_from_linear_u8(g);
|
||||
let b = linear_f32_from_linear_u8(b);
|
||||
let a = linear_f32_from_linear_u8(a);
|
||||
|
||||
PremultipliedAlpha {
|
||||
color: EncodedSrgb { r, g, b },
|
||||
alpha: a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorInterop for Color32 {
|
||||
type CintTy = PremultipliedAlpha<EncodedSrgb<u8>>;
|
||||
}
|
||||
|
||||
// ---- Rgba ----
|
||||
|
||||
impl From<PremultipliedAlpha<LinearSrgb<f32>>> for Rgba {
|
||||
fn from(srgba: PremultipliedAlpha<LinearSrgb<f32>>) -> Self {
|
||||
let PremultipliedAlpha {
|
||||
color: LinearSrgb { r, g, b },
|
||||
alpha: a,
|
||||
} = srgba;
|
||||
|
||||
Rgba([r, g, b, a])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rgba> for PremultipliedAlpha<LinearSrgb<f32>> {
|
||||
fn from(col: Rgba) -> Self {
|
||||
let (r, g, b, a) = col.to_tuple();
|
||||
|
||||
PremultipliedAlpha {
|
||||
color: LinearSrgb { r, g, b },
|
||||
alpha: a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorInterop for Rgba {
|
||||
type CintTy = PremultipliedAlpha<LinearSrgb<f32>>;
|
||||
}
|
||||
|
||||
// ---- Hsva ----
|
||||
|
||||
impl From<Alpha<Hsv<f32>>> for Hsva {
|
||||
fn from(srgba: Alpha<Hsv<f32>>) -> Self {
|
||||
let Alpha {
|
||||
color: Hsv { h, s, v },
|
||||
alpha: a,
|
||||
} = srgba;
|
||||
|
||||
Hsva::new(h, s, v, a)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsva> for Alpha<Hsv<f32>> {
|
||||
fn from(col: Hsva) -> Self {
|
||||
let Hsva { h, s, v, a } = col;
|
||||
|
||||
Alpha {
|
||||
color: Hsv { h, s, v },
|
||||
alpha: a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorInterop for Hsva {
|
||||
type CintTy = Alpha<Hsv<f32>>;
|
||||
}
|
||||
|
||||
// ---- HsvaGamma ----
|
||||
|
||||
impl ColorInterop for HsvaGamma {
|
||||
type CintTy = Alpha<Hsv<f32>>;
|
||||
}
|
||||
|
||||
impl From<Alpha<Hsv<f32>>> for HsvaGamma {
|
||||
fn from(srgba: Alpha<Hsv<f32>>) -> Self {
|
||||
let Alpha {
|
||||
color: Hsv { h, s, v },
|
||||
alpha: a,
|
||||
} = srgba;
|
||||
|
||||
Hsva::new(h, s, v, a).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HsvaGamma> for Alpha<Hsv<f32>> {
|
||||
fn from(col: HsvaGamma) -> Self {
|
||||
let Hsva { h, s, v, a } = col.into();
|
||||
|
||||
Alpha {
|
||||
color: Hsv { h, s, v },
|
||||
alpha: a,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,216 +0,0 @@
|
|||
use crate::{gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_linear_u8, Rgba};
|
||||
|
||||
/// This format is used for space-efficient color representation (32 bits).
|
||||
///
|
||||
/// Instead of manipulating this directly it is often better
|
||||
/// to first convert it to either [`Rgba`] or [`crate::Hsva`].
|
||||
///
|
||||
/// Internally this uses 0-255 gamma space `sRGBA` color with premultiplied alpha.
|
||||
/// Alpha channel is in linear space.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
pub struct Color32(pub(crate) [u8; 4]);
|
||||
|
||||
impl std::ops::Index<usize> for Color32 {
|
||||
type Output = u8;
|
||||
|
||||
#[inline(always)]
|
||||
fn index(&self, index: usize) -> &u8 {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<usize> for Color32 {
|
||||
#[inline(always)]
|
||||
fn index_mut(&mut self, index: usize) -> &mut u8 {
|
||||
&mut self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl Color32 {
|
||||
// Mostly follows CSS names:
|
||||
|
||||
pub const TRANSPARENT: Color32 = Color32::from_rgba_premultiplied(0, 0, 0, 0);
|
||||
pub const BLACK: Color32 = Color32::from_rgb(0, 0, 0);
|
||||
pub const DARK_GRAY: Color32 = Color32::from_rgb(96, 96, 96);
|
||||
pub const GRAY: Color32 = Color32::from_rgb(160, 160, 160);
|
||||
pub const LIGHT_GRAY: Color32 = Color32::from_rgb(220, 220, 220);
|
||||
pub const WHITE: Color32 = Color32::from_rgb(255, 255, 255);
|
||||
|
||||
pub const BROWN: Color32 = Color32::from_rgb(165, 42, 42);
|
||||
pub const DARK_RED: Color32 = Color32::from_rgb(0x8B, 0, 0);
|
||||
pub const RED: Color32 = Color32::from_rgb(255, 0, 0);
|
||||
pub const LIGHT_RED: Color32 = Color32::from_rgb(255, 128, 128);
|
||||
|
||||
pub const YELLOW: Color32 = Color32::from_rgb(255, 255, 0);
|
||||
pub const LIGHT_YELLOW: Color32 = Color32::from_rgb(255, 255, 0xE0);
|
||||
pub const KHAKI: Color32 = Color32::from_rgb(240, 230, 140);
|
||||
|
||||
pub const DARK_GREEN: Color32 = Color32::from_rgb(0, 0x64, 0);
|
||||
pub const GREEN: Color32 = Color32::from_rgb(0, 255, 0);
|
||||
pub const LIGHT_GREEN: Color32 = Color32::from_rgb(0x90, 0xEE, 0x90);
|
||||
|
||||
pub const DARK_BLUE: Color32 = Color32::from_rgb(0, 0, 0x8B);
|
||||
pub const BLUE: Color32 = Color32::from_rgb(0, 0, 255);
|
||||
pub const LIGHT_BLUE: Color32 = Color32::from_rgb(0xAD, 0xD8, 0xE6);
|
||||
|
||||
pub const GOLD: Color32 = Color32::from_rgb(255, 215, 0);
|
||||
|
||||
pub const DEBUG_COLOR: Color32 = Color32::from_rgba_premultiplied(0, 200, 0, 128);
|
||||
|
||||
/// An ugly color that is planned to be replaced before making it to the screen.
|
||||
pub const TEMPORARY_COLOR: Color32 = Color32::from_rgb(64, 254, 0);
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
|
||||
Self([r, g, b, 255])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn from_rgb_additive(r: u8, g: u8, b: u8) -> Self {
|
||||
Self([r, g, b, 0])
|
||||
}
|
||||
|
||||
/// From `sRGBA` with premultiplied alpha.
|
||||
#[inline(always)]
|
||||
pub const fn from_rgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self([r, g, b, a])
|
||||
}
|
||||
|
||||
/// From `sRGBA` WITHOUT premultiplied alpha.
|
||||
pub fn from_rgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
if a == 255 {
|
||||
Self::from_rgb(r, g, b) // common-case optimization
|
||||
} else if a == 0 {
|
||||
Self::TRANSPARENT // common-case optimization
|
||||
} else {
|
||||
let r_lin = linear_f32_from_gamma_u8(r);
|
||||
let g_lin = linear_f32_from_gamma_u8(g);
|
||||
let b_lin = linear_f32_from_gamma_u8(b);
|
||||
let a_lin = linear_f32_from_linear_u8(a);
|
||||
|
||||
let r = gamma_u8_from_linear_f32(r_lin * a_lin);
|
||||
let g = gamma_u8_from_linear_f32(g_lin * a_lin);
|
||||
let b = gamma_u8_from_linear_f32(b_lin * a_lin);
|
||||
|
||||
Self::from_rgba_premultiplied(r, g, b, a)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn from_gray(l: u8) -> Self {
|
||||
Self([l, l, l, 255])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn from_black_alpha(a: u8) -> Self {
|
||||
Self([0, 0, 0, a])
|
||||
}
|
||||
|
||||
pub fn from_white_alpha(a: u8) -> Self {
|
||||
Rgba::from_white_alpha(linear_f32_from_linear_u8(a)).into()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn from_additive_luminance(l: u8) -> Self {
|
||||
Self([l, l, l, 0])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn is_opaque(&self) -> bool {
|
||||
self.a() == 255
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn r(&self) -> u8 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn g(&self) -> u8 {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn b(&self) -> u8 {
|
||||
self.0[2]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn a(&self) -> u8 {
|
||||
self.0[3]
|
||||
}
|
||||
|
||||
/// Returns an opaque version of self
|
||||
pub fn to_opaque(self) -> Self {
|
||||
Rgba::from(self).to_opaque().into()
|
||||
}
|
||||
|
||||
/// Returns an additive version of self
|
||||
#[inline(always)]
|
||||
pub const fn additive(self) -> Self {
|
||||
let [r, g, b, _] = self.to_array();
|
||||
Self([r, g, b, 0])
|
||||
}
|
||||
|
||||
/// Premultiplied RGBA
|
||||
#[inline(always)]
|
||||
pub const fn to_array(&self) -> [u8; 4] {
|
||||
[self.r(), self.g(), self.b(), self.a()]
|
||||
}
|
||||
|
||||
/// Premultiplied RGBA
|
||||
#[inline(always)]
|
||||
pub const fn to_tuple(&self) -> (u8, u8, u8, u8) {
|
||||
(self.r(), self.g(), self.b(), self.a())
|
||||
}
|
||||
|
||||
pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
|
||||
Rgba::from(*self).to_srgba_unmultiplied()
|
||||
}
|
||||
|
||||
/// Multiply with 0.5 to make color half as opaque, perceptually.
|
||||
///
|
||||
/// Fast multiplication in gamma-space.
|
||||
///
|
||||
/// This is perceptually even, and faster that [`Self::linear_multiply`].
|
||||
#[inline]
|
||||
pub fn gamma_multiply(self, factor: f32) -> Color32 {
|
||||
crate::ecolor_assert!(0.0 <= factor && factor <= 1.0);
|
||||
let Self([r, g, b, a]) = self;
|
||||
Self([
|
||||
(r as f32 * factor + 0.5) as u8,
|
||||
(g as f32 * factor + 0.5) as u8,
|
||||
(b as f32 * factor + 0.5) as u8,
|
||||
(a as f32 * factor + 0.5) as u8,
|
||||
])
|
||||
}
|
||||
|
||||
/// Multiply with 0.5 to make color half as opaque in linear space.
|
||||
///
|
||||
/// This is using linear space, which is not perceptually even.
|
||||
/// You may want to use [`Self::gamma_multiply`] instead.
|
||||
pub fn linear_multiply(self, factor: f32) -> Color32 {
|
||||
crate::ecolor_assert!(0.0 <= factor && factor <= 1.0);
|
||||
// As an unfortunate side-effect of using premultiplied alpha
|
||||
// we need a somewhat expensive conversion to linear space and back.
|
||||
Rgba::from(self).multiply(factor).into()
|
||||
}
|
||||
|
||||
/// Converts to floating point values in the range 0-1 without any gamma space conversion.
|
||||
///
|
||||
/// Use this with great care! In almost all cases, you want to convert to [`crate::Rgba`] instead
|
||||
/// in order to obtain linear space color values.
|
||||
#[inline]
|
||||
pub fn to_normalized_gamma_f32(self) -> [f32; 4] {
|
||||
let Self([r, g, b, a]) = self;
|
||||
[
|
||||
r as f32 / 255.0,
|
||||
g as f32 / 255.0,
|
||||
b as f32 / 255.0,
|
||||
a as f32 / 255.0,
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/// Construct a [`crate::Color32`] from a hex RGB or RGBA string.
|
||||
///
|
||||
/// ```
|
||||
/// # use ecolor::{hex_color, Color32};
|
||||
/// assert_eq!(hex_color!("#202122"), Color32::from_rgb(0x20, 0x21, 0x22));
|
||||
/// assert_eq!(hex_color!("#abcdef12"), Color32::from_rgba_unmultiplied(0xab, 0xcd, 0xef, 0x12));
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! hex_color {
|
||||
($s:literal) => {{
|
||||
let array = color_hex::color_from_hex!($s);
|
||||
if array.len() == 3 {
|
||||
$crate::Color32::from_rgb(array[0], array[1], array[2])
|
||||
} else {
|
||||
#[allow(unconditional_panic)]
|
||||
$crate::Color32::from_rgba_unmultiplied(array[0], array[1], array[2], array[3])
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_rgb_hex() {
|
||||
assert_eq!(
|
||||
crate::Color32::from_rgb(0x20, 0x21, 0x22),
|
||||
hex_color!("#202122")
|
||||
);
|
||||
assert_eq!(
|
||||
crate::Color32::from_rgb_additive(0x20, 0x21, 0x22),
|
||||
hex_color!("#202122").additive()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_rgba_hex() {
|
||||
assert_eq!(
|
||||
crate::Color32::from_rgba_unmultiplied(0x20, 0x21, 0x22, 0x50),
|
||||
hex_color!("20212250")
|
||||
);
|
||||
}
|
|
@ -1,231 +0,0 @@
|
|||
use crate::{
|
||||
gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_linear_u8,
|
||||
linear_u8_from_linear_f32, Color32, Rgba,
|
||||
};
|
||||
|
||||
/// Hue, saturation, value, alpha. All in the range [0, 1].
|
||||
/// No premultiplied alpha.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Hsva {
|
||||
/// hue 0-1
|
||||
pub h: f32,
|
||||
|
||||
/// saturation 0-1
|
||||
pub s: f32,
|
||||
|
||||
/// value 0-1
|
||||
pub v: f32,
|
||||
|
||||
/// alpha 0-1. A negative value signifies an additive color (and alpha is ignored).
|
||||
pub a: f32,
|
||||
}
|
||||
|
||||
impl Hsva {
|
||||
pub fn new(h: f32, s: f32, v: f32, a: f32) -> Self {
|
||||
Self { h, s, v, a }
|
||||
}
|
||||
|
||||
/// From `sRGBA` with premultiplied alpha
|
||||
pub fn from_srgba_premultiplied(srgba: [u8; 4]) -> Self {
|
||||
Self::from_rgba_premultiplied(
|
||||
linear_f32_from_gamma_u8(srgba[0]),
|
||||
linear_f32_from_gamma_u8(srgba[1]),
|
||||
linear_f32_from_gamma_u8(srgba[2]),
|
||||
linear_f32_from_linear_u8(srgba[3]),
|
||||
)
|
||||
}
|
||||
|
||||
/// From `sRGBA` without premultiplied alpha
|
||||
pub fn from_srgba_unmultiplied(srgba: [u8; 4]) -> Self {
|
||||
Self::from_rgba_unmultiplied(
|
||||
linear_f32_from_gamma_u8(srgba[0]),
|
||||
linear_f32_from_gamma_u8(srgba[1]),
|
||||
linear_f32_from_gamma_u8(srgba[2]),
|
||||
linear_f32_from_linear_u8(srgba[3]),
|
||||
)
|
||||
}
|
||||
|
||||
/// From linear RGBA with premultiplied alpha
|
||||
pub fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
if a == 0.0 {
|
||||
if r == 0.0 && b == 0.0 && a == 0.0 {
|
||||
Hsva::default()
|
||||
} else {
|
||||
Hsva::from_additive_rgb([r, g, b])
|
||||
}
|
||||
} else {
|
||||
let (h, s, v) = hsv_from_rgb([r / a, g / a, b / a]);
|
||||
Hsva { h, s, v, a }
|
||||
}
|
||||
}
|
||||
|
||||
/// From linear RGBA without premultiplied alpha
|
||||
pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
let (h, s, v) = hsv_from_rgb([r, g, b]);
|
||||
Hsva { h, s, v, a }
|
||||
}
|
||||
|
||||
pub fn from_additive_rgb(rgb: [f32; 3]) -> Self {
|
||||
let (h, s, v) = hsv_from_rgb(rgb);
|
||||
Hsva {
|
||||
h,
|
||||
s,
|
||||
v,
|
||||
a: -0.5, // anything negative is treated as additive
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_rgb(rgb: [f32; 3]) -> Self {
|
||||
let (h, s, v) = hsv_from_rgb(rgb);
|
||||
Hsva { h, s, v, a: 1.0 }
|
||||
}
|
||||
|
||||
pub fn from_srgb([r, g, b]: [u8; 3]) -> Self {
|
||||
Self::from_rgb([
|
||||
linear_f32_from_gamma_u8(r),
|
||||
linear_f32_from_gamma_u8(g),
|
||||
linear_f32_from_gamma_u8(b),
|
||||
])
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
pub fn to_opaque(self) -> Self {
|
||||
Self { a: 1.0, ..self }
|
||||
}
|
||||
|
||||
pub fn to_rgb(&self) -> [f32; 3] {
|
||||
rgb_from_hsv((self.h, self.s, self.v))
|
||||
}
|
||||
|
||||
pub fn to_srgb(&self) -> [u8; 3] {
|
||||
let [r, g, b] = self.to_rgb();
|
||||
[
|
||||
gamma_u8_from_linear_f32(r),
|
||||
gamma_u8_from_linear_f32(g),
|
||||
gamma_u8_from_linear_f32(b),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn to_rgba_premultiplied(&self) -> [f32; 4] {
|
||||
let [r, g, b, a] = self.to_rgba_unmultiplied();
|
||||
let additive = a < 0.0;
|
||||
if additive {
|
||||
[r, g, b, 0.0]
|
||||
} else {
|
||||
[a * r, a * g, a * b, a]
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents additive colors using a negative alpha.
|
||||
pub fn to_rgba_unmultiplied(&self) -> [f32; 4] {
|
||||
let Hsva { h, s, v, a } = *self;
|
||||
let [r, g, b] = rgb_from_hsv((h, s, v));
|
||||
[r, g, b, a]
|
||||
}
|
||||
|
||||
pub fn to_srgba_premultiplied(&self) -> [u8; 4] {
|
||||
let [r, g, b, a] = self.to_rgba_premultiplied();
|
||||
[
|
||||
gamma_u8_from_linear_f32(r),
|
||||
gamma_u8_from_linear_f32(g),
|
||||
gamma_u8_from_linear_f32(b),
|
||||
linear_u8_from_linear_f32(a),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
|
||||
let [r, g, b, a] = self.to_rgba_unmultiplied();
|
||||
[
|
||||
gamma_u8_from_linear_f32(r),
|
||||
gamma_u8_from_linear_f32(g),
|
||||
gamma_u8_from_linear_f32(b),
|
||||
linear_u8_from_linear_f32(a.abs()),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsva> for Rgba {
|
||||
fn from(hsva: Hsva) -> Rgba {
|
||||
Rgba(hsva.to_rgba_premultiplied())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rgba> for Hsva {
|
||||
fn from(rgba: Rgba) -> Hsva {
|
||||
Self::from_rgba_premultiplied(rgba.0[0], rgba.0[1], rgba.0[2], rgba.0[3])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsva> for Color32 {
|
||||
fn from(hsva: Hsva) -> Color32 {
|
||||
Color32::from(Rgba::from(hsva))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color32> for Hsva {
|
||||
fn from(srgba: Color32) -> Hsva {
|
||||
Hsva::from(Rgba::from(srgba))
|
||||
}
|
||||
}
|
||||
|
||||
/// All ranges in 0-1, rgb is linear.
|
||||
pub fn hsv_from_rgb([r, g, b]: [f32; 3]) -> (f32, f32, f32) {
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
let min = r.min(g.min(b));
|
||||
let max = r.max(g.max(b)); // value
|
||||
|
||||
let range = max - min;
|
||||
|
||||
let h = if max == min {
|
||||
0.0 // hue is undefined
|
||||
} else if max == r {
|
||||
(g - b) / (6.0 * range)
|
||||
} else if max == g {
|
||||
(b - r) / (6.0 * range) + 1.0 / 3.0
|
||||
} else {
|
||||
// max == b
|
||||
(r - g) / (6.0 * range) + 2.0 / 3.0
|
||||
};
|
||||
let h = (h + 1.0).fract(); // wrap
|
||||
let s = if max == 0.0 { 0.0 } else { 1.0 - min / max };
|
||||
(h, s, max)
|
||||
}
|
||||
|
||||
/// All ranges in 0-1, rgb is linear.
|
||||
pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> [f32; 3] {
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
let h = (h.fract() + 1.0).fract(); // wrap
|
||||
let s = s.clamp(0.0, 1.0);
|
||||
|
||||
let f = h * 6.0 - (h * 6.0).floor();
|
||||
let p = v * (1.0 - s);
|
||||
let q = v * (1.0 - f * s);
|
||||
let t = v * (1.0 - (1.0 - f) * s);
|
||||
|
||||
match (h * 6.0).floor() as i32 % 6 {
|
||||
0 => [v, t, p],
|
||||
1 => [q, v, p],
|
||||
2 => [p, v, t],
|
||||
3 => [p, q, v],
|
||||
4 => [t, p, v],
|
||||
5 => [v, p, q],
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // a bit expensive
|
||||
fn test_hsv_roundtrip() {
|
||||
for r in 0..=255 {
|
||||
for g in 0..=255 {
|
||||
for b in 0..=255 {
|
||||
let srgba = Color32::from_rgb(r, g, b);
|
||||
let hsva = Hsva::from(srgba);
|
||||
assert_eq!(srgba, Color32::from(hsva));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
use crate::{gamma_from_linear, linear_from_gamma, Color32, Hsva, Rgba};
|
||||
|
||||
/// Like Hsva but with the `v` value (brightness) being gamma corrected
|
||||
/// so that it is somewhat perceptually even.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct HsvaGamma {
|
||||
/// hue 0-1
|
||||
pub h: f32,
|
||||
|
||||
/// saturation 0-1
|
||||
pub s: f32,
|
||||
|
||||
/// value 0-1, in gamma-space (~perceptually even)
|
||||
pub v: f32,
|
||||
|
||||
/// alpha 0-1. A negative value signifies an additive color (and alpha is ignored).
|
||||
pub a: f32,
|
||||
}
|
||||
|
||||
impl From<HsvaGamma> for Rgba {
|
||||
fn from(hsvag: HsvaGamma) -> Rgba {
|
||||
Hsva::from(hsvag).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HsvaGamma> for Color32 {
|
||||
fn from(hsvag: HsvaGamma) -> Color32 {
|
||||
Rgba::from(hsvag).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HsvaGamma> for Hsva {
|
||||
fn from(hsvag: HsvaGamma) -> Hsva {
|
||||
let HsvaGamma { h, s, v, a } = hsvag;
|
||||
Hsva {
|
||||
h,
|
||||
s,
|
||||
v: linear_from_gamma(v),
|
||||
a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rgba> for HsvaGamma {
|
||||
fn from(rgba: Rgba) -> HsvaGamma {
|
||||
Hsva::from(rgba).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color32> for HsvaGamma {
|
||||
fn from(srgba: Color32) -> HsvaGamma {
|
||||
Hsva::from(srgba).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsva> for HsvaGamma {
|
||||
fn from(hsva: Hsva) -> HsvaGamma {
|
||||
let Hsva { h, s, v, a } = hsva;
|
||||
HsvaGamma {
|
||||
h,
|
||||
s,
|
||||
v: gamma_from_linear(v),
|
||||
a,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
//! Color conversions and types.
|
||||
//!
|
||||
//! If you want a compact color representation, use [`Color32`].
|
||||
//! If you want to manipulate RGBA colors use [`Rgba`].
|
||||
//! If you want to manipulate colors in a way closer to how humans think about colors, use [`HsvaGamma`].
|
||||
//!
|
||||
//! ## Feature flags
|
||||
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
|
||||
//!
|
||||
|
||||
#![allow(clippy::wrong_self_convention)]
|
||||
|
||||
#[cfg(feature = "cint")]
|
||||
mod cint_impl;
|
||||
#[cfg(feature = "cint")]
|
||||
pub use cint_impl::*;
|
||||
|
||||
mod color32;
|
||||
pub use color32::*;
|
||||
|
||||
mod hsva_gamma;
|
||||
pub use hsva_gamma::*;
|
||||
|
||||
mod hsva;
|
||||
pub use hsva::*;
|
||||
|
||||
#[cfg(feature = "color-hex")]
|
||||
mod hex_color_macro;
|
||||
|
||||
mod rgba;
|
||||
pub use rgba::*;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Color conversion:
|
||||
|
||||
impl From<Color32> for Rgba {
|
||||
fn from(srgba: Color32) -> Rgba {
|
||||
Rgba([
|
||||
linear_f32_from_gamma_u8(srgba.0[0]),
|
||||
linear_f32_from_gamma_u8(srgba.0[1]),
|
||||
linear_f32_from_gamma_u8(srgba.0[2]),
|
||||
linear_f32_from_linear_u8(srgba.0[3]),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rgba> for Color32 {
|
||||
fn from(rgba: Rgba) -> Color32 {
|
||||
Color32([
|
||||
gamma_u8_from_linear_f32(rgba.0[0]),
|
||||
gamma_u8_from_linear_f32(rgba.0[1]),
|
||||
gamma_u8_from_linear_f32(rgba.0[2]),
|
||||
linear_u8_from_linear_f32(rgba.0[3]),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/// gamma [0, 255] -> linear [0, 1].
|
||||
pub fn linear_f32_from_gamma_u8(s: u8) -> f32 {
|
||||
if s <= 10 {
|
||||
s as f32 / 3294.6
|
||||
} else {
|
||||
((s as f32 + 14.025) / 269.025).powf(2.4)
|
||||
}
|
||||
}
|
||||
|
||||
/// linear [0, 255] -> linear [0, 1].
|
||||
/// Useful for alpha-channel.
|
||||
#[inline(always)]
|
||||
pub fn linear_f32_from_linear_u8(a: u8) -> f32 {
|
||||
a as f32 / 255.0
|
||||
}
|
||||
|
||||
/// linear [0, 1] -> gamma [0, 255] (clamped).
|
||||
/// Values outside this range will be clamped to the range.
|
||||
pub fn gamma_u8_from_linear_f32(l: f32) -> u8 {
|
||||
if l <= 0.0 {
|
||||
0
|
||||
} else if l <= 0.0031308 {
|
||||
fast_round(3294.6 * l)
|
||||
} else if l <= 1.0 {
|
||||
fast_round(269.025 * l.powf(1.0 / 2.4) - 14.025)
|
||||
} else {
|
||||
255
|
||||
}
|
||||
}
|
||||
|
||||
/// linear [0, 1] -> linear [0, 255] (clamped).
|
||||
/// Useful for alpha-channel.
|
||||
#[inline(always)]
|
||||
pub fn linear_u8_from_linear_f32(a: f32) -> u8 {
|
||||
fast_round(a * 255.0)
|
||||
}
|
||||
|
||||
fn fast_round(r: f32) -> u8 {
|
||||
(r + 0.5).floor() as _ // rust does a saturating cast since 1.45
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_srgba_conversion() {
|
||||
for b in 0..=255 {
|
||||
let l = linear_f32_from_gamma_u8(b);
|
||||
assert!(0.0 <= l && l <= 1.0);
|
||||
assert_eq!(gamma_u8_from_linear_f32(l), b);
|
||||
}
|
||||
}
|
||||
|
||||
/// gamma [0, 1] -> linear [0, 1] (not clamped).
|
||||
/// Works for numbers outside this range (e.g. negative numbers).
|
||||
pub fn linear_from_gamma(gamma: f32) -> f32 {
|
||||
if gamma < 0.0 {
|
||||
-linear_from_gamma(-gamma)
|
||||
} else if gamma <= 0.04045 {
|
||||
gamma / 12.92
|
||||
} else {
|
||||
((gamma + 0.055) / 1.055).powf(2.4)
|
||||
}
|
||||
}
|
||||
|
||||
/// linear [0, 1] -> gamma [0, 1] (not clamped).
|
||||
/// Works for numbers outside this range (e.g. negative numbers).
|
||||
pub fn gamma_from_linear(linear: f32) -> f32 {
|
||||
if linear < 0.0 {
|
||||
-gamma_from_linear(-linear)
|
||||
} else if linear <= 0.0031308 {
|
||||
12.92 * linear
|
||||
} else {
|
||||
1.055 * linear.powf(1.0 / 2.4) - 0.055
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// An assert that is only active when `epaint` is compiled with the `extra_asserts` feature
|
||||
/// or with the `extra_debug_asserts` feature in debug builds.
|
||||
#[macro_export]
|
||||
macro_rules! ecolor_assert {
|
||||
($($arg: tt)*) => {
|
||||
if cfg!(any(
|
||||
feature = "extra_asserts",
|
||||
all(feature = "extra_debug_asserts", debug_assertions),
|
||||
)) {
|
||||
assert!($($arg)*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Cheap and ugly.
|
||||
/// Made for graying out disabled `Ui`s.
|
||||
pub fn tint_color_towards(color: Color32, target: Color32) -> Color32 {
|
||||
let [mut r, mut g, mut b, mut a] = color.to_array();
|
||||
|
||||
if a == 0 {
|
||||
r /= 2;
|
||||
g /= 2;
|
||||
b /= 2;
|
||||
} else if a < 170 {
|
||||
// Cheapish and looks ok.
|
||||
// Works for e.g. grid stripes.
|
||||
let div = (2 * 255 / a as i32) as u8;
|
||||
r = r / 2 + target.r() / div;
|
||||
g = g / 2 + target.g() / div;
|
||||
b = b / 2 + target.b() / div;
|
||||
a /= 2;
|
||||
} else {
|
||||
r = r / 2 + target.r() / 2;
|
||||
g = g / 2 + target.g() / 2;
|
||||
b = b / 2 + target.b() / 2;
|
||||
}
|
||||
Color32::from_rgba_premultiplied(r, g, b, a)
|
||||
}
|
|
@ -1,266 +0,0 @@
|
|||
use crate::{
|
||||
gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_linear_u8,
|
||||
linear_u8_from_linear_f32,
|
||||
};
|
||||
|
||||
/// 0-1 linear space `RGBA` color with premultiplied alpha.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
pub struct Rgba(pub(crate) [f32; 4]);
|
||||
|
||||
impl std::ops::Index<usize> for Rgba {
|
||||
type Output = f32;
|
||||
|
||||
#[inline(always)]
|
||||
fn index(&self, index: usize) -> &f32 {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<usize> for Rgba {
|
||||
#[inline(always)]
|
||||
fn index_mut(&mut self, index: usize) -> &mut f32 {
|
||||
&mut self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn f32_hash<H: std::hash::Hasher>(state: &mut H, f: f32) {
|
||||
if f == 0.0 {
|
||||
state.write_u8(0);
|
||||
} else if f.is_nan() {
|
||||
state.write_u8(1);
|
||||
} else {
|
||||
use std::hash::Hash;
|
||||
f.to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_hash_xor_eq)]
|
||||
impl std::hash::Hash for Rgba {
|
||||
#[inline]
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
crate::f32_hash(state, self.0[0]);
|
||||
crate::f32_hash(state, self.0[1]);
|
||||
crate::f32_hash(state, self.0[2]);
|
||||
crate::f32_hash(state, self.0[3]);
|
||||
}
|
||||
}
|
||||
|
||||
impl Rgba {
|
||||
pub const TRANSPARENT: Rgba = Rgba::from_rgba_premultiplied(0.0, 0.0, 0.0, 0.0);
|
||||
pub const BLACK: Rgba = Rgba::from_rgb(0.0, 0.0, 0.0);
|
||||
pub const WHITE: Rgba = Rgba::from_rgb(1.0, 1.0, 1.0);
|
||||
pub const RED: Rgba = Rgba::from_rgb(1.0, 0.0, 0.0);
|
||||
pub const GREEN: Rgba = Rgba::from_rgb(0.0, 1.0, 0.0);
|
||||
pub const BLUE: Rgba = Rgba::from_rgb(0.0, 0.0, 1.0);
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
Self([r, g, b, a])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
Self([r * a, g * a, b * a, a])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from_srgba_premultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
let r = linear_f32_from_gamma_u8(r);
|
||||
let g = linear_f32_from_gamma_u8(g);
|
||||
let b = linear_f32_from_gamma_u8(b);
|
||||
let a = linear_f32_from_linear_u8(a);
|
||||
Self::from_rgba_premultiplied(r, g, b, a)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from_srgba_unmultiplied(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
let r = linear_f32_from_gamma_u8(r);
|
||||
let g = linear_f32_from_gamma_u8(g);
|
||||
let b = linear_f32_from_gamma_u8(b);
|
||||
let a = linear_f32_from_linear_u8(a);
|
||||
Self::from_rgba_premultiplied(r * a, g * a, b * a, a)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn from_rgb(r: f32, g: f32, b: f32) -> Self {
|
||||
Self([r, g, b, 1.0])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn from_gray(l: f32) -> Self {
|
||||
Self([l, l, l, 1.0])
|
||||
}
|
||||
|
||||
pub fn from_luminance_alpha(l: f32, a: f32) -> Self {
|
||||
crate::ecolor_assert!(0.0 <= l && l <= 1.0);
|
||||
crate::ecolor_assert!(0.0 <= a && a <= 1.0);
|
||||
Self([l * a, l * a, l * a, a])
|
||||
}
|
||||
|
||||
/// Transparent black
|
||||
#[inline(always)]
|
||||
pub fn from_black_alpha(a: f32) -> Self {
|
||||
crate::ecolor_assert!(0.0 <= a && a <= 1.0);
|
||||
Self([0.0, 0.0, 0.0, a])
|
||||
}
|
||||
|
||||
/// Transparent white
|
||||
#[inline(always)]
|
||||
pub fn from_white_alpha(a: f32) -> Self {
|
||||
crate::ecolor_assert!(0.0 <= a && a <= 1.0, "a: {}", a);
|
||||
Self([a, a, a, a])
|
||||
}
|
||||
|
||||
/// Return an additive version of this color (alpha = 0)
|
||||
#[inline(always)]
|
||||
pub fn additive(self) -> Self {
|
||||
let [r, g, b, _] = self.0;
|
||||
Self([r, g, b, 0.0])
|
||||
}
|
||||
|
||||
/// Multiply with e.g. 0.5 to make us half transparent
|
||||
#[inline(always)]
|
||||
pub fn multiply(self, alpha: f32) -> Self {
|
||||
Self([
|
||||
alpha * self[0],
|
||||
alpha * self[1],
|
||||
alpha * self[2],
|
||||
alpha * self[3],
|
||||
])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn r(&self) -> f32 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn g(&self) -> f32 {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn b(&self) -> f32 {
|
||||
self.0[2]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn a(&self) -> f32 {
|
||||
self.0[3]
|
||||
}
|
||||
|
||||
/// How perceptually intense (bright) is the color?
|
||||
#[inline]
|
||||
pub fn intensity(&self) -> f32 {
|
||||
0.3 * self.r() + 0.59 * self.g() + 0.11 * self.b()
|
||||
}
|
||||
|
||||
/// Returns an opaque version of self
|
||||
pub fn to_opaque(&self) -> Self {
|
||||
if self.a() == 0.0 {
|
||||
// Additive or fully transparent black.
|
||||
Self::from_rgb(self.r(), self.g(), self.b())
|
||||
} else {
|
||||
// un-multiply alpha:
|
||||
Self::from_rgb(
|
||||
self.r() / self.a(),
|
||||
self.g() / self.a(),
|
||||
self.b() / self.a(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Premultiplied RGBA
|
||||
#[inline(always)]
|
||||
pub fn to_array(&self) -> [f32; 4] {
|
||||
[self.r(), self.g(), self.b(), self.a()]
|
||||
}
|
||||
|
||||
/// Premultiplied RGBA
|
||||
#[inline(always)]
|
||||
pub fn to_tuple(&self) -> (f32, f32, f32, f32) {
|
||||
(self.r(), self.g(), self.b(), self.a())
|
||||
}
|
||||
|
||||
/// unmultiply the alpha
|
||||
pub fn to_rgba_unmultiplied(&self) -> [f32; 4] {
|
||||
let a = self.a();
|
||||
if a == 0.0 {
|
||||
// Additive, let's assume we are black
|
||||
self.0
|
||||
} else {
|
||||
[self.r() / a, self.g() / a, self.b() / a, a]
|
||||
}
|
||||
}
|
||||
|
||||
/// unmultiply the alpha
|
||||
pub fn to_srgba_unmultiplied(&self) -> [u8; 4] {
|
||||
let [r, g, b, a] = self.to_rgba_unmultiplied();
|
||||
[
|
||||
gamma_u8_from_linear_f32(r),
|
||||
gamma_u8_from_linear_f32(g),
|
||||
gamma_u8_from_linear_f32(b),
|
||||
linear_u8_from_linear_f32(a.abs()),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Rgba {
|
||||
type Output = Rgba;
|
||||
|
||||
#[inline(always)]
|
||||
fn add(self, rhs: Rgba) -> Rgba {
|
||||
Rgba([
|
||||
self[0] + rhs[0],
|
||||
self[1] + rhs[1],
|
||||
self[2] + rhs[2],
|
||||
self[3] + rhs[3],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Rgba> for Rgba {
|
||||
type Output = Rgba;
|
||||
|
||||
#[inline(always)]
|
||||
fn mul(self, other: Rgba) -> Rgba {
|
||||
Rgba([
|
||||
self[0] * other[0],
|
||||
self[1] * other[1],
|
||||
self[2] * other[2],
|
||||
self[3] * other[3],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f32> for Rgba {
|
||||
type Output = Rgba;
|
||||
|
||||
#[inline(always)]
|
||||
fn mul(self, factor: f32) -> Rgba {
|
||||
Rgba([
|
||||
self[0] * factor,
|
||||
self[1] * factor,
|
||||
self[2] * factor,
|
||||
self[3] * factor,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Rgba> for f32 {
|
||||
type Output = Rgba;
|
||||
|
||||
#[inline(always)]
|
||||
fn mul(self, rgba: Rgba) -> Rgba {
|
||||
Rgba([
|
||||
self * rgba[0],
|
||||
self * rgba[1],
|
||||
self * rgba[2],
|
||||
self * rgba[3],
|
||||
])
|
||||
}
|
||||
}
|
|
@ -1,229 +0,0 @@
|
|||
# Changelog for eframe
|
||||
All notable changes to the `eframe` crate.
|
||||
|
||||
NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/CHANGELOG.md), [`egui_glow`](../egui_glow/CHANGELOG.md),and [`egui-wgpu`](../egui-wgpu/CHANGELOG.md) have their own changelogs!
|
||||
|
||||
|
||||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.3 - 2023-02-15
|
||||
* Fix typing the letter 'P' on web ([#2740](https://github.com/emilk/egui/pull/2740)).
|
||||
|
||||
|
||||
## 0.21.2 - 2023-02-12
|
||||
* Allow compiling `eframe` with `--no-default-features` ([#2728](https://github.com/emilk/egui/pull/2728)).
|
||||
|
||||
|
||||
## 0.21.1 - 2023-02-12
|
||||
* Fixed crash when native window position is in an invalid state, which could happen e.g. due to changes in monitor size or DPI ([#2722](https://github.com/emilk/egui/issues/2722)).
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08 - Update to `winit` 0.28
|
||||
* ⚠️ BREAKING: `App::clear_color` now expects you to return a raw float array ([#2666](https://github.com/emilk/egui/pull/2666)).
|
||||
* The `screen_reader` feature has now been renamed `web_screen_reader` and only work on web. On other platforms, use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)).
|
||||
|
||||
#### Desktop/Native:
|
||||
* `eframe::run_native` now returns a `Result` ([#2433](https://github.com/emilk/egui/pull/2433)).
|
||||
* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)).
|
||||
* Fix bug where the cursor could get stuck using the wrong icon.
|
||||
* `NativeOptions::transparent` now works with the wgpu backend ([#2684](https://github.com/emilk/egui/pull/2684)).
|
||||
* Add `Frame::set_minimized` and `set_maximized` ([#2292](https://github.com/emilk/egui/pull/2292), [#2672](https://github.com/emilk/egui/pull/2672)).
|
||||
* Fixed persistence of native window position on Windows OS ([#2583](https://github.com/emilk/egui/issues/2583)).
|
||||
|
||||
#### Web:
|
||||
* Prevent ctrl-P/cmd-P from opening the print dialog ([#2598](https://github.com/emilk/egui/pull/2598)).
|
||||
|
||||
|
||||
## 0.20.1 - 2022-12-11
|
||||
* Fix [docs.rs](https://docs.rs/eframe) build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08 - AccessKit integration and `wgpu` web support
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.65.0` ([#2314](https://github.com/emilk/egui/pull/2314)).
|
||||
* Allow empty textures with the glow renderer.
|
||||
|
||||
#### Desktop/Native:
|
||||
* Don't repaint when just moving window ([#1980](https://github.com/emilk/egui/pull/1980)).
|
||||
* Added `NativeOptions::event_loop_builder` hook for apps to change platform specific event loop options ([#1952](https://github.com/emilk/egui/pull/1952)).
|
||||
* Enabled deferred render state initialization to support Android ([#1952](https://github.com/emilk/egui/pull/1952)).
|
||||
* Added `shader_version` to `NativeOptions` for cross compiling support on different target OpenGL | ES versions (on native `glow` renderer only) ([#1993](https://github.com/emilk/egui/pull/1993)).
|
||||
* Fix: app state is now saved when user presses Cmd-Q on Mac ([#2013](https://github.com/emilk/egui/pull/2013)).
|
||||
* Added `center` to `NativeOptions` and `monitor_size` to `WindowInfo` on desktop ([#2035](https://github.com/emilk/egui/pull/2035)).
|
||||
* Improve IME support ([#2046](https://github.com/emilk/egui/pull/2046)).
|
||||
* Added mouse-passthrough option ([#2080](https://github.com/emilk/egui/pull/2080)).
|
||||
* Added `NativeOptions::fullsize_content` option on Mac to build titlebar-less windows with floating window controls ([#2049](https://github.com/emilk/egui/pull/2049)).
|
||||
* Wgpu device/adapter/surface creation has now various configuration options exposed via `NativeOptions/WebOptions::wgpu_options` ([#2207](https://github.com/emilk/egui/pull/2207)).
|
||||
* Fix: Make sure that `native_pixels_per_point` is updated ([#2256](https://github.com/emilk/egui/pull/2256)).
|
||||
* Added optional, but enabled by default, integration with [AccessKit](https://accesskit.dev/) for implementing platform accessibility APIs ([#2294](https://github.com/emilk/egui/pull/2294)).
|
||||
* Fix: Less flickering on resize on Windows ([#2280](https://github.com/emilk/egui/pull/2280)).
|
||||
|
||||
#### Web:
|
||||
* ⚠️ BREAKING: `start_web` is a now `async` ([#2107](https://github.com/emilk/egui/pull/2107)).
|
||||
* Web: You can now use WebGL on top of `wgpu` by enabling the `wgpu` feature (and disabling `glow` via disabling default features) ([#2107](https://github.com/emilk/egui/pull/2107)).
|
||||
* Web: Add `WebInfo::user_agent` ([#2202](https://github.com/emilk/egui/pull/2202)).
|
||||
* Web: you can access your application from JS using `AppRunner::app_mut`. See `crates/egui_demo_app/src/lib.rs` ([#1886](https://github.com/emilk/egui/pull/1886)).
|
||||
|
||||
|
||||
## 0.19.0 - 2022-08-20
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
|
||||
* Added `wgpu` rendering backed ([#1564](https://github.com/emilk/egui/pull/1564)):
|
||||
* Added features `wgpu` and `glow`.
|
||||
* Added `NativeOptions::renderer` to switch between the rendering backends.
|
||||
* `egui_glow`: remove calls to `gl.get_error` in release builds to speed up rendering ([#1583](https://github.com/emilk/egui/pull/1583)).
|
||||
* Added `App::post_rendering` for e.g. reading the framebuffer ([#1591](https://github.com/emilk/egui/pull/1591)).
|
||||
* Use `Arc` for `glow::Context` instead of `Rc` ([#1640](https://github.com/emilk/egui/pull/1640)).
|
||||
* Fixed bug where the result returned from `App::on_exit_event` would sometimes be ignored ([#1696](https://github.com/emilk/egui/pull/1696)).
|
||||
* Added `NativeOptions::follow_system_theme` and `NativeOptions::default_theme` ([#1726](https://github.com/emilk/egui/pull/1726)).
|
||||
* Selectively expose parts of the API based on target arch (`wasm32` or not) ([#1867](https://github.com/emilk/egui/pull/1867)).
|
||||
|
||||
#### Desktop/Native:
|
||||
* Fixed clipboard on Wayland ([#1613](https://github.com/emilk/egui/pull/1613)).
|
||||
* Added ability to read window position and size with `frame.info().window_info` ([#1617](https://github.com/emilk/egui/pull/1617)).
|
||||
* Allow running on native without hardware accelerated rendering. Change with `NativeOptions::hardware_acceleration` ([#1681](https://github.com/emilk/egui/pull/1681), [#1693](https://github.com/emilk/egui/pull/1693)).
|
||||
* Fixed window position persistence ([#1745](https://github.com/emilk/egui/pull/1745)).
|
||||
* Fixed mouse cursor change on Linux ([#1747](https://github.com/emilk/egui/pull/1747)).
|
||||
* Added `Frame::set_visible` ([#1808](https://github.com/emilk/egui/pull/1808)).
|
||||
* Added fullscreen support ([#1866](https://github.com/emilk/egui/pull/1866)).
|
||||
* You can now continue execution after closing the native desktop window ([#1889](https://github.com/emilk/egui/pull/1889)).
|
||||
* `Frame::quit` has been renamed to `Frame::close` and `App::on_exit_event` is now `App::on_close_event` ([#1943](https://github.com/emilk/egui/pull/1943)).
|
||||
|
||||
#### Web:
|
||||
* Added ability to stop/re-run web app from JavaScript. ⚠️ You need to update your CSS with `html, body: { height: 100%; width: 100%; }` ([#1803](https://github.com/emilk/egui/pull/1650)).
|
||||
* Added `WebOptions::follow_system_theme` and `WebOptions::default_theme` ([#1726](https://github.com/emilk/egui/pull/1726)).
|
||||
* Added option to select WebGL version ([#1803](https://github.com/emilk/egui/pull/1803)).
|
||||
|
||||
|
||||
## 0.18.0 - 2022-04-30
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* Removed `eframe::epi` - everything is now in `eframe` (`eframe::App`, `eframe::Frame` etc) ([#1545](https://github.com/emilk/egui/pull/1545)).
|
||||
* Removed `Frame::request_repaint` - just call `egui::Context::request_repaint` for the same effect ([#1366](https://github.com/emilk/egui/pull/1366)).
|
||||
* Changed app creation/setup ([#1363](https://github.com/emilk/egui/pull/1363)):
|
||||
* Removed `App::setup` and `App::name`.
|
||||
* Provide `CreationContext` when creating app with egui context, storage, integration info and glow context.
|
||||
* Change interface of `run_native` and `start_web`.
|
||||
* Added `Frame::storage()` and `Frame::storage_mut()` ([#1418](https://github.com/emilk/egui/pull/1418)).
|
||||
* You can now load/save state in `App::update`
|
||||
* Changed `App::update` to take `&mut Frame` instead of `&Frame`.
|
||||
* `Frame` is no longer `Clone` or `Sync`.
|
||||
* Added `glow` (OpenGL) context to `Frame` ([#1425](https://github.com/emilk/egui/pull/1425)).
|
||||
|
||||
#### Desktop/Native:
|
||||
* 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)).
|
||||
* Added new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
|
||||
* `dark-light` (dark mode detection) is now an opt-in feature ([#1437](https://github.com/emilk/egui/pull/1437)).
|
||||
* Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)).
|
||||
* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)).
|
||||
* Moved app persistence to a background thread, allowing for smoother frame rates (on native).
|
||||
* Added `Frame::set_window_pos` ([#1505](https://github.com/emilk/egui/pull/1505)).
|
||||
|
||||
#### Web:
|
||||
* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)).
|
||||
* egui code will no longer be called after panic ([#1306](https://github.com/emilk/egui/pull/1306)).
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
* Removed `Frame::alloc_texture`. Use `egui::Context::load_texture` instead ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
* Shift-scroll will now result in horizontal scrolling on all platforms ([#1136](https://github.com/emilk/egui/pull/1136)).
|
||||
* Log using the `tracing` crate. Log to stdout by adding `tracing_subscriber::fmt::init();` to your `main` ([#1192](https://github.com/emilk/egui/pull/1192)).
|
||||
|
||||
#### Desktop/Native:
|
||||
* The default native backend is now `egui_glow` (instead of `egui_glium`) ([#1020](https://github.com/emilk/egui/pull/1020)).
|
||||
* Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)).
|
||||
* Fixed horizontal scrolling direction on Linux.
|
||||
* Added `App::on_exit_event` ([#1038](https://github.com/emilk/egui/pull/1038))
|
||||
* Added `NativeOptions::initial_window_pos`.
|
||||
* Fixed `enable_drag` for Windows OS ([#1108](https://github.com/emilk/egui/pull/1108)).
|
||||
|
||||
#### Web:
|
||||
* The default web painter is now `egui_glow` (instead of WebGL) ([#1020](https://github.com/emilk/egui/pull/1020)).
|
||||
* Fixed glow failure on Chromium ([#1092](https://github.com/emilk/egui/pull/1092)).
|
||||
* Updated `eframe::IntegrationInfo::web_location_hash` on `hashchange` event ([#1140](https://github.com/emilk/egui/pull/1140)).
|
||||
* Expose all parts of the location/url in `frame.info().web_info` ([#1258](https://github.com/emilk/egui/pull/1258)).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
* `Frame` can now be cloned, saved, and passed to background threads ([#999](https://github.com/emilk/egui/pull/999)).
|
||||
* Added `Frame::request_repaint` to replace `repaint_signal` ([#999](https://github.com/emilk/egui/pull/999)).
|
||||
* Added `Frame::alloc_texture/free_texture` to replace `tex_allocator` ([#999](https://github.com/emilk/egui/pull/999)).
|
||||
|
||||
#### Web:
|
||||
* Fixed [dark rendering in WebKitGTK](https://github.com/emilk/egui/issues/794) ([#888](https://github.com/emilk/egui/pull/888/)).
|
||||
* Added feature `glow` to switch to a [`glow`](https://github.com/grovesNL/glow) based painter ([#868](https://github.com/emilk/egui/pull/868)).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
* `Frame` now provides `set_window_title` to set window title dynamically ([#828](https://github.com/emilk/egui/pull/828)).
|
||||
* `Frame` now provides `set_decorations` to set whether to show window decorations.
|
||||
* Remove "http" feature (use https://github.com/emilk/ehttp instead!).
|
||||
* Added `App::persist_native_window` and `App::persist_egui_memory` to control what gets persisted.
|
||||
|
||||
#### Desktop/Native:
|
||||
* Increase native scroll speed.
|
||||
* Added new backend `egui_glow` as an alternative to `egui_glium`. Enable with `default-features = false, features = ["default_fonts", "egui_glow"]`.
|
||||
|
||||
#### Web:
|
||||
* Implement `eframe::NativeTexture` trait for the WebGL painter.
|
||||
* Deprecate `Painter::register_webgl_texture.
|
||||
* Fixed multiline paste.
|
||||
* Fixed painting with non-opaque backgrounds.
|
||||
* Improve text input on mobile and for IME.
|
||||
|
||||
|
||||
## 0.14.0 - 2021-08-24
|
||||
* Added dragging and dropping files into egui.
|
||||
* Improve http fetch API.
|
||||
* `run_native` now returns when the app is closed.
|
||||
* Web: Made text thicker and less pixelated.
|
||||
|
||||
|
||||
## 0.13.1 - 2021-06-24
|
||||
* Fixed `http` feature flag and docs
|
||||
|
||||
|
||||
## 0.13.0 - 2021-06-24
|
||||
* `App::setup` now takes a `Frame` and `Storage` by argument.
|
||||
* `App::load` has been removed. Implement `App::setup` instead.
|
||||
* Web: Default to light visuals unless the system reports a preference for dark mode.
|
||||
* Web: Improve alpha blending, making fonts look much better (especially in light mode)
|
||||
* Web: Fix double-paste bug
|
||||
|
||||
|
||||
## 0.12.0 - 2021-05-10
|
||||
* Moved options out of `trait App` into new `NativeOptions`.
|
||||
* Added option for `always_on_top`.
|
||||
* Web: Scroll faster when scrolling with mouse wheel.
|
||||
|
||||
|
||||
## 0.11.0 - 2021-04-05
|
||||
* You can now turn your window transparent with the `App::transparent` option.
|
||||
* You can now disable window decorations with the `App::decorated` option.
|
||||
* Web: [Fix mobile and IME text input](https://github.com/emilk/egui/pull/253)
|
||||
* Web: Hold down a modifier key when clicking a link to open it in a new tab.
|
||||
|
||||
Contributors: [n2](https://github.com/n2)
|
||||
|
||||
|
||||
## 0.10.0 - 2021-02-28
|
||||
* [You can now set your own app icons](https://github.com/emilk/egui/pull/193).
|
||||
* You can control the initial size of the native window with `App::initial_window_size`.
|
||||
* You can control the maximum egui web canvas size with `App::max_size_points`.
|
||||
* `Frame::tex_allocator()` no longer returns an `Option` (there is always a texture allocator).
|
||||
|
||||
|
||||
## 0.9.0 - 2021-02-07
|
||||
* [Added support for HTTP body](https://github.com/emilk/egui/pull/139).
|
||||
* Web: Right-clicks will no longer open browser context menu.
|
||||
* Web: Fix a bug where one couldn't select items in a combo box on a touch screen.
|
||||
|
||||
|
||||
## 0.8.0 - 2021-01-17
|
||||
* Simplify `TextureAllocator` interface.
|
||||
* WebGL2 is now supported, with improved texture sampler. WebGL1 will be used as a fallback.
|
||||
* Web: Slightly improved alpha-blending (work-around for non-existing linear-space blending).
|
||||
* Web: Call `prevent_default` for arrow keys when entering text
|
||||
|
||||
|
||||
## 0.7.0 - 2021-01-04
|
||||
* Initial release of `eframe`
|
|
@ -1,169 +0,0 @@
|
|||
[package]
|
||||
name = "eframe"
|
||||
version = "0.21.3"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "egui framework - write GUI apps that compiles to web and/or natively"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/crates/eframe"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui/tree/master/crates/eframe"
|
||||
categories = ["gui", "game-development"]
|
||||
keywords = ["egui", "gui", "gamedev"]
|
||||
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
|
||||
|
||||
[lib]
|
||||
|
||||
|
||||
[features]
|
||||
default = ["accesskit", "default_fonts", "glow"]
|
||||
|
||||
## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/).
|
||||
accesskit = ["egui/accesskit", "egui-winit/accesskit"]
|
||||
|
||||
## Detect dark mode system preference using [`dark-light`](https://docs.rs/dark-light).
|
||||
##
|
||||
## See also [`NativeOptions::follow_system_theme`] and [`NativeOptions::default_theme`].
|
||||
dark-light = ["dep:dark-light"]
|
||||
|
||||
## If set, egui will use `include_bytes!` to bundle some fonts.
|
||||
## If you plan on specifying your own fonts you may disable this feature.
|
||||
default_fonts = ["egui/default_fonts"]
|
||||
|
||||
## Use [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow).
|
||||
glow = ["dep:glow", "dep:egui_glow", "dep:glutin", "dep:glutin-winit"]
|
||||
|
||||
## Enable saving app state to disk.
|
||||
persistence = [
|
||||
"directories-next",
|
||||
"egui-winit/serde",
|
||||
"egui/persistence",
|
||||
"ron",
|
||||
"serde",
|
||||
]
|
||||
|
||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
||||
##
|
||||
## Only enabled on native, because of the low resolution (1ms) of time keeping in browsers.
|
||||
## `eframe` will call `puffin::GlobalProfiler::lock().new_frame()` for you
|
||||
puffin = ["dep:puffin", "egui_glow?/puffin", "egui-wgpu?/puffin"]
|
||||
|
||||
## Enable screen reader support (requires `ctx.options_mut(|o| o.screen_reader = true);`) on web.
|
||||
##
|
||||
## For other platforms, use the "accesskit" feature instead.
|
||||
web_screen_reader = ["tts"]
|
||||
|
||||
## If set, eframe will look for the env-var `EFRAME_SCREENSHOT_TO` and write a screenshot to that location, and then quit.
|
||||
## This is used to generate images for the examples.
|
||||
__screenshot = ["dep:image"]
|
||||
|
||||
## Use [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu)).
|
||||
## This overrides the `glow` feature.
|
||||
wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
|
||||
"bytemuck",
|
||||
"tracing",
|
||||
] }
|
||||
thiserror = "1.0.37"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
#! ### Optional dependencies
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
egui_glow = { version = "0.21.0", path = "../egui_glow", optional = true, default-features = false }
|
||||
glow = { version = "0.12", optional = true }
|
||||
ron = { version = "0.8", optional = true, features = ["integer128"] }
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
|
||||
# -------------------------------------------
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
egui-winit = { version = "0.21.1", path = "../egui-winit", default-features = false, features = [
|
||||
"clipboard",
|
||||
"links",
|
||||
] }
|
||||
raw-window-handle = { version = "0.5.0" }
|
||||
winit = "0.28.1"
|
||||
|
||||
# optional native:
|
||||
dark-light = { version = "1.0", optional = true }
|
||||
directories-next = { version = "2", optional = true }
|
||||
egui-wgpu = { version = "0.21.0", path = "../egui-wgpu", optional = true, features = [
|
||||
"winit",
|
||||
] } # if wgpu is used, use it with winit
|
||||
pollster = { version = "0.3", optional = true } # needed for wgpu
|
||||
|
||||
# we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps.
|
||||
# this can be done at the same time we expose x11/wayland features of winit crate.
|
||||
glutin = { version = "0.30", optional = true }
|
||||
glutin-winit = { version = "0.3.0", optional = true }
|
||||
image = { version = "0.24", optional = true, default-features = false, features = [
|
||||
"png",
|
||||
] }
|
||||
puffin = { version = "0.14", optional = true }
|
||||
wgpu = { version = "0.15.0", optional = true }
|
||||
|
||||
# -------------------------------------------
|
||||
# web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
bytemuck = "1.7"
|
||||
js-sys = "0.3"
|
||||
percent-encoding = "2.1"
|
||||
wasm-bindgen = "=0.2.84"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = { version = "0.3.58", features = [
|
||||
"BinaryType",
|
||||
"Blob",
|
||||
"Clipboard",
|
||||
"ClipboardEvent",
|
||||
"CompositionEvent",
|
||||
"console",
|
||||
"CssStyleDeclaration",
|
||||
"DataTransfer",
|
||||
"DataTransferItem",
|
||||
"DataTransferItemList",
|
||||
"Document",
|
||||
"DomRect",
|
||||
"DragEvent",
|
||||
"Element",
|
||||
"Event",
|
||||
"EventListener",
|
||||
"EventTarget",
|
||||
"ExtSRgb",
|
||||
"File",
|
||||
"FileList",
|
||||
"FocusEvent",
|
||||
"HtmlCanvasElement",
|
||||
"HtmlElement",
|
||||
"HtmlInputElement",
|
||||
"InputEvent",
|
||||
"KeyboardEvent",
|
||||
"Location",
|
||||
"MediaQueryList",
|
||||
"MouseEvent",
|
||||
"Navigator",
|
||||
"Performance",
|
||||
"Storage",
|
||||
"Touch",
|
||||
"TouchEvent",
|
||||
"TouchList",
|
||||
"WebGl2RenderingContext",
|
||||
"WebglDebugRendererInfo",
|
||||
"WebGlRenderingContext",
|
||||
"WheelEvent",
|
||||
"Window",
|
||||
] }
|
||||
|
||||
# optional web:
|
||||
egui-wgpu = { version = "0.21.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit
|
||||
tts = { version = "0.25", optional = true, default-features = false }
|
||||
wgpu = { version = "0.15.0", optional = true, features = ["webgl"] }
|
|
@ -1,64 +0,0 @@
|
|||
# eframe: the [`egui`](https://github.com/emilk/egui) framework
|
||||
|
||||
[](https://crates.io/crates/eframe)
|
||||
[](https://docs.rs/eframe)
|
||||
[](https://github.com/rust-secure-code/safety-dance/)
|
||||

|
||||

|
||||
|
||||
`eframe` is the official framework library for writing apps using [`egui`](https://github.com/emilk/egui). The app can be compiled both to run natively (cross platform) or be compiled to a web app (using WASM).
|
||||
|
||||
To get started, see the [examples](https://github.com/emilk/egui/tree/master/examples).
|
||||
To learn how to set up `eframe` for web and native, go to <https://github.com/emilk/eframe_template/> and follow the instructions there!
|
||||
|
||||
There is also a tutorial video at <https://www.youtube.com/watch?v=NtUkr_z7l84>.
|
||||
|
||||
For how to use `egui`, see [the egui docs](https://docs.rs/egui).
|
||||
|
||||
---
|
||||
|
||||
`eframe` uses [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) for rendering, and on native it uses [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit).
|
||||
|
||||
To use on Linux, first run:
|
||||
|
||||
```
|
||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
|
||||
```
|
||||
|
||||
You need to either use `edition = "2021"`, or set `resolver = "2"` in the `[workspace]` section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info.
|
||||
|
||||
You can opt-in to the using [`egui_wgpu`](https://github.com/emilk/egui/tree/master/crates/egui_wgpu) for rendering by enabling the `wgpu` feature and setting `NativeOptions::renderer` to `Renderer::Wgpu`.
|
||||
|
||||
|
||||
## Alternatives
|
||||
`eframe` is not the only way to write an app using `egui`! You can also try [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad), [`bevy_egui`](https://github.com/mvlabat/bevy_egui), [`egui_sdl2_gl`](https://github.com/ArjunNair/egui_sdl2_gl), and others.
|
||||
|
||||
You can also use `egui_glow` and [`winit`](https://github.com/rust-windowing/winit) to build your own app as demonstrated in <https://github.com/emilk/egui/blob/master/crates/egui_glow/examples/pure_glow.rs>.
|
||||
|
||||
|
||||
## Problems with running egui on the web
|
||||
`eframe` uses WebGL (via [`glow`](https://crates.io/crates/glow)) and WASM, and almost nothing else from the web tech stack. This has some benefits, but also produces some challenges and serious downsides.
|
||||
|
||||
* Rendering: Getting pixel-perfect rendering right on the web is very difficult.
|
||||
* Search: you cannot search an egui web page like you would a normal web page.
|
||||
* Bringing up an on-screen keyboard on mobile: there is no JS function to do this, so `eframe` fakes it by adding some invisible DOM elements. It doesn't always work.
|
||||
* Mobile text editing is not as good as for a normal web app.
|
||||
* Accessibility: There is an experimental screen reader for `eframe`, but it has to be enabled explicitly. There is no JS function to ask "Does the user want a screen reader?" (and there should probably not be such a function, due to user tracking/integrity concerns).
|
||||
* No integration with browser settings for colors and fonts.
|
||||
|
||||
In many ways, `eframe` is trying to make the browser do something it wasn't designed to do (though there are many things browser vendors could do to improve how well libraries like egui work).
|
||||
|
||||
The suggested use for `eframe` are for web apps where performance and responsiveness are more important than accessibility and mobile text editing.
|
||||
|
||||
|
||||
## Companion crates
|
||||
Not all rust crates work when compiled to WASM, but here are some useful crates have been designed to work well both natively and as WASM:
|
||||
|
||||
* Audio: [`cpal`](https://github.com/RustAudio/cpal).
|
||||
* HTTP client: [`ehttp`](https://github.com/emilk/ehttp) and [`reqwest`](https://github.com/seanmonstar/reqwest).
|
||||
* Time: [`chrono`](https://github.com/chronotope/chrono).
|
||||
* WebSockets: [`ewebsock`](https://github.com/rerun-io/ewebsock).
|
||||
|
||||
|
||||
## Name
|
||||
The _frame_ in `eframe` stands both for the frame in which your `egui` app resides and also for "framework" (`frame` is a framework, `egui` is a library).
|
File diff suppressed because it is too large
Load diff
|
@ -1,262 +0,0 @@
|
|||
//! eframe - the [`egui`] framework crate
|
||||
//!
|
||||
//! If you are planning to write an app for web or native,
|
||||
//! and want to use [`egui`] for everything, then `eframe` is for you!
|
||||
//!
|
||||
//! To get started, see the [examples](https://github.com/emilk/egui/tree/master/examples).
|
||||
//! To learn how to set up `eframe` for web and native, go to <https://github.com/emilk/eframe_template/> and follow the instructions there!
|
||||
//!
|
||||
//! In short, you implement [`App`] (especially [`App::update`]) and then
|
||||
//! call [`crate::run_native`] from your `main.rs`, and/or call `eframe::start_web` from your `lib.rs`.
|
||||
//!
|
||||
//! ## Usage, native:
|
||||
//! ``` no_run
|
||||
//! use eframe::egui;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! let native_options = eframe::NativeOptions::default();
|
||||
//! eframe::run_native("My egui App", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))));
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Default)]
|
||||
//! struct MyEguiApp {}
|
||||
//!
|
||||
//! impl MyEguiApp {
|
||||
//! fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
|
||||
//! // Restore app state using cc.storage (requires the "persistence" feature).
|
||||
//! // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
|
||||
//! // for e.g. egui::PaintCallback.
|
||||
//! Self::default()
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! impl eframe::App for MyEguiApp {
|
||||
//! fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
//! egui::CentralPanel::default().show(ctx, |ui| {
|
||||
//! ui.heading("Hello World!");
|
||||
//! });
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Usage, web:
|
||||
//! ``` no_run
|
||||
//! #[cfg(target_arch = "wasm32")]
|
||||
//! use wasm_bindgen::prelude::*;
|
||||
//!
|
||||
//! /// Call this once from the HTML.
|
||||
//! #[cfg(target_arch = "wasm32")]
|
||||
//! #[wasm_bindgen]
|
||||
//! pub async fn start(canvas_id: &str) -> Result<AppRunnerRef, eframe::wasm_bindgen::JsValue> {
|
||||
//! let web_options = eframe::WebOptions::default();
|
||||
//! eframe::start_web(canvas_id, web_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))).await
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Feature flags
|
||||
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
|
||||
//!
|
||||
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
|
||||
// Re-export all useful libraries:
|
||||
pub use {egui, egui::emath, egui::epaint};
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
pub use {egui_glow, glow};
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub use {egui_wgpu, wgpu};
|
||||
|
||||
mod epi;
|
||||
|
||||
// Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is:
|
||||
pub use epi::*;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// When compiling for web
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod web;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm_bindgen;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use web::AppRunnerRef;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web_sys;
|
||||
|
||||
/// Install event listeners to register different input events
|
||||
/// and start running the given app.
|
||||
///
|
||||
/// ``` no_run
|
||||
/// #[cfg(target_arch = "wasm32")]
|
||||
/// use wasm_bindgen::prelude::*;
|
||||
///
|
||||
/// /// This is the entry-point for all the web-assembly.
|
||||
/// /// This is called from the HTML.
|
||||
/// /// It loads the app, installs some callbacks, then returns.
|
||||
/// /// It returns a handle to the running app that can be stopped calling `AppRunner::stop_web`.
|
||||
/// /// You can add more callbacks like this if you want to call in to your code.
|
||||
/// #[cfg(target_arch = "wasm32")]
|
||||
/// #[wasm_bindgen]
|
||||
/// pub struct WebHandle {
|
||||
/// handle: AppRunnerRef,
|
||||
/// }
|
||||
/// #[cfg(target_arch = "wasm32")]
|
||||
/// #[wasm_bindgen]
|
||||
/// pub async fn start(canvas_id: &str) -> Result<WebHandle, eframe::wasm_bindgen::JsValue> {
|
||||
/// let web_options = eframe::WebOptions::default();
|
||||
/// eframe::start_web(
|
||||
/// canvas_id,
|
||||
/// web_options,
|
||||
/// Box::new(|cc| Box::new(MyEguiApp::new(cc))),
|
||||
/// )
|
||||
/// .await
|
||||
/// .map(|handle| WebHandle { handle })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
/// Failing to initialize WebGL graphics.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub async fn start_web(
|
||||
canvas_id: &str,
|
||||
web_options: WebOptions,
|
||||
app_creator: AppCreator,
|
||||
) -> std::result::Result<AppRunnerRef, wasm_bindgen::JsValue> {
|
||||
let handle = web::start(canvas_id, web_options, app_creator).await?;
|
||||
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// When compiling natively
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
mod native;
|
||||
|
||||
/// 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 [`App::save`]).
|
||||
///
|
||||
/// Call from `fn main` like this:
|
||||
/// ``` no_run
|
||||
/// use eframe::egui;
|
||||
///
|
||||
/// fn main() {
|
||||
/// let native_options = eframe::NativeOptions::default();
|
||||
/// eframe::run_native("MyApp", native_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))));
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Default)]
|
||||
/// struct MyEguiApp {}
|
||||
///
|
||||
/// impl MyEguiApp {
|
||||
/// fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
|
||||
/// // Restore app state using cc.storage (requires the "persistence" feature).
|
||||
/// // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
|
||||
/// // for e.g. egui::PaintCallback.
|
||||
/// Self::default()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl eframe::App for MyEguiApp {
|
||||
/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||
/// ui.heading("Hello World!");
|
||||
/// });
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
/// This function can fail if we fail to set up a graphics context.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
pub fn run_native(
|
||||
app_name: &str,
|
||||
native_options: NativeOptions,
|
||||
app_creator: AppCreator,
|
||||
) -> Result<()> {
|
||||
let renderer = native_options.renderer;
|
||||
|
||||
#[cfg(not(feature = "__screenshot"))]
|
||||
assert!(
|
||||
std::env::var("EFRAME_SCREENSHOT_TO").is_err(),
|
||||
"EFRAME_SCREENSHOT_TO found without compiling with the '__screenshot' feature"
|
||||
);
|
||||
|
||||
match renderer {
|
||||
#[cfg(feature = "glow")]
|
||||
Renderer::Glow => {
|
||||
tracing::debug!("Using the glow renderer");
|
||||
native::run::run_glow(app_name, native_options, app_creator)
|
||||
}
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
Renderer::Wgpu => {
|
||||
tracing::debug!("Using the wgpu renderer");
|
||||
native::run::run_wgpu(app_name, native_options, app_creator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// The different problems that can occur when trying to run `eframe`.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[error("winit error: {0}")]
|
||||
Winit(#[from] winit::error::OsError),
|
||||
|
||||
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
|
||||
#[error("glutin error: {0}")]
|
||||
Glutin(#[from] glutin::error::Error),
|
||||
|
||||
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
|
||||
#[error("Found no glutin configs matching the template: {0:?}. error: {1:?}")]
|
||||
NoGlutinConfigs(glutin::config::ConfigTemplate, Box<dyn std::error::Error>),
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
#[error("WGPU error: {0}")]
|
||||
Wgpu(#[from] egui_wgpu::WgpuError),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
mod profiling_scopes {
|
||||
/// Profiling macro for feature "puffin"
|
||||
macro_rules! profile_function {
|
||||
($($arg: tt)*) => {
|
||||
#[cfg(feature = "puffin")]
|
||||
puffin::profile_function!($($arg)*);
|
||||
};
|
||||
}
|
||||
pub(crate) use profile_function;
|
||||
|
||||
/// Profiling macro for feature "puffin"
|
||||
macro_rules! profile_scope {
|
||||
($($arg: tt)*) => {
|
||||
#[cfg(feature = "puffin")]
|
||||
puffin::profile_scope!($($arg)*);
|
||||
};
|
||||
}
|
||||
pub(crate) use profile_scope;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
pub(crate) use profiling_scopes::*;
|
|
@ -1,567 +0,0 @@
|
|||
use winit::event_loop::EventLoopWindowTarget;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use winit::platform::macos::WindowBuilderExtMacOS as _;
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
use egui::accesskit;
|
||||
use egui::NumExt as _;
|
||||
#[cfg(feature = "accesskit")]
|
||||
use egui_winit::accesskit_winit;
|
||||
use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings};
|
||||
|
||||
use crate::{epi, Theme, WindowInfo};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WindowState {
|
||||
// We cannot simply call `winit::Window::is_minimized/is_maximized`
|
||||
// because that deadlocks on mac.
|
||||
pub minimized: bool,
|
||||
pub maximized: bool,
|
||||
}
|
||||
|
||||
pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
|
||||
winit::dpi::LogicalSize {
|
||||
width: points.x as f64,
|
||||
height: points.y as f64,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_window_info(
|
||||
window: &winit::window::Window,
|
||||
pixels_per_point: f32,
|
||||
window_state: &WindowState,
|
||||
) -> WindowInfo {
|
||||
let position = window
|
||||
.outer_position()
|
||||
.ok()
|
||||
.map(|pos| pos.to_logical::<f32>(pixels_per_point.into()))
|
||||
.map(|pos| egui::Pos2 { x: pos.x, y: pos.y });
|
||||
|
||||
let monitor = window.current_monitor().is_some();
|
||||
let monitor_size = if monitor {
|
||||
let size = window.current_monitor().unwrap().size();
|
||||
Some(egui::vec2(size.width as _, size.height as _))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let size = window
|
||||
.inner_size()
|
||||
.to_logical::<f32>(pixels_per_point.into());
|
||||
|
||||
// NOTE: calling window.is_minimized() or window.is_maximized() deadlocks on Mac.
|
||||
|
||||
WindowInfo {
|
||||
position,
|
||||
fullscreen: window.fullscreen().is_some(),
|
||||
minimized: window_state.minimized,
|
||||
maximized: window_state.maximized,
|
||||
size: egui::Vec2 {
|
||||
x: size.width,
|
||||
y: size.height,
|
||||
},
|
||||
monitor_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_builder<E>(
|
||||
event_loop: &EventLoopWindowTarget<E>,
|
||||
title: &str,
|
||||
native_options: &epi::NativeOptions,
|
||||
window_settings: Option<WindowSettings>,
|
||||
) -> winit::window::WindowBuilder {
|
||||
let epi::NativeOptions {
|
||||
maximized,
|
||||
decorated,
|
||||
fullscreen,
|
||||
#[cfg(target_os = "macos")]
|
||||
fullsize_content,
|
||||
drag_and_drop_support,
|
||||
icon_data,
|
||||
initial_window_pos,
|
||||
initial_window_size,
|
||||
min_window_size,
|
||||
max_window_size,
|
||||
resizable,
|
||||
transparent,
|
||||
centered,
|
||||
..
|
||||
} = native_options;
|
||||
|
||||
let window_icon = icon_data.clone().and_then(load_icon);
|
||||
|
||||
let mut window_builder = winit::window::WindowBuilder::new()
|
||||
.with_title(title)
|
||||
.with_decorations(*decorated)
|
||||
.with_fullscreen(fullscreen.then(|| winit::window::Fullscreen::Borderless(None)))
|
||||
.with_maximized(*maximized)
|
||||
.with_resizable(*resizable)
|
||||
.with_transparent(*transparent)
|
||||
.with_window_icon(window_icon)
|
||||
// Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
// We must also keep the window hidden until AccessKit is initialized.
|
||||
.with_visible(false);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if *fullsize_content {
|
||||
window_builder = window_builder
|
||||
.with_title_hidden(true)
|
||||
.with_titlebar_transparent(true)
|
||||
.with_fullsize_content_view(true);
|
||||
}
|
||||
|
||||
if let Some(min_size) = *min_window_size {
|
||||
window_builder = window_builder.with_min_inner_size(points_to_size(min_size));
|
||||
}
|
||||
if let Some(max_size) = *max_window_size {
|
||||
window_builder = window_builder.with_max_inner_size(points_to_size(max_size));
|
||||
}
|
||||
|
||||
window_builder = window_builder_drag_and_drop(window_builder, *drag_and_drop_support);
|
||||
|
||||
let inner_size_points = if let Some(mut window_settings) = window_settings {
|
||||
// Restore pos/size from previous session
|
||||
window_settings.clamp_to_sane_values(largest_monitor_point_size(event_loop));
|
||||
#[cfg(windows)]
|
||||
window_settings.clamp_window_to_sane_position(&event_loop);
|
||||
window_builder = window_settings.initialize_window(window_builder);
|
||||
window_settings.inner_size_points()
|
||||
} else {
|
||||
if let Some(pos) = *initial_window_pos {
|
||||
window_builder = window_builder.with_position(winit::dpi::LogicalPosition {
|
||||
x: pos.x as f64,
|
||||
y: pos.y as f64,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(initial_window_size) = *initial_window_size {
|
||||
let initial_window_size =
|
||||
initial_window_size.at_most(largest_monitor_point_size(event_loop));
|
||||
window_builder = window_builder.with_inner_size(points_to_size(initial_window_size));
|
||||
}
|
||||
|
||||
*initial_window_size
|
||||
};
|
||||
|
||||
if *centered {
|
||||
if let Some(monitor) = event_loop.available_monitors().next() {
|
||||
let monitor_size = monitor.size();
|
||||
let inner_size = inner_size_points.unwrap_or(egui::Vec2 { x: 800.0, y: 600.0 });
|
||||
if monitor_size.width > 0 && monitor_size.height > 0 {
|
||||
let x = (monitor_size.width - inner_size.x as u32) / 2;
|
||||
let y = (monitor_size.height - inner_size.y as u32) / 2;
|
||||
window_builder = window_builder.with_position(winit::dpi::LogicalPosition {
|
||||
x: x as f64,
|
||||
y: y as f64,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
window_builder
|
||||
}
|
||||
|
||||
pub fn apply_native_options_to_window(
|
||||
window: &winit::window::Window,
|
||||
native_options: &crate::NativeOptions,
|
||||
) {
|
||||
use winit::window::WindowLevel;
|
||||
window.set_window_level(if native_options.always_on_top {
|
||||
WindowLevel::AlwaysOnTop
|
||||
} else {
|
||||
WindowLevel::Normal
|
||||
});
|
||||
}
|
||||
|
||||
fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui::Vec2 {
|
||||
let mut max_size = egui::Vec2::ZERO;
|
||||
|
||||
for monitor in event_loop.available_monitors() {
|
||||
let size = monitor.size().to_logical::<f32>(monitor.scale_factor());
|
||||
let size = egui::vec2(size.width, size.height);
|
||||
max_size = max_size.max(size);
|
||||
}
|
||||
|
||||
if max_size == egui::Vec2::ZERO {
|
||||
egui::Vec2::splat(16000.0)
|
||||
} else {
|
||||
max_size
|
||||
}
|
||||
}
|
||||
|
||||
fn load_icon(icon_data: epi::IconData) -> Option<winit::window::Icon> {
|
||||
winit::window::Icon::from_rgba(icon_data.rgba, icon_data.width, icon_data.height).ok()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn window_builder_drag_and_drop(
|
||||
window_builder: winit::window::WindowBuilder,
|
||||
enable: bool,
|
||||
) -> winit::window::WindowBuilder {
|
||||
use winit::platform::windows::WindowBuilderExtWindows as _;
|
||||
window_builder.with_drag_and_drop(enable)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn window_builder_drag_and_drop(
|
||||
window_builder: winit::window::WindowBuilder,
|
||||
_enable: bool,
|
||||
) -> winit::window::WindowBuilder {
|
||||
// drag and drop can only be disabled on windows
|
||||
window_builder
|
||||
}
|
||||
|
||||
pub fn handle_app_output(
|
||||
window: &winit::window::Window,
|
||||
current_pixels_per_point: f32,
|
||||
app_output: epi::backend::AppOutput,
|
||||
window_state: &mut WindowState,
|
||||
) {
|
||||
let epi::backend::AppOutput {
|
||||
close: _,
|
||||
window_size,
|
||||
window_title,
|
||||
decorated,
|
||||
fullscreen,
|
||||
drag_window,
|
||||
window_pos,
|
||||
visible: _, // handled in post_present
|
||||
always_on_top,
|
||||
minimized,
|
||||
maximized,
|
||||
} = app_output;
|
||||
|
||||
if let Some(decorated) = decorated {
|
||||
window.set_decorations(decorated);
|
||||
}
|
||||
|
||||
if let Some(window_size) = window_size {
|
||||
window.set_inner_size(
|
||||
winit::dpi::PhysicalSize {
|
||||
width: (current_pixels_per_point * window_size.x).round(),
|
||||
height: (current_pixels_per_point * window_size.y).round(),
|
||||
}
|
||||
.to_logical::<f32>(native_pixels_per_point(window) as f64),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(fullscreen) = fullscreen {
|
||||
window.set_fullscreen(fullscreen.then_some(winit::window::Fullscreen::Borderless(None)));
|
||||
}
|
||||
|
||||
if let Some(window_title) = window_title {
|
||||
window.set_title(&window_title);
|
||||
}
|
||||
|
||||
if let Some(window_pos) = window_pos {
|
||||
window.set_outer_position(winit::dpi::PhysicalPosition {
|
||||
x: window_pos.x as f64,
|
||||
y: window_pos.y as f64,
|
||||
});
|
||||
}
|
||||
|
||||
if drag_window {
|
||||
let _ = window.drag_window();
|
||||
}
|
||||
|
||||
if let Some(always_on_top) = always_on_top {
|
||||
use winit::window::WindowLevel;
|
||||
window.set_window_level(if always_on_top {
|
||||
WindowLevel::AlwaysOnTop
|
||||
} else {
|
||||
WindowLevel::Normal
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(minimized) = minimized {
|
||||
window.set_minimized(minimized);
|
||||
window_state.minimized = minimized;
|
||||
}
|
||||
|
||||
if let Some(maximized) = maximized {
|
||||
window.set_maximized(maximized);
|
||||
window_state.maximized = maximized;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// For loading/saving app state and/or egui memory to disk.
|
||||
pub fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
|
||||
#[cfg(feature = "persistence")]
|
||||
if let Some(storage) = super::file_storage::FileStorage::from_app_name(_app_name) {
|
||||
return Some(Box::new(storage));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Everything needed to make a winit-based integration for [`epi`].
|
||||
pub struct EpiIntegration {
|
||||
pub frame: epi::Frame,
|
||||
last_auto_save: std::time::Instant,
|
||||
pub egui_ctx: egui::Context,
|
||||
pending_full_output: egui::FullOutput,
|
||||
egui_winit: egui_winit::State,
|
||||
/// When set, it is time to close the native window.
|
||||
close: bool,
|
||||
can_drag_window: bool,
|
||||
window_state: WindowState,
|
||||
}
|
||||
|
||||
impl EpiIntegration {
|
||||
pub fn new<E>(
|
||||
event_loop: &EventLoopWindowTarget<E>,
|
||||
max_texture_side: usize,
|
||||
window: &winit::window::Window,
|
||||
system_theme: Option<Theme>,
|
||||
storage: Option<Box<dyn epi::Storage>>,
|
||||
#[cfg(feature = "glow")] gl: Option<std::sync::Arc<glow::Context>>,
|
||||
#[cfg(feature = "wgpu")] wgpu_render_state: Option<egui_wgpu::RenderState>,
|
||||
) -> Self {
|
||||
let egui_ctx = egui::Context::default();
|
||||
|
||||
let memory = load_egui_memory(storage.as_deref()).unwrap_or_default();
|
||||
egui_ctx.memory_mut(|mem| *mem = memory);
|
||||
|
||||
let native_pixels_per_point = window.scale_factor() as f32;
|
||||
|
||||
let window_state = WindowState {
|
||||
minimized: window.is_minimized().unwrap_or(false),
|
||||
maximized: window.is_maximized(),
|
||||
};
|
||||
|
||||
let frame = epi::Frame {
|
||||
info: epi::IntegrationInfo {
|
||||
system_theme,
|
||||
cpu_usage: None,
|
||||
native_pixels_per_point: Some(native_pixels_per_point),
|
||||
window_info: read_window_info(window, egui_ctx.pixels_per_point(), &window_state),
|
||||
},
|
||||
output: epi::backend::AppOutput {
|
||||
visible: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
storage,
|
||||
#[cfg(feature = "glow")]
|
||||
gl,
|
||||
#[cfg(feature = "wgpu")]
|
||||
wgpu_render_state,
|
||||
};
|
||||
|
||||
let mut egui_winit = egui_winit::State::new(event_loop);
|
||||
egui_winit.set_max_texture_side(max_texture_side);
|
||||
egui_winit.set_pixels_per_point(native_pixels_per_point);
|
||||
|
||||
Self {
|
||||
frame,
|
||||
last_auto_save: std::time::Instant::now(),
|
||||
egui_ctx,
|
||||
egui_winit,
|
||||
pending_full_output: Default::default(),
|
||||
close: false,
|
||||
can_drag_window: false,
|
||||
window_state,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub fn init_accesskit<E: From<accesskit_winit::ActionRequestEvent> + Send>(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
event_loop_proxy: winit::event_loop::EventLoopProxy<E>,
|
||||
) {
|
||||
let egui_ctx = self.egui_ctx.clone();
|
||||
self.egui_winit
|
||||
.init_accesskit(window, event_loop_proxy, move || {
|
||||
// This function is called when an accessibility client
|
||||
// (e.g. screen reader) makes its first request. If we got here,
|
||||
// we know that an accessibility tree is actually wanted.
|
||||
egui_ctx.enable_accesskit();
|
||||
// Enqueue a repaint so we'll receive a full tree update soon.
|
||||
egui_ctx.request_repaint();
|
||||
egui_ctx.accesskit_placeholder_tree_update()
|
||||
});
|
||||
}
|
||||
|
||||
pub fn warm_up(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
|
||||
crate::profile_function!();
|
||||
let saved_memory: egui::Memory = self.egui_ctx.memory(|mem| mem.clone());
|
||||
self.egui_ctx
|
||||
.memory_mut(|mem| mem.set_everything_is_visible(true));
|
||||
let full_output = self.update(app, window);
|
||||
self.pending_full_output.append(full_output); // Handle it next frame
|
||||
self.egui_ctx.memory_mut(|mem| *mem = saved_memory); // We don't want to remember that windows were huge.
|
||||
self.egui_ctx.clear_animations();
|
||||
}
|
||||
|
||||
/// If `true`, it is time to close the native window.
|
||||
pub fn should_close(&self) -> bool {
|
||||
self.close
|
||||
}
|
||||
|
||||
pub fn on_event(
|
||||
&mut self,
|
||||
app: &mut dyn epi::App,
|
||||
event: &winit::event::WindowEvent<'_>,
|
||||
) -> EventResponse {
|
||||
use winit::event::{ElementState, MouseButton, WindowEvent};
|
||||
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
tracing::debug!("Received WindowEvent::CloseRequested");
|
||||
self.close = app.on_close_event();
|
||||
tracing::debug!("App::on_close_event returned {}", self.close);
|
||||
}
|
||||
WindowEvent::Destroyed => {
|
||||
tracing::debug!("Received WindowEvent::Destroyed");
|
||||
self.close = true;
|
||||
}
|
||||
WindowEvent::MouseInput {
|
||||
button: MouseButton::Left,
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
} => self.can_drag_window = true,
|
||||
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
||||
self.frame.info.native_pixels_per_point = Some(*scale_factor as _);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.egui_winit.on_event(&self.egui_ctx, event)
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub fn on_accesskit_action_request(&mut self, request: accesskit::ActionRequest) {
|
||||
self.egui_winit.on_accesskit_action_request(request);
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
app: &mut dyn epi::App,
|
||||
window: &winit::window::Window,
|
||||
) -> egui::FullOutput {
|
||||
let frame_start = std::time::Instant::now();
|
||||
|
||||
self.frame.info.window_info =
|
||||
read_window_info(window, self.egui_ctx.pixels_per_point(), &self.window_state);
|
||||
let raw_input = self.egui_winit.take_egui_input(window);
|
||||
|
||||
// Run user code:
|
||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
crate::profile_scope!("App::update");
|
||||
app.update(egui_ctx, &mut self.frame);
|
||||
});
|
||||
|
||||
self.pending_full_output.append(full_output);
|
||||
let full_output = std::mem::take(&mut self.pending_full_output);
|
||||
|
||||
{
|
||||
let mut app_output = self.frame.take_app_output();
|
||||
app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
|
||||
self.can_drag_window = false;
|
||||
if app_output.close {
|
||||
self.close = app.on_close_event();
|
||||
tracing::debug!("App::on_close_event returned {}", self.close);
|
||||
}
|
||||
self.frame.output.visible = app_output.visible; // this is handled by post_present
|
||||
handle_app_output(
|
||||
window,
|
||||
self.egui_ctx.pixels_per_point(),
|
||||
app_output,
|
||||
&mut self.window_state,
|
||||
);
|
||||
}
|
||||
|
||||
let frame_time = frame_start.elapsed().as_secs_f64() as f32;
|
||||
self.frame.info.cpu_usage = Some(frame_time);
|
||||
|
||||
full_output
|
||||
}
|
||||
|
||||
pub fn post_rendering(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
|
||||
let inner_size = window.inner_size();
|
||||
let window_size_px = [inner_size.width, inner_size.height];
|
||||
|
||||
app.post_rendering(window_size_px, &self.frame);
|
||||
}
|
||||
|
||||
pub fn post_present(&mut self, window: &winit::window::Window) {
|
||||
if let Some(visible) = self.frame.output.visible.take() {
|
||||
window.set_visible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_platform_output(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
platform_output: egui::PlatformOutput,
|
||||
) {
|
||||
self.egui_winit
|
||||
.handle_platform_output(window, &self.egui_ctx, platform_output);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Persistance stuff:
|
||||
|
||||
pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
|
||||
let now = std::time::Instant::now();
|
||||
if now - self.last_auto_save > app.auto_save_interval() {
|
||||
self.save(app, window);
|
||||
self.last_auto_save = now;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&mut self, _app: &mut dyn epi::App, _window: &winit::window::Window) {
|
||||
#[cfg(feature = "persistence")]
|
||||
if let Some(storage) = self.frame.storage_mut() {
|
||||
crate::profile_function!();
|
||||
|
||||
if _app.persist_native_window() {
|
||||
crate::profile_scope!("native_window");
|
||||
epi::set_value(
|
||||
storage,
|
||||
STORAGE_WINDOW_KEY,
|
||||
&WindowSettings::from_display(_window),
|
||||
);
|
||||
}
|
||||
if _app.persist_egui_memory() {
|
||||
crate::profile_scope!("egui_memory");
|
||||
self.egui_ctx
|
||||
.memory(|mem| epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, mem));
|
||||
}
|
||||
{
|
||||
crate::profile_scope!("App::save");
|
||||
_app.save(storage);
|
||||
}
|
||||
|
||||
crate::profile_scope!("Storage::flush");
|
||||
storage.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
const STORAGE_EGUI_MEMORY_KEY: &str = "egui";
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
const STORAGE_WINDOW_KEY: &str = "window";
|
||||
|
||||
pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option<WindowSettings> {
|
||||
#[cfg(feature = "persistence")]
|
||||
{
|
||||
epi::get_value(_storage?, STORAGE_WINDOW_KEY)
|
||||
}
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
None
|
||||
}
|
||||
|
||||
pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option<egui::Memory> {
|
||||
#[cfg(feature = "persistence")]
|
||||
{
|
||||
epi::get_value(_storage?, STORAGE_EGUI_MEMORY_KEY)
|
||||
}
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
None
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
mod epi_integration;
|
||||
pub mod run;
|
||||
|
||||
/// File storage which can be used by native backends.
|
||||
#[cfg(feature = "persistence")]
|
||||
pub mod file_storage;
|
File diff suppressed because it is too large
Load diff
|
@ -1,585 +0,0 @@
|
|||
use egui::{
|
||||
mutex::{Mutex, MutexGuard},
|
||||
TexturesDelta,
|
||||
};
|
||||
|
||||
use crate::{epi, App};
|
||||
|
||||
use super::{web_painter::WebPainter, *};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Data gathered between frames.
|
||||
#[derive(Default)]
|
||||
pub struct WebInput {
|
||||
/// Required because we don't get a position on touched
|
||||
pub latest_touch_pos: Option<egui::Pos2>,
|
||||
|
||||
/// Required to maintain a stable touch position for multi-touch gestures.
|
||||
pub latest_touch_pos_id: Option<egui::TouchId>,
|
||||
|
||||
pub raw: egui::RawInput,
|
||||
}
|
||||
|
||||
impl WebInput {
|
||||
pub fn new_frame(&mut self, canvas_size: egui::Vec2) -> egui::RawInput {
|
||||
egui::RawInput {
|
||||
screen_rect: Some(egui::Rect::from_min_size(Default::default(), canvas_size)),
|
||||
pixels_per_point: Some(native_pixels_per_point()), // We ALWAYS use the native pixels-per-point
|
||||
time: Some(now_sec()),
|
||||
..self.raw.take()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
|
||||
/// Stores when to do the next repaint.
|
||||
pub struct NeedRepaint(Mutex<f64>);
|
||||
|
||||
impl Default for NeedRepaint {
|
||||
fn default() -> Self {
|
||||
Self(Mutex::new(f64::NEG_INFINITY)) // start with a repaint
|
||||
}
|
||||
}
|
||||
|
||||
impl NeedRepaint {
|
||||
/// Returns the time (in [`now_sec`] scale) when
|
||||
/// we should next repaint.
|
||||
pub fn when_to_repaint(&self) -> f64 {
|
||||
*self.0.lock()
|
||||
}
|
||||
|
||||
/// Unschedule repainting.
|
||||
pub fn clear(&self) {
|
||||
*self.0.lock() = f64::INFINITY;
|
||||
}
|
||||
|
||||
pub fn repaint_after(&self, num_seconds: f64) {
|
||||
let mut repaint_time = self.0.lock();
|
||||
*repaint_time = repaint_time.min(now_sec() + num_seconds);
|
||||
}
|
||||
|
||||
pub fn repaint_asap(&self) {
|
||||
*self.0.lock() = f64::NEG_INFINITY;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IsDestroyed(std::sync::atomic::AtomicBool);
|
||||
|
||||
impl Default for IsDestroyed {
|
||||
fn default() -> Self {
|
||||
Self(false.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl IsDestroyed {
|
||||
pub fn fetch(&self) -> bool {
|
||||
self.0.load(SeqCst)
|
||||
}
|
||||
|
||||
pub fn set_true(&self) {
|
||||
self.0.store(true, SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
fn user_agent() -> Option<String> {
|
||||
web_sys::window()?.navigator().user_agent().ok()
|
||||
}
|
||||
|
||||
fn web_location() -> epi::Location {
|
||||
let location = web_sys::window().unwrap().location();
|
||||
|
||||
let hash = percent_decode(&location.hash().unwrap_or_default());
|
||||
|
||||
let query = location
|
||||
.search()
|
||||
.unwrap_or_default()
|
||||
.strip_prefix('?')
|
||||
.map(percent_decode)
|
||||
.unwrap_or_default();
|
||||
|
||||
let query_map = parse_query_map(&query)
|
||||
.iter()
|
||||
.map(|(k, v)| ((*k).to_owned(), (*v).to_owned()))
|
||||
.collect();
|
||||
|
||||
epi::Location {
|
||||
url: percent_decode(&location.href().unwrap_or_default()),
|
||||
protocol: percent_decode(&location.protocol().unwrap_or_default()),
|
||||
host: percent_decode(&location.host().unwrap_or_default()),
|
||||
hostname: percent_decode(&location.hostname().unwrap_or_default()),
|
||||
port: percent_decode(&location.port().unwrap_or_default()),
|
||||
hash,
|
||||
query,
|
||||
query_map,
|
||||
origin: percent_decode(&location.origin().unwrap_or_default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_query_map(query: &str) -> BTreeMap<&str, &str> {
|
||||
query
|
||||
.split('&')
|
||||
.filter_map(|pair| {
|
||||
if pair.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(if let Some((key, value)) = pair.split_once('=') {
|
||||
(key, value)
|
||||
} else {
|
||||
(pair, "")
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_query() {
|
||||
assert_eq!(parse_query_map(""), BTreeMap::default());
|
||||
assert_eq!(parse_query_map("foo"), BTreeMap::from_iter([("foo", "")]));
|
||||
assert_eq!(
|
||||
parse_query_map("foo=bar"),
|
||||
BTreeMap::from_iter([("foo", "bar")])
|
||||
);
|
||||
assert_eq!(
|
||||
parse_query_map("foo=bar&baz=42"),
|
||||
BTreeMap::from_iter([("foo", "bar"), ("baz", "42")])
|
||||
);
|
||||
assert_eq!(
|
||||
parse_query_map("foo&baz=42"),
|
||||
BTreeMap::from_iter([("foo", ""), ("baz", "42")])
|
||||
);
|
||||
assert_eq!(
|
||||
parse_query_map("foo&baz&&"),
|
||||
BTreeMap::from_iter([("foo", ""), ("baz", "")])
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct AppRunner {
|
||||
pub(crate) frame: epi::Frame,
|
||||
egui_ctx: egui::Context,
|
||||
painter: ActiveWebPainter,
|
||||
pub(crate) input: WebInput,
|
||||
app: Box<dyn epi::App>,
|
||||
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
||||
pub(crate) is_destroyed: std::sync::Arc<IsDestroyed>,
|
||||
last_save_time: f64,
|
||||
screen_reader: super::screen_reader::ScreenReader,
|
||||
pub(crate) text_cursor_pos: Option<egui::Pos2>,
|
||||
pub(crate) mutable_text_under_cursor: bool,
|
||||
textures_delta: TexturesDelta,
|
||||
pub events_to_unsubscribe: Vec<EventToUnsubscribe>,
|
||||
}
|
||||
|
||||
impl Drop for AppRunner {
|
||||
fn drop(&mut self) {
|
||||
tracing::debug!("AppRunner has fully dropped");
|
||||
}
|
||||
}
|
||||
|
||||
impl AppRunner {
|
||||
/// # Errors
|
||||
/// Failure to initialize WebGL renderer.
|
||||
pub async fn new(
|
||||
canvas_id: &str,
|
||||
web_options: crate::WebOptions,
|
||||
app_creator: epi::AppCreator,
|
||||
) -> Result<Self, String> {
|
||||
let painter = ActiveWebPainter::new(canvas_id, &web_options).await?;
|
||||
|
||||
let system_theme = if web_options.follow_system_theme {
|
||||
super::system_theme()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let info = epi::IntegrationInfo {
|
||||
web_info: epi::WebInfo {
|
||||
user_agent: user_agent().unwrap_or_default(),
|
||||
location: web_location(),
|
||||
},
|
||||
system_theme,
|
||||
cpu_usage: None,
|
||||
native_pixels_per_point: Some(native_pixels_per_point()),
|
||||
};
|
||||
let storage = LocalStorage::default();
|
||||
|
||||
let egui_ctx = egui::Context::default();
|
||||
egui_ctx.set_os(egui::os::OperatingSystem::from_user_agent(
|
||||
&user_agent().unwrap_or_default(),
|
||||
));
|
||||
load_memory(&egui_ctx);
|
||||
|
||||
let theme = system_theme.unwrap_or(web_options.default_theme);
|
||||
egui_ctx.set_visuals(theme.egui_visuals());
|
||||
|
||||
let app = app_creator(&epi::CreationContext {
|
||||
egui_ctx: egui_ctx.clone(),
|
||||
integration_info: info.clone(),
|
||||
storage: Some(&storage),
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
gl: Some(painter.gl().clone()),
|
||||
|
||||
#[cfg(all(feature = "wgpu", not(feature = "glow")))]
|
||||
wgpu_render_state: painter.render_state(),
|
||||
#[cfg(all(feature = "wgpu", feature = "glow"))]
|
||||
wgpu_render_state: None,
|
||||
});
|
||||
|
||||
let frame = epi::Frame {
|
||||
info,
|
||||
output: Default::default(),
|
||||
storage: Some(Box::new(storage)),
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
gl: Some(painter.gl().clone()),
|
||||
|
||||
#[cfg(all(feature = "wgpu", not(feature = "glow")))]
|
||||
wgpu_render_state: painter.render_state(),
|
||||
#[cfg(all(feature = "wgpu", feature = "glow"))]
|
||||
wgpu_render_state: None,
|
||||
};
|
||||
|
||||
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
||||
{
|
||||
let needs_repaint = needs_repaint.clone();
|
||||
egui_ctx.set_request_repaint_callback(move || {
|
||||
needs_repaint.repaint_asap();
|
||||
});
|
||||
}
|
||||
|
||||
let mut runner = Self {
|
||||
frame,
|
||||
egui_ctx,
|
||||
painter,
|
||||
input: Default::default(),
|
||||
app,
|
||||
needs_repaint,
|
||||
is_destroyed: Default::default(),
|
||||
last_save_time: now_sec(),
|
||||
screen_reader: Default::default(),
|
||||
text_cursor_pos: None,
|
||||
mutable_text_under_cursor: false,
|
||||
textures_delta: Default::default(),
|
||||
events_to_unsubscribe: Default::default(),
|
||||
};
|
||||
|
||||
runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side());
|
||||
|
||||
Ok(runner)
|
||||
}
|
||||
|
||||
pub fn egui_ctx(&self) -> &egui::Context {
|
||||
&self.egui_ctx
|
||||
}
|
||||
|
||||
/// Get mutable access to the concrete [`App`] we enclose.
|
||||
///
|
||||
/// This will panic if your app does not implement [`App::as_any_mut`].
|
||||
pub fn app_mut<ConreteApp: 'static + App>(&mut self) -> &mut ConreteApp {
|
||||
self.app
|
||||
.as_any_mut()
|
||||
.expect("Your app must implement `as_any_mut`, but it doesn't")
|
||||
.downcast_mut::<ConreteApp>()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn auto_save(&mut self) {
|
||||
let now = now_sec();
|
||||
let time_since_last_save = now - self.last_save_time;
|
||||
|
||||
if time_since_last_save > self.app.auto_save_interval().as_secs_f64() {
|
||||
if self.app.persist_egui_memory() {
|
||||
save_memory(&self.egui_ctx);
|
||||
}
|
||||
if let Some(storage) = self.frame.storage_mut() {
|
||||
self.app.save(storage);
|
||||
}
|
||||
self.last_save_time = now;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn canvas_id(&self) -> &str {
|
||||
self.painter.canvas_id()
|
||||
}
|
||||
|
||||
pub fn warm_up(&mut self) -> Result<(), JsValue> {
|
||||
if self.app.warm_up_enabled() {
|
||||
let saved_memory: egui::Memory = self.egui_ctx.memory(|m| m.clone());
|
||||
self.egui_ctx
|
||||
.memory_mut(|m| m.set_everything_is_visible(true));
|
||||
self.logic()?;
|
||||
self.egui_ctx.memory_mut(|m| *m = saved_memory); // We don't want to remember that windows were huge.
|
||||
self.egui_ctx.clear_animations();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn destroy(&mut self) -> Result<(), JsValue> {
|
||||
let is_destroyed_already = self.is_destroyed.fetch();
|
||||
|
||||
if is_destroyed_already {
|
||||
tracing::warn!("App was destroyed already");
|
||||
Ok(())
|
||||
} else {
|
||||
tracing::debug!("Destroying");
|
||||
for x in self.events_to_unsubscribe.drain(..) {
|
||||
x.unsubscribe()?;
|
||||
}
|
||||
|
||||
self.painter.destroy();
|
||||
self.is_destroyed.set_true();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns how long to wait until the next repaint.
|
||||
///
|
||||
/// Call [`Self::paint`] later to paint
|
||||
pub fn logic(&mut self) -> Result<(std::time::Duration, Vec<egui::ClippedPrimitive>), JsValue> {
|
||||
let frame_start = now_sec();
|
||||
|
||||
resize_canvas_to_screen_size(self.canvas_id(), self.app.max_size_points());
|
||||
let canvas_size = canvas_size_in_points(self.canvas_id());
|
||||
let raw_input = self.input.new_frame(canvas_size);
|
||||
|
||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
self.app.update(egui_ctx, &mut self.frame);
|
||||
});
|
||||
let egui::FullOutput {
|
||||
platform_output,
|
||||
repaint_after,
|
||||
textures_delta,
|
||||
shapes,
|
||||
} = full_output;
|
||||
|
||||
self.handle_platform_output(platform_output);
|
||||
self.textures_delta.append(textures_delta);
|
||||
let clipped_primitives = self.egui_ctx.tessellate(shapes);
|
||||
|
||||
{
|
||||
let app_output = self.frame.take_app_output();
|
||||
let epi::backend::AppOutput {} = app_output;
|
||||
}
|
||||
|
||||
self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32);
|
||||
Ok((repaint_after, clipped_primitives))
|
||||
}
|
||||
|
||||
/// Paint the results of the last call to [`Self::logic`].
|
||||
pub fn paint(&mut self, clipped_primitives: &[egui::ClippedPrimitive]) -> Result<(), JsValue> {
|
||||
let textures_delta = std::mem::take(&mut self.textures_delta);
|
||||
|
||||
self.painter.paint_and_update_textures(
|
||||
self.app.clear_color(&self.egui_ctx.style().visuals),
|
||||
clipped_primitives,
|
||||
self.egui_ctx.pixels_per_point(),
|
||||
&textures_delta,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
|
||||
if self.egui_ctx.options(|o| o.screen_reader) {
|
||||
self.screen_reader
|
||||
.speak(&platform_output.events_description());
|
||||
}
|
||||
|
||||
let egui::PlatformOutput {
|
||||
cursor_icon,
|
||||
open_url,
|
||||
copied_text,
|
||||
events: _, // already handled
|
||||
mutable_text_under_cursor,
|
||||
text_cursor_pos,
|
||||
#[cfg(feature = "accesskit")]
|
||||
accesskit_update: _, // not currently implemented
|
||||
} = platform_output;
|
||||
|
||||
set_cursor_icon(cursor_icon);
|
||||
if let Some(open) = open_url {
|
||||
super::open_url(&open.url, open.new_tab);
|
||||
}
|
||||
|
||||
#[cfg(web_sys_unstable_apis)]
|
||||
if !copied_text.is_empty() {
|
||||
set_clipboard_text(&copied_text);
|
||||
}
|
||||
|
||||
#[cfg(not(web_sys_unstable_apis))]
|
||||
let _ = copied_text;
|
||||
|
||||
self.mutable_text_under_cursor = mutable_text_under_cursor;
|
||||
|
||||
if self.text_cursor_pos != text_cursor_pos {
|
||||
text_agent::move_text_cursor(text_cursor_pos, self.canvas_id());
|
||||
self.text_cursor_pos = text_cursor_pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub type AppRunnerRef = Arc<Mutex<AppRunner>>;
|
||||
|
||||
pub struct TargetEvent {
|
||||
target: EventTarget,
|
||||
event_name: String,
|
||||
closure: Closure<dyn FnMut(web_sys::Event)>,
|
||||
}
|
||||
|
||||
pub struct IntervalHandle {
|
||||
pub handle: i32,
|
||||
pub closure: Closure<dyn FnMut()>,
|
||||
}
|
||||
|
||||
pub enum EventToUnsubscribe {
|
||||
TargetEvent(TargetEvent),
|
||||
#[allow(dead_code)]
|
||||
IntervalHandle(IntervalHandle),
|
||||
}
|
||||
|
||||
impl EventToUnsubscribe {
|
||||
pub fn unsubscribe(self) -> Result<(), JsValue> {
|
||||
match self {
|
||||
EventToUnsubscribe::TargetEvent(handle) => {
|
||||
handle.target.remove_event_listener_with_callback(
|
||||
handle.event_name.as_str(),
|
||||
handle.closure.as_ref().unchecked_ref(),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
EventToUnsubscribe::IntervalHandle(handle) => {
|
||||
let window = web_sys::window().unwrap();
|
||||
window.clear_interval_with_handle(handle.handle);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppRunnerContainer {
|
||||
pub runner: AppRunnerRef,
|
||||
|
||||
/// Set to `true` if there is a panic.
|
||||
/// Used to ignore callbacks after a panic.
|
||||
pub panicked: Arc<AtomicBool>,
|
||||
pub events: Vec<EventToUnsubscribe>,
|
||||
}
|
||||
|
||||
impl AppRunnerContainer {
|
||||
/// Convenience function to reduce boilerplate and ensure that all event handlers
|
||||
/// are dealt with in the same way
|
||||
pub fn add_event_listener<E: wasm_bindgen::JsCast>(
|
||||
&mut self,
|
||||
target: &EventTarget,
|
||||
event_name: &'static str,
|
||||
mut closure: impl FnMut(E, MutexGuard<'_, AppRunner>) + 'static,
|
||||
) -> Result<(), JsValue> {
|
||||
// Create a JS closure based on the FnMut provided
|
||||
let closure = Closure::wrap({
|
||||
// Clone atomics
|
||||
let runner_ref = self.runner.clone();
|
||||
let panicked = self.panicked.clone();
|
||||
|
||||
Box::new(move |event: web_sys::Event| {
|
||||
// Only call the wrapped closure if the egui code has not panicked
|
||||
if !panicked.load(Ordering::SeqCst) {
|
||||
// Cast the event to the expected event type
|
||||
let event = event.unchecked_into::<E>();
|
||||
|
||||
closure(event, runner_ref.lock());
|
||||
}
|
||||
}) as Box<dyn FnMut(web_sys::Event)>
|
||||
});
|
||||
|
||||
// Add the event listener to the target
|
||||
target.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
|
||||
|
||||
let handle = TargetEvent {
|
||||
target: target.clone(),
|
||||
event_name: event_name.to_owned(),
|
||||
closure,
|
||||
};
|
||||
|
||||
self.events.push(EventToUnsubscribe::TargetEvent(handle));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Install event listeners to register different input events
|
||||
/// and start running the given app.
|
||||
pub async fn start(
|
||||
canvas_id: &str,
|
||||
web_options: crate::WebOptions,
|
||||
app_creator: epi::AppCreator,
|
||||
) -> Result<AppRunnerRef, JsValue> {
|
||||
#[cfg(not(web_sys_unstable_apis))]
|
||||
tracing::warn!(
|
||||
"eframe compiled without RUSTFLAGS='--cfg=web_sys_unstable_apis'. Copying text won't work."
|
||||
);
|
||||
|
||||
let mut runner = AppRunner::new(canvas_id, web_options, app_creator).await?;
|
||||
runner.warm_up()?;
|
||||
start_runner(runner)
|
||||
}
|
||||
|
||||
/// Install event listeners to register different input events
|
||||
/// and starts running the given [`AppRunner`].
|
||||
fn start_runner(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
|
||||
let mut runner_container = AppRunnerContainer {
|
||||
runner: Arc::new(Mutex::new(app_runner)),
|
||||
panicked: Arc::new(AtomicBool::new(false)),
|
||||
events: Vec::with_capacity(20),
|
||||
};
|
||||
|
||||
super::events::install_canvas_events(&mut runner_container)?;
|
||||
super::events::install_document_events(&mut runner_container)?;
|
||||
text_agent::install_text_agent(&mut runner_container)?;
|
||||
|
||||
super::events::paint_and_schedule(&runner_container.runner, runner_container.panicked.clone())?;
|
||||
|
||||
// Disable all event handlers on panic
|
||||
let previous_hook = std::panic::take_hook();
|
||||
|
||||
runner_container.runner.lock().events_to_unsubscribe = runner_container.events;
|
||||
|
||||
std::panic::set_hook(Box::new(move |panic_info| {
|
||||
tracing::info!("egui disabled all event handlers due to panic");
|
||||
runner_container.panicked.store(true, SeqCst);
|
||||
|
||||
// Propagate panic info to the previously registered panic hook
|
||||
previous_hook(panic_info);
|
||||
}));
|
||||
|
||||
Ok(runner_container.runner)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
struct LocalStorage {}
|
||||
|
||||
impl epi::Storage for LocalStorage {
|
||||
fn get_string(&self, key: &str) -> Option<String> {
|
||||
local_storage_get(key)
|
||||
}
|
||||
|
||||
fn set_string(&mut self, key: &str, value: String) {
|
||||
local_storage_set(key, &value);
|
||||
}
|
||||
|
||||
fn flush(&mut self) {}
|
||||
}
|
|
@ -1,258 +0,0 @@
|
|||
//! [`egui`] bindings for web apps (compiling to WASM).
|
||||
|
||||
#![allow(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>`
|
||||
|
||||
pub mod backend;
|
||||
mod events;
|
||||
mod input;
|
||||
pub mod screen_reader;
|
||||
pub mod storage;
|
||||
mod text_agent;
|
||||
|
||||
#[cfg(not(any(feature = "glow", feature = "wgpu")))]
|
||||
compile_error!("You must enable either the 'glow' or 'wgpu' feature");
|
||||
|
||||
mod web_painter;
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
mod web_painter_glow;
|
||||
#[cfg(feature = "glow")]
|
||||
pub(crate) type ActiveWebPainter = web_painter_glow::WebPainterGlow;
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
mod web_painter_wgpu;
|
||||
#[cfg(all(feature = "wgpu", not(feature = "glow")))]
|
||||
pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu;
|
||||
|
||||
pub use backend::*;
|
||||
pub use events::*;
|
||||
pub use storage::*;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
use egui::Vec2;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::EventTarget;
|
||||
|
||||
use input::*;
|
||||
|
||||
use crate::Theme;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Current time in seconds (since undefined point in time).
|
||||
///
|
||||
/// Monotonically increasing.
|
||||
pub fn now_sec() -> f64 {
|
||||
web_sys::window()
|
||||
.expect("should have a Window")
|
||||
.performance()
|
||||
.expect("should have a Performance")
|
||||
.now()
|
||||
/ 1000.0
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn screen_size_in_native_points() -> Option<egui::Vec2> {
|
||||
let window = web_sys::window()?;
|
||||
Some(egui::vec2(
|
||||
window.inner_width().ok()?.as_f64()? as f32,
|
||||
window.inner_height().ok()?.as_f64()? as f32,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn native_pixels_per_point() -> f32 {
|
||||
let pixels_per_point = web_sys::window().unwrap().device_pixel_ratio() as f32;
|
||||
if pixels_per_point > 0.0 && pixels_per_point.is_finite() {
|
||||
pixels_per_point
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn system_theme() -> Option<Theme> {
|
||||
let dark_mode = web_sys::window()?
|
||||
.match_media("(prefers-color-scheme: dark)")
|
||||
.ok()??
|
||||
.matches();
|
||||
Some(if dark_mode { Theme::Dark } else { Theme::Light })
|
||||
}
|
||||
|
||||
pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
|
||||
let document = web_sys::window()?.document()?;
|
||||
let canvas = document.get_element_by_id(canvas_id)?;
|
||||
canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
|
||||
}
|
||||
|
||||
pub fn canvas_element_or_die(canvas_id: &str) -> web_sys::HtmlCanvasElement {
|
||||
canvas_element(canvas_id)
|
||||
.unwrap_or_else(|| panic!("Failed to find canvas with id {:?}", canvas_id))
|
||||
}
|
||||
|
||||
fn canvas_origin(canvas_id: &str) -> egui::Pos2 {
|
||||
let rect = canvas_element(canvas_id)
|
||||
.unwrap()
|
||||
.get_bounding_client_rect();
|
||||
egui::pos2(rect.left() as f32, rect.top() as f32)
|
||||
}
|
||||
|
||||
pub fn canvas_size_in_points(canvas_id: &str) -> egui::Vec2 {
|
||||
let canvas = canvas_element(canvas_id).unwrap();
|
||||
let pixels_per_point = native_pixels_per_point();
|
||||
egui::vec2(
|
||||
canvas.width() as f32 / pixels_per_point,
|
||||
canvas.height() as f32 / pixels_per_point,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn resize_canvas_to_screen_size(canvas_id: &str, max_size_points: egui::Vec2) -> Option<()> {
|
||||
let canvas = canvas_element(canvas_id)?;
|
||||
let parent = canvas.parent_element()?;
|
||||
|
||||
let width = parent.scroll_width();
|
||||
let height = parent.scroll_height();
|
||||
|
||||
let canvas_real_size = Vec2 {
|
||||
x: width as f32,
|
||||
y: height as f32,
|
||||
};
|
||||
|
||||
if width <= 0 || height <= 0 {
|
||||
tracing::error!("egui canvas parent size is {}x{}. Try adding `html, body {{ height: 100%; width: 100% }}` to your CSS!", width, height);
|
||||
}
|
||||
|
||||
let pixels_per_point = native_pixels_per_point();
|
||||
|
||||
let max_size_pixels = pixels_per_point * max_size_points;
|
||||
|
||||
let canvas_size_pixels = pixels_per_point * canvas_real_size;
|
||||
let canvas_size_pixels = canvas_size_pixels.min(max_size_pixels);
|
||||
let canvas_size_points = canvas_size_pixels / pixels_per_point;
|
||||
|
||||
// Make sure that the height and width are always even numbers.
|
||||
// otherwise, the page renders blurry on some platforms.
|
||||
// See https://github.com/emilk/egui/issues/103
|
||||
fn round_to_even(v: f32) -> f32 {
|
||||
(v / 2.0).round() * 2.0
|
||||
}
|
||||
|
||||
canvas
|
||||
.style()
|
||||
.set_property(
|
||||
"width",
|
||||
&format!("{}px", round_to_even(canvas_size_points.x)),
|
||||
)
|
||||
.ok()?;
|
||||
canvas
|
||||
.style()
|
||||
.set_property(
|
||||
"height",
|
||||
&format!("{}px", round_to_even(canvas_size_points.y)),
|
||||
)
|
||||
.ok()?;
|
||||
canvas.set_width(round_to_even(canvas_size_pixels.x) as u32);
|
||||
canvas.set_height(round_to_even(canvas_size_pixels.y) as u32);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> {
|
||||
let document = web_sys::window()?.document()?;
|
||||
document
|
||||
.body()?
|
||||
.style()
|
||||
.set_property("cursor", cursor_web_name(cursor))
|
||||
.ok()
|
||||
}
|
||||
|
||||
#[cfg(web_sys_unstable_apis)]
|
||||
pub fn set_clipboard_text(s: &str) {
|
||||
if let Some(window) = web_sys::window() {
|
||||
if let Some(clipboard) = window.navigator().clipboard() {
|
||||
let promise = clipboard.write_text(s);
|
||||
let future = wasm_bindgen_futures::JsFuture::from(promise);
|
||||
let future = async move {
|
||||
if let Err(err) = future.await {
|
||||
tracing::error!("Copy/cut action denied: {:?}", err);
|
||||
}
|
||||
};
|
||||
wasm_bindgen_futures::spawn_local(future);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cursor_web_name(cursor: egui::CursorIcon) -> &'static str {
|
||||
match cursor {
|
||||
egui::CursorIcon::Alias => "alias",
|
||||
egui::CursorIcon::AllScroll => "all-scroll",
|
||||
egui::CursorIcon::Cell => "cell",
|
||||
egui::CursorIcon::ContextMenu => "context-menu",
|
||||
egui::CursorIcon::Copy => "copy",
|
||||
egui::CursorIcon::Crosshair => "crosshair",
|
||||
egui::CursorIcon::Default => "default",
|
||||
egui::CursorIcon::Grab => "grab",
|
||||
egui::CursorIcon::Grabbing => "grabbing",
|
||||
egui::CursorIcon::Help => "help",
|
||||
egui::CursorIcon::Move => "move",
|
||||
egui::CursorIcon::NoDrop => "no-drop",
|
||||
egui::CursorIcon::None => "none",
|
||||
egui::CursorIcon::NotAllowed => "not-allowed",
|
||||
egui::CursorIcon::PointingHand => "pointer",
|
||||
egui::CursorIcon::Progress => "progress",
|
||||
egui::CursorIcon::ResizeHorizontal => "ew-resize",
|
||||
egui::CursorIcon::ResizeNeSw => "nesw-resize",
|
||||
egui::CursorIcon::ResizeNwSe => "nwse-resize",
|
||||
egui::CursorIcon::ResizeVertical => "ns-resize",
|
||||
|
||||
egui::CursorIcon::ResizeEast => "e-resize",
|
||||
egui::CursorIcon::ResizeSouthEast => "se-resize",
|
||||
egui::CursorIcon::ResizeSouth => "s-resize",
|
||||
egui::CursorIcon::ResizeSouthWest => "sw-resize",
|
||||
egui::CursorIcon::ResizeWest => "w-resize",
|
||||
egui::CursorIcon::ResizeNorthWest => "nw-resize",
|
||||
egui::CursorIcon::ResizeNorth => "n-resize",
|
||||
egui::CursorIcon::ResizeNorthEast => "ne-resize",
|
||||
egui::CursorIcon::ResizeColumn => "col-resize",
|
||||
egui::CursorIcon::ResizeRow => "row-resize",
|
||||
|
||||
egui::CursorIcon::Text => "text",
|
||||
egui::CursorIcon::VerticalText => "vertical-text",
|
||||
egui::CursorIcon::Wait => "wait",
|
||||
egui::CursorIcon::ZoomIn => "zoom-in",
|
||||
egui::CursorIcon::ZoomOut => "zoom-out",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_url(url: &str, new_tab: bool) -> Option<()> {
|
||||
let name = if new_tab { "_blank" } else { "_self" };
|
||||
|
||||
web_sys::window()?
|
||||
.open_with_url_and_target(url, name)
|
||||
.ok()?;
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// e.g. "#fragment" part of "www.example.com/index.html#fragment",
|
||||
///
|
||||
/// Percent decoded
|
||||
pub fn location_hash() -> String {
|
||||
percent_decode(
|
||||
&web_sys::window()
|
||||
.unwrap()
|
||||
.location()
|
||||
.hash()
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn percent_decode(s: &str) -> String {
|
||||
percent_encoding::percent_decode_str(s)
|
||||
.decode_utf8_lossy()
|
||||
.to_string()
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
fn local_storage() -> Option<web_sys::Storage> {
|
||||
web_sys::window()?.local_storage().ok()?
|
||||
}
|
||||
|
||||
pub fn local_storage_get(key: &str) -> Option<String> {
|
||||
local_storage().map(|storage| storage.get_item(key).ok())??
|
||||
}
|
||||
|
||||
pub fn local_storage_set(key: &str, value: &str) {
|
||||
local_storage().map(|storage| storage.set_item(key, value));
|
||||
}
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
pub fn load_memory(ctx: &egui::Context) {
|
||||
if let Some(memory_string) = local_storage_get("egui_memory_ron") {
|
||||
match ron::from_str(&memory_string) {
|
||||
Ok(memory) => {
|
||||
ctx.memory_mut(|m| *m = memory);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to parse memory RON: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
pub fn load_memory(_: &egui::Context) {}
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
pub fn save_memory(ctx: &egui::Context) {
|
||||
match ctx.memory(|mem| ron::to_string(mem)) {
|
||||
Ok(ron) => {
|
||||
local_storage_set("egui_memory_ron", &ron);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to serialize memory as RON: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
pub fn save_memory(_: &egui::Context) {}
|
|
@ -1,29 +0,0 @@
|
|||
use wasm_bindgen::JsValue;
|
||||
|
||||
/// Renderer for a browser canvas.
|
||||
/// As of writing we're not allowing to decide on the painter at runtime,
|
||||
/// therefore this trait is merely there for specifying and documenting the interface.
|
||||
pub(crate) trait WebPainter {
|
||||
// Create a new web painter targeting a given canvas.
|
||||
// fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String>
|
||||
// where
|
||||
// Self: Sized;
|
||||
|
||||
/// Id of the canvas in use.
|
||||
fn canvas_id(&self) -> &str;
|
||||
|
||||
/// Maximum size of a texture in one direction.
|
||||
fn max_texture_side(&self) -> usize;
|
||||
|
||||
/// Update all internal textures and paint gui.
|
||||
fn paint_and_update_textures(
|
||||
&mut self,
|
||||
clear_color: [f32; 4],
|
||||
clipped_primitives: &[egui::ClippedPrimitive],
|
||||
pixels_per_point: f32,
|
||||
textures_delta: &egui::TexturesDelta,
|
||||
) -> Result<(), JsValue>;
|
||||
|
||||
/// Destroy all resources.
|
||||
fn destroy(&mut self);
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
use egui_glow::glow;
|
||||
|
||||
use crate::{WebGlContextOption, WebOptions};
|
||||
|
||||
use super::web_painter::WebPainter;
|
||||
|
||||
pub(crate) struct WebPainterGlow {
|
||||
canvas: HtmlCanvasElement,
|
||||
canvas_id: String,
|
||||
painter: egui_glow::Painter,
|
||||
}
|
||||
|
||||
impl WebPainterGlow {
|
||||
pub fn gl(&self) -> &std::sync::Arc<glow::Context> {
|
||||
self.painter.gl()
|
||||
}
|
||||
|
||||
pub async fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String> {
|
||||
let canvas = super::canvas_element_or_die(canvas_id);
|
||||
|
||||
let (gl, shader_prefix) =
|
||||
init_glow_context_from_canvas(&canvas, options.webgl_context_option)?;
|
||||
let gl = std::sync::Arc::new(gl);
|
||||
|
||||
let painter = egui_glow::Painter::new(gl, shader_prefix, None)
|
||||
.map_err(|error| format!("Error starting glow painter: {}", error))?;
|
||||
|
||||
Ok(Self {
|
||||
canvas,
|
||||
canvas_id: canvas_id.to_owned(),
|
||||
painter,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WebPainter for WebPainterGlow {
|
||||
fn max_texture_side(&self) -> usize {
|
||||
self.painter.max_texture_side()
|
||||
}
|
||||
|
||||
fn canvas_id(&self) -> &str {
|
||||
&self.canvas_id
|
||||
}
|
||||
|
||||
fn paint_and_update_textures(
|
||||
&mut self,
|
||||
clear_color: [f32; 4],
|
||||
clipped_primitives: &[egui::ClippedPrimitive],
|
||||
pixels_per_point: f32,
|
||||
textures_delta: &egui::TexturesDelta,
|
||||
) -> Result<(), JsValue> {
|
||||
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
||||
|
||||
for (id, image_delta) in &textures_delta.set {
|
||||
self.painter.set_texture(*id, image_delta);
|
||||
}
|
||||
|
||||
egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color);
|
||||
self.painter
|
||||
.paint_primitives(canvas_dimension, pixels_per_point, clipped_primitives);
|
||||
|
||||
for &id in &textures_delta.free {
|
||||
self.painter.free_texture(id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy(&mut self) {
|
||||
self.painter.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns glow context and shader prefix.
|
||||
fn init_glow_context_from_canvas(
|
||||
canvas: &HtmlCanvasElement,
|
||||
options: WebGlContextOption,
|
||||
) -> Result<(glow::Context, &'static str), String> {
|
||||
let result = match options {
|
||||
// Force use WebGl1
|
||||
WebGlContextOption::WebGl1 => init_webgl1(canvas),
|
||||
// Force use WebGl2
|
||||
WebGlContextOption::WebGl2 => init_webgl2(canvas),
|
||||
// Trying WebGl2 first
|
||||
WebGlContextOption::BestFirst => init_webgl2(canvas).or_else(|| init_webgl1(canvas)),
|
||||
// Trying WebGl1 first (useful for testing).
|
||||
WebGlContextOption::CompatibilityFirst => {
|
||||
init_webgl1(canvas).or_else(|| init_webgl2(canvas))
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(result) = result {
|
||||
Ok(result)
|
||||
} else {
|
||||
Err("WebGL isn't supported".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn init_webgl1(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static str)> {
|
||||
let gl1_ctx = canvas
|
||||
.get_context("webgl")
|
||||
.expect("Failed to query about WebGL2 context");
|
||||
|
||||
let gl1_ctx = gl1_ctx?;
|
||||
tracing::debug!("WebGL1 selected.");
|
||||
|
||||
let gl1_ctx = gl1_ctx
|
||||
.dyn_into::<web_sys::WebGlRenderingContext>()
|
||||
.unwrap();
|
||||
|
||||
let shader_prefix = if webgl1_requires_brightening(&gl1_ctx) {
|
||||
tracing::debug!("Enabling webkitGTK brightening workaround.");
|
||||
"#define APPLY_BRIGHTENING_GAMMA"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let gl = glow::Context::from_webgl1_context(gl1_ctx);
|
||||
|
||||
Some((gl, shader_prefix))
|
||||
}
|
||||
|
||||
fn init_webgl2(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static str)> {
|
||||
let gl2_ctx = canvas
|
||||
.get_context("webgl2")
|
||||
.expect("Failed to query about WebGL2 context");
|
||||
|
||||
let gl2_ctx = gl2_ctx?;
|
||||
tracing::debug!("WebGL2 selected.");
|
||||
|
||||
let gl2_ctx = gl2_ctx
|
||||
.dyn_into::<web_sys::WebGl2RenderingContext>()
|
||||
.unwrap();
|
||||
let gl = glow::Context::from_webgl2_context(gl2_ctx);
|
||||
let shader_prefix = "";
|
||||
|
||||
Some((gl, shader_prefix))
|
||||
}
|
||||
|
||||
fn webgl1_requires_brightening(gl: &web_sys::WebGlRenderingContext) -> bool {
|
||||
// See https://github.com/emilk/egui/issues/794
|
||||
|
||||
// detect WebKitGTK
|
||||
|
||||
// WebKitGTK use WebKit default unmasked vendor and renderer
|
||||
// but safari use same vendor and renderer
|
||||
// so exclude "Mac OS X" user-agent.
|
||||
let user_agent = web_sys::window().unwrap().navigator().user_agent().unwrap();
|
||||
!user_agent.contains("Mac OS X") && is_safari_and_webkit_gtk(gl)
|
||||
}
|
||||
|
||||
/// detecting Safari and `webkitGTK`.
|
||||
///
|
||||
/// Safari and `webkitGTK` use unmasked renderer :Apple GPU
|
||||
///
|
||||
/// If we detect safari or `webkitGTKs` returns true.
|
||||
///
|
||||
/// This function used to avoid displaying linear color with `sRGB` supported systems.
|
||||
fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool {
|
||||
// This call produces a warning in Firefox ("WEBGL_debug_renderer_info is deprecated in Firefox and will be removed.")
|
||||
// but unless we call it we get errors in Chrome when we call `get_parameter` below.
|
||||
// TODO(emilk): do something smart based on user agent?
|
||||
if gl
|
||||
.get_extension("WEBGL_debug_renderer_info")
|
||||
.unwrap()
|
||||
.is_some()
|
||||
{
|
||||
if let Ok(renderer) =
|
||||
gl.get_parameter(web_sys::WebglDebugRendererInfo::UNMASKED_RENDERER_WEBGL)
|
||||
{
|
||||
if let Some(renderer) = renderer.as_string() {
|
||||
if renderer.contains("Apple") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
|
@ -1,282 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
use egui::mutex::RwLock;
|
||||
use egui_wgpu::{renderer::ScreenDescriptor, RenderState, SurfaceErrorAction};
|
||||
|
||||
use crate::WebOptions;
|
||||
|
||||
use super::web_painter::WebPainter;
|
||||
|
||||
pub(crate) struct WebPainterWgpu {
|
||||
canvas: HtmlCanvasElement,
|
||||
canvas_id: String,
|
||||
surface: wgpu::Surface,
|
||||
surface_configuration: wgpu::SurfaceConfiguration,
|
||||
limits: wgpu::Limits,
|
||||
render_state: Option<RenderState>,
|
||||
on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction>,
|
||||
depth_format: Option<wgpu::TextureFormat>,
|
||||
depth_texture_view: Option<wgpu::TextureView>,
|
||||
}
|
||||
|
||||
impl WebPainterWgpu {
|
||||
#[allow(unused)] // only used if `wgpu` is the only active feature.
|
||||
pub fn render_state(&self) -> Option<RenderState> {
|
||||
self.render_state.clone()
|
||||
}
|
||||
|
||||
pub fn generate_depth_texture_view(
|
||||
&self,
|
||||
render_state: &RenderState,
|
||||
width_in_pixels: u32,
|
||||
height_in_pixels: u32,
|
||||
) -> Option<wgpu::TextureView> {
|
||||
let device = &render_state.device;
|
||||
self.depth_format.map(|depth_format| {
|
||||
device
|
||||
.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("egui_depth_texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: width_in_pixels,
|
||||
height: height_in_pixels,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: depth_format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[depth_format],
|
||||
})
|
||||
.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(unused)] // only used if `wgpu` is the only active feature.
|
||||
pub async fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String> {
|
||||
tracing::debug!("Creating wgpu painter");
|
||||
|
||||
let canvas = super::canvas_element_or_die(canvas_id);
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: options.wgpu_options.backends,
|
||||
dx12_shader_compiler: Default::default(),
|
||||
});
|
||||
let surface = instance
|
||||
.create_surface_from_canvas(&canvas)
|
||||
.map_err(|err| format!("failed to create wgpu surface: {err}"))?;
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: options.wgpu_options.power_preference,
|
||||
force_fallback_adapter: false,
|
||||
compatible_surface: None,
|
||||
})
|
||||
.await
|
||||
.ok_or_else(|| "No suitable GPU adapters found on the system".to_owned())?;
|
||||
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&options.wgpu_options.device_descriptor,
|
||||
None, // Capture doesn't work in the browser environment.
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("Failed to find wgpu device: {}", err))?;
|
||||
|
||||
let target_format =
|
||||
egui_wgpu::preferred_framebuffer_format(&surface.get_capabilities(&adapter).formats);
|
||||
|
||||
let depth_format = options.wgpu_options.depth_format;
|
||||
let renderer = egui_wgpu::Renderer::new(&device, target_format, depth_format, 1);
|
||||
let render_state = RenderState {
|
||||
device: Arc::new(device),
|
||||
queue: Arc::new(queue),
|
||||
target_format,
|
||||
renderer: Arc::new(RwLock::new(renderer)),
|
||||
};
|
||||
|
||||
let surface_configuration = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: target_format,
|
||||
width: 0,
|
||||
height: 0,
|
||||
present_mode: options.wgpu_options.present_mode,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![target_format],
|
||||
};
|
||||
|
||||
tracing::debug!("wgpu painter initialized.");
|
||||
|
||||
Ok(Self {
|
||||
canvas,
|
||||
canvas_id: canvas_id.to_owned(),
|
||||
render_state: Some(render_state),
|
||||
surface,
|
||||
surface_configuration,
|
||||
depth_format,
|
||||
depth_texture_view: None,
|
||||
limits: options.wgpu_options.device_descriptor.limits.clone(),
|
||||
on_surface_error: options.wgpu_options.on_surface_error.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WebPainter for WebPainterWgpu {
|
||||
fn canvas_id(&self) -> &str {
|
||||
&self.canvas_id
|
||||
}
|
||||
|
||||
fn max_texture_side(&self) -> usize {
|
||||
self.limits.max_texture_dimension_2d as _
|
||||
}
|
||||
|
||||
fn paint_and_update_textures(
|
||||
&mut self,
|
||||
clear_color: [f32; 4],
|
||||
clipped_primitives: &[egui::ClippedPrimitive],
|
||||
pixels_per_point: f32,
|
||||
textures_delta: &egui::TexturesDelta,
|
||||
) -> Result<(), JsValue> {
|
||||
let size_in_pixels = [self.canvas.width(), self.canvas.height()];
|
||||
|
||||
let render_state = if let Some(render_state) = &self.render_state {
|
||||
render_state
|
||||
} else {
|
||||
return Err(JsValue::from_str(
|
||||
"Can't paint, wgpu renderer was already disposed",
|
||||
));
|
||||
};
|
||||
|
||||
let mut encoder =
|
||||
render_state
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("egui_webpainter_paint_and_update_textures"),
|
||||
});
|
||||
|
||||
// Upload all resources for the GPU.
|
||||
let screen_descriptor = ScreenDescriptor {
|
||||
size_in_pixels,
|
||||
pixels_per_point,
|
||||
};
|
||||
|
||||
let user_cmd_bufs = {
|
||||
let mut renderer = render_state.renderer.write();
|
||||
for (id, image_delta) in &textures_delta.set {
|
||||
renderer.update_texture(
|
||||
&render_state.device,
|
||||
&render_state.queue,
|
||||
*id,
|
||||
image_delta,
|
||||
);
|
||||
}
|
||||
|
||||
renderer.update_buffers(
|
||||
&render_state.device,
|
||||
&render_state.queue,
|
||||
&mut encoder,
|
||||
clipped_primitives,
|
||||
&screen_descriptor,
|
||||
)
|
||||
};
|
||||
|
||||
// Resize surface if needed
|
||||
let is_zero_sized_surface = size_in_pixels[0] == 0 || size_in_pixels[1] == 0;
|
||||
let frame = if is_zero_sized_surface {
|
||||
None
|
||||
} else {
|
||||
if size_in_pixels[0] != self.surface_configuration.width
|
||||
|| size_in_pixels[1] != self.surface_configuration.height
|
||||
{
|
||||
self.surface_configuration.width = size_in_pixels[0];
|
||||
self.surface_configuration.height = size_in_pixels[1];
|
||||
self.surface
|
||||
.configure(&render_state.device, &self.surface_configuration);
|
||||
self.depth_texture_view = self.generate_depth_texture_view(
|
||||
render_state,
|
||||
size_in_pixels[0],
|
||||
size_in_pixels[1],
|
||||
);
|
||||
}
|
||||
|
||||
let frame = match self.surface.get_current_texture() {
|
||||
Ok(frame) => frame,
|
||||
#[allow(clippy::single_match_else)]
|
||||
Err(e) => match (*self.on_surface_error)(e) {
|
||||
SurfaceErrorAction::RecreateSurface => {
|
||||
self.surface
|
||||
.configure(&render_state.device, &self.surface_configuration);
|
||||
return Ok(());
|
||||
}
|
||||
SurfaceErrorAction::SkipFrame => {
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
{
|
||||
let renderer = render_state.renderer.read();
|
||||
let frame_view = frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &frame_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: clear_color[0] as f64,
|
||||
g: clear_color[1] as f64,
|
||||
b: clear_color[2] as f64,
|
||||
a: clear_color[3] as f64,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| {
|
||||
wgpu::RenderPassDepthStencilAttachment {
|
||||
view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: false,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}
|
||||
}),
|
||||
label: Some("egui_render"),
|
||||
});
|
||||
|
||||
renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor);
|
||||
}
|
||||
|
||||
Some(frame)
|
||||
};
|
||||
|
||||
{
|
||||
let mut renderer = render_state.renderer.write();
|
||||
for id in &textures_delta.free {
|
||||
renderer.free_texture(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Submit the commands: both the main buffer and user-defined ones.
|
||||
render_state.queue.submit(
|
||||
user_cmd_bufs
|
||||
.into_iter()
|
||||
.chain(std::iter::once(encoder.finish())),
|
||||
);
|
||||
|
||||
if let Some(frame) = frame {
|
||||
frame.present();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy(&mut self) {
|
||||
self.render_state = None;
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
# Changelog for egui-wgpu
|
||||
All notable changes to the `egui-wgpu` integration will be noted in this file.
|
||||
|
||||
|
||||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08
|
||||
* Update to `wgpu` 0.15 ([#2629](https://github.com/emilk/egui/pull/2629))
|
||||
* Return `Err` instead of panic if we can't find a device ([#2428](https://github.com/emilk/egui/pull/2428)).
|
||||
* `winit::Painter::set_window` is now `async` ([#2434](https://github.com/emilk/egui/pull/2434)).
|
||||
* `egui-wgpu` now only depends on `epaint` instead of the entire `egui` ([#2438](https://github.com/emilk/egui/pull/2438)).
|
||||
* `winit::Painter` now supports transparent backbuffer ([#2684](https://github.com/emilk/egui/pull/2684)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08 - web support
|
||||
* Renamed `RenderPass` to `Renderer`.
|
||||
* Renamed `RenderPass::execute` to `RenderPass::render`.
|
||||
* Renamed `RenderPass::execute_with_renderpass` to `Renderer::render` (replacing existing `Renderer::render`)
|
||||
* Reexported `Renderer`.
|
||||
* You can now use `egui-wgpu` on web, using WebGL ([#2107](https://github.com/emilk/egui/pull/2107)).
|
||||
* `Renderer` no longer handles pass creation and depth buffer creation ([#2136](https://github.com/emilk/egui/pull/2136))
|
||||
* `PrepareCallback` now passes `wgpu::CommandEncoder` ([#2136](https://github.com/emilk/egui/pull/2136))
|
||||
* `PrepareCallback` can now returns `wgpu::CommandBuffer` that are bundled into a single `wgpu::Queue::submit` call ([#2230](https://github.com/emilk/egui/pull/2230))
|
||||
* Only a single vertex & index buffer is now created and resized when necessary (previously, vertex/index buffers were allocated for every mesh) ([#2148](https://github.com/emilk/egui/pull/2148)).
|
||||
* `Renderer::update_texture` no longer creates a new `wgpu::Sampler` with every new texture ([#2198](https://github.com/emilk/egui/pull/2198))
|
||||
* `Painter`'s instance/device/adapter/surface creation is now configurable via `WgpuConfiguration` ([#2207](https://github.com/emilk/egui/pull/2207))
|
||||
* Fix panic on using a depth buffer ([#2316](https://github.com/emilk/egui/pull/2316))
|
||||
|
||||
|
||||
## 0.19.0 - 2022-08-20
|
||||
* Enables deferred render + surface state initialization for Android ([#1634](https://github.com/emilk/egui/pull/1634)).
|
||||
* Make `RenderPass` `Send` and `Sync` ([#1883](https://github.com/emilk/egui/pull/1883)).
|
||||
|
||||
|
||||
## 0.18.0 - 2022-05-15
|
||||
First published version since moving the code into the `egui` repository from <https://github.com/LU15W1R7H/eww>.
|
|
@ -1,56 +0,0 @@
|
|||
[package]
|
||||
name = "egui-wgpu"
|
||||
version = "0.21.0"
|
||||
description = "Bindings for using egui natively using the wgpu library"
|
||||
authors = [
|
||||
"Nils Hasenbanck <nils@hasenbanck.de>",
|
||||
"embotech <opensource@embotech.com>",
|
||||
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
|
||||
]
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/crates/egui-wgpu"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui/tree/master/crates/egui-wgpu"
|
||||
categories = ["gui", "game-development"]
|
||||
keywords = ["wgpu", "egui", "gui", "gamedev"]
|
||||
include = [
|
||||
"../LICENSE-APACHE",
|
||||
"../LICENSE-MIT",
|
||||
"**/*.rs",
|
||||
"**/*.wgsl",
|
||||
"Cargo.toml",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
|
||||
[features]
|
||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
||||
puffin = ["dep:puffin"]
|
||||
|
||||
## Enable [`winit`](https://docs.rs/winit) integration.
|
||||
winit = ["dep:winit"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
epaint = { version = "0.21.0", path = "../epaint", default-features = false, features = [
|
||||
"bytemuck",
|
||||
] }
|
||||
|
||||
bytemuck = "1.7"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
type-map = "0.5.0"
|
||||
wgpu = "0.15.0"
|
||||
|
||||
#! ### Optional dependencies
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
winit = { version = "0.28", optional = true }
|
||||
|
||||
# Native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
puffin = { version = "0.14", optional = true }
|
|
@ -1,10 +0,0 @@
|
|||
# egui-wgpu
|
||||
|
||||
[](https://crates.io/crates/egui-wgpu)
|
||||
[](https://docs.rs/egui-wgpu)
|
||||

|
||||

|
||||
|
||||
This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [wgpu](https://crates.io/crates/wgpu).
|
||||
|
||||
This was originally hosted at https://github.com/hasenbanck/egui_wgpu_backend
|
|
@ -1,91 +0,0 @@
|
|||
// Vertex shader bindings
|
||||
|
||||
struct VertexOutput {
|
||||
@location(0) tex_coord: vec2<f32>,
|
||||
@location(1) color: vec4<f32>, // gamma 0-1
|
||||
@builtin(position) position: vec4<f32>,
|
||||
};
|
||||
|
||||
struct Locals {
|
||||
screen_size: vec2<f32>,
|
||||
// Uniform buffers need to be at least 16 bytes in WebGL.
|
||||
// See https://github.com/gfx-rs/wgpu/issues/2072
|
||||
_padding: vec2<u32>,
|
||||
};
|
||||
@group(0) @binding(0) var<uniform> r_locals: Locals;
|
||||
|
||||
// 0-1 linear from 0-1 sRGB gamma
|
||||
fn linear_from_gamma_rgb(srgb: vec3<f32>) -> vec3<f32> {
|
||||
let cutoff = srgb < vec3<f32>(0.04045);
|
||||
let lower = srgb / vec3<f32>(12.92);
|
||||
let higher = pow((srgb + vec3<f32>(0.055)) / vec3<f32>(1.055), vec3<f32>(2.4));
|
||||
return select(higher, lower, cutoff);
|
||||
}
|
||||
|
||||
// 0-1 sRGB gamma from 0-1 linear
|
||||
fn gamma_from_linear_rgb(rgb: vec3<f32>) -> vec3<f32> {
|
||||
let cutoff = rgb < vec3<f32>(0.0031308);
|
||||
let lower = rgb * vec3<f32>(12.92);
|
||||
let higher = vec3<f32>(1.055) * pow(rgb, vec3<f32>(1.0 / 2.4)) - vec3<f32>(0.055);
|
||||
return select(higher, lower, cutoff);
|
||||
}
|
||||
|
||||
// 0-1 sRGBA gamma from 0-1 linear
|
||||
fn gamma_from_linear_rgba(linear_rgba: vec4<f32>) -> vec4<f32> {
|
||||
return vec4<f32>(gamma_from_linear_rgb(linear_rgba.rgb), linear_rgba.a);
|
||||
}
|
||||
|
||||
// [u8; 4] SRGB as u32 -> [r, g, b, a] in 0.-1
|
||||
fn unpack_color(color: u32) -> vec4<f32> {
|
||||
return vec4<f32>(
|
||||
f32(color & 255u),
|
||||
f32((color >> 8u) & 255u),
|
||||
f32((color >> 16u) & 255u),
|
||||
f32((color >> 24u) & 255u),
|
||||
) / 255.0;
|
||||
}
|
||||
|
||||
fn position_from_screen(screen_pos: vec2<f32>) -> vec4<f32> {
|
||||
return vec4<f32>(
|
||||
2.0 * screen_pos.x / r_locals.screen_size.x - 1.0,
|
||||
1.0 - 2.0 * screen_pos.y / r_locals.screen_size.y,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
@location(0) a_pos: vec2<f32>,
|
||||
@location(1) a_tex_coord: vec2<f32>,
|
||||
@location(2) a_color: u32,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.tex_coord = a_tex_coord;
|
||||
out.color = unpack_color(a_color);
|
||||
out.position = position_from_screen(a_pos);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fragment shader bindings
|
||||
|
||||
@group(1) @binding(0) var r_tex_color: texture_2d<f32>;
|
||||
@group(1) @binding(1) var r_tex_sampler: sampler;
|
||||
|
||||
@fragment
|
||||
fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// We always have an sRGB aware texture at the moment.
|
||||
let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
|
||||
let tex_gamma = gamma_from_linear_rgba(tex_linear);
|
||||
let out_color_gamma = in.color * tex_gamma;
|
||||
return vec4<f32>(linear_from_gamma_rgb(out_color_gamma.rgb), out_color_gamma.a);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main_gamma_framebuffer(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// We always have an sRGB aware texture at the moment.
|
||||
let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord);
|
||||
let tex_gamma = gamma_from_linear_rgba(tex_linear);
|
||||
let out_color_gamma = in.color * tex_gamma;
|
||||
return out_color_gamma;
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
//! This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [wgpu](https://crates.io/crates/wgpu).
|
||||
//!
|
||||
//! ## Feature flags
|
||||
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
|
||||
//!
|
||||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
pub use wgpu;
|
||||
|
||||
/// Low-level painting of [`egui`](https://github.com/emilk/egui) on [`wgpu`].
|
||||
pub mod renderer;
|
||||
pub use renderer::CallbackFn;
|
||||
pub use renderer::Renderer;
|
||||
|
||||
/// Module for painting [`egui`](https://github.com/emilk/egui) with [`wgpu`] on [`winit`].
|
||||
#[cfg(feature = "winit")]
|
||||
pub mod winit;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use epaint::mutex::RwLock;
|
||||
|
||||
/// Access to the render state for egui.
|
||||
#[derive(Clone)]
|
||||
pub struct RenderState {
|
||||
pub device: Arc<wgpu::Device>,
|
||||
pub queue: Arc<wgpu::Queue>,
|
||||
pub target_format: wgpu::TextureFormat,
|
||||
pub renderer: Arc<RwLock<Renderer>>,
|
||||
}
|
||||
|
||||
/// Specifies which action should be taken as consequence of a [`wgpu::SurfaceError`]
|
||||
pub enum SurfaceErrorAction {
|
||||
/// Do nothing and skip the current frame.
|
||||
SkipFrame,
|
||||
|
||||
/// Instructs egui to recreate the surface, then skip the current frame.
|
||||
RecreateSurface,
|
||||
}
|
||||
|
||||
/// Configuration for using wgpu with eframe or the egui-wgpu winit feature.
|
||||
#[derive(Clone)]
|
||||
pub struct WgpuConfiguration {
|
||||
/// Configuration passed on device request.
|
||||
pub device_descriptor: wgpu::DeviceDescriptor<'static>,
|
||||
|
||||
/// Backends that should be supported (wgpu will pick one of these)
|
||||
pub backends: wgpu::Backends,
|
||||
|
||||
/// Present mode used for the primary surface.
|
||||
pub present_mode: wgpu::PresentMode,
|
||||
|
||||
/// Power preference for the adapter.
|
||||
pub power_preference: wgpu::PowerPreference,
|
||||
|
||||
/// Callback for surface errors.
|
||||
pub on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction>,
|
||||
|
||||
pub depth_format: Option<wgpu::TextureFormat>,
|
||||
}
|
||||
|
||||
impl Default for WgpuConfiguration {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
device_descriptor: wgpu::DeviceDescriptor {
|
||||
label: Some("egui wgpu device"),
|
||||
features: wgpu::Features::default(),
|
||||
limits: wgpu::Limits::default(),
|
||||
},
|
||||
backends: wgpu::Backends::PRIMARY | wgpu::Backends::GL,
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
depth_format: None,
|
||||
|
||||
on_surface_error: Arc::new(|err| {
|
||||
if err == wgpu::SurfaceError::Outdated {
|
||||
// This error occurs when the app is minimized on Windows.
|
||||
// Silently return here to prevent spamming the console with:
|
||||
// "The underlying surface has changed, and therefore the swap chain must be updated"
|
||||
} else {
|
||||
tracing::warn!("Dropped frame with error: {err}");
|
||||
}
|
||||
SurfaceErrorAction::SkipFrame
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the framebuffer format that egui prefers
|
||||
pub fn preferred_framebuffer_format(formats: &[wgpu::TextureFormat]) -> wgpu::TextureFormat {
|
||||
for &format in formats {
|
||||
if matches!(
|
||||
format,
|
||||
wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
|
||||
) {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
formats[0] // take the first
|
||||
}
|
||||
// maybe use this-error?
|
||||
#[derive(Debug)]
|
||||
pub enum WgpuError {
|
||||
DeviceError(wgpu::RequestDeviceError),
|
||||
SurfaceError(wgpu::CreateSurfaceError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for WgpuError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Debug::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for WgpuError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
WgpuError::DeviceError(e) => e.source(),
|
||||
WgpuError::SurfaceError(e) => e.source(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wgpu::RequestDeviceError> for WgpuError {
|
||||
fn from(e: wgpu::RequestDeviceError) -> Self {
|
||||
Self::DeviceError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wgpu::CreateSurfaceError> for WgpuError {
|
||||
fn from(e: wgpu::CreateSurfaceError) -> Self {
|
||||
Self::SurfaceError(e)
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Profiling macro for feature "puffin"
|
||||
macro_rules! profile_function {
|
||||
($($arg: tt)*) => {
|
||||
#[cfg(feature = "puffin")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
puffin::profile_function!($($arg)*);
|
||||
};
|
||||
}
|
||||
pub(crate) use profile_function;
|
||||
|
||||
/// Profiling macro for feature "puffin"
|
||||
macro_rules! profile_scope {
|
||||
($($arg: tt)*) => {
|
||||
#[cfg(feature = "puffin")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
puffin::profile_scope!($($arg)*);
|
||||
};
|
||||
}
|
||||
pub(crate) use profile_scope;
|
|
@ -1,931 +0,0 @@
|
|||
#![allow(unsafe_code)]
|
||||
|
||||
use std::num::NonZeroU64;
|
||||
use std::ops::Range;
|
||||
use std::{borrow::Cow, collections::HashMap, num::NonZeroU32};
|
||||
|
||||
use type_map::concurrent::TypeMap;
|
||||
use wgpu;
|
||||
use wgpu::util::DeviceExt as _;
|
||||
|
||||
use epaint::{emath::NumExt, PaintCallbackInfo, Primitive, Vertex};
|
||||
|
||||
/// A callback function that can be used to compose an [`epaint::PaintCallback`] for custom WGPU
|
||||
/// rendering.
|
||||
///
|
||||
/// The callback is composed of two functions: `prepare` and `paint`:
|
||||
/// - `prepare` is called every frame before `paint`, and can use the passed-in
|
||||
/// [`wgpu::Device`] and [`wgpu::Buffer`] to allocate or modify GPU resources such as buffers.
|
||||
/// - `paint` is called after `prepare` and is given access to the [`wgpu::RenderPass`] so
|
||||
/// that it can issue draw commands into the same [`wgpu::RenderPass`] that is used for
|
||||
/// all other egui elements.
|
||||
///
|
||||
/// The final argument of both the `prepare` and `paint` callbacks is a the
|
||||
/// [`paint_callback_resources`][crate::renderer::Renderer::paint_callback_resources].
|
||||
/// `paint_callback_resources` has the same lifetime as the Egui render pass, so it can be used to
|
||||
/// store buffers, pipelines, and other information that needs to be accessed during the render
|
||||
/// pass.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// See the [`custom3d_wgpu`](https://github.com/emilk/egui/blob/master/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
|
||||
pub struct CallbackFn {
|
||||
prepare: Box<PrepareCallback>,
|
||||
paint: Box<PaintCallback>,
|
||||
}
|
||||
|
||||
type PrepareCallback = dyn Fn(
|
||||
&wgpu::Device,
|
||||
&wgpu::Queue,
|
||||
&mut wgpu::CommandEncoder,
|
||||
&mut TypeMap,
|
||||
) -> Vec<wgpu::CommandBuffer>
|
||||
+ Sync
|
||||
+ Send;
|
||||
|
||||
type PaintCallback =
|
||||
dyn for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap) + Sync + Send;
|
||||
|
||||
impl Default for CallbackFn {
|
||||
fn default() -> Self {
|
||||
CallbackFn {
|
||||
prepare: Box::new(|_, _, _, _| Vec::new()),
|
||||
paint: Box::new(|_, _, _| ()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CallbackFn {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Set the prepare callback.
|
||||
///
|
||||
/// The passed-in `CommandEncoder` is egui's and can be used directly to register
|
||||
/// wgpu commands for simple use cases.
|
||||
/// This allows reusing the same [`wgpu::CommandEncoder`] for all callbacks and egui
|
||||
/// rendering itself.
|
||||
///
|
||||
/// For more complicated use cases, one can also return a list of arbitrary
|
||||
/// `CommandBuffer`s and have complete control over how they get created and fed.
|
||||
/// In particular, this gives an opportunity to parallelize command registration and
|
||||
/// prevents a faulty callback from poisoning the main wgpu pipeline.
|
||||
///
|
||||
/// When using eframe, the main egui command buffer, as well as all user-defined
|
||||
/// command buffers returned by this function, are guaranteed to all be submitted
|
||||
/// at once in a single call.
|
||||
pub fn prepare<F>(mut self, prepare: F) -> Self
|
||||
where
|
||||
F: Fn(
|
||||
&wgpu::Device,
|
||||
&wgpu::Queue,
|
||||
&mut wgpu::CommandEncoder,
|
||||
&mut TypeMap,
|
||||
) -> Vec<wgpu::CommandBuffer>
|
||||
+ Sync
|
||||
+ Send
|
||||
+ 'static,
|
||||
{
|
||||
self.prepare = Box::new(prepare) as _;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the paint callback
|
||||
pub fn paint<F>(mut self, paint: F) -> Self
|
||||
where
|
||||
F: for<'a, 'b> Fn(PaintCallbackInfo, &'a mut wgpu::RenderPass<'b>, &'b TypeMap)
|
||||
+ Sync
|
||||
+ Send
|
||||
+ 'static,
|
||||
{
|
||||
self.paint = Box::new(paint) as _;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the screen used for rendering.
|
||||
pub struct ScreenDescriptor {
|
||||
/// Size of the window in physical pixels.
|
||||
pub size_in_pixels: [u32; 2],
|
||||
|
||||
/// HiDPI scale factor (pixels per point).
|
||||
pub pixels_per_point: f32,
|
||||
}
|
||||
|
||||
impl ScreenDescriptor {
|
||||
/// size in "logical" points
|
||||
fn screen_size_in_points(&self) -> [f32; 2] {
|
||||
[
|
||||
self.size_in_pixels[0] as f32 / self.pixels_per_point,
|
||||
self.size_in_pixels[1] as f32 / self.pixels_per_point,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Uniform buffer used when rendering.
|
||||
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(C)]
|
||||
struct UniformBuffer {
|
||||
screen_size_in_points: [f32; 2],
|
||||
// Uniform buffers need to be at least 16 bytes in WebGL.
|
||||
// See https://github.com/gfx-rs/wgpu/issues/2072
|
||||
_padding: [u32; 2],
|
||||
}
|
||||
|
||||
struct SlicedBuffer {
|
||||
buffer: wgpu::Buffer,
|
||||
slices: Vec<Range<wgpu::BufferAddress>>,
|
||||
capacity: wgpu::BufferAddress,
|
||||
}
|
||||
|
||||
/// Renderer for a egui based GUI.
|
||||
pub struct Renderer {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
|
||||
index_buffer: SlicedBuffer,
|
||||
vertex_buffer: SlicedBuffer,
|
||||
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
texture_bind_group_layout: wgpu::BindGroupLayout,
|
||||
|
||||
/// Map of egui texture IDs to textures and their associated bindgroups (texture view +
|
||||
/// sampler). The texture may be None if the TextureId is just a handle to a user-provided
|
||||
/// sampler.
|
||||
textures: HashMap<epaint::TextureId, (Option<wgpu::Texture>, wgpu::BindGroup)>,
|
||||
next_user_texture_id: u64,
|
||||
samplers: HashMap<epaint::textures::TextureOptions, wgpu::Sampler>,
|
||||
|
||||
/// Storage for use by [`epaint::PaintCallback`]'s that need to store resources such as render
|
||||
/// pipelines that must have the lifetime of the renderpass.
|
||||
pub paint_callback_resources: TypeMap,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
/// Creates a renderer for a egui UI.
|
||||
///
|
||||
/// `output_color_format` should preferably be [`wgpu::TextureFormat::Rgba8Unorm`] or
|
||||
/// [`wgpu::TextureFormat::Bgra8Unorm`], i.e. in gamma-space.
|
||||
pub fn new(
|
||||
device: &wgpu::Device,
|
||||
output_color_format: wgpu::TextureFormat,
|
||||
output_depth_format: Option<wgpu::TextureFormat>,
|
||||
msaa_samples: u32,
|
||||
) -> Self {
|
||||
crate::profile_function!();
|
||||
|
||||
let shader = wgpu::ShaderModuleDescriptor {
|
||||
label: Some("egui"),
|
||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("egui.wgsl"))),
|
||||
};
|
||||
let module = device.create_shader_module(shader);
|
||||
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("egui_uniform_buffer"),
|
||||
contents: bytemuck::cast_slice(&[UniformBuffer {
|
||||
screen_size_in_points: [0.0, 0.0],
|
||||
_padding: Default::default(),
|
||||
}]),
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let uniform_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("egui_uniform_bind_group_layout"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: NonZeroU64::new(std::mem::size_of::<UniformBuffer>() as _),
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("egui_uniform_bind_group"),
|
||||
layout: &uniform_bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &uniform_buffer,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
}],
|
||||
});
|
||||
|
||||
let texture_bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("egui_texture_bind_group_layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
multisampled: false,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("egui_pipeline_layout"),
|
||||
bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let depth_stencil = output_depth_format.map(|format| wgpu::DepthStencilState {
|
||||
format,
|
||||
depth_write_enabled: false,
|
||||
depth_compare: wgpu::CompareFunction::Always,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("egui_pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
entry_point: "vs_main",
|
||||
module: &module,
|
||||
buffers: &[wgpu::VertexBufferLayout {
|
||||
array_stride: 5 * 4,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
// 0: vec2 position
|
||||
// 1: vec2 texture coordinates
|
||||
// 2: uint color
|
||||
attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2, 2 => Uint32],
|
||||
}],
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
unclipped_depth: false,
|
||||
conservative: false,
|
||||
cull_mode: None,
|
||||
front_face: wgpu::FrontFace::default(),
|
||||
polygon_mode: wgpu::PolygonMode::default(),
|
||||
strip_index_format: None,
|
||||
},
|
||||
depth_stencil,
|
||||
multisample: wgpu::MultisampleState {
|
||||
alpha_to_coverage_enabled: false,
|
||||
count: msaa_samples,
|
||||
mask: !0,
|
||||
},
|
||||
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &module,
|
||||
entry_point: if output_color_format.describe().srgb {
|
||||
tracing::warn!("Detected a linear (sRGBA aware) framebuffer {:?}. egui prefers Rgba8Unorm or Bgra8Unorm", output_color_format);
|
||||
"fs_main_linear_framebuffer"
|
||||
} else {
|
||||
"fs_main_gamma_framebuffer" // this is what we prefer
|
||||
},
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: output_color_format,
|
||||
blend: Some(wgpu::BlendState {
|
||||
color: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::One,
|
||||
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
alpha: wgpu::BlendComponent {
|
||||
src_factor: wgpu::BlendFactor::OneMinusDstAlpha,
|
||||
dst_factor: wgpu::BlendFactor::One,
|
||||
operation: wgpu::BlendOperation::Add,
|
||||
},
|
||||
}),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
const VERTEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =
|
||||
(std::mem::size_of::<Vertex>() * 1024) as _;
|
||||
const INDEX_BUFFER_START_CAPACITY: wgpu::BufferAddress =
|
||||
(std::mem::size_of::<u32>() * 1024 * 3) as _;
|
||||
|
||||
Self {
|
||||
pipeline,
|
||||
vertex_buffer: SlicedBuffer {
|
||||
buffer: create_vertex_buffer(device, VERTEX_BUFFER_START_CAPACITY),
|
||||
slices: Vec::with_capacity(64),
|
||||
capacity: VERTEX_BUFFER_START_CAPACITY,
|
||||
},
|
||||
index_buffer: SlicedBuffer {
|
||||
buffer: create_index_buffer(device, INDEX_BUFFER_START_CAPACITY),
|
||||
slices: Vec::with_capacity(64),
|
||||
capacity: INDEX_BUFFER_START_CAPACITY,
|
||||
},
|
||||
uniform_buffer,
|
||||
uniform_bind_group,
|
||||
texture_bind_group_layout,
|
||||
textures: HashMap::new(),
|
||||
next_user_texture_id: 0,
|
||||
samplers: HashMap::new(),
|
||||
paint_callback_resources: TypeMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the egui renderer onto an existing wgpu renderpass.
|
||||
pub fn render<'rp>(
|
||||
&'rp self,
|
||||
render_pass: &mut wgpu::RenderPass<'rp>,
|
||||
paint_jobs: &[epaint::ClippedPrimitive],
|
||||
screen_descriptor: &ScreenDescriptor,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
|
||||
let pixels_per_point = screen_descriptor.pixels_per_point;
|
||||
let size_in_pixels = screen_descriptor.size_in_pixels;
|
||||
|
||||
// Whether or not we need to reset the render pass because a paint callback has just
|
||||
// run.
|
||||
let mut needs_reset = true;
|
||||
|
||||
let mut index_buffer_slices = self.index_buffer.slices.iter();
|
||||
let mut vertex_buffer_slices = self.vertex_buffer.slices.iter();
|
||||
|
||||
for epaint::ClippedPrimitive {
|
||||
clip_rect,
|
||||
primitive,
|
||||
} in paint_jobs
|
||||
{
|
||||
if needs_reset {
|
||||
render_pass.set_viewport(
|
||||
0.0,
|
||||
0.0,
|
||||
size_in_pixels[0] as f32,
|
||||
size_in_pixels[1] as f32,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
needs_reset = false;
|
||||
}
|
||||
|
||||
{
|
||||
let rect = ScissorRect::new(clip_rect, pixels_per_point, size_in_pixels);
|
||||
|
||||
if rect.width == 0 || rect.height == 0 {
|
||||
// Skip rendering zero-sized clip areas.
|
||||
if let Primitive::Mesh(_) = primitive {
|
||||
// If this is a mesh, we need to advance the index and vertex buffer iterators:
|
||||
index_buffer_slices.next().unwrap();
|
||||
vertex_buffer_slices.next().unwrap();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
render_pass.set_scissor_rect(rect.x, rect.y, rect.width, rect.height);
|
||||
}
|
||||
|
||||
match primitive {
|
||||
Primitive::Mesh(mesh) => {
|
||||
let index_buffer_slice = index_buffer_slices.next().unwrap();
|
||||
let vertex_buffer_slice = vertex_buffer_slices.next().unwrap();
|
||||
|
||||
if let Some((_texture, bind_group)) = self.textures.get(&mesh.texture_id) {
|
||||
render_pass.set_bind_group(1, bind_group, &[]);
|
||||
render_pass.set_index_buffer(
|
||||
self.index_buffer.buffer.slice(index_buffer_slice.clone()),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
render_pass.set_vertex_buffer(
|
||||
0,
|
||||
self.vertex_buffer.buffer.slice(vertex_buffer_slice.clone()),
|
||||
);
|
||||
render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1);
|
||||
} else {
|
||||
tracing::warn!("Missing texture: {:?}", mesh.texture_id);
|
||||
}
|
||||
}
|
||||
Primitive::Callback(callback) => {
|
||||
let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
|
||||
c
|
||||
} else {
|
||||
// We already warned in the `prepare` callback
|
||||
continue;
|
||||
};
|
||||
|
||||
if callback.rect.is_positive() {
|
||||
crate::profile_scope!("callback");
|
||||
|
||||
needs_reset = true;
|
||||
|
||||
{
|
||||
// We're setting a default viewport for the render pass as a
|
||||
// courtesy for the user, so that they don't have to think about
|
||||
// it in the simple case where they just want to fill the whole
|
||||
// paint area.
|
||||
//
|
||||
// The user still has the possibility of setting their own custom
|
||||
// viewport during the paint callback, effectively overriding this
|
||||
// one.
|
||||
|
||||
let min = (callback.rect.min.to_vec2() * pixels_per_point).round();
|
||||
let max = (callback.rect.max.to_vec2() * pixels_per_point).round();
|
||||
|
||||
render_pass.set_viewport(
|
||||
min.x,
|
||||
min.y,
|
||||
max.x - min.x,
|
||||
max.y - min.y,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
}
|
||||
|
||||
(cbfn.paint)(
|
||||
PaintCallbackInfo {
|
||||
viewport: callback.rect,
|
||||
clip_rect: *clip_rect,
|
||||
pixels_per_point,
|
||||
screen_size_px: size_in_pixels,
|
||||
},
|
||||
render_pass,
|
||||
&self.paint_callback_resources,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render_pass.set_scissor_rect(0, 0, size_in_pixels[0], size_in_pixels[1]);
|
||||
}
|
||||
|
||||
/// Should be called before `render()`.
|
||||
pub fn update_texture(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
id: epaint::TextureId,
|
||||
image_delta: &epaint::ImageDelta,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
|
||||
let width = image_delta.image.width() as u32;
|
||||
let height = image_delta.image.height() as u32;
|
||||
|
||||
let size = wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
let data_color32 = match &image_delta.image {
|
||||
epaint::ImageData::Color(image) => {
|
||||
assert_eq!(
|
||||
width as usize * height as usize,
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
Cow::Borrowed(&image.pixels)
|
||||
}
|
||||
epaint::ImageData::Font(image) => {
|
||||
assert_eq!(
|
||||
width as usize * height as usize,
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
Cow::Owned(image.srgba_pixels(None).collect::<Vec<_>>())
|
||||
}
|
||||
};
|
||||
let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());
|
||||
|
||||
let queue_write_data_to_texture = |texture, origin| {
|
||||
queue.write_texture(
|
||||
wgpu::ImageCopyTexture {
|
||||
texture,
|
||||
mip_level: 0,
|
||||
origin,
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
},
|
||||
data_bytes,
|
||||
wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: NonZeroU32::new(4 * width),
|
||||
rows_per_image: NonZeroU32::new(height),
|
||||
},
|
||||
size,
|
||||
);
|
||||
};
|
||||
|
||||
if let Some(pos) = image_delta.pos {
|
||||
// update the existing texture
|
||||
let (texture, _bind_group) = self
|
||||
.textures
|
||||
.get(&id)
|
||||
.expect("Tried to update a texture that has not been allocated yet.");
|
||||
let origin = wgpu::Origin3d {
|
||||
x: pos[0] as u32,
|
||||
y: pos[1] as u32,
|
||||
z: 0,
|
||||
};
|
||||
queue_write_data_to_texture(
|
||||
texture.as_ref().expect("Tried to update user texture."),
|
||||
origin,
|
||||
);
|
||||
} else {
|
||||
// allocate a new texture
|
||||
// Use same label for all resources associated with this texture id (no point in retyping the type)
|
||||
let label_str = format!("egui_texid_{:?}", id);
|
||||
let label = Some(label_str.as_str());
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label,
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb, // Minspec for wgpu WebGL emulation is WebGL2, so this should always be supported.
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb],
|
||||
});
|
||||
let sampler = self
|
||||
.samplers
|
||||
.entry(image_delta.options)
|
||||
.or_insert_with(|| create_sampler(image_delta.options, device));
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label,
|
||||
layout: &self.texture_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(
|
||||
&texture.create_view(&wgpu::TextureViewDescriptor::default()),
|
||||
),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
let origin = wgpu::Origin3d::ZERO;
|
||||
queue_write_data_to_texture(&texture, origin);
|
||||
self.textures.insert(id, (Some(texture), bind_group));
|
||||
};
|
||||
}
|
||||
|
||||
pub fn free_texture(&mut self, id: &epaint::TextureId) {
|
||||
self.textures.remove(id);
|
||||
}
|
||||
|
||||
/// Get the WGPU texture and bind group associated to a texture that has been allocated by egui.
|
||||
///
|
||||
/// This could be used by custom paint hooks to render images that have been added through with
|
||||
/// [`egui_extras::RetainedImage`](https://docs.rs/egui_extras/latest/egui_extras/image/struct.RetainedImage.html)
|
||||
/// or [`epaint::Context::load_texture`](https://docs.rs/egui/latest/egui/struct.Context.html#method.load_texture).
|
||||
pub fn texture(
|
||||
&self,
|
||||
id: &epaint::TextureId,
|
||||
) -> Option<&(Option<wgpu::Texture>, wgpu::BindGroup)> {
|
||||
self.textures.get(id)
|
||||
}
|
||||
|
||||
/// Registers a `wgpu::Texture` with a `epaint::TextureId`.
|
||||
///
|
||||
/// This enables the application to reference the texture inside an image ui element.
|
||||
/// This effectively enables off-screen rendering inside the egui UI. Texture must have
|
||||
/// the texture format `TextureFormat::Rgba8UnormSrgb` and
|
||||
/// Texture usage `TextureUsage::SAMPLED`.
|
||||
pub fn register_native_texture(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
texture: &wgpu::TextureView,
|
||||
texture_filter: wgpu::FilterMode,
|
||||
) -> epaint::TextureId {
|
||||
self.register_native_texture_with_sampler_options(
|
||||
device,
|
||||
texture,
|
||||
wgpu::SamplerDescriptor {
|
||||
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
|
||||
mag_filter: texture_filter,
|
||||
min_filter: texture_filter,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Registers a `wgpu::Texture` with an existing `epaint::TextureId`.
|
||||
///
|
||||
/// This enables applications to reuse `TextureId`s.
|
||||
pub fn update_egui_texture_from_wgpu_texture(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
texture: &wgpu::TextureView,
|
||||
texture_filter: wgpu::FilterMode,
|
||||
id: epaint::TextureId,
|
||||
) {
|
||||
self.update_egui_texture_from_wgpu_texture_with_sampler_options(
|
||||
device,
|
||||
texture,
|
||||
wgpu::SamplerDescriptor {
|
||||
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
|
||||
mag_filter: texture_filter,
|
||||
min_filter: texture_filter,
|
||||
..Default::default()
|
||||
},
|
||||
id,
|
||||
);
|
||||
}
|
||||
|
||||
/// Registers a `wgpu::Texture` with a `epaint::TextureId` while also accepting custom
|
||||
/// `wgpu::SamplerDescriptor` options.
|
||||
///
|
||||
/// This allows applications to specify individual minification/magnification filters as well as
|
||||
/// custom mipmap and tiling options.
|
||||
///
|
||||
/// The `Texture` must have the format `TextureFormat::Rgba8UnormSrgb` and usage
|
||||
/// `TextureUsage::SAMPLED`. Any compare function supplied in the `SamplerDescriptor` will be
|
||||
/// ignored.
|
||||
#[allow(clippy::needless_pass_by_value)] // false positive
|
||||
pub fn register_native_texture_with_sampler_options(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
texture: &wgpu::TextureView,
|
||||
sampler_descriptor: wgpu::SamplerDescriptor<'_>,
|
||||
) -> epaint::TextureId {
|
||||
crate::profile_function!();
|
||||
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
compare: None,
|
||||
..sampler_descriptor
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
|
||||
layout: &self.texture_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(texture),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let id = epaint::TextureId::User(self.next_user_texture_id);
|
||||
self.textures.insert(id, (None, bind_group));
|
||||
self.next_user_texture_id += 1;
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
/// Registers a `wgpu::Texture` with an existing `epaint::TextureId` while also accepting custom
|
||||
/// `wgpu::SamplerDescriptor` options.
|
||||
///
|
||||
/// This allows applications to reuse `TextureId`s created with custom sampler options.
|
||||
#[allow(clippy::needless_pass_by_value)] // false positive
|
||||
pub fn update_egui_texture_from_wgpu_texture_with_sampler_options(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
texture: &wgpu::TextureView,
|
||||
sampler_descriptor: wgpu::SamplerDescriptor<'_>,
|
||||
id: epaint::TextureId,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
|
||||
let (_user_texture, user_texture_binding) = self
|
||||
.textures
|
||||
.get_mut(&id)
|
||||
.expect("Tried to update a texture that has not been allocated yet.");
|
||||
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
compare: None,
|
||||
..sampler_descriptor
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
|
||||
layout: &self.texture_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(texture),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(&sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
*user_texture_binding = bind_group;
|
||||
}
|
||||
|
||||
/// Uploads the uniform, vertex and index data used by the renderer.
|
||||
/// Should be called before `render()`.
|
||||
///
|
||||
/// Returns all user-defined command buffers gathered from prepare callbacks.
|
||||
pub fn update_buffers(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
paint_jobs: &[epaint::ClippedPrimitive],
|
||||
screen_descriptor: &ScreenDescriptor,
|
||||
) -> Vec<wgpu::CommandBuffer> {
|
||||
crate::profile_function!();
|
||||
|
||||
let screen_size_in_points = screen_descriptor.screen_size_in_points();
|
||||
|
||||
{
|
||||
crate::profile_scope!("uniforms");
|
||||
// Update uniform buffer
|
||||
queue.write_buffer(
|
||||
&self.uniform_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[UniformBuffer {
|
||||
screen_size_in_points,
|
||||
_padding: Default::default(),
|
||||
}]),
|
||||
);
|
||||
}
|
||||
|
||||
// Determine how many vertices & indices need to be rendered.
|
||||
let (vertex_count, index_count) = {
|
||||
crate::profile_scope!("count_vertices_indices");
|
||||
paint_jobs.iter().fold((0, 0), |acc, clipped_primitive| {
|
||||
match &clipped_primitive.primitive {
|
||||
Primitive::Mesh(mesh) => {
|
||||
(acc.0 + mesh.vertices.len(), acc.1 + mesh.indices.len())
|
||||
}
|
||||
Primitive::Callback(_) => acc,
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
{
|
||||
// Resize index buffer if needed:
|
||||
self.index_buffer.slices.clear();
|
||||
let required_size = (std::mem::size_of::<u32>() * index_count) as u64;
|
||||
if self.index_buffer.capacity < required_size {
|
||||
self.index_buffer.capacity =
|
||||
(self.index_buffer.capacity * 2).at_least(required_size);
|
||||
self.index_buffer.buffer = create_index_buffer(device, self.index_buffer.capacity);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Resize vertex buffer if needed:
|
||||
self.vertex_buffer.slices.clear();
|
||||
let required_size = (std::mem::size_of::<Vertex>() * vertex_count) as u64;
|
||||
if self.vertex_buffer.capacity < required_size {
|
||||
self.vertex_buffer.capacity =
|
||||
(self.vertex_buffer.capacity * 2).at_least(required_size);
|
||||
self.vertex_buffer.buffer =
|
||||
create_vertex_buffer(device, self.vertex_buffer.capacity);
|
||||
}
|
||||
}
|
||||
|
||||
// Upload index & vertex data and call user callbacks
|
||||
let mut user_cmd_bufs = Vec::new(); // collect user command buffers
|
||||
|
||||
crate::profile_scope!("primitives");
|
||||
for epaint::ClippedPrimitive { primitive, .. } in paint_jobs.iter() {
|
||||
match primitive {
|
||||
Primitive::Mesh(mesh) => {
|
||||
{
|
||||
let index_offset = self.index_buffer.slices.last().unwrap_or(&(0..0)).end;
|
||||
let data = bytemuck::cast_slice(&mesh.indices);
|
||||
queue.write_buffer(&self.index_buffer.buffer, index_offset, data);
|
||||
self.index_buffer
|
||||
.slices
|
||||
.push(index_offset..(data.len() as wgpu::BufferAddress + index_offset));
|
||||
}
|
||||
{
|
||||
let vertex_offset = self.vertex_buffer.slices.last().unwrap_or(&(0..0)).end;
|
||||
let data = bytemuck::cast_slice(&mesh.vertices);
|
||||
queue.write_buffer(&self.vertex_buffer.buffer, vertex_offset, data);
|
||||
self.vertex_buffer.slices.push(
|
||||
vertex_offset..(data.len() as wgpu::BufferAddress + vertex_offset),
|
||||
);
|
||||
}
|
||||
}
|
||||
Primitive::Callback(callback) => {
|
||||
let cbfn = if let Some(c) = callback.callback.downcast_ref::<CallbackFn>() {
|
||||
c
|
||||
} else {
|
||||
tracing::warn!("Unknown paint callback: expected `egui_wgpu::CallbackFn`");
|
||||
continue;
|
||||
};
|
||||
|
||||
crate::profile_scope!("callback");
|
||||
user_cmd_bufs.extend((cbfn.prepare)(
|
||||
device,
|
||||
queue,
|
||||
encoder,
|
||||
&mut self.paint_callback_resources,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user_cmd_bufs
|
||||
}
|
||||
}
|
||||
|
||||
fn create_sampler(
|
||||
options: epaint::textures::TextureOptions,
|
||||
device: &wgpu::Device,
|
||||
) -> wgpu::Sampler {
|
||||
let mag_filter = match options.magnification {
|
||||
epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
|
||||
epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear,
|
||||
};
|
||||
let min_filter = match options.minification {
|
||||
epaint::textures::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
|
||||
epaint::textures::TextureFilter::Linear => wgpu::FilterMode::Linear,
|
||||
};
|
||||
device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some(&format!(
|
||||
"egui sampler (mag: {:?}, min {:?})",
|
||||
mag_filter, min_filter
|
||||
)),
|
||||
mag_filter,
|
||||
min_filter,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn create_vertex_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
|
||||
crate::profile_function!();
|
||||
device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("egui_vertex_buffer"),
|
||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||
size,
|
||||
mapped_at_creation: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_index_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
|
||||
crate::profile_function!();
|
||||
device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("egui_index_buffer"),
|
||||
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
|
||||
size,
|
||||
mapped_at_creation: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// A Rect in physical pixel space, used for setting cliipping rectangles.
|
||||
struct ScissorRect {
|
||||
x: u32,
|
||||
y: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl ScissorRect {
|
||||
fn new(clip_rect: &epaint::Rect, pixels_per_point: f32, target_size: [u32; 2]) -> Self {
|
||||
// Transform clip rect to physical pixels:
|
||||
let clip_min_x = pixels_per_point * clip_rect.min.x;
|
||||
let clip_min_y = pixels_per_point * clip_rect.min.y;
|
||||
let clip_max_x = pixels_per_point * clip_rect.max.x;
|
||||
let clip_max_y = pixels_per_point * clip_rect.max.y;
|
||||
|
||||
// Round to integer:
|
||||
let clip_min_x = clip_min_x.round() as u32;
|
||||
let clip_min_y = clip_min_y.round() as u32;
|
||||
let clip_max_x = clip_max_x.round() as u32;
|
||||
let clip_max_y = clip_max_y.round() as u32;
|
||||
|
||||
// Clamp:
|
||||
let clip_min_x = clip_min_x.clamp(0, target_size[0]);
|
||||
let clip_min_y = clip_min_y.clamp(0, target_size[1]);
|
||||
let clip_max_x = clip_max_x.clamp(clip_min_x, target_size[0]);
|
||||
let clip_max_y = clip_max_y.clamp(clip_min_y, target_size[1]);
|
||||
|
||||
Self {
|
||||
x: clip_min_x,
|
||||
y: clip_min_y,
|
||||
width: clip_max_x - clip_min_x,
|
||||
height: clip_max_y - clip_min_y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renderer_impl_send_sync() {
|
||||
fn assert_send_sync<T: Send + Sync>() {}
|
||||
assert_send_sync::<Renderer>();
|
||||
}
|
|
@ -1,418 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use epaint::mutex::RwLock;
|
||||
|
||||
use tracing::error;
|
||||
|
||||
use crate::{renderer, RenderState, Renderer, SurfaceErrorAction, WgpuConfiguration};
|
||||
|
||||
struct SurfaceState {
|
||||
surface: wgpu::Surface,
|
||||
alpha_mode: wgpu::CompositeAlphaMode,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
/// Everything you need to paint egui with [`wgpu`] on [`winit`].
|
||||
///
|
||||
/// Alternatively you can use [`crate::renderer`] directly.
|
||||
pub struct Painter {
|
||||
configuration: WgpuConfiguration,
|
||||
msaa_samples: u32,
|
||||
support_transparent_backbuffer: bool,
|
||||
depth_format: Option<wgpu::TextureFormat>,
|
||||
depth_texture_view: Option<wgpu::TextureView>,
|
||||
|
||||
instance: wgpu::Instance,
|
||||
adapter: Option<wgpu::Adapter>,
|
||||
render_state: Option<RenderState>,
|
||||
surface_state: Option<SurfaceState>,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
/// Manages [`wgpu`] state, including surface state, required to render egui.
|
||||
///
|
||||
/// Only the [`wgpu::Instance`] is initialized here. Device selection and the initialization
|
||||
/// of render + surface state is deferred until the painter is given its first window target
|
||||
/// via [`set_window()`](Self::set_window). (Ensuring that a device that's compatible with the
|
||||
/// native window is chosen)
|
||||
///
|
||||
/// Before calling [`paint_and_update_textures()`](Self::paint_and_update_textures) a
|
||||
/// [`wgpu::Surface`] must be initialized (and corresponding render state) by calling
|
||||
/// [`set_window()`](Self::set_window) once you have
|
||||
/// a [`winit::window::Window`] with a valid `.raw_window_handle()`
|
||||
/// associated.
|
||||
pub fn new(
|
||||
configuration: WgpuConfiguration,
|
||||
msaa_samples: u32,
|
||||
depth_bits: u8,
|
||||
support_transparent_backbuffer: bool,
|
||||
) -> Self {
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: configuration.backends,
|
||||
dx12_shader_compiler: Default::default(), //
|
||||
});
|
||||
|
||||
Self {
|
||||
configuration,
|
||||
msaa_samples,
|
||||
support_transparent_backbuffer,
|
||||
depth_format: (depth_bits > 0).then_some(wgpu::TextureFormat::Depth32Float),
|
||||
depth_texture_view: None,
|
||||
|
||||
instance,
|
||||
adapter: None,
|
||||
render_state: None,
|
||||
surface_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the [`RenderState`].
|
||||
///
|
||||
/// Will return [`None`] if the render state has not been initialized yet.
|
||||
pub fn render_state(&self) -> Option<RenderState> {
|
||||
self.render_state.clone()
|
||||
}
|
||||
|
||||
async fn init_render_state(
|
||||
&self,
|
||||
adapter: &wgpu::Adapter,
|
||||
target_format: wgpu::TextureFormat,
|
||||
) -> Result<RenderState, wgpu::RequestDeviceError> {
|
||||
adapter
|
||||
.request_device(&self.configuration.device_descriptor, None)
|
||||
.await
|
||||
.map(|(device, queue)| {
|
||||
let renderer =
|
||||
Renderer::new(&device, target_format, self.depth_format, self.msaa_samples);
|
||||
RenderState {
|
||||
device: Arc::new(device),
|
||||
queue: Arc::new(queue),
|
||||
target_format,
|
||||
renderer: Arc::new(RwLock::new(renderer)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// We want to defer the initialization of our render state until we have a surface
|
||||
// so we can take its format into account.
|
||||
//
|
||||
// After we've initialized our render state once though we expect all future surfaces
|
||||
// will have the same format and so this render state will remain valid.
|
||||
async fn ensure_render_state_for_surface(
|
||||
&mut self,
|
||||
surface: &wgpu::Surface,
|
||||
) -> Result<(), wgpu::RequestDeviceError> {
|
||||
if self.adapter.is_none() {
|
||||
self.adapter = self
|
||||
.instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: self.configuration.power_preference,
|
||||
compatible_surface: Some(surface),
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
if self.render_state.is_none() {
|
||||
match &self.adapter {
|
||||
Some(adapter) => {
|
||||
let swapchain_format = crate::preferred_framebuffer_format(
|
||||
&surface.get_capabilities(adapter).formats,
|
||||
);
|
||||
let rs = self.init_render_state(adapter, swapchain_format).await?;
|
||||
self.render_state = Some(rs);
|
||||
}
|
||||
None => return Err(wgpu::RequestDeviceError {}),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_surface(
|
||||
surface_state: &SurfaceState,
|
||||
render_state: &RenderState,
|
||||
present_mode: wgpu::PresentMode,
|
||||
) {
|
||||
surface_state.surface.configure(
|
||||
&render_state.device,
|
||||
&wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: render_state.target_format,
|
||||
width: surface_state.width,
|
||||
height: surface_state.height,
|
||||
present_mode,
|
||||
alpha_mode: surface_state.alpha_mode,
|
||||
view_formats: vec![render_state.target_format],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`]
|
||||
///
|
||||
/// This creates a [`wgpu::Surface`] for the given Window (as well as initializing render
|
||||
/// state if needed) that is used for egui rendering.
|
||||
///
|
||||
/// This must be called before trying to render via
|
||||
/// [`paint_and_update_textures`](Self::paint_and_update_textures)
|
||||
///
|
||||
/// # Portability
|
||||
///
|
||||
/// _In particular it's important to note that on Android a it's only possible to create
|
||||
/// a window surface between `Resumed` and `Paused` lifecycle events, and Winit will panic on
|
||||
/// attempts to query the raw window handle while paused._
|
||||
///
|
||||
/// On Android [`set_window`](Self::set_window) should be called with `Some(window)` for each
|
||||
/// `Resumed` event and `None` for each `Paused` event. Currently, on all other platforms
|
||||
/// [`set_window`](Self::set_window) may be called with `Some(window)` as soon as you have a
|
||||
/// valid [`winit::window::Window`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The raw Window handle associated with the given `window` must be a valid object to create a
|
||||
/// surface upon and must remain valid for the lifetime of the created surface. (The surface may
|
||||
/// be cleared by passing `None`).
|
||||
///
|
||||
/// # Errors
|
||||
/// If the provided wgpu configuration does not match an available device.
|
||||
pub async unsafe fn set_window(
|
||||
&mut self,
|
||||
window: Option<&winit::window::Window>,
|
||||
) -> Result<(), crate::WgpuError> {
|
||||
match window {
|
||||
Some(window) => {
|
||||
let surface = self.instance.create_surface(&window)?;
|
||||
|
||||
self.ensure_render_state_for_surface(&surface).await?;
|
||||
|
||||
let alpha_mode = if self.support_transparent_backbuffer {
|
||||
let supported_alpha_modes = surface
|
||||
.get_capabilities(self.adapter.as_ref().unwrap())
|
||||
.alpha_modes;
|
||||
|
||||
// Prefer pre multiplied over post multiplied!
|
||||
if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
|
||||
wgpu::CompositeAlphaMode::PreMultiplied
|
||||
} else if supported_alpha_modes
|
||||
.contains(&wgpu::CompositeAlphaMode::PostMultiplied)
|
||||
{
|
||||
wgpu::CompositeAlphaMode::PostMultiplied
|
||||
} else {
|
||||
tracing::warn!("Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency.");
|
||||
wgpu::CompositeAlphaMode::Auto
|
||||
}
|
||||
} else {
|
||||
wgpu::CompositeAlphaMode::Auto
|
||||
};
|
||||
|
||||
let size = window.inner_size();
|
||||
self.surface_state = Some(SurfaceState {
|
||||
surface,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
alpha_mode,
|
||||
});
|
||||
self.resize_and_generate_depth_texture_view(size.width, size.height);
|
||||
}
|
||||
None => {
|
||||
self.surface_state = None;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the maximum texture dimension supported if known
|
||||
///
|
||||
/// This API will only return a known dimension after `set_window()` has been called
|
||||
/// at least once, since the underlying device and render state are initialized lazily
|
||||
/// once we have a window (that may determine the choice of adapter/device).
|
||||
pub fn max_texture_side(&self) -> Option<usize> {
|
||||
self.render_state
|
||||
.as_ref()
|
||||
.map(|rs| rs.device.limits().max_texture_dimension_2d as usize)
|
||||
}
|
||||
|
||||
fn resize_and_generate_depth_texture_view(
|
||||
&mut self,
|
||||
width_in_pixels: u32,
|
||||
height_in_pixels: u32,
|
||||
) {
|
||||
let render_state = self.render_state.as_ref().unwrap();
|
||||
let surface_state = self.surface_state.as_mut().unwrap();
|
||||
|
||||
surface_state.width = width_in_pixels;
|
||||
surface_state.height = height_in_pixels;
|
||||
|
||||
Self::configure_surface(surface_state, render_state, self.configuration.present_mode);
|
||||
|
||||
self.depth_texture_view = self.depth_format.map(|depth_format| {
|
||||
render_state
|
||||
.device
|
||||
.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("egui_depth_texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: width_in_pixels,
|
||||
height: height_in_pixels,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: depth_format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[depth_format],
|
||||
})
|
||||
.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
});
|
||||
}
|
||||
|
||||
pub fn on_window_resized(&mut self, width_in_pixels: u32, height_in_pixels: u32) {
|
||||
if self.surface_state.is_some() {
|
||||
self.resize_and_generate_depth_texture_view(width_in_pixels, height_in_pixels);
|
||||
} else {
|
||||
error!("Ignoring window resize notification with no surface created via Painter::set_window()");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paint_and_update_textures(
|
||||
&mut self,
|
||||
pixels_per_point: f32,
|
||||
clear_color: [f32; 4],
|
||||
clipped_primitives: &[epaint::ClippedPrimitive],
|
||||
textures_delta: &epaint::textures::TexturesDelta,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
|
||||
let render_state = match self.render_state.as_mut() {
|
||||
Some(rs) => rs,
|
||||
None => return,
|
||||
};
|
||||
let surface_state = match self.surface_state.as_ref() {
|
||||
Some(rs) => rs,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let output_frame = {
|
||||
crate::profile_scope!("get_current_texture");
|
||||
// This is what vsync-waiting happens, at least on Mac.
|
||||
surface_state.surface.get_current_texture()
|
||||
};
|
||||
|
||||
let output_frame = match output_frame {
|
||||
Ok(frame) => frame,
|
||||
#[allow(clippy::single_match_else)]
|
||||
Err(e) => match (*self.configuration.on_surface_error)(e) {
|
||||
SurfaceErrorAction::RecreateSurface => {
|
||||
Self::configure_surface(
|
||||
surface_state,
|
||||
render_state,
|
||||
self.configuration.present_mode,
|
||||
);
|
||||
return;
|
||||
}
|
||||
SurfaceErrorAction::SkipFrame => {
|
||||
return;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let mut encoder =
|
||||
render_state
|
||||
.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("encoder"),
|
||||
});
|
||||
|
||||
// Upload all resources for the GPU.
|
||||
let screen_descriptor = renderer::ScreenDescriptor {
|
||||
size_in_pixels: [surface_state.width, surface_state.height],
|
||||
pixels_per_point,
|
||||
};
|
||||
|
||||
let user_cmd_bufs = {
|
||||
let mut renderer = render_state.renderer.write();
|
||||
for (id, image_delta) in &textures_delta.set {
|
||||
renderer.update_texture(
|
||||
&render_state.device,
|
||||
&render_state.queue,
|
||||
*id,
|
||||
image_delta,
|
||||
);
|
||||
}
|
||||
|
||||
renderer.update_buffers(
|
||||
&render_state.device,
|
||||
&render_state.queue,
|
||||
&mut encoder,
|
||||
clipped_primitives,
|
||||
&screen_descriptor,
|
||||
)
|
||||
};
|
||||
|
||||
{
|
||||
let renderer = render_state.renderer.read();
|
||||
let frame_view = output_frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &frame_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: clear_color[0] as f64,
|
||||
g: clear_color[1] as f64,
|
||||
b: clear_color[2] as f64,
|
||||
a: clear_color[3] as f64,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| {
|
||||
wgpu::RenderPassDepthStencilAttachment {
|
||||
view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: true,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}
|
||||
}),
|
||||
label: Some("egui_render"),
|
||||
});
|
||||
|
||||
renderer.render(&mut render_pass, clipped_primitives, &screen_descriptor);
|
||||
}
|
||||
|
||||
{
|
||||
let mut renderer = render_state.renderer.write();
|
||||
for id in &textures_delta.free {
|
||||
renderer.free_texture(id);
|
||||
}
|
||||
}
|
||||
|
||||
let encoded = {
|
||||
crate::profile_scope!("CommandEncoder::finish");
|
||||
encoder.finish()
|
||||
};
|
||||
|
||||
// Submit the commands: both the main buffer and user-defined ones.
|
||||
{
|
||||
crate::profile_scope!("Queue::submit");
|
||||
render_state
|
||||
.queue
|
||||
.submit(user_cmd_bufs.into_iter().chain(std::iter::once(encoded)));
|
||||
};
|
||||
|
||||
// Redraw egui
|
||||
{
|
||||
crate::profile_scope!("present");
|
||||
output_frame.present();
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn destroy(&mut self) {
|
||||
// TODO(emilk): something here?
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
# Changelog for egui-winit
|
||||
All notable changes to the `egui-winit` integration will be noted in this file.
|
||||
|
||||
|
||||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.1 - 2023-02-12
|
||||
* Fixed crash when window position is in an invalid state, which could happen e.g. due to changes in monitor size or DPI ([#2722](https://github.com/emilk/egui/issues/2722)).
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08
|
||||
* Fixed persistence of native window position on Windows OS ([#2583](https://github.com/emilk/egui/issues/2583)).
|
||||
* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)).
|
||||
* Remove the `screen_reader` feature. Use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)).
|
||||
* Fix bug where the cursor could get stuck using the wrong icon.
|
||||
|
||||
|
||||
## 0.20.1 - 2022-12-11
|
||||
* Fix [docs.rs](https://docs.rs/egui-winit) build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08
|
||||
* The default features of the `winit` crate are not enabled if the default features of `egui-winit` are disabled too ([#1971](https://github.com/emilk/egui/pull/1971)).
|
||||
* Added new feature `wayland` which enables Wayland support ([#1971](https://github.com/emilk/egui/pull/1971)).
|
||||
* Don't repaint when just moving window ([#1980](https://github.com/emilk/egui/pull/1980)).
|
||||
* Added optional integration with [AccessKit](https://accesskit.dev/) for implementing platform accessibility APIs ([#2294](https://github.com/emilk/egui/pull/2294)).
|
||||
|
||||
## 0.19.0 - 2022-08-20
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
|
||||
* Fixed clipboard on Wayland ([#1613](https://github.com/emilk/egui/pull/1613)).
|
||||
* Allow deferred render + surface state initialization for Android ([#1634](https://github.com/emilk/egui/pull/1634)).
|
||||
* Fixed window position persistence ([#1745](https://github.com/emilk/egui/pull/1745)).
|
||||
* Fixed mouse cursor change on Linux ([#1747](https://github.com/emilk/egui/pull/1747)).
|
||||
* Use the new `RawInput::has_focus` field to indicate whether the window has the keyboard focus ([#1859](https://github.com/emilk/egui/pull/1859)).
|
||||
|
||||
|
||||
## 0.18.0 - 2022-04-30
|
||||
* Reexport `egui` crate
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)).
|
||||
* Renamed the feature `convert_bytemuck` to `bytemuck` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* Renamed the feature `serialize` to `serde` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* Removed the features `dark-light` and `persistence` ([#1542](https://github.com/emilk/egui/pull/1542)).
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
* Fixed horizontal scrolling direction on Linux.
|
||||
* Replaced `std::time::Instant` with `instant::Instant` for WebAssembly compatability ([#1023](https://github.com/emilk/egui/pull/1023))
|
||||
* Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)).
|
||||
* Fixed `enable_drag` on Windows OS ([#1108](https://github.com/emilk/egui/pull/1108)).
|
||||
* Shift-scroll will now result in horizontal scrolling on all platforms ([#1136](https://github.com/emilk/egui/pull/1136)).
|
||||
* Require knowledge about max texture side (e.g. `GL_MAX_TEXTURE_SIZE`)) ([#1154](https://github.com/emilk/egui/pull/1154)).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
* Added helper `EpiIntegration` ([#871](https://github.com/emilk/egui/pull/871)).
|
||||
* Fixed shift key getting stuck enabled with the X11 option `shift:both_capslock` enabled ([#849](https://github.com/emilk/egui/pull/849)).
|
||||
* Removed `State::is_quit_event` and `State::is_quit_shortcut` ([#881](https://github.com/emilk/egui/pull/881)).
|
||||
* Updated `winit` to 0.26 ([#930](https://github.com/emilk/egui/pull/930)).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
First stand-alone release. Previously part of `egui_glium`.
|
|
@ -1,76 +0,0 @@
|
|||
[package]
|
||||
name = "egui-winit"
|
||||
version = "0.21.1"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Bindings for using egui with winit"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/crates/egui-winit"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui/tree/master/crates/egui-winit"
|
||||
categories = ["gui", "game-development"]
|
||||
keywords = ["winit", "egui", "gui", "gamedev"]
|
||||
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
|
||||
[features]
|
||||
default = ["clipboard", "links", "wayland", "winit/default"]
|
||||
|
||||
## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/).
|
||||
accesskit = ["accesskit_winit", "egui/accesskit"]
|
||||
|
||||
## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`egui::epaint::Vertex`], [`egui::Vec2`] etc to `&[u8]`.
|
||||
bytemuck = ["egui/bytemuck"]
|
||||
|
||||
## Enable cut/copy/paste to OS clipboard.
|
||||
## If disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
||||
clipboard = ["arboard", "smithay-clipboard"]
|
||||
|
||||
## Enable opening links in a browser when an egui hyperlink is clicked.
|
||||
links = ["webbrowser"]
|
||||
|
||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
||||
puffin = ["dep:puffin"]
|
||||
|
||||
## Allow serialization of [`WindowSettings`] using [`serde`](https://docs.rs/serde).
|
||||
serde = ["egui/serde", "dep:serde"]
|
||||
|
||||
## Enables Wayland support.
|
||||
wayland = ["winit/wayland"]
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
|
||||
"tracing",
|
||||
] }
|
||||
instant = { version = "0.1", features = [
|
||||
"wasm-bindgen",
|
||||
] } # We use instant so we can (maybe) compile for web
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
winit = { version = "0.28", default-features = false }
|
||||
|
||||
#! ### Optional dependencies
|
||||
|
||||
# feature accesskit
|
||||
accesskit_winit = { version = "0.10.0", optional = true }
|
||||
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
puffin = { version = "0.14", optional = true }
|
||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||
|
||||
webbrowser = { version = "0.8.3", optional = true }
|
||||
|
||||
[target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies]
|
||||
smithay-clipboard = { version = "0.6.3", optional = true }
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
arboard = { version = "3.2", optional = true, default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
# TODO(emilk): this is probably not the right place for specifying native-activity, but we need to do it somewhere for the CI
|
||||
android-activity = { version = "0.4", features = ["native-activity"] }
|
|
@ -1,146 +0,0 @@
|
|||
use std::os::raw::c_void;
|
||||
|
||||
/// Handles interfacing with the OS clipboard.
|
||||
///
|
||||
/// If the "clipboard" feature is off, or we cannot connect to the OS clipboard,
|
||||
/// then a fallback clipboard that just works works within the same app is used instead.
|
||||
pub struct Clipboard {
|
||||
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||
arboard: Option<arboard::Clipboard>,
|
||||
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
),
|
||||
feature = "smithay-clipboard"
|
||||
))]
|
||||
smithay: Option<smithay_clipboard::Clipboard>,
|
||||
|
||||
/// Fallback manual clipboard.
|
||||
clipboard: String,
|
||||
}
|
||||
|
||||
impl Clipboard {
|
||||
#[allow(unused_variables)]
|
||||
pub fn new(#[allow(unused_variables)] wayland_display: Option<*mut c_void>) -> Self {
|
||||
Self {
|
||||
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||
arboard: init_arboard(),
|
||||
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
),
|
||||
feature = "smithay-clipboard"
|
||||
))]
|
||||
smithay: init_smithay_clipboard(wayland_display),
|
||||
|
||||
clipboard: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&mut self) -> Option<String> {
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
),
|
||||
feature = "smithay-clipboard"
|
||||
))]
|
||||
if let Some(clipboard) = &mut self.smithay {
|
||||
return match clipboard.load() {
|
||||
Ok(text) => Some(text),
|
||||
Err(err) => {
|
||||
tracing::error!("smithay paste error: {err}");
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||
if let Some(clipboard) = &mut self.arboard {
|
||||
return match clipboard.get_text() {
|
||||
Ok(text) => Some(text),
|
||||
Err(err) => {
|
||||
tracing::error!("arboard paste error: {err}");
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Some(self.clipboard.clone())
|
||||
}
|
||||
|
||||
pub fn set(&mut self, text: String) {
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
),
|
||||
feature = "smithay-clipboard"
|
||||
))]
|
||||
if let Some(clipboard) = &mut self.smithay {
|
||||
clipboard.store(text);
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||
if let Some(clipboard) = &mut self.arboard {
|
||||
if let Err(err) = clipboard.set_text(text) {
|
||||
tracing::error!("arboard copy/cut error: {err}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
self.clipboard = text;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||
fn init_arboard() -> Option<arboard::Clipboard> {
|
||||
tracing::debug!("Initializing arboard clipboard…");
|
||||
match arboard::Clipboard::new() {
|
||||
Ok(clipboard) => Some(clipboard),
|
||||
Err(err) => {
|
||||
tracing::warn!("Failed to initialize arboard clipboard: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
),
|
||||
feature = "smithay-clipboard"
|
||||
))]
|
||||
fn init_smithay_clipboard(
|
||||
wayland_display: Option<*mut c_void>,
|
||||
) -> Option<smithay_clipboard::Clipboard> {
|
||||
if let Some(display) = wayland_display {
|
||||
tracing::debug!("Initializing smithay clipboard…");
|
||||
#[allow(unsafe_code)]
|
||||
Some(unsafe { smithay_clipboard::Clipboard::new(display) })
|
||||
} else {
|
||||
tracing::debug!("Cannot initialize smithay clipboard without a display handle");
|
||||
None
|
||||
}
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
/// Can be used to store native window settings (position and size).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct WindowSettings {
|
||||
/// Position of window in physical pixels. This is either
|
||||
/// the inner or outer position depending on the platform.
|
||||
/// See [`winit::window::WindowBuilder::with_position`] for details.
|
||||
position: Option<egui::Pos2>,
|
||||
|
||||
fullscreen: bool,
|
||||
|
||||
/// Inner size of window in logical pixels
|
||||
inner_size_points: Option<egui::Vec2>,
|
||||
}
|
||||
|
||||
impl WindowSettings {
|
||||
pub fn from_display(window: &winit::window::Window) -> Self {
|
||||
let inner_size_points = window.inner_size().to_logical::<f32>(window.scale_factor());
|
||||
let position = if cfg!(macos) {
|
||||
// MacOS uses inner position when positioning windows.
|
||||
window
|
||||
.inner_position()
|
||||
.ok()
|
||||
.map(|p| egui::pos2(p.x as f32, p.y as f32))
|
||||
} else {
|
||||
// Other platforms use the outer position.
|
||||
window
|
||||
.outer_position()
|
||||
.ok()
|
||||
.map(|p| egui::pos2(p.x as f32, p.y as f32))
|
||||
};
|
||||
|
||||
Self {
|
||||
position,
|
||||
|
||||
fullscreen: window.fullscreen().is_some(),
|
||||
|
||||
inner_size_points: Some(egui::vec2(
|
||||
inner_size_points.width,
|
||||
inner_size_points.height,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_size_points(&self) -> Option<egui::Vec2> {
|
||||
self.inner_size_points
|
||||
}
|
||||
|
||||
pub fn initialize_window(
|
||||
&self,
|
||||
mut window: winit::window::WindowBuilder,
|
||||
) -> winit::window::WindowBuilder {
|
||||
// If the app last ran on two monitors and only one is now connected, then
|
||||
// the given position is invalid.
|
||||
// If this happens on Mac, the window is clamped into valid area.
|
||||
// If this happens on Windows, the clamping behavior is managed by the function
|
||||
// clamp_window_to_sane_position.
|
||||
if let Some(pos) = self.position {
|
||||
window = window.with_position(winit::dpi::PhysicalPosition {
|
||||
x: pos.x as f64,
|
||||
y: pos.y as f64,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(inner_size_points) = self.inner_size_points {
|
||||
window
|
||||
.with_inner_size(winit::dpi::LogicalSize {
|
||||
width: inner_size_points.x as f64,
|
||||
height: inner_size_points.y as f64,
|
||||
})
|
||||
.with_fullscreen(
|
||||
self.fullscreen
|
||||
.then_some(winit::window::Fullscreen::Borderless(None)),
|
||||
)
|
||||
} else {
|
||||
window
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_to_sane_values(&mut self, max_size: egui::Vec2) {
|
||||
use egui::NumExt as _;
|
||||
|
||||
if let Some(size) = &mut self.inner_size_points {
|
||||
// Prevent ridiculously small windows
|
||||
let min_size = egui::Vec2::splat(64.0);
|
||||
*size = size.at_least(min_size);
|
||||
*size = size.at_most(max_size);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_window_to_sane_position<E>(
|
||||
&mut self,
|
||||
event_loop: &winit::event_loop::EventLoopWindowTarget<E>,
|
||||
) {
|
||||
if let (Some(position), Some(inner_size_points)) =
|
||||
(&mut self.position, &self.inner_size_points)
|
||||
{
|
||||
let monitors = event_loop.available_monitors();
|
||||
// default to primary monitor, in case the correct monitor was disconnected.
|
||||
let mut active_monitor = if let Some(active_monitor) = event_loop
|
||||
.primary_monitor()
|
||||
.or_else(|| event_loop.available_monitors().next())
|
||||
{
|
||||
active_monitor
|
||||
} else {
|
||||
return; // no monitors 🤷
|
||||
};
|
||||
for monitor in monitors {
|
||||
let monitor_x_range = (monitor.position().x - inner_size_points.x as i32)
|
||||
..(monitor.position().x + monitor.size().width as i32);
|
||||
let monitor_y_range = (monitor.position().y - inner_size_points.y as i32)
|
||||
..(monitor.position().y + monitor.size().height as i32);
|
||||
|
||||
if monitor_x_range.contains(&(position.x as i32))
|
||||
&& monitor_y_range.contains(&(position.y as i32))
|
||||
{
|
||||
active_monitor = monitor;
|
||||
}
|
||||
}
|
||||
|
||||
let mut inner_size_pixels = *inner_size_points * (active_monitor.scale_factor() as f32);
|
||||
// Add size of title bar. This is 32 px by default in Win 10/11.
|
||||
if cfg!(target_os = "windows") {
|
||||
inner_size_pixels +=
|
||||
egui::Vec2::new(0.0, 32.0 * active_monitor.scale_factor() as f32);
|
||||
}
|
||||
let monitor_position = egui::Pos2::new(
|
||||
active_monitor.position().x as f32,
|
||||
active_monitor.position().y as f32,
|
||||
);
|
||||
let monitor_size = egui::Vec2::new(
|
||||
active_monitor.size().width as f32,
|
||||
active_monitor.size().height as f32,
|
||||
);
|
||||
|
||||
// Window size cannot be negative or the subsequent `clamp` will panic.
|
||||
let window_size = (monitor_size - inner_size_pixels).max(egui::Vec2::ZERO);
|
||||
// To get the maximum position, we get the rightmost corner of the display, then
|
||||
// subtract the size of the window to get the bottom right most value window.position
|
||||
// can have.
|
||||
*position = position.clamp(monitor_position, monitor_position + window_size);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
[package]
|
||||
name = "egui"
|
||||
version = "0.21.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "An easy-to-use immediate mode GUI that runs on both web and native"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "../../README.md"
|
||||
repository = "https://github.com/emilk/egui"
|
||||
categories = ["gui", "game-development"]
|
||||
keywords = ["gui", "imgui", "immediate", "portable", "gamedev"]
|
||||
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
|
||||
|
||||
[features]
|
||||
default = ["default_fonts"]
|
||||
|
||||
## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`epaint::Vertex`], [`emath::Vec2`] etc to `&[u8]`.
|
||||
bytemuck = ["epaint/bytemuck"]
|
||||
|
||||
## [`cint`](https://docs.rs/cint) enables interopability with other color libraries.
|
||||
cint = ["epaint/cint"]
|
||||
|
||||
## Enable the [`hex_color`] macro.
|
||||
color-hex = ["epaint/color-hex"]
|
||||
|
||||
## This will automatically detect deadlocks due to double-locking on the same thread.
|
||||
## If your app freezes, you may want to enable this!
|
||||
## Only affects [`epaint::mutex::RwLock`] (which egui uses a lot).
|
||||
deadlock_detection = ["epaint/deadlock_detection"]
|
||||
|
||||
## If set, egui will use `include_bytes!` to bundle some fonts.
|
||||
## If you plan on specifying your own fonts you may disable this feature.
|
||||
default_fonts = ["epaint/default_fonts"]
|
||||
|
||||
## Enable additional checks if debug assertions are enabled (debug builds).
|
||||
extra_debug_asserts = ["epaint/extra_debug_asserts"]
|
||||
## Always enable additional checks.
|
||||
extra_asserts = ["epaint/extra_asserts"]
|
||||
|
||||
## [`mint`](https://docs.rs/mint) enables interopability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra).
|
||||
mint = ["epaint/mint"]
|
||||
|
||||
## Enable persistence of memory (window positions etc).
|
||||
persistence = ["serde", "epaint/serde", "ron"]
|
||||
|
||||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||
serde = ["dep:serde", "epaint/serde", "accesskit?/serde"]
|
||||
|
||||
## Change Vertex layout to be compatible with unity
|
||||
unity = ["epaint/unity"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
epaint = { version = "0.21.0", path = "../epaint", default-features = false }
|
||||
|
||||
ahash = { version = "0.8.1", default-features = false, features = [
|
||||
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
|
||||
"std",
|
||||
] }
|
||||
nohash-hasher = "0.2"
|
||||
|
||||
#! ### Optional dependencies
|
||||
## Exposes detailed accessibility implementation required by platform
|
||||
## accessibility APIs. Also requires support in the egui integration.
|
||||
accesskit = { version = "0.9.0", optional = true }
|
||||
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
ron = { version = "0.8", optional = true }
|
||||
serde = { version = "1", optional = true, features = ["derive", "rc"] }
|
||||
|
||||
# egui doesn't log much, but when it does, it uses [`tracing`](https://docs.rs/tracing).
|
||||
tracing = { version = "0.1", optional = true, default-features = false, features = [
|
||||
"std",
|
||||
] }
|
|
@ -1,7 +0,0 @@
|
|||
There are no stand-alone egui examples, because egui is not stand-alone!
|
||||
|
||||
See the top-level [examples](https://github.com/emilk/egui/tree/master/examples/) folder instead.
|
||||
|
||||
There are also plenty of examples in [the online demo](https://www.egui.rs/#demo). You can find the source code for it at <https://github.com/emilk/egui/tree/master/crates/egui_demo_lib>.
|
||||
|
||||
To learn how to set up `eframe` for web and native, go to <https://github.com/emilk/eframe_template/> and follow the instructions there!
|
|
@ -1,681 +0,0 @@
|
|||
use std::hash::Hash;
|
||||
|
||||
use crate::*;
|
||||
use epaint::Shape;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub(crate) struct InnerState {
|
||||
open: bool,
|
||||
|
||||
/// Height of the region when open. Used for animations
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
open_height: Option<f32>,
|
||||
}
|
||||
|
||||
/// This is a a building block for building collapsing regions.
|
||||
///
|
||||
/// It is used by [`CollapsingHeader`] and [`Window`], but can also be used on its own.
|
||||
///
|
||||
/// See [`CollapsingState::show_header`] for how to show a collapsing header with a custom header.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CollapsingState {
|
||||
id: Id,
|
||||
state: InnerState,
|
||||
}
|
||||
|
||||
impl CollapsingState {
|
||||
pub fn load(ctx: &Context, id: Id) -> Option<Self> {
|
||||
ctx.data_mut(|d| {
|
||||
d.get_persisted::<InnerState>(id)
|
||||
.map(|state| Self { id, state })
|
||||
})
|
||||
}
|
||||
|
||||
pub fn store(&self, ctx: &Context) {
|
||||
ctx.data_mut(|d| d.insert_persisted(self.id, self.state));
|
||||
}
|
||||
|
||||
pub fn id(&self) -> Id {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn load_with_default_open(ctx: &Context, id: Id, default_open: bool) -> Self {
|
||||
Self::load(ctx, id).unwrap_or(CollapsingState {
|
||||
id,
|
||||
state: InnerState {
|
||||
open: default_open,
|
||||
open_height: None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_open(&self) -> bool {
|
||||
self.state.open
|
||||
}
|
||||
|
||||
pub fn set_open(&mut self, open: bool) {
|
||||
self.state.open = open;
|
||||
}
|
||||
|
||||
pub fn toggle(&mut self, ui: &Ui) {
|
||||
self.state.open = !self.state.open;
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
|
||||
/// 0 for closed, 1 for open, with tweening
|
||||
pub fn openness(&self, ctx: &Context) -> f32 {
|
||||
if ctx.memory(|mem| mem.everything_is_visible()) {
|
||||
1.0
|
||||
} else {
|
||||
ctx.animate_bool(self.id, self.state.open)
|
||||
}
|
||||
}
|
||||
|
||||
/// Will toggle when clicked, etc.
|
||||
pub(crate) fn show_default_button_with_size(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
button_size: Vec2,
|
||||
) -> Response {
|
||||
let (_id, rect) = ui.allocate_space(button_size);
|
||||
let response = ui.interact(rect, self.id, Sense::click());
|
||||
if response.clicked() {
|
||||
self.toggle(ui);
|
||||
}
|
||||
let openness = self.openness(ui.ctx());
|
||||
paint_default_icon(ui, openness, &response);
|
||||
response
|
||||
}
|
||||
|
||||
/// Will toggle when clicked, etc.
|
||||
fn show_default_button_indented(&mut self, ui: &mut Ui) -> Response {
|
||||
self.show_button_indented(ui, paint_default_icon)
|
||||
}
|
||||
|
||||
/// Will toggle when clicked, etc.
|
||||
fn show_button_indented(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
icon_fn: impl FnOnce(&mut Ui, f32, &Response) + 'static,
|
||||
) -> Response {
|
||||
let size = vec2(ui.spacing().indent, ui.spacing().icon_width);
|
||||
let (_id, rect) = ui.allocate_space(size);
|
||||
let response = ui.interact(rect, self.id, Sense::click());
|
||||
if response.clicked() {
|
||||
self.toggle(ui);
|
||||
}
|
||||
|
||||
let (mut icon_rect, _) = ui.spacing().icon_rectangles(response.rect);
|
||||
icon_rect.set_center(pos2(
|
||||
response.rect.left() + ui.spacing().indent / 2.0,
|
||||
response.rect.center().y,
|
||||
));
|
||||
let openness = self.openness(ui.ctx());
|
||||
let small_icon_response = response.clone().with_new_rect(icon_rect);
|
||||
icon_fn(ui, openness, &small_icon_response);
|
||||
response
|
||||
}
|
||||
|
||||
/// Shows header and body (if expanded).
|
||||
///
|
||||
/// The header will start with the default button in a horizontal layout, followed by whatever you add.
|
||||
///
|
||||
/// Will also store the state.
|
||||
///
|
||||
/// Returns the response of the collapsing button, the custom header, and the custom body.
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// let id = ui.make_persistent_id("my_collapsing_header");
|
||||
/// egui::collapsing_header::CollapsingState::load_with_default_open(ui.ctx(), id, false)
|
||||
/// .show_header(ui, |ui| {
|
||||
/// ui.label("Header"); // you can put checkboxes or whatever here
|
||||
/// })
|
||||
/// .body(|ui| ui.label("Body"));
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn show_header<HeaderRet>(
|
||||
mut self,
|
||||
ui: &mut Ui,
|
||||
add_header: impl FnOnce(&mut Ui) -> HeaderRet,
|
||||
) -> HeaderResponse<'_, HeaderRet> {
|
||||
let header_response = ui.horizontal(|ui| {
|
||||
let prev_item_spacing = ui.spacing_mut().item_spacing;
|
||||
ui.spacing_mut().item_spacing.x = 0.0; // the toggler button uses the full indent width
|
||||
let collapser = self.show_default_button_indented(ui);
|
||||
ui.spacing_mut().item_spacing = prev_item_spacing;
|
||||
(collapser, add_header(ui))
|
||||
});
|
||||
HeaderResponse {
|
||||
state: self,
|
||||
ui,
|
||||
toggle_button_response: header_response.inner.0,
|
||||
header_response: InnerResponse {
|
||||
response: header_response.response,
|
||||
inner: header_response.inner.1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Show body if we are open, with a nice animation between closed and open.
|
||||
/// Indent the body to show it belongs to the header.
|
||||
///
|
||||
/// Will also store the state.
|
||||
pub fn show_body_indented<R>(
|
||||
&mut self,
|
||||
header_response: &Response,
|
||||
ui: &mut Ui,
|
||||
add_body: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<InnerResponse<R>> {
|
||||
let id = self.id;
|
||||
self.show_body_unindented(ui, |ui| {
|
||||
ui.indent(id, |ui| {
|
||||
// make as wide as the header:
|
||||
ui.expand_to_include_x(header_response.rect.right());
|
||||
add_body(ui)
|
||||
})
|
||||
.inner
|
||||
})
|
||||
}
|
||||
|
||||
/// Show body if we are open, with a nice animation between closed and open.
|
||||
/// Will also store the state.
|
||||
pub fn show_body_unindented<R>(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
add_body: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<InnerResponse<R>> {
|
||||
let openness = self.openness(ui.ctx());
|
||||
if openness <= 0.0 {
|
||||
self.store(ui.ctx()); // we store any earlier toggling as promised in the docstring
|
||||
None
|
||||
} else if openness < 1.0 {
|
||||
Some(ui.scope(|child_ui| {
|
||||
let max_height = if self.state.open && self.state.open_height.is_none() {
|
||||
// First frame of expansion.
|
||||
// We don't know full height yet, but we will next frame.
|
||||
// Just use a placeholder value that shows some movement:
|
||||
10.0
|
||||
} else {
|
||||
let full_height = self.state.open_height.unwrap_or_default();
|
||||
remap_clamp(openness, 0.0..=1.0, 0.0..=full_height)
|
||||
};
|
||||
|
||||
let mut clip_rect = child_ui.clip_rect();
|
||||
clip_rect.max.y = clip_rect.max.y.min(child_ui.max_rect().top() + max_height);
|
||||
child_ui.set_clip_rect(clip_rect);
|
||||
|
||||
let ret = add_body(child_ui);
|
||||
|
||||
let mut min_rect = child_ui.min_rect();
|
||||
self.state.open_height = Some(min_rect.height());
|
||||
self.store(child_ui.ctx()); // remember the height
|
||||
|
||||
// Pretend children took up at most `max_height` space:
|
||||
min_rect.max.y = min_rect.max.y.at_most(min_rect.top() + max_height);
|
||||
child_ui.force_set_min_rect(min_rect);
|
||||
ret
|
||||
}))
|
||||
} else {
|
||||
let ret_response = ui.scope(add_body);
|
||||
let full_size = ret_response.response.rect.size();
|
||||
self.state.open_height = Some(full_size.y);
|
||||
self.store(ui.ctx()); // remember the height
|
||||
Some(ret_response)
|
||||
}
|
||||
}
|
||||
|
||||
/// Paint this [CollapsingState](CollapsingState)'s toggle button. Takes an [IconPainter](IconPainter) as the icon.
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) {
|
||||
/// let stroke = ui.style().interact(&response).fg_stroke;
|
||||
/// let radius = egui::lerp(2.0..=3.0, openness);
|
||||
/// ui.painter().circle_filled(response.rect.center(), radius, stroke.color);
|
||||
/// }
|
||||
///
|
||||
/// let mut state = egui::collapsing_header::CollapsingState::load_with_default_open(
|
||||
/// ui.ctx(),
|
||||
/// ui.make_persistent_id("my_collapsing_state"),
|
||||
/// false,
|
||||
/// );
|
||||
///
|
||||
/// let header_res = ui.horizontal(|ui| {
|
||||
/// ui.label("Header");
|
||||
/// state.show_toggle_button(ui, circle_icon);
|
||||
/// });
|
||||
///
|
||||
/// state.show_body_indented(&header_res.response, ui, |ui| ui.label("Body"));
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn show_toggle_button(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
icon_fn: impl FnOnce(&mut Ui, f32, &Response) + 'static,
|
||||
) -> Response {
|
||||
self.show_button_indented(ui, icon_fn)
|
||||
}
|
||||
}
|
||||
|
||||
/// From [`CollapsingState::show_header`].
|
||||
#[must_use = "Remember to show the body"]
|
||||
pub struct HeaderResponse<'ui, HeaderRet> {
|
||||
state: CollapsingState,
|
||||
ui: &'ui mut Ui,
|
||||
toggle_button_response: Response,
|
||||
header_response: InnerResponse<HeaderRet>,
|
||||
}
|
||||
|
||||
impl<'ui, HeaderRet> HeaderResponse<'ui, HeaderRet> {
|
||||
/// Returns the response of the collapsing button, the custom header, and the custom body.
|
||||
pub fn body<BodyRet>(
|
||||
mut self,
|
||||
add_body: impl FnOnce(&mut Ui) -> BodyRet,
|
||||
) -> (
|
||||
Response,
|
||||
InnerResponse<HeaderRet>,
|
||||
Option<InnerResponse<BodyRet>>,
|
||||
) {
|
||||
let body_response =
|
||||
self.state
|
||||
.show_body_indented(&self.header_response.response, self.ui, add_body);
|
||||
(
|
||||
self.toggle_button_response,
|
||||
self.header_response,
|
||||
body_response,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the response of the collapsing button, the custom header, and the custom body, without indentation.
|
||||
pub fn body_unindented<BodyRet>(
|
||||
mut self,
|
||||
add_body: impl FnOnce(&mut Ui) -> BodyRet,
|
||||
) -> (
|
||||
Response,
|
||||
InnerResponse<HeaderRet>,
|
||||
Option<InnerResponse<BodyRet>>,
|
||||
) {
|
||||
let body_response = self.state.show_body_unindented(self.ui, add_body);
|
||||
(
|
||||
self.toggle_button_response,
|
||||
self.header_response,
|
||||
body_response,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Paint the arrow icon that indicated if the region is open or not
|
||||
pub fn paint_default_icon(ui: &mut Ui, openness: f32, response: &Response) {
|
||||
let visuals = ui.style().interact(response);
|
||||
|
||||
let rect = response.rect;
|
||||
|
||||
// Draw a pointy triangle arrow:
|
||||
let rect = Rect::from_center_size(rect.center(), vec2(rect.width(), rect.height()) * 0.75);
|
||||
let rect = rect.expand(visuals.expansion);
|
||||
let mut points = vec![rect.left_top(), rect.right_top(), rect.center_bottom()];
|
||||
use std::f32::consts::TAU;
|
||||
let rotation = emath::Rot2::from_angle(remap(openness, 0.0..=1.0, -TAU / 4.0..=0.0));
|
||||
for p in &mut points {
|
||||
*p = rect.center() + rotation * (*p - rect.center());
|
||||
}
|
||||
|
||||
ui.painter().add(Shape::convex_polygon(
|
||||
points,
|
||||
visuals.fg_stroke.color,
|
||||
Stroke::NONE,
|
||||
));
|
||||
}
|
||||
|
||||
/// A function that paints an icon indicating if the region is open or not
|
||||
pub type IconPainter = Box<dyn FnOnce(&mut Ui, f32, &Response)>;
|
||||
|
||||
/// A header which can be collapsed/expanded, revealing a contained [`Ui`] region.
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// egui::CollapsingHeader::new("Heading")
|
||||
/// .show(ui, |ui| {
|
||||
/// ui.label("Body");
|
||||
/// });
|
||||
///
|
||||
/// // Short version:
|
||||
/// ui.collapsing("Heading", |ui| { ui.label("Body"); });
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// If you want to customize the header contents, see [`CollapsingState::show_header`].
|
||||
#[must_use = "You should call .show()"]
|
||||
pub struct CollapsingHeader {
|
||||
text: WidgetText,
|
||||
default_open: bool,
|
||||
open: Option<bool>,
|
||||
id_source: Id,
|
||||
enabled: bool,
|
||||
selectable: bool,
|
||||
selected: bool,
|
||||
show_background: bool,
|
||||
icon: Option<IconPainter>,
|
||||
}
|
||||
|
||||
impl CollapsingHeader {
|
||||
/// The [`CollapsingHeader`] starts out collapsed unless you call `default_open`.
|
||||
///
|
||||
/// The label is used as an [`Id`] source.
|
||||
/// If the label is unique and static this is fine,
|
||||
/// but if it changes or there are several [`CollapsingHeader`] with the same title
|
||||
/// you need to provide a unique id source with [`Self::id_source`].
|
||||
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||
let text = text.into();
|
||||
let id_source = Id::new(text.text());
|
||||
Self {
|
||||
text,
|
||||
default_open: false,
|
||||
open: None,
|
||||
id_source,
|
||||
enabled: true,
|
||||
selectable: false,
|
||||
selected: false,
|
||||
show_background: false,
|
||||
icon: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// By default, the [`CollapsingHeader`] is collapsed.
|
||||
/// Call `.default_open(true)` to change this.
|
||||
pub fn default_open(mut self, open: bool) -> Self {
|
||||
self.default_open = open;
|
||||
self
|
||||
}
|
||||
|
||||
/// Calling `.open(Some(true))` will make the collapsing header open this frame (or stay open).
|
||||
///
|
||||
/// Calling `.open(Some(false))` will make the collapsing header close this frame (or stay closed).
|
||||
///
|
||||
/// Calling `.open(None)` has no effect (default).
|
||||
pub fn open(mut self, open: Option<bool>) -> Self {
|
||||
self.open = open;
|
||||
self
|
||||
}
|
||||
|
||||
/// Explicitly set the source of the [`Id`] of this widget, instead of using title label.
|
||||
/// This is useful if the title label is dynamic or not unique.
|
||||
pub fn id_source(mut self, id_source: impl Hash) -> Self {
|
||||
self.id_source = Id::new(id_source);
|
||||
self
|
||||
}
|
||||
|
||||
/// If you set this to `false`, the [`CollapsingHeader`] will be grayed out and un-clickable.
|
||||
///
|
||||
/// This is a convenience for [`Ui::set_enabled`].
|
||||
pub fn enabled(mut self, enabled: bool) -> Self {
|
||||
self.enabled = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Can the [`CollapsingHeader`] be selected by clicking it? Default: `false`.
|
||||
#[deprecated = "Use the more powerful egui::collapsing_header::CollapsingState::show_header"] // Deprecated in 2022-04-28, before egui 0.18
|
||||
pub fn selectable(mut self, selectable: bool) -> Self {
|
||||
self.selectable = selectable;
|
||||
self
|
||||
}
|
||||
|
||||
/// If you set this to 'true', the [`CollapsingHeader`] will be shown as selected.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// let mut selected = false;
|
||||
/// let response = egui::CollapsingHeader::new("Select and open me")
|
||||
/// .selectable(true)
|
||||
/// .selected(selected)
|
||||
/// .show(ui, |ui| ui.label("Body"));
|
||||
/// if response.header_response.clicked() {
|
||||
/// selected = true;
|
||||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
#[deprecated = "Use the more powerful egui::collapsing_header::CollapsingState::show_header"] // Deprecated in 2022-04-28, before egui 0.18
|
||||
pub fn selected(mut self, selected: bool) -> Self {
|
||||
self.selected = selected;
|
||||
self
|
||||
}
|
||||
|
||||
/// Should the [`CollapsingHeader`] show a background behind it? Default: `false`.
|
||||
///
|
||||
/// To show it behind all [`CollapsingHeader`] you can just use:
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// ui.visuals_mut().collapsing_header_frame = true;
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn show_background(mut self, show_background: bool) -> Self {
|
||||
self.show_background = show_background;
|
||||
self
|
||||
}
|
||||
|
||||
/// Use the provided function to render a different [`CollapsingHeader`] icon.
|
||||
/// Defaults to a triangle that animates as the [`CollapsingHeader`] opens and closes.
|
||||
///
|
||||
/// For example:
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) {
|
||||
/// let stroke = ui.style().interact(&response).fg_stroke;
|
||||
/// let radius = egui::lerp(2.0..=3.0, openness);
|
||||
/// ui.painter().circle_filled(response.rect.center(), radius, stroke.color);
|
||||
/// }
|
||||
///
|
||||
/// egui::CollapsingHeader::new("Circles")
|
||||
/// .icon(circle_icon)
|
||||
/// .show(ui, |ui| { ui.label("Hi!"); });
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn icon(mut self, icon_fn: impl FnOnce(&mut Ui, f32, &Response) + 'static) -> Self {
|
||||
self.icon = Some(Box::new(icon_fn));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct Prepared {
|
||||
header_response: Response,
|
||||
state: CollapsingState,
|
||||
openness: f32,
|
||||
}
|
||||
|
||||
impl CollapsingHeader {
|
||||
fn begin(self, ui: &mut Ui) -> Prepared {
|
||||
assert!(
|
||||
ui.layout().main_dir().is_vertical(),
|
||||
"Horizontal collapsing is unimplemented"
|
||||
);
|
||||
let Self {
|
||||
icon,
|
||||
text,
|
||||
default_open,
|
||||
open,
|
||||
id_source,
|
||||
enabled: _,
|
||||
selectable,
|
||||
selected,
|
||||
show_background,
|
||||
} = self;
|
||||
|
||||
// TODO(emilk): horizontal layout, with icon and text as labels. Insert background behind using Frame.
|
||||
|
||||
let id = ui.make_persistent_id(id_source);
|
||||
let button_padding = ui.spacing().button_padding;
|
||||
|
||||
let available = ui.available_rect_before_wrap();
|
||||
let text_pos = available.min + vec2(ui.spacing().indent, 0.0);
|
||||
let wrap_width = available.right() - text_pos.x;
|
||||
let wrap = Some(false);
|
||||
let text = text.into_galley(ui, wrap, wrap_width, TextStyle::Button);
|
||||
let text_max_x = text_pos.x + text.size().x;
|
||||
|
||||
let mut desired_width = text_max_x + button_padding.x - available.left();
|
||||
if ui.visuals().collapsing_header_frame {
|
||||
desired_width = desired_width.max(available.width()); // fill full width
|
||||
}
|
||||
|
||||
let mut desired_size = vec2(desired_width, text.size().y + 2.0 * button_padding.y);
|
||||
desired_size = desired_size.at_least(ui.spacing().interact_size);
|
||||
let (_, rect) = ui.allocate_space(desired_size);
|
||||
|
||||
let mut header_response = ui.interact(rect, id, Sense::click());
|
||||
let text_pos = pos2(
|
||||
text_pos.x,
|
||||
header_response.rect.center().y - text.size().y / 2.0,
|
||||
);
|
||||
|
||||
let mut state = CollapsingState::load_with_default_open(ui.ctx(), id, default_open);
|
||||
if let Some(open) = open {
|
||||
if open != state.is_open() {
|
||||
state.toggle(ui);
|
||||
header_response.mark_changed();
|
||||
}
|
||||
} else if header_response.clicked() {
|
||||
state.toggle(ui);
|
||||
header_response.mark_changed();
|
||||
}
|
||||
|
||||
header_response
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text()));
|
||||
|
||||
let openness = state.openness(ui.ctx());
|
||||
|
||||
if ui.is_rect_visible(rect) {
|
||||
let visuals = ui.style().interact_selectable(&header_response, selected);
|
||||
|
||||
if ui.visuals().collapsing_header_frame || show_background {
|
||||
ui.painter().add(epaint::RectShape {
|
||||
rect: header_response.rect.expand(visuals.expansion),
|
||||
rounding: visuals.rounding,
|
||||
fill: visuals.weak_bg_fill,
|
||||
stroke: visuals.bg_stroke,
|
||||
// stroke: Default::default(),
|
||||
});
|
||||
}
|
||||
|
||||
if selected || selectable && (header_response.hovered() || header_response.has_focus())
|
||||
{
|
||||
let rect = rect.expand(visuals.expansion);
|
||||
|
||||
ui.painter()
|
||||
.rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke);
|
||||
}
|
||||
|
||||
{
|
||||
let (mut icon_rect, _) = ui.spacing().icon_rectangles(header_response.rect);
|
||||
icon_rect.set_center(pos2(
|
||||
header_response.rect.left() + ui.spacing().indent / 2.0,
|
||||
header_response.rect.center().y,
|
||||
));
|
||||
let icon_response = header_response.clone().with_new_rect(icon_rect);
|
||||
if let Some(icon) = icon {
|
||||
icon(ui, openness, &icon_response);
|
||||
} else {
|
||||
paint_default_icon(ui, openness, &icon_response);
|
||||
}
|
||||
}
|
||||
|
||||
text.paint_with_visuals(ui.painter(), text_pos, &visuals);
|
||||
}
|
||||
|
||||
Prepared {
|
||||
header_response,
|
||||
state,
|
||||
openness,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show<R>(
|
||||
self,
|
||||
ui: &mut Ui,
|
||||
add_body: impl FnOnce(&mut Ui) -> R,
|
||||
) -> CollapsingResponse<R> {
|
||||
self.show_dyn(ui, Box::new(add_body), true)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show_unindented<R>(
|
||||
self,
|
||||
ui: &mut Ui,
|
||||
add_body: impl FnOnce(&mut Ui) -> R,
|
||||
) -> CollapsingResponse<R> {
|
||||
self.show_dyn(ui, Box::new(add_body), false)
|
||||
}
|
||||
|
||||
fn show_dyn<'c, R>(
|
||||
self,
|
||||
ui: &mut Ui,
|
||||
add_body: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
indented: bool,
|
||||
) -> CollapsingResponse<R> {
|
||||
// Make sure body is bellow header,
|
||||
// and make sure it is one unit (necessary for putting a [`CollapsingHeader`] in a grid).
|
||||
ui.vertical(|ui| {
|
||||
ui.set_enabled(self.enabled);
|
||||
|
||||
let Prepared {
|
||||
header_response,
|
||||
mut state,
|
||||
openness,
|
||||
} = self.begin(ui); // show the header
|
||||
|
||||
let ret_response = if indented {
|
||||
state.show_body_indented(&header_response, ui, add_body)
|
||||
} else {
|
||||
state.show_body_unindented(ui, add_body)
|
||||
};
|
||||
|
||||
if let Some(ret_response) = ret_response {
|
||||
CollapsingResponse {
|
||||
header_response,
|
||||
body_response: Some(ret_response.response),
|
||||
body_returned: Some(ret_response.inner),
|
||||
openness,
|
||||
}
|
||||
} else {
|
||||
CollapsingResponse {
|
||||
header_response,
|
||||
body_response: None,
|
||||
body_returned: None,
|
||||
openness,
|
||||
}
|
||||
}
|
||||
})
|
||||
.inner
|
||||
}
|
||||
}
|
||||
|
||||
/// The response from showing a [`CollapsingHeader`].
|
||||
pub struct CollapsingResponse<R> {
|
||||
/// Response of the actual clickable header.
|
||||
pub header_response: Response,
|
||||
|
||||
/// None iff collapsed.
|
||||
pub body_response: Option<Response>,
|
||||
|
||||
/// None iff collapsed.
|
||||
pub body_returned: Option<R>,
|
||||
|
||||
/// 0.0 if fully closed, 1.0 if fully open, and something in-between while animating.
|
||||
pub openness: f32,
|
||||
}
|
||||
|
||||
impl<R> CollapsingResponse<R> {
|
||||
/// Was the [`CollapsingHeader`] fully closed (and not being animated)?
|
||||
pub fn fully_closed(&self) -> bool {
|
||||
self.openness <= 0.0
|
||||
}
|
||||
|
||||
/// Was the [`CollapsingHeader`] fully open (and not being animated)?
|
||||
pub fn fully_open(&self) -> bool {
|
||||
self.openness >= 1.0
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,117 +0,0 @@
|
|||
//! Helpers for zooming the whole GUI of an app (changing [`Context::pixels_per_point`].
|
||||
//!
|
||||
use crate::*;
|
||||
|
||||
/// The suggested keyboard shortcuts for global gui zooming.
|
||||
pub mod kb_shortcuts {
|
||||
use super::*;
|
||||
|
||||
pub const ZOOM_IN: KeyboardShortcut =
|
||||
KeyboardShortcut::new(Modifiers::COMMAND, Key::PlusEquals);
|
||||
pub const ZOOM_OUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::Minus);
|
||||
pub const ZOOM_RESET: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::Num0);
|
||||
}
|
||||
|
||||
/// Let the user scale the GUI (change `Context::pixels_per_point`) by pressing
|
||||
/// Cmd+Plus, Cmd+Minus or Cmd+0, just like in a browser.
|
||||
///
|
||||
/// When using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe), you want to call this as:
|
||||
/// ```ignore
|
||||
/// // On web, the browser controls the gui zoom.
|
||||
/// if !frame.is_web() {
|
||||
/// egui::gui_zoom::zoom_with_keyboard_shortcuts(
|
||||
/// ctx,
|
||||
/// frame.info().native_pixels_per_point,
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub fn zoom_with_keyboard_shortcuts(ctx: &Context, native_pixels_per_point: Option<f32>) {
|
||||
if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_RESET)) {
|
||||
if let Some(native_pixels_per_point) = native_pixels_per_point {
|
||||
ctx.set_pixels_per_point(native_pixels_per_point);
|
||||
}
|
||||
} else {
|
||||
if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_IN)) {
|
||||
zoom_in(ctx);
|
||||
}
|
||||
if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_OUT)) {
|
||||
zoom_out(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MIN_PIXELS_PER_POINT: f32 = 0.2;
|
||||
const MAX_PIXELS_PER_POINT: f32 = 4.0;
|
||||
|
||||
/// Make everything larger.
|
||||
pub fn zoom_in(ctx: &Context) {
|
||||
let mut pixels_per_point = ctx.pixels_per_point();
|
||||
pixels_per_point += 0.1;
|
||||
pixels_per_point = pixels_per_point.clamp(MIN_PIXELS_PER_POINT, MAX_PIXELS_PER_POINT);
|
||||
pixels_per_point = (pixels_per_point * 10.).round() / 10.;
|
||||
ctx.set_pixels_per_point(pixels_per_point);
|
||||
}
|
||||
|
||||
/// Make everything smaller.
|
||||
pub fn zoom_out(ctx: &Context) {
|
||||
let mut pixels_per_point = ctx.pixels_per_point();
|
||||
pixels_per_point -= 0.1;
|
||||
pixels_per_point = pixels_per_point.clamp(MIN_PIXELS_PER_POINT, MAX_PIXELS_PER_POINT);
|
||||
pixels_per_point = (pixels_per_point * 10.).round() / 10.;
|
||||
ctx.set_pixels_per_point(pixels_per_point);
|
||||
}
|
||||
|
||||
/// Show buttons for zooming the ui.
|
||||
///
|
||||
/// This is meant to be called from within a menu (See [`Ui::menu_button`]).
|
||||
///
|
||||
/// When using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe), you want to call this as:
|
||||
/// ```ignore
|
||||
/// // On web, the browser controls the gui zoom.
|
||||
/// if !frame.is_web() {
|
||||
/// ui.menu_button("View", |ui| {
|
||||
/// egui::gui_zoom::zoom_menu_buttons(
|
||||
/// ui,
|
||||
/// frame.info().native_pixels_per_point,
|
||||
/// );
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
pub fn zoom_menu_buttons(ui: &mut Ui, native_pixels_per_point: Option<f32>) {
|
||||
if ui
|
||||
.add_enabled(
|
||||
ui.ctx().pixels_per_point() < MAX_PIXELS_PER_POINT,
|
||||
Button::new("Zoom In").shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_IN)),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
zoom_in(ui.ctx());
|
||||
ui.close_menu();
|
||||
}
|
||||
|
||||
if ui
|
||||
.add_enabled(
|
||||
ui.ctx().pixels_per_point() > MIN_PIXELS_PER_POINT,
|
||||
Button::new("Zoom Out")
|
||||
.shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_OUT)),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
zoom_out(ui.ctx());
|
||||
ui.close_menu();
|
||||
}
|
||||
|
||||
if let Some(native_pixels_per_point) = native_pixels_per_point {
|
||||
if ui
|
||||
.add_enabled(
|
||||
ui.ctx().pixels_per_point() != native_pixels_per_point,
|
||||
Button::new("Reset Zoom")
|
||||
.shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_RESET)),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
ui.ctx().set_pixels_per_point(native_pixels_per_point);
|
||||
ui.close_menu();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum OperatingSystem {
|
||||
/// Unknown OS - could be wasm
|
||||
Unknown,
|
||||
|
||||
/// Android OS.
|
||||
Android,
|
||||
|
||||
/// Apple iPhone OS.
|
||||
IOS,
|
||||
|
||||
/// Linux or Unix other than Android.
|
||||
Nix,
|
||||
|
||||
/// MacOS.
|
||||
Mac,
|
||||
|
||||
/// Windows.
|
||||
Windows,
|
||||
}
|
||||
|
||||
impl Default for OperatingSystem {
|
||||
fn default() -> Self {
|
||||
Self::from_target_os()
|
||||
}
|
||||
}
|
||||
|
||||
impl OperatingSystem {
|
||||
pub const fn from_target_os() -> Self {
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
Self::Unknown
|
||||
} else if cfg!(target_os = "android") {
|
||||
Self::Android
|
||||
} else if cfg!(target_os = "ios") {
|
||||
Self::IOS
|
||||
} else if cfg!(target_os = "macos") {
|
||||
Self::Mac
|
||||
} else if cfg!(target_os = "windows") {
|
||||
Self::Android
|
||||
} else if cfg!(target_os = "linux")
|
||||
|| cfg!(target_os = "dragonfly")
|
||||
|| cfg!(target_os = "freebsd")
|
||||
|| cfg!(target_os = "netbsd")
|
||||
|| cfg!(target_os = "openbsd")
|
||||
{
|
||||
Self::Nix
|
||||
} else {
|
||||
Self::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper: try to guess from the user-agent of a browser.
|
||||
pub fn from_user_agent(user_agent: &str) -> Self {
|
||||
if user_agent.contains("Android") {
|
||||
Self::Android
|
||||
} else if user_agent.contains("like Mac") {
|
||||
Self::IOS
|
||||
} else if user_agent.contains("Win") {
|
||||
Self::Windows
|
||||
} else if user_agent.contains("Mac") {
|
||||
Self::Mac
|
||||
} else if user_agent.contains("Linux")
|
||||
|| user_agent.contains("X11")
|
||||
|| user_agent.contains("Unix")
|
||||
{
|
||||
Self::Nix
|
||||
} else {
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::warn!(
|
||||
"egui: Failed to guess operating system from User-Agent {:?}. Please file an issue at https://github.com/emilk/egui/issues",
|
||||
user_agent);
|
||||
|
||||
Self::Unknown
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,662 +0,0 @@
|
|||
#![allow(clippy::needless_pass_by_value)] // False positives with `impl ToString`
|
||||
|
||||
use std::{cmp::Ordering, ops::RangeInclusive};
|
||||
|
||||
use crate::*;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Same state for all [`DragValue`]s.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct MonoState {
|
||||
last_dragged_id: Option<Id>,
|
||||
last_dragged_value: Option<f64>,
|
||||
/// For temporary edit of a [`DragValue`] value.
|
||||
/// Couples with the current focus id.
|
||||
edit_string: Option<String>,
|
||||
}
|
||||
|
||||
impl MonoState {
|
||||
pub(crate) fn end_frame(&mut self, input: &InputState) {
|
||||
if input.pointer.any_pressed() || input.pointer.any_released() {
|
||||
self.last_dragged_id = None;
|
||||
self.last_dragged_value = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type NumFormatter<'a> = Box<dyn 'a + Fn(f64, RangeInclusive<usize>) -> String>;
|
||||
type NumParser<'a> = Box<dyn 'a + Fn(&str) -> Option<f64>>;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Combined into one function (rather than two) to make it easier
|
||||
/// for the borrow checker.
|
||||
type GetSetValue<'a> = Box<dyn 'a + FnMut(Option<f64>) -> f64>;
|
||||
|
||||
fn get(get_set_value: &mut GetSetValue<'_>) -> f64 {
|
||||
(get_set_value)(None)
|
||||
}
|
||||
|
||||
fn set(get_set_value: &mut GetSetValue<'_>, value: f64) {
|
||||
(get_set_value)(Some(value));
|
||||
}
|
||||
|
||||
/// A numeric value that you can change by dragging the number. More compact than a [`Slider`].
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let mut my_f32: f32 = 0.0;
|
||||
/// ui.add(egui::DragValue::new(&mut my_f32).speed(0.1));
|
||||
/// # });
|
||||
/// ```
|
||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||
pub struct DragValue<'a> {
|
||||
get_set_value: GetSetValue<'a>,
|
||||
speed: f64,
|
||||
prefix: String,
|
||||
suffix: String,
|
||||
clamp_range: RangeInclusive<f64>,
|
||||
min_decimals: usize,
|
||||
max_decimals: Option<usize>,
|
||||
custom_formatter: Option<NumFormatter<'a>>,
|
||||
custom_parser: Option<NumParser<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> DragValue<'a> {
|
||||
pub fn new<Num: emath::Numeric>(value: &'a mut Num) -> Self {
|
||||
let slf = Self::from_get_set(move |v: Option<f64>| {
|
||||
if let Some(v) = v {
|
||||
*value = Num::from_f64(v);
|
||||
}
|
||||
value.to_f64()
|
||||
});
|
||||
|
||||
if Num::INTEGRAL {
|
||||
slf.max_decimals(0)
|
||||
.clamp_range(Num::MIN..=Num::MAX)
|
||||
.speed(0.25)
|
||||
} else {
|
||||
slf
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_get_set(get_set_value: impl 'a + FnMut(Option<f64>) -> f64) -> Self {
|
||||
Self {
|
||||
get_set_value: Box::new(get_set_value),
|
||||
speed: 1.0,
|
||||
prefix: Default::default(),
|
||||
suffix: Default::default(),
|
||||
clamp_range: f64::NEG_INFINITY..=f64::INFINITY,
|
||||
min_decimals: 0,
|
||||
max_decimals: None,
|
||||
custom_formatter: None,
|
||||
custom_parser: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// How much the value changes when dragged one point (logical pixel).
|
||||
pub fn speed(mut self, speed: impl Into<f64>) -> Self {
|
||||
self.speed = speed.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Clamp incoming and outgoing values to this range.
|
||||
pub fn clamp_range<Num: emath::Numeric>(mut self, clamp_range: RangeInclusive<Num>) -> Self {
|
||||
self.clamp_range = clamp_range.start().to_f64()..=clamp_range.end().to_f64();
|
||||
self
|
||||
}
|
||||
|
||||
/// Show a prefix before the number, e.g. "x: "
|
||||
pub fn prefix(mut self, prefix: impl ToString) -> Self {
|
||||
self.prefix = prefix.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a suffix to the number, this can be e.g. a unit ("°" or " m")
|
||||
pub fn suffix(mut self, suffix: impl ToString) -> Self {
|
||||
self.suffix = suffix.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
// TODO(emilk): we should also have a "min precision".
|
||||
/// Set a minimum number of decimals to display.
|
||||
/// Normally you don't need to pick a precision, as the slider will intelligently pick a precision for you.
|
||||
/// Regardless of precision the slider will use "smart aim" to help the user select nice, round values.
|
||||
pub fn min_decimals(mut self, min_decimals: usize) -> Self {
|
||||
self.min_decimals = min_decimals;
|
||||
self
|
||||
}
|
||||
|
||||
// TODO(emilk): we should also have a "max precision".
|
||||
/// Set a maximum number of decimals to display.
|
||||
/// Values will also be rounded to this number of decimals.
|
||||
/// Normally you don't need to pick a precision, as the slider will intelligently pick a precision for you.
|
||||
/// Regardless of precision the slider will use "smart aim" to help the user select nice, round values.
|
||||
pub fn max_decimals(mut self, max_decimals: usize) -> Self {
|
||||
self.max_decimals = Some(max_decimals);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_decimals_opt(mut self, max_decimals: Option<usize>) -> Self {
|
||||
self.max_decimals = max_decimals;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set an exact number of decimals to display.
|
||||
/// Values will also be rounded to this number of decimals.
|
||||
/// Normally you don't need to pick a precision, as the slider will intelligently pick a precision for you.
|
||||
/// Regardless of precision the slider will use "smart aim" to help the user select nice, round values.
|
||||
pub fn fixed_decimals(mut self, num_decimals: usize) -> Self {
|
||||
self.min_decimals = num_decimals;
|
||||
self.max_decimals = Some(num_decimals);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set custom formatter defining how numbers are converted into text.
|
||||
///
|
||||
/// A custom formatter takes a `f64` for the numeric value and a `RangeInclusive<usize>` representing
|
||||
/// the decimal range i.e. minimum and maximum number of decimal places shown.
|
||||
///
|
||||
/// See also: [`DragValue::custom_parser`]
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let mut my_i32: i32 = 0;
|
||||
/// ui.add(egui::DragValue::new(&mut my_i32)
|
||||
/// .clamp_range(0..=((60 * 60 * 24) - 1))
|
||||
/// .custom_formatter(|n, _| {
|
||||
/// let n = n as i32;
|
||||
/// let hours = n / (60 * 60);
|
||||
/// let mins = (n / 60) % 60;
|
||||
/// let secs = n % 60;
|
||||
/// format!("{hours:02}:{mins:02}:{secs:02}")
|
||||
/// })
|
||||
/// .custom_parser(|s| {
|
||||
/// let parts: Vec<&str> = s.split(':').collect();
|
||||
/// if parts.len() == 3 {
|
||||
/// parts[0].parse::<i32>().and_then(|h| {
|
||||
/// parts[1].parse::<i32>().and_then(|m| {
|
||||
/// parts[2].parse::<i32>().map(|s| {
|
||||
/// ((h * 60 * 60) + (m * 60) + s) as f64
|
||||
/// })
|
||||
/// })
|
||||
/// })
|
||||
/// .ok()
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// }));
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn custom_formatter(
|
||||
mut self,
|
||||
formatter: impl 'a + Fn(f64, RangeInclusive<usize>) -> String,
|
||||
) -> Self {
|
||||
self.custom_formatter = Some(Box::new(formatter));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set custom parser defining how the text input is parsed into a number.
|
||||
///
|
||||
/// A custom parser takes an `&str` to parse into a number and returns a `f64` if it was successfully parsed
|
||||
/// or `None` otherwise.
|
||||
///
|
||||
/// See also: [`DragValue::custom_formatter`]
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let mut my_i32: i32 = 0;
|
||||
/// ui.add(egui::DragValue::new(&mut my_i32)
|
||||
/// .clamp_range(0..=((60 * 60 * 24) - 1))
|
||||
/// .custom_formatter(|n, _| {
|
||||
/// let n = n as i32;
|
||||
/// let hours = n / (60 * 60);
|
||||
/// let mins = (n / 60) % 60;
|
||||
/// let secs = n % 60;
|
||||
/// format!("{hours:02}:{mins:02}:{secs:02}")
|
||||
/// })
|
||||
/// .custom_parser(|s| {
|
||||
/// let parts: Vec<&str> = s.split(':').collect();
|
||||
/// if parts.len() == 3 {
|
||||
/// parts[0].parse::<i32>().and_then(|h| {
|
||||
/// parts[1].parse::<i32>().and_then(|m| {
|
||||
/// parts[2].parse::<i32>().map(|s| {
|
||||
/// ((h * 60 * 60) + (m * 60) + s) as f64
|
||||
/// })
|
||||
/// })
|
||||
/// })
|
||||
/// .ok()
|
||||
/// } else {
|
||||
/// None
|
||||
/// }
|
||||
/// }));
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn custom_parser(mut self, parser: impl 'a + Fn(&str) -> Option<f64>) -> Self {
|
||||
self.custom_parser = Some(Box::new(parser));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set `custom_formatter` and `custom_parser` to display and parse numbers as binary integers. Floating point
|
||||
/// numbers are *not* supported.
|
||||
///
|
||||
/// `min_width` specifies the minimum number of displayed digits; if the number is shorter than this, it will be
|
||||
/// prefixed with additional 0s to match `min_width`.
|
||||
///
|
||||
/// If `twos_complement` is true, negative values will be displayed as the 2's complement representation. Otherwise
|
||||
/// they will be prefixed with a '-' sign.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `min_width` is 0.
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let mut my_i32: i32 = 0;
|
||||
/// ui.add(egui::DragValue::new(&mut my_i32).binary(64, false));
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn binary(self, min_width: usize, twos_complement: bool) -> Self {
|
||||
assert!(
|
||||
min_width > 0,
|
||||
"DragValue::binary: `min_width` must be greater than 0"
|
||||
);
|
||||
if twos_complement {
|
||||
self.custom_formatter(move |n, _| format!("{:0>min_width$b}", n as i64))
|
||||
} else {
|
||||
self.custom_formatter(move |n, _| {
|
||||
let sign = if n < 0.0 { "-" } else { "" };
|
||||
format!("{sign}{:0>min_width$b}", n.abs() as i64)
|
||||
})
|
||||
}
|
||||
.custom_parser(|s| i64::from_str_radix(s, 2).map(|n| n as f64).ok())
|
||||
}
|
||||
|
||||
/// Set `custom_formatter` and `custom_parser` to display and parse numbers as octal integers. Floating point
|
||||
/// numbers are *not* supported.
|
||||
///
|
||||
/// `min_width` specifies the minimum number of displayed digits; if the number is shorter than this, it will be
|
||||
/// prefixed with additional 0s to match `min_width`.
|
||||
///
|
||||
/// If `twos_complement` is true, negative values will be displayed as the 2's complement representation. Otherwise
|
||||
/// they will be prefixed with a '-' sign.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `min_width` is 0.
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let mut my_i32: i32 = 0;
|
||||
/// ui.add(egui::DragValue::new(&mut my_i32).octal(22, false));
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn octal(self, min_width: usize, twos_complement: bool) -> Self {
|
||||
assert!(
|
||||
min_width > 0,
|
||||
"DragValue::octal: `min_width` must be greater than 0"
|
||||
);
|
||||
if twos_complement {
|
||||
self.custom_formatter(move |n, _| format!("{:0>min_width$o}", n as i64))
|
||||
} else {
|
||||
self.custom_formatter(move |n, _| {
|
||||
let sign = if n < 0.0 { "-" } else { "" };
|
||||
format!("{sign}{:0>min_width$o}", n.abs() as i64)
|
||||
})
|
||||
}
|
||||
.custom_parser(|s| i64::from_str_radix(s, 8).map(|n| n as f64).ok())
|
||||
}
|
||||
|
||||
/// Set `custom_formatter` and `custom_parser` to display and parse numbers as hexadecimal integers. Floating point
|
||||
/// numbers are *not* supported.
|
||||
///
|
||||
/// `min_width` specifies the minimum number of displayed digits; if the number is shorter than this, it will be
|
||||
/// prefixed with additional 0s to match `min_width`.
|
||||
///
|
||||
/// If `twos_complement` is true, negative values will be displayed as the 2's complement representation. Otherwise
|
||||
/// they will be prefixed with a '-' sign.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `min_width` is 0.
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let mut my_i32: i32 = 0;
|
||||
/// ui.add(egui::DragValue::new(&mut my_i32).hexadecimal(16, false, true));
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn hexadecimal(self, min_width: usize, twos_complement: bool, upper: bool) -> Self {
|
||||
assert!(
|
||||
min_width > 0,
|
||||
"DragValue::hexadecimal: `min_width` must be greater than 0"
|
||||
);
|
||||
match (twos_complement, upper) {
|
||||
(true, true) => {
|
||||
self.custom_formatter(move |n, _| format!("{:0>min_width$X}", n as i64))
|
||||
}
|
||||
(true, false) => {
|
||||
self.custom_formatter(move |n, _| format!("{:0>min_width$x}", n as i64))
|
||||
}
|
||||
(false, true) => self.custom_formatter(move |n, _| {
|
||||
let sign = if n < 0.0 { "-" } else { "" };
|
||||
format!("{sign}{:0>min_width$X}", n.abs() as i64)
|
||||
}),
|
||||
(false, false) => self.custom_formatter(move |n, _| {
|
||||
let sign = if n < 0.0 { "-" } else { "" };
|
||||
format!("{sign}{:0>min_width$x}", n.abs() as i64)
|
||||
}),
|
||||
}
|
||||
.custom_parser(|s| i64::from_str_radix(s, 16).map(|n| n as f64).ok())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for DragValue<'a> {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let Self {
|
||||
mut get_set_value,
|
||||
speed,
|
||||
clamp_range,
|
||||
prefix,
|
||||
suffix,
|
||||
min_decimals,
|
||||
max_decimals,
|
||||
custom_formatter,
|
||||
custom_parser,
|
||||
} = self;
|
||||
|
||||
let shift = ui.input(|i| i.modifiers.shift_only());
|
||||
// The widget has the same ID whether it's in edit or button mode.
|
||||
let id = ui.next_auto_id();
|
||||
let is_slow_speed = shift && ui.memory(|mem| mem.is_being_dragged(id));
|
||||
|
||||
// The following ensures that when a `DragValue` receives focus,
|
||||
// it is immediately rendered in edit mode, rather than being rendered
|
||||
// in button mode for just one frame. This is important for
|
||||
// screen readers.
|
||||
let is_kb_editing = ui.memory_mut(|mem| {
|
||||
mem.interested_in_focus(id);
|
||||
let is_kb_editing = mem.has_focus(id);
|
||||
if mem.gained_focus(id) {
|
||||
mem.drag_value.edit_string = None;
|
||||
}
|
||||
is_kb_editing
|
||||
});
|
||||
|
||||
let old_value = get(&mut get_set_value);
|
||||
let mut value = old_value;
|
||||
let aim_rad = ui.input(|i| i.aim_radius() as f64);
|
||||
|
||||
let auto_decimals = (aim_rad / speed.abs()).log10().ceil().clamp(0.0, 15.0) as usize;
|
||||
let auto_decimals = auto_decimals + is_slow_speed as usize;
|
||||
let max_decimals = max_decimals.unwrap_or(auto_decimals + 2);
|
||||
let auto_decimals = auto_decimals.clamp(min_decimals, max_decimals);
|
||||
|
||||
let change = ui.input_mut(|input| {
|
||||
let mut change = 0.0;
|
||||
|
||||
if is_kb_editing {
|
||||
// This deliberately doesn't listen for left and right arrow keys,
|
||||
// because when editing, these are used to move the caret.
|
||||
// This behavior is consistent with other editable spinner/stepper
|
||||
// implementations, such as Chromium's (for HTML5 number input).
|
||||
// It is also normal for such controls to go directly into edit mode
|
||||
// when they receive keyboard focus, and some screen readers
|
||||
// assume this behavior, so having a separate mode for incrementing
|
||||
// and decrementing, that supports all arrow keys, would be
|
||||
// problematic.
|
||||
change += input.count_and_consume_key(Modifiers::NONE, Key::ArrowUp) as f64
|
||||
- input.count_and_consume_key(Modifiers::NONE, Key::ArrowDown) as f64;
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
{
|
||||
use accesskit::Action;
|
||||
change += input.num_accesskit_action_requests(id, Action::Increment) as f64
|
||||
- input.num_accesskit_action_requests(id, Action::Decrement) as f64;
|
||||
}
|
||||
|
||||
change
|
||||
});
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
{
|
||||
use accesskit::{Action, ActionData};
|
||||
ui.input(|input| {
|
||||
for request in input.accesskit_action_requests(id, Action::SetValue) {
|
||||
if let Some(ActionData::NumericValue(new_value)) = request.data {
|
||||
value = new_value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if change != 0.0 {
|
||||
value += speed * change;
|
||||
value = emath::round_to_decimals(value, auto_decimals);
|
||||
}
|
||||
|
||||
value = clamp_to_range(value, clamp_range.clone());
|
||||
if old_value != value {
|
||||
set(&mut get_set_value, value);
|
||||
ui.memory_mut(|mem| mem.drag_value.edit_string = None);
|
||||
}
|
||||
|
||||
let value_text = match custom_formatter {
|
||||
Some(custom_formatter) => custom_formatter(value, auto_decimals..=max_decimals),
|
||||
None => {
|
||||
if value == 0.0 {
|
||||
"0".to_owned()
|
||||
} else {
|
||||
emath::format_with_decimals_in_range(value, auto_decimals..=max_decimals)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let text_style = ui.style().drag_value_text_style.clone();
|
||||
|
||||
// some clones below are redundant if AccessKit is disabled
|
||||
#[allow(clippy::redundant_clone)]
|
||||
let mut response = if is_kb_editing {
|
||||
let mut value_text = ui
|
||||
.memory_mut(|mem| mem.drag_value.edit_string.take())
|
||||
.unwrap_or_else(|| value_text.clone());
|
||||
let response = ui.add(
|
||||
TextEdit::singleline(&mut value_text)
|
||||
.clip_text(false)
|
||||
.horizontal_align(ui.layout().horizontal_align())
|
||||
.vertical_align(ui.layout().vertical_align())
|
||||
.margin(ui.spacing().button_padding)
|
||||
.min_size(ui.spacing().interact_size)
|
||||
.id(id)
|
||||
.desired_width(ui.spacing().interact_size.x)
|
||||
.font(text_style),
|
||||
);
|
||||
// Only update the value when the user presses enter, or clicks elsewhere. NOT every frame.
|
||||
// See https://github.com/emilk/egui/issues/2687
|
||||
if response.lost_focus() {
|
||||
let parsed_value = match custom_parser {
|
||||
Some(parser) => parser(&value_text),
|
||||
None => value_text.parse().ok(),
|
||||
};
|
||||
if let Some(parsed_value) = parsed_value {
|
||||
let parsed_value = clamp_to_range(parsed_value, clamp_range.clone());
|
||||
set(&mut get_set_value, parsed_value);
|
||||
}
|
||||
}
|
||||
ui.memory_mut(|mem| mem.drag_value.edit_string = Some(value_text));
|
||||
response
|
||||
} else {
|
||||
let button = Button::new(
|
||||
RichText::new(format!("{}{}{}", prefix, value_text.clone(), suffix))
|
||||
.text_style(text_style),
|
||||
)
|
||||
.wrap(false)
|
||||
.sense(Sense::click_and_drag())
|
||||
.min_size(ui.spacing().interact_size); // TODO(emilk): find some more generic solution to `min_size`
|
||||
|
||||
let response = ui.add(button);
|
||||
let mut response = response.on_hover_cursor(CursorIcon::ResizeHorizontal);
|
||||
|
||||
if ui.style().explanation_tooltips {
|
||||
response = response .on_hover_text(format!(
|
||||
"{}{}{}\nDrag to edit or click to enter a value.\nPress 'Shift' while dragging for better control.",
|
||||
prefix,
|
||||
value as f32, // Show full precision value on-hover. TODO(emilk): figure out f64 vs f32
|
||||
suffix
|
||||
));
|
||||
}
|
||||
|
||||
if response.clicked() {
|
||||
ui.memory_mut(|mem| {
|
||||
mem.drag_value.edit_string = None;
|
||||
mem.request_focus(id);
|
||||
});
|
||||
let mut state = TextEdit::load_state(ui.ctx(), id).unwrap_or_default();
|
||||
state.set_ccursor_range(Some(text::CCursorRange::two(
|
||||
epaint::text::cursor::CCursor::default(),
|
||||
epaint::text::cursor::CCursor::new(value_text.chars().count()),
|
||||
)));
|
||||
state.store(ui.ctx(), response.id);
|
||||
} else if response.dragged() {
|
||||
ui.ctx().set_cursor_icon(CursorIcon::ResizeHorizontal);
|
||||
|
||||
let mdelta = response.drag_delta();
|
||||
let delta_points = mdelta.x - mdelta.y; // Increase to the right and up
|
||||
|
||||
let speed = if is_slow_speed { speed / 10.0 } else { speed };
|
||||
|
||||
let delta_value = delta_points as f64 * speed;
|
||||
|
||||
if delta_value != 0.0 {
|
||||
let mut drag_state = ui.memory_mut(|mem| std::mem::take(&mut mem.drag_value));
|
||||
|
||||
// Since we round the value being dragged, we need to store the full precision value in memory:
|
||||
let stored_value = (drag_state.last_dragged_id == Some(response.id))
|
||||
.then_some(drag_state.last_dragged_value)
|
||||
.flatten();
|
||||
let stored_value = stored_value.unwrap_or(value);
|
||||
let stored_value = stored_value + delta_value;
|
||||
|
||||
let aim_delta = aim_rad * speed;
|
||||
let rounded_new_value = emath::smart_aim::best_in_range_f64(
|
||||
stored_value - aim_delta,
|
||||
stored_value + aim_delta,
|
||||
);
|
||||
let rounded_new_value =
|
||||
emath::round_to_decimals(rounded_new_value, auto_decimals);
|
||||
let rounded_new_value = clamp_to_range(rounded_new_value, clamp_range.clone());
|
||||
set(&mut get_set_value, rounded_new_value);
|
||||
|
||||
drag_state.last_dragged_id = Some(response.id);
|
||||
drag_state.last_dragged_value = Some(stored_value);
|
||||
ui.memory_mut(|mem| mem.drag_value = drag_state);
|
||||
}
|
||||
}
|
||||
|
||||
response
|
||||
};
|
||||
|
||||
response.changed = get(&mut get_set_value) != old_value;
|
||||
|
||||
response.widget_info(|| WidgetInfo::drag_value(value));
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
ui.ctx().accesskit_node_builder(response.id, |builder| {
|
||||
use accesskit::Action;
|
||||
// If either end of the range is unbounded, it's better
|
||||
// to leave the corresponding AccessKit field set to None,
|
||||
// to allow for platform-specific default behavior.
|
||||
if clamp_range.start().is_finite() {
|
||||
builder.set_min_numeric_value(*clamp_range.start());
|
||||
}
|
||||
if clamp_range.end().is_finite() {
|
||||
builder.set_max_numeric_value(*clamp_range.end());
|
||||
}
|
||||
builder.set_numeric_value_step(speed);
|
||||
builder.add_action(Action::SetValue);
|
||||
if value < *clamp_range.end() {
|
||||
builder.add_action(Action::Increment);
|
||||
}
|
||||
if value > *clamp_range.start() {
|
||||
builder.add_action(Action::Decrement);
|
||||
}
|
||||
// The name field is set to the current value by the button,
|
||||
// but we don't want it set that way on this widget type.
|
||||
builder.clear_name();
|
||||
// Always expose the value as a string. This makes the widget
|
||||
// more stable to accessibility users as it switches
|
||||
// between edit and button modes. This is particularly important
|
||||
// for VoiceOver on macOS; if the value is not exposed as a string
|
||||
// when the widget is in button mode, then VoiceOver speaks
|
||||
// the value (or a percentage if the widget has a clamp range)
|
||||
// when the widget loses focus, overriding the announcement
|
||||
// of the newly focused widget. This is certainly a VoiceOver bug,
|
||||
// but it's good to make our software work as well as possible
|
||||
// with existing assistive technology. However, if the widget
|
||||
// has a prefix and/or suffix, expose those when in button mode,
|
||||
// just as they're exposed on the screen. This triggers the
|
||||
// VoiceOver bug just described, but exposing all information
|
||||
// is more important, and at least we can avoid the bug
|
||||
// for instances of the widget with no prefix or suffix.
|
||||
//
|
||||
// The value is exposed as a string by the text edit widget
|
||||
// when in edit mode.
|
||||
if !is_kb_editing {
|
||||
let value_text = format!("{}{}{}", prefix, value_text, suffix);
|
||||
builder.set_value(value_text);
|
||||
}
|
||||
});
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
fn clamp_to_range(x: f64, range: RangeInclusive<f64>) -> f64 {
|
||||
let (mut min, mut max) = (*range.start(), *range.end());
|
||||
|
||||
if min.total_cmp(&max) == Ordering::Greater {
|
||||
(min, max) = (max, min);
|
||||
}
|
||||
|
||||
match x.total_cmp(&min) {
|
||||
Ordering::Less | Ordering::Equal => min,
|
||||
Ordering::Greater => match x.total_cmp(&max) {
|
||||
Ordering::Greater | Ordering::Equal => max,
|
||||
Ordering::Less => x,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::clamp_to_range;
|
||||
|
||||
macro_rules! total_assert_eq {
|
||||
($a:expr, $b:expr) => {
|
||||
assert!(
|
||||
matches!($a.total_cmp(&$b), std::cmp::Ordering::Equal),
|
||||
"{} != {}",
|
||||
$a,
|
||||
$b
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_total_cmp_clamp_to_range() {
|
||||
total_assert_eq!(0.0_f64, clamp_to_range(-0.0, 0.0..=f64::MAX));
|
||||
total_assert_eq!(-0.0_f64, clamp_to_range(0.0, -1.0..=-0.0));
|
||||
total_assert_eq!(-1.0_f64, clamp_to_range(-25.0, -1.0..=1.0));
|
||||
total_assert_eq!(5.0_f64, clamp_to_range(5.0, -1.0..=10.0));
|
||||
total_assert_eq!(15.0_f64, clamp_to_range(25.0, -1.0..=15.0));
|
||||
total_assert_eq!(1.0_f64, clamp_to_range(1.0, 1.0..=10.0));
|
||||
total_assert_eq!(10.0_f64, clamp_to_range(10.0, 1.0..=10.0));
|
||||
total_assert_eq!(5.0_f64, clamp_to_range(5.0, 10.0..=1.0));
|
||||
total_assert_eq!(5.0_f64, clamp_to_range(15.0, 5.0..=1.0));
|
||||
total_assert_eq!(1.0_f64, clamp_to_range(-5.0, 5.0..=1.0));
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
[package]
|
||||
name = "egui_demo_app"
|
||||
version = "0.21.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
publish = false
|
||||
default-run = "egui_demo_app"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
|
||||
[features]
|
||||
default = ["glow", "persistence"]
|
||||
|
||||
http = ["ehttp", "image", "poll-promise", "egui_extras/image"]
|
||||
persistence = ["eframe/persistence", "egui/persistence", "serde"]
|
||||
web_screen_reader = ["eframe/web_screen_reader"] # experimental
|
||||
serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"]
|
||||
syntax_highlighting = ["egui_demo_lib/syntax_highlighting"]
|
||||
|
||||
glow = ["eframe/glow"]
|
||||
wgpu = ["eframe/wgpu", "bytemuck"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4", features = ["js-sys", "wasmbind"] }
|
||||
eframe = { version = "0.21.0", path = "../eframe", default-features = false }
|
||||
egui = { version = "0.21.0", path = "../egui", features = [
|
||||
"extra_debug_asserts",
|
||||
] }
|
||||
egui_demo_lib = { version = "0.21.0", path = "../egui_demo_lib", features = [
|
||||
"chrono",
|
||||
] }
|
||||
tracing = "0.1"
|
||||
|
||||
# Optional dependencies:
|
||||
|
||||
bytemuck = { version = "1.7.1", optional = true }
|
||||
egui_extras = { version = "0.21.0", optional = true, path = "../egui_extras" }
|
||||
|
||||
# feature "http":
|
||||
ehttp = { version = "0.2.0", optional = true }
|
||||
image = { version = "0.24", optional = true, default-features = false, features = [
|
||||
"jpeg",
|
||||
"png",
|
||||
] }
|
||||
poll-promise = { version = "0.2", optional = true, default-features = false }
|
||||
|
||||
# feature "persistence":
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
|
||||
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tracing-subscriber = "0.3"
|
||||
|
||||
# web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
console_error_panic_hook = "0.1.6"
|
||||
tracing-wasm = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
|
@ -1,19 +0,0 @@
|
|||
# egui demo app
|
||||
This app demonstrates [`egui`](https://github.com/emilk/egui/) and [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe).
|
||||
|
||||
View the demo app online at <https://egui.rs>.
|
||||
|
||||
Run it locally with `cargo run --release -p egui_demo_app`.
|
||||
|
||||
`egui_demo_app` can be compiled to WASM and viewed in a browser locally with:
|
||||
|
||||
```sh
|
||||
./sh/start_server.sh &
|
||||
./sh/build_demo_web.sh --open
|
||||
```
|
||||
|
||||
`egui_demo_app` uses [`egui_demo_lib`](https://github.com/emilk/egui/tree/master/crates/egui_demo_lib).
|
||||
|
||||
|
||||
## Running with `wgpu` backend
|
||||
`(cd egui_demo_app && cargo r --features wgpu)`
|
|
@ -1,201 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use eframe::egui_glow;
|
||||
use egui::mutex::Mutex;
|
||||
use egui_glow::glow;
|
||||
|
||||
pub struct Custom3d {
|
||||
/// Behind an `Arc<Mutex<…>>` so we can pass it to [`egui::PaintCallback`] and paint later.
|
||||
rotating_triangle: Arc<Mutex<RotatingTriangle>>,
|
||||
angle: f32,
|
||||
}
|
||||
|
||||
impl Custom3d {
|
||||
pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Option<Self> {
|
||||
let gl = cc.gl.as_ref()?;
|
||||
Some(Self {
|
||||
rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(gl)?)),
|
||||
angle: 0.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for Custom3d {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::ScrollArea::both()
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("The triangle is being painted using ");
|
||||
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
|
||||
ui.label(" (OpenGL).");
|
||||
});
|
||||
ui.label("It's not a very impressive demo, but it shows you can embed 3D inside of egui.");
|
||||
|
||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
self.custom_painting(ui);
|
||||
});
|
||||
ui.label("Drag to rotate!");
|
||||
ui.add(egui_demo_lib::egui_github_link_file!());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn on_exit(&mut self, gl: Option<&glow::Context>) {
|
||||
if let Some(gl) = gl {
|
||||
self.rotating_triangle.lock().destroy(gl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Custom3d {
|
||||
fn custom_painting(&mut self, ui: &mut egui::Ui) {
|
||||
let (rect, response) =
|
||||
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
|
||||
|
||||
self.angle += response.drag_delta().x * 0.01;
|
||||
|
||||
// Clone locals so we can move them into the paint callback:
|
||||
let angle = self.angle;
|
||||
let rotating_triangle = self.rotating_triangle.clone();
|
||||
|
||||
let cb = egui_glow::CallbackFn::new(move |_info, painter| {
|
||||
rotating_triangle.lock().paint(painter.gl(), angle);
|
||||
});
|
||||
|
||||
let callback = egui::PaintCallback {
|
||||
rect,
|
||||
callback: Arc::new(cb),
|
||||
};
|
||||
ui.painter().add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
struct RotatingTriangle {
|
||||
program: glow::Program,
|
||||
vertex_array: glow::VertexArray,
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)] // we need unsafe code to use glow
|
||||
impl RotatingTriangle {
|
||||
fn new(gl: &glow::Context) -> Option<Self> {
|
||||
use glow::HasContext as _;
|
||||
|
||||
let shader_version = egui_glow::ShaderVersion::get(gl);
|
||||
|
||||
unsafe {
|
||||
let program = gl.create_program().expect("Cannot create program");
|
||||
|
||||
if !shader_version.is_new_shader_interface() {
|
||||
tracing::warn!(
|
||||
"Custom 3D painting hasn't been ported to {:?}",
|
||||
shader_version
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let (vertex_shader_source, fragment_shader_source) = (
|
||||
r#"
|
||||
const vec2 verts[3] = vec2[3](
|
||||
vec2(0.0, 1.0),
|
||||
vec2(-1.0, -1.0),
|
||||
vec2(1.0, -1.0)
|
||||
);
|
||||
const vec4 colors[3] = vec4[3](
|
||||
vec4(1.0, 0.0, 0.0, 1.0),
|
||||
vec4(0.0, 1.0, 0.0, 1.0),
|
||||
vec4(0.0, 0.0, 1.0, 1.0)
|
||||
);
|
||||
out vec4 v_color;
|
||||
uniform float u_angle;
|
||||
void main() {
|
||||
v_color = colors[gl_VertexID];
|
||||
gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0);
|
||||
gl_Position.x *= cos(u_angle);
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
precision mediump float;
|
||||
in vec4 v_color;
|
||||
out vec4 out_color;
|
||||
void main() {
|
||||
out_color = v_color;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
let shader_sources = [
|
||||
(glow::VERTEX_SHADER, vertex_shader_source),
|
||||
(glow::FRAGMENT_SHADER, fragment_shader_source),
|
||||
];
|
||||
|
||||
let shaders: Vec<_> = shader_sources
|
||||
.iter()
|
||||
.map(|(shader_type, shader_source)| {
|
||||
let shader = gl
|
||||
.create_shader(*shader_type)
|
||||
.expect("Cannot create shader");
|
||||
gl.shader_source(
|
||||
shader,
|
||||
&format!(
|
||||
"{}\n{}",
|
||||
shader_version.version_declaration(),
|
||||
shader_source
|
||||
),
|
||||
);
|
||||
gl.compile_shader(shader);
|
||||
assert!(
|
||||
gl.get_shader_compile_status(shader),
|
||||
"Failed to compile custom_3d_glow {shader_type}: {}",
|
||||
gl.get_shader_info_log(shader)
|
||||
);
|
||||
|
||||
gl.attach_shader(program, shader);
|
||||
shader
|
||||
})
|
||||
.collect();
|
||||
|
||||
gl.link_program(program);
|
||||
if !gl.get_program_link_status(program) {
|
||||
panic!("{}", gl.get_program_info_log(program));
|
||||
}
|
||||
|
||||
for shader in shaders {
|
||||
gl.detach_shader(program, shader);
|
||||
gl.delete_shader(shader);
|
||||
}
|
||||
|
||||
let vertex_array = gl
|
||||
.create_vertex_array()
|
||||
.expect("Cannot create vertex array");
|
||||
|
||||
Some(Self {
|
||||
program,
|
||||
vertex_array,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn destroy(&self, gl: &glow::Context) {
|
||||
use glow::HasContext as _;
|
||||
unsafe {
|
||||
gl.delete_program(self.program);
|
||||
gl.delete_vertex_array(self.vertex_array);
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(&self, gl: &glow::Context, angle: f32) {
|
||||
use glow::HasContext as _;
|
||||
unsafe {
|
||||
gl.use_program(Some(self.program));
|
||||
gl.uniform_1_f32(
|
||||
gl.get_uniform_location(self.program, "u_angle").as_ref(),
|
||||
angle,
|
||||
);
|
||||
gl.bind_vertex_array(Some(self.vertex_array));
|
||||
gl.draw_arrays(glow::TRIANGLES, 0, 3);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
use std::{num::NonZeroU64, sync::Arc};
|
||||
|
||||
use eframe::{
|
||||
egui_wgpu::wgpu::util::DeviceExt,
|
||||
egui_wgpu::{self, wgpu},
|
||||
};
|
||||
|
||||
pub struct Custom3d {
|
||||
angle: f32,
|
||||
}
|
||||
|
||||
impl Custom3d {
|
||||
pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Option<Self> {
|
||||
// Get the WGPU render state from the eframe creation context. This can also be retrieved
|
||||
// from `eframe::Frame` when you don't have a `CreationContext` available.
|
||||
let wgpu_render_state = cc.wgpu_render_state.as_ref()?;
|
||||
|
||||
let device = &wgpu_render_state.device;
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("custom3d"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("./custom3d_wgpu_shader.wgsl").into()),
|
||||
});
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("custom3d"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: NonZeroU64::new(16),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("custom3d"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("custom3d"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu_render_state.target_format.into())],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("custom3d"),
|
||||
contents: bytemuck::cast_slice(&[0.0_f32; 4]), // 16 bytes aligned!
|
||||
// Mapping at creation (as done by the create_buffer_init utility) doesn't require us to to add the MAP_WRITE usage
|
||||
// (this *happens* to workaround this bug )
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("custom3d"),
|
||||
layout: &bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
|
||||
// Because the graphics pipeline must have the same lifetime as the egui render pass,
|
||||
// instead of storing the pipeline in our `Custom3D` struct, we insert it into the
|
||||
// `paint_callback_resources` type map, which is stored alongside the render pass.
|
||||
wgpu_render_state
|
||||
.renderer
|
||||
.write()
|
||||
.paint_callback_resources
|
||||
.insert(TriangleRenderResources {
|
||||
pipeline,
|
||||
bind_group,
|
||||
uniform_buffer,
|
||||
});
|
||||
|
||||
Some(Self { angle: 0.0 })
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for Custom3d {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::ScrollArea::both()
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("The triangle is being painted using ");
|
||||
ui.hyperlink_to("WGPU", "https://wgpu.rs");
|
||||
ui.label(" (Portable Rust graphics API awesomeness)");
|
||||
});
|
||||
ui.label("It's not a very impressive demo, but it shows you can embed 3D inside of egui.");
|
||||
|
||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
self.custom_painting(ui);
|
||||
});
|
||||
ui.label("Drag to rotate!");
|
||||
ui.add(egui_demo_lib::egui_github_link_file!());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Custom3d {
|
||||
fn custom_painting(&mut self, ui: &mut egui::Ui) {
|
||||
let (rect, response) =
|
||||
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
|
||||
|
||||
self.angle += response.drag_delta().x * 0.01;
|
||||
|
||||
// Clone locals so we can move them into the paint callback:
|
||||
let angle = self.angle;
|
||||
|
||||
// The callback function for WGPU is in two stages: prepare, and paint.
|
||||
//
|
||||
// The prepare callback is called every frame before paint and is given access to the wgpu
|
||||
// Device and Queue, which can be used, for instance, to update buffers and uniforms before
|
||||
// rendering.
|
||||
//
|
||||
// You can use the main `CommandEncoder` that is passed-in, return an arbitrary number
|
||||
// of user-defined `CommandBuffer`s, or both.
|
||||
// The main command buffer, as well as all user-defined ones, will be submitted together
|
||||
// to the GPU in a single call.
|
||||
//
|
||||
// The paint callback is called after prepare and is given access to the render pass, which
|
||||
// can be used to issue draw commands.
|
||||
let cb = egui_wgpu::CallbackFn::new()
|
||||
.prepare(move |device, queue, _encoder, paint_callback_resources| {
|
||||
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
||||
resources.prepare(device, queue, angle);
|
||||
Vec::new()
|
||||
})
|
||||
.paint(move |_info, render_pass, paint_callback_resources| {
|
||||
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
||||
resources.paint(render_pass);
|
||||
});
|
||||
|
||||
let callback = egui::PaintCallback {
|
||||
rect,
|
||||
callback: Arc::new(cb),
|
||||
};
|
||||
|
||||
ui.painter().add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
struct TriangleRenderResources {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
bind_group: wgpu::BindGroup,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
}
|
||||
|
||||
impl TriangleRenderResources {
|
||||
fn prepare(&self, _device: &wgpu::Device, queue: &wgpu::Queue, angle: f32) {
|
||||
// Update our uniform buffer with the angle from the UI
|
||||
queue.write_buffer(
|
||||
&self.uniform_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[angle, 0.0, 0.0, 0.0]),
|
||||
);
|
||||
}
|
||||
|
||||
fn paint<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>) {
|
||||
// Draw our triangle!
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
render_pass.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
struct VertexOut {
|
||||
@location(0) color: vec4<f32>,
|
||||
@builtin(position) position: vec4<f32>,
|
||||
};
|
||||
|
||||
struct Uniforms {
|
||||
@size(16) angle: f32, // pad to 16 bytes
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> uniforms: Uniforms;
|
||||
|
||||
var<private> v_positions: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
|
||||
vec2<f32>(0.0, 1.0),
|
||||
vec2<f32>(1.0, -1.0),
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
);
|
||||
|
||||
var<private> v_colors: array<vec4<f32>, 3> = array<vec4<f32>, 3>(
|
||||
vec4<f32>(1.0, 0.0, 0.0, 1.0),
|
||||
vec4<f32>(0.0, 1.0, 0.0, 1.0),
|
||||
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
||||
);
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) v_idx: u32) -> VertexOut {
|
||||
var out: VertexOut;
|
||||
|
||||
out.position = vec4<f32>(v_positions[v_idx], 0.0, 1.0);
|
||||
out.position.x = out.position.x * cos(uniforms.angle);
|
||||
out.color = v_colors[v_idx];
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
||||
return in.color;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
#[cfg(all(feature = "glow", not(feature = "wgpu")))]
|
||||
mod custom3d_glow;
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
mod custom3d_wgpu;
|
||||
|
||||
mod fractal_clock;
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
mod http_app;
|
||||
|
||||
#[cfg(all(feature = "glow", not(feature = "wgpu")))]
|
||||
pub use custom3d_glow::Custom3d;
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub use custom3d_wgpu::Custom3d;
|
||||
|
||||
pub use fractal_clock::FractalClock;
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
pub use http_app::HttpApp;
|
|
@ -1,80 +0,0 @@
|
|||
//! Demo app for egui
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
mod apps;
|
||||
mod backend_panel;
|
||||
pub(crate) mod frame_history;
|
||||
mod wrap_app;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use eframe::web::AppRunnerRef;
|
||||
|
||||
pub use wrap_app::WrapApp;
|
||||
|
||||
/// Time of day as seconds since midnight. Used for clock in demo app.
|
||||
pub(crate) fn seconds_since_midnight() -> f64 {
|
||||
use chrono::Timelike;
|
||||
let time = chrono::Local::now().time();
|
||||
time.num_seconds_from_midnight() as f64 + 1e-9 * (time.nanosecond() as f64)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use eframe::wasm_bindgen::{self, prelude::*};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub struct WebHandle {
|
||||
handle: AppRunnerRef,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
impl WebHandle {
|
||||
#[wasm_bindgen]
|
||||
pub fn stop_web(&self) -> Result<(), wasm_bindgen::JsValue> {
|
||||
let mut app = self.handle.lock();
|
||||
app.destroy()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn set_some_content_from_javasript(&mut self, _some_data: &str) {
|
||||
let _app = self.handle.lock().app_mut::<WrapApp>();
|
||||
// _app.data = some_data;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub fn init_wasm_hooks() {
|
||||
// Make sure panics are logged using `console.error`.
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
// Redirect tracing to console.log and friends:
|
||||
tracing_wasm::set_as_global_default();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub async fn start_separate(canvas_id: &str) -> Result<WebHandle, wasm_bindgen::JsValue> {
|
||||
let web_options = eframe::WebOptions::default();
|
||||
eframe::start_web(
|
||||
canvas_id,
|
||||
web_options,
|
||||
Box::new(|cc| Box::new(WrapApp::new(cc))),
|
||||
)
|
||||
.await
|
||||
.map(|handle| WebHandle { handle })
|
||||
}
|
||||
|
||||
/// This is the entry-point for all the web-assembly.
|
||||
/// This is called once from the HTML.
|
||||
/// It loads the app, installs some callbacks, then returns.
|
||||
/// You can add more callbacks like this if you want to call in to your code.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub async fn start(canvas_id: &str) -> Result<WebHandle, wasm_bindgen::JsValue> {
|
||||
init_wasm_hooks();
|
||||
start_separate(canvas_id).await
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
//! Demo app for egui
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
// When compiling natively:
|
||||
fn main() -> Result<(), eframe::Error> {
|
||||
{
|
||||
// Silence wgpu log spam (https://github.com/gfx-rs/wgpu/issues/3206)
|
||||
let mut rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_owned());
|
||||
for loud_crate in ["naga", "wgpu_core", "wgpu_hal"] {
|
||||
if !rust_log.contains(&format!("{loud_crate}=")) {
|
||||
rust_log += &format!(",{loud_crate}=warn");
|
||||
}
|
||||
}
|
||||
std::env::set_var("RUST_LOG", rust_log);
|
||||
}
|
||||
|
||||
// Log to stdout (if you run with `RUST_LOG=debug`).
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
drag_and_drop_support: true,
|
||||
|
||||
initial_window_size: Some([1280.0, 1024.0].into()),
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
renderer: eframe::Renderer::Wgpu,
|
||||
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"egui demo app",
|
||||
options,
|
||||
Box::new(|cc| Box::new(egui_demo_app::WrapApp::new(cc))),
|
||||
)
|
||||
}
|
|
@ -1,417 +0,0 @@
|
|||
use egui_demo_lib::is_mobile;
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
use eframe::glow;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use core::any::Any;
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
struct EasyMarkApp {
|
||||
editor: egui_demo_lib::easy_mark::EasyMarkEditor,
|
||||
}
|
||||
|
||||
impl eframe::App for EasyMarkApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
self.editor.panels(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct DemoApp {
|
||||
demo_windows: egui_demo_lib::DemoWindows,
|
||||
}
|
||||
|
||||
impl eframe::App for DemoApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
self.demo_windows.ui(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct FractalClockApp {
|
||||
fractal_clock: crate::apps::FractalClock,
|
||||
}
|
||||
|
||||
impl eframe::App for FractalClockApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame::dark_canvas(&ctx.style()))
|
||||
.show(ctx, |ui| {
|
||||
self.fractal_clock
|
||||
.ui(ui, Some(crate::seconds_since_midnight()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct ColorTestApp {
|
||||
color_test: egui_demo_lib::ColorTest,
|
||||
}
|
||||
|
||||
impl eframe::App for ColorTestApp {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if frame.is_web() {
|
||||
ui.label(
|
||||
"NOTE: Some old browsers stuck on WebGL1 without sRGB support will not pass the color test.",
|
||||
);
|
||||
ui.separator();
|
||||
}
|
||||
egui::ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| {
|
||||
self.color_test.ui(ui);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// The state that we persist (serialize).
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct State {
|
||||
demo: DemoApp,
|
||||
easy_mark_editor: EasyMarkApp,
|
||||
#[cfg(feature = "http")]
|
||||
http: crate::apps::HttpApp,
|
||||
clock: FractalClockApp,
|
||||
color_test: ColorTestApp,
|
||||
|
||||
selected_anchor: String,
|
||||
backend_panel: super::backend_panel::BackendPanel,
|
||||
}
|
||||
|
||||
/// Wraps many demo/test apps into one.
|
||||
pub struct WrapApp {
|
||||
state: State,
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
custom3d: Option<crate::apps::Custom3d>,
|
||||
|
||||
dropped_files: Vec<egui::DroppedFile>,
|
||||
}
|
||||
|
||||
impl WrapApp {
|
||||
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
#[allow(unused_mut)]
|
||||
let mut slf = Self {
|
||||
state: State::default(),
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
custom3d: crate::apps::Custom3d::new(_cc),
|
||||
|
||||
dropped_files: Default::default(),
|
||||
};
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
if let Some(storage) = _cc.storage {
|
||||
if let Some(state) = eframe::get_value(storage, eframe::APP_KEY) {
|
||||
slf.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
slf
|
||||
}
|
||||
|
||||
fn apps_iter_mut(&mut self) -> impl Iterator<Item = (&str, &str, &mut dyn eframe::App)> {
|
||||
let mut vec = vec![
|
||||
(
|
||||
"✨ Demos",
|
||||
"demo",
|
||||
&mut self.state.demo as &mut dyn eframe::App,
|
||||
),
|
||||
(
|
||||
"🖹 EasyMark editor",
|
||||
"easymark",
|
||||
&mut self.state.easy_mark_editor as &mut dyn eframe::App,
|
||||
),
|
||||
#[cfg(feature = "http")]
|
||||
(
|
||||
"⬇ HTTP",
|
||||
"http",
|
||||
&mut self.state.http as &mut dyn eframe::App,
|
||||
),
|
||||
(
|
||||
"🕑 Fractal Clock",
|
||||
"clock",
|
||||
&mut self.state.clock as &mut dyn eframe::App,
|
||||
),
|
||||
];
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
if let Some(custom3d) = &mut self.custom3d {
|
||||
vec.push((
|
||||
"🔺 3D painting",
|
||||
"custom3d",
|
||||
custom3d as &mut dyn eframe::App,
|
||||
));
|
||||
}
|
||||
|
||||
vec.push((
|
||||
"🎨 Color test",
|
||||
"colors",
|
||||
&mut self.state.color_test as &mut dyn eframe::App,
|
||||
));
|
||||
|
||||
vec.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for WrapApp {
|
||||
#[cfg(feature = "persistence")]
|
||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||
eframe::set_value(storage, eframe::APP_KEY, &self.state);
|
||||
}
|
||||
|
||||
fn clear_color(&self, visuals: &egui::Visuals) -> [f32; 4] {
|
||||
visuals.panel_fill.to_normalized_gamma_f32()
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if let Some(anchor) = frame.info().web_info.location.hash.strip_prefix('#') {
|
||||
self.state.selected_anchor = anchor.to_owned();
|
||||
}
|
||||
|
||||
if self.state.selected_anchor.is_empty() {
|
||||
let selected_anchor = self.apps_iter_mut().next().unwrap().0.to_owned();
|
||||
self.state.selected_anchor = selected_anchor;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::F11)) {
|
||||
frame.set_fullscreen(!frame.info().window_info.fullscreen);
|
||||
}
|
||||
|
||||
egui::TopBottomPanel::top("wrap_app_top_bar").show(ctx, |ui| {
|
||||
egui::trace!(ui);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.visuals_mut().button_frame = false;
|
||||
self.bar_contents(ui, frame);
|
||||
});
|
||||
});
|
||||
|
||||
self.state.backend_panel.update(ctx, frame);
|
||||
|
||||
if !is_mobile(ctx) {
|
||||
self.backend_panel(ctx, frame);
|
||||
}
|
||||
|
||||
self.show_selected_app(ctx, frame);
|
||||
|
||||
self.state.backend_panel.end_of_frame(ctx);
|
||||
|
||||
self.ui_file_drag_and_drop(ctx);
|
||||
|
||||
// On web, the browser controls `pixels_per_point`.
|
||||
if !frame.is_web() {
|
||||
egui::gui_zoom::zoom_with_keyboard_shortcuts(ctx, frame.info().native_pixels_per_point);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
fn on_exit(&mut self, gl: Option<&glow::Context>) {
|
||||
if let Some(custom3d) = &mut self.custom3d {
|
||||
custom3d.on_exit(gl);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn as_any_mut(&mut self) -> Option<&mut dyn Any> {
|
||||
Some(&mut *self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapApp {
|
||||
fn backend_panel(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
// The backend-panel can be toggled on/off.
|
||||
// We show a little animation when the user switches it.
|
||||
let is_open =
|
||||
self.state.backend_panel.open || ctx.memory(|mem| mem.everything_is_visible());
|
||||
|
||||
egui::SidePanel::left("backend_panel")
|
||||
.resizable(false)
|
||||
.show_animated(ctx, is_open, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading("💻 Backend");
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
self.backend_panel_contents(ui, frame);
|
||||
});
|
||||
}
|
||||
|
||||
fn backend_panel_contents(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
self.state.backend_panel.ui(ui, frame);
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui
|
||||
.button("Reset egui")
|
||||
.on_hover_text("Forget scroll, positions, sizes etc")
|
||||
.clicked()
|
||||
{
|
||||
ui.ctx().memory_mut(|mem| *mem = Default::default());
|
||||
ui.close_menu();
|
||||
}
|
||||
|
||||
if ui.button("Reset everything").clicked() {
|
||||
self.state = Default::default();
|
||||
ui.ctx().memory_mut(|mem| *mem = Default::default());
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn show_selected_app(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
let mut found_anchor = false;
|
||||
let selected_anchor = self.state.selected_anchor.clone();
|
||||
for (_name, anchor, app) in self.apps_iter_mut() {
|
||||
if anchor == selected_anchor || ctx.memory(|mem| mem.everything_is_visible()) {
|
||||
app.update(ctx, frame);
|
||||
found_anchor = true;
|
||||
}
|
||||
}
|
||||
if !found_anchor {
|
||||
self.state.selected_anchor = "demo".into();
|
||||
}
|
||||
}
|
||||
|
||||
fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
egui::widgets::global_dark_light_mode_switch(ui);
|
||||
|
||||
ui.separator();
|
||||
|
||||
if is_mobile(ui.ctx()) {
|
||||
ui.menu_button("💻 Backend", |ui| {
|
||||
ui.set_style(ui.ctx().style()); // ignore the "menu" style set by `menu_button`.
|
||||
self.backend_panel_contents(ui, frame);
|
||||
});
|
||||
} else {
|
||||
ui.toggle_value(&mut self.state.backend_panel.open, "💻 Backend");
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
let mut selected_anchor = self.state.selected_anchor.clone();
|
||||
for (name, anchor, _app) in self.apps_iter_mut() {
|
||||
if ui
|
||||
.selectable_label(selected_anchor == anchor, name)
|
||||
.clicked()
|
||||
{
|
||||
selected_anchor = anchor.to_owned();
|
||||
if frame.is_web() {
|
||||
ui.output_mut(|o| o.open_url(format!("#{}", anchor)));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.state.selected_anchor = selected_anchor;
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if false {
|
||||
// TODO(emilk): fix the overlap on small screens
|
||||
if clock_button(ui, crate::seconds_since_midnight()).clicked() {
|
||||
self.state.selected_anchor = "clock".to_owned();
|
||||
if frame.is_web() {
|
||||
ui.output_mut(|o| o.open_url("#clock"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
egui::warn_if_debug_build(ui);
|
||||
});
|
||||
}
|
||||
|
||||
fn ui_file_drag_and_drop(&mut self, ctx: &egui::Context) {
|
||||
use egui::*;
|
||||
use std::fmt::Write as _;
|
||||
|
||||
// Preview hovering files:
|
||||
if !ctx.input(|i| i.raw.hovered_files.is_empty()) {
|
||||
let text = ctx.input(|i| {
|
||||
let mut text = "Dropping files:\n".to_owned();
|
||||
for file in &i.raw.hovered_files {
|
||||
if let Some(path) = &file.path {
|
||||
write!(text, "\n{}", path.display()).ok();
|
||||
} else if !file.mime.is_empty() {
|
||||
write!(text, "\n{}", file.mime).ok();
|
||||
} else {
|
||||
text += "\n???";
|
||||
}
|
||||
}
|
||||
text
|
||||
});
|
||||
|
||||
let painter =
|
||||
ctx.layer_painter(LayerId::new(Order::Foreground, Id::new("file_drop_target")));
|
||||
|
||||
let screen_rect = ctx.screen_rect();
|
||||
painter.rect_filled(screen_rect, 0.0, Color32::from_black_alpha(192));
|
||||
painter.text(
|
||||
screen_rect.center(),
|
||||
Align2::CENTER_CENTER,
|
||||
text,
|
||||
TextStyle::Heading.resolve(&ctx.style()),
|
||||
Color32::WHITE,
|
||||
);
|
||||
}
|
||||
|
||||
// Collect dropped files:
|
||||
ctx.input(|i| {
|
||||
if !i.raw.dropped_files.is_empty() {
|
||||
self.dropped_files = i.raw.dropped_files.clone();
|
||||
}
|
||||
});
|
||||
|
||||
// Show dropped files (if any):
|
||||
if !self.dropped_files.is_empty() {
|
||||
let mut open = true;
|
||||
egui::Window::new("Dropped files")
|
||||
.open(&mut open)
|
||||
.show(ctx, |ui| {
|
||||
for file in &self.dropped_files {
|
||||
let mut info = if let Some(path) = &file.path {
|
||||
path.display().to_string()
|
||||
} else if !file.name.is_empty() {
|
||||
file.name.clone()
|
||||
} else {
|
||||
"???".to_owned()
|
||||
};
|
||||
if let Some(bytes) = &file.bytes {
|
||||
write!(info, " ({} bytes)", bytes.len()).ok();
|
||||
}
|
||||
ui.label(info);
|
||||
}
|
||||
});
|
||||
if !open {
|
||||
self.dropped_files.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clock_button(ui: &mut egui::Ui, seconds_since_midnight: f64) -> egui::Response {
|
||||
let time = seconds_since_midnight;
|
||||
let time = format!(
|
||||
"{:02}:{:02}:{:02}.{:02}",
|
||||
(time % (24.0 * 60.0 * 60.0) / 3600.0).floor(),
|
||||
(time % (60.0 * 60.0) / 60.0).floor(),
|
||||
(time % 60.0).floor(),
|
||||
(time % 1.0 * 100.0).floor()
|
||||
);
|
||||
|
||||
ui.button(egui::RichText::new(time).monospace())
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
[package]
|
||||
name = "egui_demo_lib"
|
||||
version = "0.21.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Example library for egui"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/crates/egui_demo_lib"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui/tree/master/crates/egui_demo_lib"
|
||||
categories = ["gui", "graphics"]
|
||||
keywords = ["glium", "egui", "gui", "gamedev"]
|
||||
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
chrono = ["egui_extras/datepicker", "dep:chrono"]
|
||||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||
serde = ["egui/serde", "dep:serde"]
|
||||
## Enable better syntax highlighting using [`syntect`](https://docs.rs/syntect).
|
||||
syntax_highlighting = ["syntect"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.21.0", path = "../egui", default-features = false }
|
||||
egui_extras = { version = "0.21.0", path = "../egui_extras" }
|
||||
enum-map = { version = "2", features = ["serde"] }
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
unicode_names2 = { version = "0.6.0", default-features = false }
|
||||
|
||||
#! ### Optional dependencies
|
||||
chrono = { version = "0.4", optional = true, features = ["js-sys", "wasmbind"] }
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
syntect = { version = "5", optional = true, default-features = false, features = [
|
||||
"default-fancy",
|
||||
] }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.4", default-features = false }
|
||||
|
||||
|
||||
[[bench]]
|
||||
name = "benchmark"
|
||||
harness = false
|
|
@ -1,94 +0,0 @@
|
|||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct About {}
|
||||
|
||||
impl super::Demo for About {
|
||||
fn name(&self) -> &'static str {
|
||||
"About egui"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.default_width(320.0)
|
||||
.open(open)
|
||||
.show(ctx, |ui| {
|
||||
use super::View as _;
|
||||
self.ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl super::View for About {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
use egui::special_emojis::{OS_APPLE, OS_LINUX, OS_WINDOWS};
|
||||
|
||||
ui.heading("egui");
|
||||
ui.label(format!(
|
||||
"egui is an immediate mode GUI library written in Rust. egui runs both on the web and natively on {}{}{}. \
|
||||
On the web it is compiled to WebAssembly and rendered with WebGL.{}",
|
||||
OS_APPLE, OS_LINUX, OS_WINDOWS,
|
||||
if cfg!(target_arch = "wasm32") {
|
||||
" Everything you see is rendered as textured triangles. There is no DOM, HTML, JS or CSS. Just Rust."
|
||||
} else {""}
|
||||
));
|
||||
ui.label("egui is designed to be easy to use, portable, and fast.");
|
||||
|
||||
ui.add_space(12.0); // ui.separator();
|
||||
ui.heading("Immediate mode");
|
||||
about_immediate_mode(ui);
|
||||
|
||||
ui.add_space(12.0); // ui.separator();
|
||||
ui.heading("Links");
|
||||
links(ui);
|
||||
}
|
||||
}
|
||||
|
||||
fn about_immediate_mode(ui: &mut egui::Ui) {
|
||||
use crate::syntax_highlighting::code_view_ui;
|
||||
ui.style_mut().spacing.interact_size.y = 0.0; // hack to make `horizontal_wrapped` work better with text.
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("Immediate mode is a GUI paradigm that lets you create a GUI with less code and simpler control flow. For example, this is how you create a ");
|
||||
let _ = ui.small_button("button");
|
||||
ui.label(" in egui:");
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
code_view_ui(
|
||||
ui,
|
||||
r#"
|
||||
if ui.button("Save").clicked() {
|
||||
my_state.save();
|
||||
}"#
|
||||
.trim_start_matches('\n'),
|
||||
);
|
||||
ui.add_space(8.0);
|
||||
|
||||
ui.label("Note how there are no callbacks or messages, and no button state to store.");
|
||||
|
||||
ui.label("Immediate mode has its roots in gaming, where everything on the screen is painted at the display refresh rate, i.e. at 60+ frames per second. \
|
||||
In immediate mode GUIs, the entire interface is layed out and painted at the same high rate. \
|
||||
This makes immediate mode GUIs especially well suited for highly interactive applications.");
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("More about immediate mode ");
|
||||
ui.hyperlink_to("here", "https://github.com/emilk/egui#why-immediate-mode");
|
||||
ui.label(".");
|
||||
});
|
||||
}
|
||||
|
||||
fn links(ui: &mut egui::Ui) {
|
||||
use egui::special_emojis::{GITHUB, TWITTER};
|
||||
ui.hyperlink_to(
|
||||
format!("{} egui on GitHub", GITHUB),
|
||||
"https://github.com/emilk/egui",
|
||||
);
|
||||
ui.hyperlink_to(
|
||||
format!("{} @ernerfeldt", TWITTER),
|
||||
"https://twitter.com/ernerfeldt",
|
||||
);
|
||||
ui.hyperlink_to("egui documentation", "https://docs.rs/egui/");
|
||||
}
|
|
@ -1,355 +0,0 @@
|
|||
use egui::{Context, Modifiers, ScrollArea, Ui};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use super::About;
|
||||
use super::Demo;
|
||||
use super::View;
|
||||
use crate::is_mobile;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
struct Demos {
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
demos: Vec<Box<dyn Demo>>,
|
||||
|
||||
open: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl Default for Demos {
|
||||
fn default() -> Self {
|
||||
Self::from_demos(vec![
|
||||
Box::new(super::paint_bezier::PaintBezier::default()),
|
||||
Box::new(super::code_editor::CodeEditor::default()),
|
||||
Box::new(super::code_example::CodeExample::default()),
|
||||
Box::new(super::context_menu::ContextMenus::default()),
|
||||
Box::new(super::dancing_strings::DancingStrings::default()),
|
||||
Box::new(super::drag_and_drop::DragAndDropDemo::default()),
|
||||
Box::new(super::font_book::FontBook::default()),
|
||||
Box::new(super::MiscDemoWindow::default()),
|
||||
Box::new(super::multi_touch::MultiTouch::default()),
|
||||
Box::new(super::painting::Painting::default()),
|
||||
Box::new(super::plot_demo::PlotDemo::default()),
|
||||
Box::new(super::scrolling::Scrolling::default()),
|
||||
Box::new(super::sliders::Sliders::default()),
|
||||
Box::new(super::strip_demo::StripDemo::default()),
|
||||
Box::new(super::table_demo::TableDemo::default()),
|
||||
Box::new(super::text_edit::TextEdit::default()),
|
||||
Box::new(super::widget_gallery::WidgetGallery::default()),
|
||||
Box::new(super::window_options::WindowOptions::default()),
|
||||
Box::new(super::tests::WindowResizeTest::default()),
|
||||
Box::new(super::window_with_panels::WindowWithPanels::default()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl Demos {
|
||||
pub fn from_demos(demos: Vec<Box<dyn Demo>>) -> Self {
|
||||
let mut open = BTreeSet::new();
|
||||
open.insert(
|
||||
super::widget_gallery::WidgetGallery::default()
|
||||
.name()
|
||||
.to_owned(),
|
||||
);
|
||||
|
||||
Self { demos, open }
|
||||
}
|
||||
|
||||
pub fn checkboxes(&mut self, ui: &mut Ui) {
|
||||
let Self { demos, open } = self;
|
||||
for demo in demos {
|
||||
let mut is_open = open.contains(demo.name());
|
||||
ui.toggle_value(&mut is_open, demo.name());
|
||||
set_open(open, demo.name(), is_open);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn windows(&mut self, ctx: &Context) {
|
||||
let Self { demos, open } = self;
|
||||
for demo in demos {
|
||||
let mut is_open = open.contains(demo.name());
|
||||
demo.show(ctx, &mut is_open);
|
||||
set_open(open, demo.name(), is_open);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
struct Tests {
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
demos: Vec<Box<dyn Demo>>,
|
||||
|
||||
open: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl Default for Tests {
|
||||
fn default() -> Self {
|
||||
Self::from_demos(vec![
|
||||
Box::new(super::tests::CursorTest::default()),
|
||||
Box::new(super::highlighting::Highlighting::default()),
|
||||
Box::new(super::tests::IdTest::default()),
|
||||
Box::new(super::tests::InputTest::default()),
|
||||
Box::new(super::layout_test::LayoutTest::default()),
|
||||
Box::new(super::tests::ManualLayoutTest::default()),
|
||||
Box::new(super::tests::TableTest::default()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
impl Tests {
|
||||
pub fn from_demos(demos: Vec<Box<dyn Demo>>) -> Self {
|
||||
let mut open = BTreeSet::new();
|
||||
open.insert(
|
||||
super::widget_gallery::WidgetGallery::default()
|
||||
.name()
|
||||
.to_owned(),
|
||||
);
|
||||
|
||||
Self { demos, open }
|
||||
}
|
||||
|
||||
pub fn checkboxes(&mut self, ui: &mut Ui) {
|
||||
let Self { demos, open } = self;
|
||||
for demo in demos {
|
||||
let mut is_open = open.contains(demo.name());
|
||||
ui.toggle_value(&mut is_open, demo.name());
|
||||
set_open(open, demo.name(), is_open);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn windows(&mut self, ctx: &Context) {
|
||||
let Self { demos, open } = self;
|
||||
for demo in demos {
|
||||
let mut is_open = open.contains(demo.name());
|
||||
demo.show(ctx, &mut is_open);
|
||||
set_open(open, demo.name(), is_open);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
fn set_open(open: &mut BTreeSet<String>, key: &'static str, is_open: bool) {
|
||||
if is_open {
|
||||
if !open.contains(key) {
|
||||
open.insert(key.to_owned());
|
||||
}
|
||||
} else {
|
||||
open.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A menu bar in which you can select different demo windows to show.
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct DemoWindows {
|
||||
about_is_open: bool,
|
||||
about: About,
|
||||
demos: Demos,
|
||||
tests: Tests,
|
||||
}
|
||||
|
||||
impl Default for DemoWindows {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
about_is_open: true,
|
||||
about: Default::default(),
|
||||
demos: Default::default(),
|
||||
tests: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DemoWindows {
|
||||
/// Show the app ui (menu bar and windows).
|
||||
pub fn ui(&mut self, ctx: &Context) {
|
||||
if is_mobile(ctx) {
|
||||
self.mobile_ui(ctx);
|
||||
} else {
|
||||
self.desktop_ui(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn mobile_ui(&mut self, ctx: &Context) {
|
||||
if self.about_is_open {
|
||||
let screen_size = ctx.input(|i| i.screen_rect.size());
|
||||
let default_width = (screen_size.x - 20.0).min(400.0);
|
||||
|
||||
let mut close = false;
|
||||
egui::Window::new(self.about.name())
|
||||
.anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
|
||||
.default_width(default_width)
|
||||
.default_height(ctx.available_rect().height() - 46.0)
|
||||
.vscroll(true)
|
||||
.open(&mut self.about_is_open)
|
||||
.resizable(false)
|
||||
.collapsible(false)
|
||||
.show(ctx, |ui| {
|
||||
self.about.ui(ui);
|
||||
ui.add_space(12.0);
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
if ui
|
||||
.button(egui::RichText::new("Continue to the demo!").size(20.0))
|
||||
.clicked()
|
||||
{
|
||||
close = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
self.about_is_open &= !close;
|
||||
} else {
|
||||
self.mobile_top_bar(ctx);
|
||||
self.show_windows(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn mobile_top_bar(&mut self, ctx: &Context) {
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
let font_size = 16.5;
|
||||
|
||||
ui.menu_button(egui::RichText::new("⏷ demos").size(font_size), |ui| {
|
||||
ui.set_style(ui.ctx().style()); // ignore the "menu" style set by `menu_button`.
|
||||
self.demo_list_ui(ui);
|
||||
if ui.ui_contains_pointer() && ui.input(|i| i.pointer.any_click()) {
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
use egui::special_emojis::{GITHUB, TWITTER};
|
||||
ui.hyperlink_to(
|
||||
egui::RichText::new(TWITTER).size(font_size),
|
||||
"https://twitter.com/ernerfeldt",
|
||||
);
|
||||
ui.hyperlink_to(
|
||||
egui::RichText::new(GITHUB).size(font_size),
|
||||
"https://github.com/emilk/egui",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn desktop_ui(&mut self, ctx: &Context) {
|
||||
egui::SidePanel::right("egui_demo_panel")
|
||||
.resizable(false)
|
||||
.default_width(150.0)
|
||||
.show(ctx, |ui| {
|
||||
egui::trace!(ui);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading("✒ egui demos");
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
use egui::special_emojis::{GITHUB, TWITTER};
|
||||
ui.hyperlink_to(
|
||||
format!("{} egui on GitHub", GITHUB),
|
||||
"https://github.com/emilk/egui",
|
||||
);
|
||||
ui.hyperlink_to(
|
||||
format!("{} @ernerfeldt", TWITTER),
|
||||
"https://twitter.com/ernerfeldt",
|
||||
);
|
||||
|
||||
ui.separator();
|
||||
|
||||
self.demo_list_ui(ui);
|
||||
});
|
||||
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
file_menu_button(ui);
|
||||
});
|
||||
});
|
||||
|
||||
self.show_windows(ctx);
|
||||
}
|
||||
|
||||
/// Show the open windows.
|
||||
fn show_windows(&mut self, ctx: &Context) {
|
||||
self.about.show(ctx, &mut self.about_is_open);
|
||||
self.demos.windows(ctx);
|
||||
self.tests.windows(ctx);
|
||||
}
|
||||
|
||||
fn demo_list_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| {
|
||||
ui.toggle_value(&mut self.about_is_open, self.about.name());
|
||||
|
||||
ui.separator();
|
||||
self.demos.checkboxes(ui);
|
||||
ui.separator();
|
||||
self.tests.checkboxes(ui);
|
||||
ui.separator();
|
||||
|
||||
if ui.button("Organize windows").clicked() {
|
||||
ui.ctx().memory_mut(|mem| mem.reset_areas());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
fn file_menu_button(ui: &mut Ui) {
|
||||
let organize_shortcut =
|
||||
egui::KeyboardShortcut::new(Modifiers::CTRL | Modifiers::SHIFT, egui::Key::O);
|
||||
let reset_shortcut =
|
||||
egui::KeyboardShortcut::new(Modifiers::CTRL | Modifiers::SHIFT, egui::Key::R);
|
||||
|
||||
// NOTE: we must check the shortcuts OUTSIDE of the actual "File" menu,
|
||||
// or else they would only be checked if the "File" menu was actually open!
|
||||
|
||||
if ui.input_mut(|i| i.consume_shortcut(&organize_shortcut)) {
|
||||
ui.ctx().memory_mut(|mem| mem.reset_areas());
|
||||
}
|
||||
|
||||
if ui.input_mut(|i| i.consume_shortcut(&reset_shortcut)) {
|
||||
ui.ctx().memory_mut(|mem| *mem = Default::default());
|
||||
}
|
||||
|
||||
ui.menu_button("File", |ui| {
|
||||
ui.set_min_width(220.0);
|
||||
ui.style_mut().wrap = Some(false);
|
||||
|
||||
// On the web the browser controls the zoom
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
egui::gui_zoom::zoom_menu_buttons(ui, None);
|
||||
ui.separator();
|
||||
}
|
||||
|
||||
if ui
|
||||
.add(
|
||||
egui::Button::new("Organize Windows")
|
||||
.shortcut_text(ui.ctx().format_shortcut(&organize_shortcut)),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
ui.ctx().memory_mut(|mem| mem.reset_areas());
|
||||
ui.close_menu();
|
||||
}
|
||||
|
||||
if ui
|
||||
.add(
|
||||
egui::Button::new("Reset egui memory")
|
||||
.shortcut_text(ui.ctx().format_shortcut(&reset_shortcut)),
|
||||
)
|
||||
.on_hover_text("Forget scroll, positions, sizes etc")
|
||||
.clicked()
|
||||
{
|
||||
ui.ctx().memory_mut(|mem| *mem = Default::default());
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct Highlighting {}
|
||||
|
||||
impl super::Demo for Highlighting {
|
||||
fn name(&self) -> &'static str {
|
||||
"Highlighting"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.default_width(320.0)
|
||||
.open(open)
|
||||
.show(ctx, |ui| {
|
||||
use super::View as _;
|
||||
self.ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl super::View for Highlighting {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label("This demo demonstrates highlighting a widget.");
|
||||
ui.add_space(4.0);
|
||||
let label_response = ui.label("Hover me to highlight the button!");
|
||||
ui.add_space(4.0);
|
||||
let mut button_response = ui.button("Hover the button to highlight the label!");
|
||||
|
||||
if label_response.hovered() {
|
||||
button_response = button_response.highlight();
|
||||
}
|
||||
if button_response.hovered() {
|
||||
label_response.highlight();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,256 +0,0 @@
|
|||
#[derive(PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
enum DemoType {
|
||||
Manual,
|
||||
ManyHomogenous,
|
||||
ManyHeterogenous,
|
||||
}
|
||||
|
||||
/// Shows off a table with dynamic layout
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct TableDemo {
|
||||
demo: DemoType,
|
||||
striped: bool,
|
||||
resizable: bool,
|
||||
num_rows: usize,
|
||||
scroll_to_row_slider: usize,
|
||||
scroll_to_row: Option<usize>,
|
||||
}
|
||||
|
||||
impl Default for TableDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
demo: DemoType::Manual,
|
||||
striped: true,
|
||||
resizable: true,
|
||||
num_rows: 10_000,
|
||||
scroll_to_row_slider: 0,
|
||||
scroll_to_row: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Demo for TableDemo {
|
||||
fn name(&self) -> &'static str {
|
||||
"☰ Table Demo"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.open(open)
|
||||
.resizable(true)
|
||||
.default_width(400.0)
|
||||
.show(ctx, |ui| {
|
||||
use super::View as _;
|
||||
self.ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const NUM_MANUAL_ROWS: usize = 20;
|
||||
|
||||
impl super::View for TableDemo {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.vertical(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.checkbox(&mut self.striped, "Striped");
|
||||
ui.checkbox(&mut self.resizable, "Resizable columns");
|
||||
});
|
||||
|
||||
ui.label("Table type:");
|
||||
ui.radio_value(&mut self.demo, DemoType::Manual, "Few, manual rows");
|
||||
ui.radio_value(
|
||||
&mut self.demo,
|
||||
DemoType::ManyHomogenous,
|
||||
"Thousands of rows of same height",
|
||||
);
|
||||
ui.radio_value(
|
||||
&mut self.demo,
|
||||
DemoType::ManyHeterogenous,
|
||||
"Thousands of rows of differing heights",
|
||||
);
|
||||
|
||||
if self.demo != DemoType::Manual {
|
||||
ui.add(
|
||||
egui::Slider::new(&mut self.num_rows, 0..=100_000)
|
||||
.logarithmic(true)
|
||||
.text("Num rows"),
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let max_rows = if self.demo == DemoType::Manual {
|
||||
NUM_MANUAL_ROWS
|
||||
} else {
|
||||
self.num_rows
|
||||
};
|
||||
|
||||
let slider_response = ui.add(
|
||||
egui::Slider::new(&mut self.scroll_to_row_slider, 0..=max_rows)
|
||||
.logarithmic(true)
|
||||
.text("Row to scroll to"),
|
||||
);
|
||||
if slider_response.changed() {
|
||||
self.scroll_to_row = Some(self.scroll_to_row_slider);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Leave room for the source code link after the table demo:
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::remainder().at_least(100.0)) // for the table
|
||||
.size(Size::exact(10.0)) // for the source code link
|
||||
.vertical(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
egui::ScrollArea::horizontal().show(ui, |ui| {
|
||||
self.table_ui(ui);
|
||||
});
|
||||
});
|
||||
strip.cell(|ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::egui_github_link_file!());
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl TableDemo {
|
||||
fn table_ui(&mut self, ui: &mut egui::Ui) {
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
|
||||
let text_height = egui::TextStyle::Body.resolve(ui.style()).size;
|
||||
|
||||
let mut table = TableBuilder::new(ui)
|
||||
.striped(self.striped)
|
||||
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
|
||||
.column(Column::auto())
|
||||
.column(Column::initial(100.0).range(40.0..=300.0).resizable(true))
|
||||
.column(
|
||||
Column::initial(100.0)
|
||||
.at_least(40.0)
|
||||
.resizable(true)
|
||||
.clip(true),
|
||||
)
|
||||
.column(Column::remainder())
|
||||
.min_scrolled_height(0.0);
|
||||
|
||||
if let Some(row_nr) = self.scroll_to_row.take() {
|
||||
table = table.scroll_to_row(row_nr, None);
|
||||
}
|
||||
|
||||
table
|
||||
.header(20.0, |mut header| {
|
||||
header.col(|ui| {
|
||||
ui.strong("Row");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.strong("Expanding content");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.strong("Clipped text");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.strong("Content");
|
||||
});
|
||||
})
|
||||
.body(|mut body| match self.demo {
|
||||
DemoType::Manual => {
|
||||
for row_index in 0..NUM_MANUAL_ROWS {
|
||||
let is_thick = thick_row(row_index);
|
||||
let row_height = if is_thick { 30.0 } else { 18.0 };
|
||||
body.row(row_height, |mut row| {
|
||||
row.col(|ui| {
|
||||
ui.label(row_index.to_string());
|
||||
});
|
||||
row.col(|ui| {
|
||||
expanding_content(ui);
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(long_text(row_index));
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.style_mut().wrap = Some(false);
|
||||
if is_thick {
|
||||
ui.heading("Extra thick row");
|
||||
} else {
|
||||
ui.label("Normal row");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
DemoType::ManyHomogenous => {
|
||||
body.rows(text_height, self.num_rows, |row_index, mut row| {
|
||||
row.col(|ui| {
|
||||
ui.label(row_index.to_string());
|
||||
});
|
||||
row.col(|ui| {
|
||||
expanding_content(ui);
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(long_text(row_index));
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.add(
|
||||
egui::Label::new("Thousands of rows of even height").wrap(false),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
DemoType::ManyHeterogenous => {
|
||||
fn row_thickness(row_index: usize) -> f32 {
|
||||
if thick_row(row_index) {
|
||||
30.0
|
||||
} else {
|
||||
18.0
|
||||
}
|
||||
}
|
||||
body.heterogeneous_rows(
|
||||
(0..self.num_rows).into_iter().map(row_thickness),
|
||||
|row_index, mut row| {
|
||||
row.col(|ui| {
|
||||
ui.label(row_index.to_string());
|
||||
});
|
||||
row.col(|ui| {
|
||||
expanding_content(ui);
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(long_text(row_index));
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.style_mut().wrap = Some(false);
|
||||
if thick_row(row_index) {
|
||||
ui.heading("Extra thick row");
|
||||
} else {
|
||||
ui.label("Normal row");
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn expanding_content(ui: &mut egui::Ui) {
|
||||
let width = ui.available_width().clamp(20.0, 200.0);
|
||||
let height = ui.available_height();
|
||||
let (rect, _response) = ui.allocate_exact_size(egui::vec2(width, height), egui::Sense::hover());
|
||||
ui.painter().hline(
|
||||
rect.x_range(),
|
||||
rect.center().y,
|
||||
(1.0, ui.visuals().text_color()),
|
||||
);
|
||||
}
|
||||
|
||||
fn long_text(row_index: usize) -> String {
|
||||
format!("Row {row_index} has some long text that you may want to clip, or it will take up too much horizontal space!")
|
||||
}
|
||||
|
||||
fn thick_row(row_index: usize) -> bool {
|
||||
row_index % 6 == 0
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
# Changelog for egui_extras
|
||||
All notable changes to the `egui_extras` integration will be noted in this file.
|
||||
|
||||
|
||||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08
|
||||
* Update to egui 0.21
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08
|
||||
* Added `RetainedImage::from_svg_bytes_with_size` to be able to specify a size for SVGs to be rasterized at.
|
||||
* Lots of `Table` improvements ([#2369](https://github.com/emilk/egui/pull/2369)):
|
||||
* Double-click column separators to auto-size the column.
|
||||
* All `Table` now store state. You may see warnings about reused table ids. Use `ui.push_id` to fix this.
|
||||
* `TableBuilder::column` takes a `Column` instead of a `Size`.
|
||||
* `Column` controls default size, size range, resizing, and clipping of columns.
|
||||
* `Column::auto` will pick a size automatically
|
||||
* Added `Table::scroll_to_row`.
|
||||
* Added `Table::min_scrolled_height` and `Table::max_scroll_height`.
|
||||
* Added `TableBody::max_size`.
|
||||
* `Table::scroll` renamed to `Table::vscroll`.
|
||||
* `egui_extras::Strip` now has `clip: false` by default.
|
||||
* Fix bugs when putting `Table` inside of a horizontal `ScrollArea`.
|
||||
* Many other bug fixes.
|
||||
* Add `Table::auto_shrink` - set to `false` to expand table to fit its containing `Ui` ([#2371](https://github.com/emilk/egui/pull/2371)).
|
||||
* Added `TableBuilder::vertical_scroll_offset`: method to set vertical scroll offset position for a table ([#1946](https://github.com/emilk/egui/pull/1946)).
|
||||
|
||||
|
||||
## 0.19.0 - 2022-08-20
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
|
||||
* You can now specify a texture filter for `RetainedImage` ([#1636](https://github.com/emilk/egui/pull/1636)).
|
||||
* Fixed uneven `Table` striping ([#1680](https://github.com/emilk/egui/pull/1680)).
|
||||
|
||||
|
||||
## 0.18.0 - 2022-04-30
|
||||
* Added `Strip`, `Table` and `DatePicker` ([#963](https://github.com/emilk/egui/pull/963)).
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* Renamed feature "persistence" to "serde" ([#1540](https://github.com/emilk/egui/pull/1540)).
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
* `RetainedImage`: convenience for loading svg, png, jpeg etc and showing them in egui.
|
|
@ -1,68 +0,0 @@
|
|||
[package]
|
||||
name = "egui_extras"
|
||||
version = "0.21.0"
|
||||
authors = [
|
||||
"Dominik Rössler <dominik@freshx.de>",
|
||||
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
|
||||
"René Rössler <rene@freshx.de>",
|
||||
]
|
||||
description = "Extra functionality and widgets for the egui GUI library"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui"
|
||||
categories = ["gui", "game-development"]
|
||||
keywords = ["gui", "imgui", "immediate", "portable", "gamedev"]
|
||||
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
## Enable [`DatePickerButton`] widget.
|
||||
datepicker = ["chrono"]
|
||||
|
||||
## Support loading svg images.
|
||||
svg = ["resvg", "tiny-skia", "usvg"]
|
||||
|
||||
## Log warnings using `tracing` crate.
|
||||
tracing = ["dep:tracing", "egui/tracing"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.21.0", path = "../egui", default-features = false }
|
||||
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
#! ### Optional dependencies
|
||||
|
||||
# Date operations needed for datepicker widget
|
||||
chrono = { version = "0.4", optional = true }
|
||||
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
## Add support for loading images with the [`image`](https://docs.rs/image) crate.
|
||||
##
|
||||
## You also need to ALSO opt-in to the image formats you want to support, like so:
|
||||
## ```toml
|
||||
## image = { version = "0.24", features = ["jpeg", "png"] }
|
||||
## ```
|
||||
image = { version = "0.24", optional = true, default-features = false }
|
||||
|
||||
# svg feature
|
||||
resvg = { version = "0.28", optional = true, default-features = false }
|
||||
tiny-skia = { version = "0.8", optional = true, default-features = false } # must be updated in lock-step with resvg
|
||||
usvg = { version = "0.28", optional = true, default-features = false }
|
||||
|
||||
# feature "tracing"
|
||||
tracing = { version = "0.1", optional = true, default-features = false, features = [
|
||||
"std",
|
||||
] }
|
|
@ -1,171 +0,0 @@
|
|||
use egui::{Pos2, Rect, Response, Sense, Ui};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum CellSize {
|
||||
/// Absolute size in points
|
||||
Absolute(f32),
|
||||
|
||||
/// Take all available space
|
||||
Remainder,
|
||||
}
|
||||
|
||||
/// Cells are positioned in two dimensions, cells go in one direction and form lines.
|
||||
///
|
||||
/// In a strip there's only one line which goes in the direction of the strip:
|
||||
///
|
||||
/// In a horizontal strip, a [`StripLayout`] with horizontal [`CellDirection`] is used.
|
||||
/// Its cells go from left to right inside this [`StripLayout`].
|
||||
///
|
||||
/// In a table there's a [`StripLayout`] for each table row with a horizontal [`CellDirection`].
|
||||
/// Its cells go from left to right. And the lines go from top to bottom.
|
||||
pub(crate) enum CellDirection {
|
||||
/// Cells go from left to right.
|
||||
Horizontal,
|
||||
|
||||
/// Cells go from top to bottom.
|
||||
Vertical,
|
||||
}
|
||||
|
||||
/// Positions cells in [`CellDirection`] and starts a new line on [`StripLayout::end_line`]
|
||||
pub struct StripLayout<'l> {
|
||||
pub(crate) ui: &'l mut Ui,
|
||||
direction: CellDirection,
|
||||
pub(crate) rect: Rect,
|
||||
pub(crate) cursor: Pos2,
|
||||
/// Keeps track of the max used position,
|
||||
/// so we know how much space we used.
|
||||
max: Pos2,
|
||||
cell_layout: egui::Layout,
|
||||
}
|
||||
|
||||
impl<'l> StripLayout<'l> {
|
||||
pub(crate) fn new(ui: &'l mut Ui, direction: CellDirection, cell_layout: egui::Layout) -> Self {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
let pos = rect.left_top();
|
||||
|
||||
Self {
|
||||
ui,
|
||||
direction,
|
||||
rect,
|
||||
cursor: pos,
|
||||
max: pos,
|
||||
cell_layout,
|
||||
}
|
||||
}
|
||||
|
||||
fn cell_rect(&self, width: &CellSize, height: &CellSize) -> Rect {
|
||||
Rect {
|
||||
min: self.cursor,
|
||||
max: Pos2 {
|
||||
x: match width {
|
||||
CellSize::Absolute(width) => self.cursor.x + width,
|
||||
CellSize::Remainder => self.rect.right(),
|
||||
},
|
||||
y: match height {
|
||||
CellSize::Absolute(height) => self.cursor.y + height,
|
||||
CellSize::Remainder => self.rect.bottom(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn set_pos(&mut self, rect: Rect) {
|
||||
self.max.x = self.max.x.max(rect.right());
|
||||
self.max.y = self.max.y.max(rect.bottom());
|
||||
|
||||
match self.direction {
|
||||
CellDirection::Horizontal => {
|
||||
self.cursor.x = rect.right() + self.ui.spacing().item_spacing.x;
|
||||
}
|
||||
CellDirection::Vertical => {
|
||||
self.cursor.y = rect.bottom() + self.ui.spacing().item_spacing.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn empty(&mut self, width: CellSize, height: CellSize) {
|
||||
self.set_pos(self.cell_rect(&width, &height));
|
||||
}
|
||||
|
||||
/// This is the innermost part of [`crate::Table`] and [`crate::Strip`].
|
||||
///
|
||||
/// Return the used space (`min_rect`) plus the [`Response`] of the whole cell.
|
||||
pub(crate) fn add(
|
||||
&mut self,
|
||||
clip: bool,
|
||||
striped: bool,
|
||||
width: CellSize,
|
||||
height: CellSize,
|
||||
add_cell_contents: impl FnOnce(&mut Ui),
|
||||
) -> (Rect, Response) {
|
||||
let max_rect = self.cell_rect(&width, &height);
|
||||
|
||||
if striped {
|
||||
// Make sure we don't have a gap in the stripe background:
|
||||
let stripe_rect = max_rect.expand2(0.5 * self.ui.spacing().item_spacing);
|
||||
|
||||
self.ui
|
||||
.painter()
|
||||
.rect_filled(stripe_rect, 0.0, self.ui.visuals().faint_bg_color);
|
||||
}
|
||||
|
||||
let used_rect = self.cell(clip, max_rect, add_cell_contents);
|
||||
|
||||
self.set_pos(max_rect);
|
||||
|
||||
let allocation_rect = if clip {
|
||||
max_rect
|
||||
} else {
|
||||
max_rect.union(used_rect)
|
||||
};
|
||||
|
||||
let response = self.ui.allocate_rect(allocation_rect, Sense::hover());
|
||||
|
||||
(used_rect, response)
|
||||
}
|
||||
|
||||
/// only needed for layouts with multiple lines, like [`Table`](crate::Table).
|
||||
pub fn end_line(&mut self) {
|
||||
match self.direction {
|
||||
CellDirection::Horizontal => {
|
||||
self.cursor.y = self.max.y + self.ui.spacing().item_spacing.y;
|
||||
self.cursor.x = self.rect.left();
|
||||
}
|
||||
CellDirection::Vertical => {
|
||||
self.cursor.x = self.max.x + self.ui.spacing().item_spacing.x;
|
||||
self.cursor.y = self.rect.top();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Skip a lot of space.
|
||||
pub(crate) fn skip_space(&mut self, delta: egui::Vec2) {
|
||||
let before = self.cursor;
|
||||
self.cursor += delta;
|
||||
let rect = Rect::from_two_pos(before, self.cursor);
|
||||
self.ui.allocate_rect(rect, Sense::hover());
|
||||
}
|
||||
|
||||
fn cell(&mut self, clip: bool, rect: Rect, add_cell_contents: impl FnOnce(&mut Ui)) -> Rect {
|
||||
let mut child_ui = self.ui.child_ui(rect, self.cell_layout);
|
||||
|
||||
if clip {
|
||||
let margin = egui::Vec2::splat(self.ui.visuals().clip_rect_margin);
|
||||
let margin = margin.min(0.5 * self.ui.spacing().item_spacing);
|
||||
let clip_rect = rect.expand2(margin);
|
||||
child_ui.set_clip_rect(clip_rect.intersect(child_ui.clip_rect()));
|
||||
}
|
||||
|
||||
add_cell_contents(&mut child_ui);
|
||||
child_ui.min_rect()
|
||||
}
|
||||
|
||||
/// Allocate the rect in [`Self::ui`] so that the scrollview knows about our size
|
||||
pub fn allocate_rect(&mut self) -> Response {
|
||||
let mut rect = self.rect;
|
||||
rect.set_right(self.max.x);
|
||||
rect.set_bottom(self.max.y);
|
||||
|
||||
self.ui.allocate_rect(rect, Sense::hover())
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
//! 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`.
|
||||
//!
|
||||
//! ## Feature flags
|
||||
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
|
||||
//!
|
||||
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
mod datepicker;
|
||||
|
||||
pub mod image;
|
||||
mod layout;
|
||||
mod sizing;
|
||||
mod strip;
|
||||
mod table;
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
pub use crate::datepicker::DatePickerButton;
|
||||
|
||||
pub use crate::image::RetainedImage;
|
||||
pub(crate) use crate::layout::StripLayout;
|
||||
pub use crate::sizing::Size;
|
||||
pub use crate::strip::*;
|
||||
pub use crate::table::*;
|
||||
|
||||
/// Log an error with either `tracing` or `eprintln`
|
||||
macro_rules! log_err {
|
||||
($fmt: literal, $($arg: tt)*) => {{
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::error!($fmt, $($arg)*);
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
eprintln!(
|
||||
concat!("egui_extras: ", $fmt), $($arg)*
|
||||
);
|
||||
}};
|
||||
}
|
||||
pub(crate) use log_err;
|
||||
|
||||
/// Panic in debug builds, log otherwise.
|
||||
macro_rules! log_or_panic {
|
||||
($fmt: literal, $($arg: tt)*) => {{
|
||||
if cfg!(debug_assertions) {
|
||||
panic!($fmt, $($arg)*);
|
||||
} else {
|
||||
$crate::log_err!($fmt, $($arg)*);
|
||||
}
|
||||
}};
|
||||
}
|
||||
pub(crate) use log_or_panic;
|
|
@ -1,177 +0,0 @@
|
|||
/// Size hint for table column/strip cell.
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub enum Size {
|
||||
/// Absolute size in points, with a given range of allowed sizes to resize within.
|
||||
Absolute { initial: f32, range: (f32, f32) },
|
||||
|
||||
/// Relative size relative to all available space.
|
||||
Relative { fraction: f32, range: (f32, f32) },
|
||||
|
||||
/// Multiple remainders each get the same space.
|
||||
Remainder { range: (f32, f32) },
|
||||
}
|
||||
|
||||
impl Size {
|
||||
/// Exactly this big, with no room for resize.
|
||||
pub fn exact(points: f32) -> Self {
|
||||
Self::Absolute {
|
||||
initial: points,
|
||||
range: (points, points),
|
||||
}
|
||||
}
|
||||
|
||||
/// Initially this big, but can resize.
|
||||
pub fn initial(points: f32) -> Self {
|
||||
Self::Absolute {
|
||||
initial: points,
|
||||
range: (0.0, f32::INFINITY),
|
||||
}
|
||||
}
|
||||
|
||||
/// Relative size relative to all available space. Values must be in range `0.0..=1.0`.
|
||||
pub fn relative(fraction: f32) -> Self {
|
||||
egui::egui_assert!(0.0 <= fraction && fraction <= 1.0);
|
||||
Self::Relative {
|
||||
fraction,
|
||||
range: (0.0, f32::INFINITY),
|
||||
}
|
||||
}
|
||||
|
||||
/// Multiple remainders each get the same space.
|
||||
pub fn remainder() -> Self {
|
||||
Self::Remainder {
|
||||
range: (0.0, f32::INFINITY),
|
||||
}
|
||||
}
|
||||
|
||||
/// Won't shrink below this size (in points).
|
||||
pub fn at_least(mut self, minimum: f32) -> Self {
|
||||
match &mut self {
|
||||
Self::Absolute { range, .. }
|
||||
| Self::Relative { range, .. }
|
||||
| Self::Remainder { range, .. } => {
|
||||
range.0 = minimum;
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Won't grow above this size (in points).
|
||||
pub fn at_most(mut self, maximum: f32) -> Self {
|
||||
match &mut self {
|
||||
Self::Absolute { range, .. }
|
||||
| Self::Relative { range, .. }
|
||||
| Self::Remainder { range, .. } => {
|
||||
range.1 = maximum;
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Allowed range of movement (in points), if in a resizable [`Table`](crate::table::Table).
|
||||
pub fn range(self) -> (f32, f32) {
|
||||
match self {
|
||||
Self::Absolute { range, .. }
|
||||
| Self::Relative { range, .. }
|
||||
| Self::Remainder { range, .. } => range,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Sizing {
|
||||
pub(crate) sizes: Vec<Size>,
|
||||
}
|
||||
|
||||
impl Sizing {
|
||||
pub fn add(&mut self, size: Size) {
|
||||
self.sizes.push(size);
|
||||
}
|
||||
|
||||
pub fn to_lengths(&self, length: f32, spacing: f32) -> Vec<f32> {
|
||||
if self.sizes.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let mut remainders = 0;
|
||||
let sum_non_remainder = self
|
||||
.sizes
|
||||
.iter()
|
||||
.map(|&size| match size {
|
||||
Size::Absolute { initial, .. } => initial,
|
||||
Size::Relative {
|
||||
fraction,
|
||||
range: (min, max),
|
||||
} => {
|
||||
assert!(0.0 <= fraction && fraction <= 1.0);
|
||||
(length * fraction).clamp(min, max)
|
||||
}
|
||||
Size::Remainder { .. } => {
|
||||
remainders += 1;
|
||||
0.0
|
||||
}
|
||||
})
|
||||
.sum::<f32>()
|
||||
+ spacing * (self.sizes.len() - 1) as f32;
|
||||
|
||||
let avg_remainder_length = if remainders == 0 {
|
||||
0.0
|
||||
} else {
|
||||
let mut remainder_length = length - sum_non_remainder;
|
||||
let avg_remainder_length = 0.0f32.max(remainder_length / remainders as f32).floor();
|
||||
self.sizes.iter().for_each(|&size| {
|
||||
if let Size::Remainder { range: (min, _max) } = size {
|
||||
if avg_remainder_length < min {
|
||||
remainder_length -= min;
|
||||
remainders -= 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
if remainders > 0 {
|
||||
0.0f32.max(remainder_length / remainders as f32)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
|
||||
self.sizes
|
||||
.iter()
|
||||
.map(|&size| match size {
|
||||
Size::Absolute { initial, .. } => initial,
|
||||
Size::Relative {
|
||||
fraction,
|
||||
range: (min, max),
|
||||
} => (length * fraction).clamp(min, max),
|
||||
Size::Remainder { range: (min, max) } => avg_remainder_length.clamp(min, max),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Size>> for Sizing {
|
||||
fn from(sizes: Vec<Size>) -> Self {
|
||||
Self { sizes }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sizing() {
|
||||
let sizing: Sizing = vec![].into();
|
||||
assert_eq!(sizing.to_lengths(50.0, 0.0), vec![]);
|
||||
|
||||
let sizing: Sizing = vec![Size::remainder().at_least(20.0), Size::remainder()].into();
|
||||
assert_eq!(sizing.to_lengths(50.0, 0.0), vec![25.0, 25.0]);
|
||||
assert_eq!(sizing.to_lengths(30.0, 0.0), vec![20.0, 10.0]);
|
||||
assert_eq!(sizing.to_lengths(20.0, 0.0), vec![20.0, 0.0]);
|
||||
assert_eq!(sizing.to_lengths(10.0, 0.0), vec![20.0, 0.0]);
|
||||
assert_eq!(sizing.to_lengths(20.0, 10.0), vec![20.0, 0.0]);
|
||||
assert_eq!(sizing.to_lengths(30.0, 10.0), vec![20.0, 0.0]);
|
||||
assert_eq!(sizing.to_lengths(40.0, 10.0), vec![20.0, 10.0]);
|
||||
assert_eq!(sizing.to_lengths(110.0, 10.0), vec![50.0, 50.0]);
|
||||
|
||||
let sizing: Sizing = vec![Size::relative(0.5).at_least(10.0), Size::exact(10.0)].into();
|
||||
assert_eq!(sizing.to_lengths(50.0, 0.0), vec![25.0, 10.0]);
|
||||
assert_eq!(sizing.to_lengths(30.0, 0.0), vec![15.0, 10.0]);
|
||||
assert_eq!(sizing.to_lengths(20.0, 0.0), vec![10.0, 10.0]);
|
||||
assert_eq!(sizing.to_lengths(10.0, 0.0), vec![10.0, 10.0]);
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,58 +0,0 @@
|
|||
[package]
|
||||
name = "egui_glium"
|
||||
version = "0.21.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Bindings for using egui natively using the glium library"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glium"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui/tree/master/crates/egui_glium"
|
||||
categories = ["gui", "game-development"]
|
||||
keywords = ["glium", "egui", "gui", "gamedev"]
|
||||
include = [
|
||||
"../LICENSE-APACHE",
|
||||
"../LICENSE-MIT",
|
||||
"**/*.rs",
|
||||
"Cargo.toml",
|
||||
"src/shader/*.glsl",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
|
||||
[features]
|
||||
default = ["clipboard", "links"]
|
||||
|
||||
## Enable cut/copy/paste to OS clipboard.
|
||||
##
|
||||
## If disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
||||
clipboard = ["egui-winit/clipboard"]
|
||||
|
||||
## Enable opening links in a browser when an egui hyperlink is clicked.
|
||||
links = ["egui-winit/links"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
|
||||
"bytemuck",
|
||||
] }
|
||||
egui-winit = { version = "0.21.1", path = "../egui-winit", default-features = false }
|
||||
|
||||
ahash = { version = "0.8.1", default-features = false, features = [
|
||||
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
|
||||
"std",
|
||||
] }
|
||||
bytemuck = "1.7"
|
||||
glium = "0.32"
|
||||
|
||||
#! ### Optional dependencies
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
egui_demo_lib = { version = "0.21.0", path = "../egui_demo_lib", default-features = false }
|
||||
image = { version = "0.24", default-features = false, features = ["png"] }
|
|
@ -1,31 +0,0 @@
|
|||
#version 120
|
||||
|
||||
uniform sampler2D u_sampler;
|
||||
varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA
|
||||
varying vec2 v_tc;
|
||||
|
||||
// 0-255 sRGB from 0-1 linear
|
||||
vec3 srgb_from_linear(vec3 rgb) {
|
||||
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
|
||||
vec3 lower = rgb * vec3(3294.6);
|
||||
vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025);
|
||||
return mix(higher, lower, vec3(cutoff));
|
||||
}
|
||||
|
||||
// 0-255 sRGBA from 0-1 linear
|
||||
vec4 srgba_from_linear(vec4 rgba) {
|
||||
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
|
||||
}
|
||||
|
||||
// 0-1 gamma from 0-1 linear
|
||||
vec4 gamma_from_linear_rgba(vec4 linear_rgba) {
|
||||
return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// The texture is set up with `SRGB8_ALPHA8`
|
||||
vec4 texture_in_gamma = gamma_from_linear_rgba(texture2D(u_sampler, v_tc));
|
||||
|
||||
// Multiply vertex color with texture color (in gamma space).
|
||||
gl_FragColor = v_rgba_gamma * texture_in_gamma;
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
#version 140
|
||||
|
||||
uniform sampler2D u_sampler;
|
||||
in vec4 v_rgba_gamma;
|
||||
in vec2 v_tc;
|
||||
out vec4 f_color;
|
||||
|
||||
// 0-255 sRGB from 0-1 linear
|
||||
vec3 srgb_from_linear(vec3 rgb) {
|
||||
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
|
||||
vec3 lower = rgb * vec3(3294.6);
|
||||
vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025);
|
||||
return mix(higher, lower, vec3(cutoff));
|
||||
}
|
||||
|
||||
// 0-255 sRGBA from 0-1 linear
|
||||
vec4 srgba_from_linear(vec4 rgba) {
|
||||
return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a);
|
||||
}
|
||||
|
||||
// 0-1 gamma from 0-1 linear
|
||||
vec4 gamma_from_linear_rgba(vec4 linear_rgba) {
|
||||
return vec4(srgb_from_linear(linear_rgba.rgb) / 255.0, linear_rgba.a);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// The texture is set up with `SRGB8_ALPHA8`
|
||||
vec4 texture_in_gamma = gamma_from_linear_rgba(texture(u_sampler, v_tc));
|
||||
|
||||
// Multiply vertex color with texture color (in gamma space).
|
||||
f_color = v_rgba_gamma * texture_in_gamma;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
#version 100
|
||||
|
||||
precision mediump float;
|
||||
uniform vec2 u_screen_size;
|
||||
attribute vec2 a_pos;
|
||||
attribute vec2 a_tc;
|
||||
attribute vec4 a_srgba;
|
||||
varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA
|
||||
varying vec2 v_tc;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(
|
||||
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
||||
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
||||
0.0,
|
||||
1.0);
|
||||
v_rgba_gamma = a_srgba / 255.0;
|
||||
v_tc = a_tc;
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
#version 120
|
||||
|
||||
uniform vec2 u_screen_size;
|
||||
attribute vec2 a_pos;
|
||||
attribute vec4 a_srgba; // 0-255 sRGB
|
||||
attribute vec2 a_tc;
|
||||
varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA
|
||||
varying vec2 v_tc;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(
|
||||
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
||||
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
||||
0.0,
|
||||
1.0);
|
||||
v_rgba_gamma = a_srgba / 255.0;
|
||||
v_tc = a_tc;
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
#version 140
|
||||
|
||||
uniform vec2 u_screen_size;
|
||||
in vec2 a_pos;
|
||||
in vec4 a_srgba; // 0-255 sRGB
|
||||
in vec2 a_tc;
|
||||
out vec4 v_rgba_gamma;
|
||||
out vec2 v_tc;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(
|
||||
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
||||
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
||||
0.0,
|
||||
1.0);
|
||||
v_rgba_gamma = a_srgba / 255.0;
|
||||
v_tc = a_tc;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
#version 300 es
|
||||
|
||||
precision mediump float;
|
||||
uniform vec2 u_screen_size;
|
||||
attribute vec2 a_pos;
|
||||
attribute vec2 a_tc;
|
||||
attribute vec4 a_srgba;
|
||||
varying vec4 v_rgba_gamma; // 0-1 gamma sRGBA
|
||||
varying vec2 v_tc;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(
|
||||
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
||||
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
||||
0.0,
|
||||
1.0);
|
||||
v_rgba_gamma = a_srgba / 255.0;
|
||||
v_tc = a_tc;
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
# Changelog for egui_glow
|
||||
All notable changes to the `egui_glow` integration will be noted in this file.
|
||||
|
||||
|
||||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08
|
||||
* Update to `glow` 0.12 ([#2695](https://github.com/emilk/egui/pull/2695)).
|
||||
* Remove the `screen_reader` feature ([#2669](https://github.com/emilk/egui/pull/2669)).
|
||||
|
||||
|
||||
## 0.20.1 - 2022-12-11
|
||||
* Fix [docs.rs](https://docs.rs/egui_glow) build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08
|
||||
* Allow empty textures.
|
||||
* Added `shader_version` variable on `EguiGlow::new` for easier cross compilling on different OpenGL | ES targets ([#1993](https://github.com/emilk/egui/pull/1993)).
|
||||
|
||||
|
||||
## 0.19.0 - 2022-08-20
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
|
||||
* `EguiGlow::new` now takes an `EventLoopWindowTarget<E>` instead of a `winit::Window` ([#1634](https://github.com/emilk/egui/pull/1634)).
|
||||
* Use `Arc` for `glow::Context` instead of `Rc` ([#1640](https://github.com/emilk/egui/pull/1640)).
|
||||
* Fixed `glClear` on WebGL1 ([#1658](https://github.com/emilk/egui/pull/1658)).
|
||||
* Add `Painter::intermediate_fbo` which tells callbacks where to render. This is only needed if the callbacks use their own FBO:s and need to know what to restore to.
|
||||
|
||||
|
||||
## 0.18.1 - 2022-05-05
|
||||
* Remove calls to `gl.get_error` in release builds to speed up rendering ([#1583](https://github.com/emilk/egui/pull/1583)).
|
||||
|
||||
|
||||
## 0.18.0 - 2022-04-30
|
||||
* Improved logging on rendering failures.
|
||||
* Added new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
|
||||
* Fixed potential scale bug when DPI scaling changes (e.g. when dragging a window between different displays) ([#1441](https://github.com/emilk/egui/pull/1441)).
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* `clipboard`, `links`, `winit` are now all opt-in features ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* Added new feature `puffin` to add [`puffin profiler`](https://github.com/EmbarkStudios/puffin) scopes ([#1483](https://github.com/emilk/egui/pull/1483)).
|
||||
* Removed the features `dark-light`, `default_fonts` and `persistence` ([#1542](https://github.com/emilk/egui/pull/1542)).
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
* `EguiGlow::run` no longer returns the shapes to paint, but stores them internally until you call `EguiGlow::paint` ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
* Added `set_texture_filter` method to `Painter` ([#1041](https://github.com/emilk/egui/pull/1041)).
|
||||
* Fixed failure to run in Chrome ([#1092](https://github.com/emilk/egui/pull/1092)).
|
||||
* `EguiGlow::new` and `EguiGlow::paint` now takes `&winit::Window` ([#1151](https://github.com/emilk/egui/pull/1151)).
|
||||
* Automatically detect and apply dark or light mode from system ([#1045](https://github.com/emilk/egui/pull/1045)).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
* Made winit/glutin an optional dependency ([#868](https://github.com/emilk/egui/pull/868)).
|
||||
* Simplified `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)).
|
||||
* Removed `EguiGlow::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)).
|
||||
* Updated `glutin` to 0.28 ([#930](https://github.com/emilk/egui/pull/930)).
|
||||
* Changed the `Painter` interface slightly ([#999](https://github.com/emilk/egui/pull/999)).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
`egui_glow` has been newly created, with feature parity to `egui_glium`.
|
||||
|
||||
As `glow` is a set of lower-level bindings to OpenGL, this crate is potentially less stable than `egui_glium`,
|
||||
but hopefully this will one day replace `egui_glium` as the default backend for `eframe`.
|
|
@ -1,79 +0,0 @@
|
|||
[package]
|
||||
name = "egui_glow"
|
||||
version = "0.21.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Bindings for using egui natively using the glow library"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/crates/egui_glow"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui/tree/master/crates/egui_glow"
|
||||
categories = ["gui", "game-development"]
|
||||
keywords = ["glow", "egui", "gui", "gamedev"]
|
||||
include = [
|
||||
"../LICENSE-APACHE",
|
||||
"../LICENSE-MIT",
|
||||
"**/*.rs",
|
||||
"Cargo.toml",
|
||||
"src/shader/*.glsl",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
## For the `winit` integration:
|
||||
## enable cut/copy/paste to os clipboard.
|
||||
##
|
||||
## if disabled a clipboard will be simulated so you can still copy/paste within the egui app.
|
||||
clipboard = ["egui-winit?/clipboard"]
|
||||
|
||||
## For the `winit` integration:
|
||||
## enable opening links in a browser when an egui hyperlink is clicked.
|
||||
links = ["egui-winit?/links"]
|
||||
|
||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
||||
puffin = ["dep:puffin", "egui-winit?/puffin"]
|
||||
|
||||
## Enable [`winit`](https://docs.rs/winit) integration.
|
||||
winit = ["egui-winit"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
|
||||
"bytemuck",
|
||||
] }
|
||||
|
||||
bytemuck = "1.7"
|
||||
glow = "0.12"
|
||||
memoffset = "0.6"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
#! ### Optional dependencies
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
# Native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
egui-winit = { version = "0.21.1", path = "../egui-winit", optional = true, default-features = false }
|
||||
puffin = { version = "0.14", optional = true }
|
||||
|
||||
# Web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
web-sys = { version = "0.3", features = ["console"] }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
glutin = "0.30" # examples/pure_glow
|
||||
raw-window-handle = "0.5.0"
|
||||
glutin-winit = "0.3.0"
|
||||
|
||||
|
||||
[[example]]
|
||||
name = "pure_glow"
|
||||
required-features = ["winit", "egui/default_fonts"]
|
|
@ -1,255 +0,0 @@
|
|||
//! Example how to use pure `egui_glow`.
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use egui_winit::winit;
|
||||
|
||||
/// The majority of `GlutinWindowContext` is taken from `eframe`
|
||||
struct GlutinWindowContext {
|
||||
window: winit::window::Window,
|
||||
gl_context: glutin::context::PossiblyCurrentContext,
|
||||
gl_display: glutin::display::Display,
|
||||
gl_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
|
||||
}
|
||||
|
||||
impl GlutinWindowContext {
|
||||
// refactor this function to use `glutin-winit` crate eventually.
|
||||
// preferably add android support at the same time.
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn new(event_loop: &winit::event_loop::EventLoopWindowTarget<()>) -> Self {
|
||||
use egui::NumExt;
|
||||
use glutin::context::NotCurrentGlContextSurfaceAccessor;
|
||||
use glutin::display::GetGlDisplay;
|
||||
use glutin::display::GlDisplay;
|
||||
use glutin::prelude::GlSurface;
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
let winit_window_builder = winit::window::WindowBuilder::new()
|
||||
.with_resizable(true)
|
||||
.with_inner_size(winit::dpi::LogicalSize {
|
||||
width: 800.0,
|
||||
height: 600.0,
|
||||
})
|
||||
.with_title("egui_glow example") // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
.with_visible(false);
|
||||
|
||||
let config_template_builder = glutin::config::ConfigTemplateBuilder::new()
|
||||
.prefer_hardware_accelerated(None)
|
||||
.with_depth_size(0)
|
||||
.with_stencil_size(0)
|
||||
.with_transparency(false);
|
||||
|
||||
tracing::debug!("trying to get gl_config");
|
||||
let (mut window, gl_config) =
|
||||
glutin_winit::DisplayBuilder::new() // let glutin-winit helper crate handle the complex parts of opengl context creation
|
||||
.with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
|
||||
.with_window_builder(Some(winit_window_builder.clone()))
|
||||
.build(
|
||||
event_loop,
|
||||
config_template_builder,
|
||||
|mut config_iterator| {
|
||||
config_iterator.next().expect(
|
||||
"failed to find a matching configuration for creating glutin config",
|
||||
)
|
||||
},
|
||||
)
|
||||
.expect("failed to create gl_config");
|
||||
let gl_display = gl_config.display();
|
||||
tracing::debug!("found gl_config: {:?}", &gl_config);
|
||||
|
||||
let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle());
|
||||
tracing::debug!("raw window handle: {:?}", raw_window_handle);
|
||||
let context_attributes =
|
||||
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
|
||||
// by default, glutin will try to create a core opengl context. but, if it is not available, try to create a gl-es context using this fallback attributes
|
||||
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
|
||||
.with_context_api(glutin::context::ContextApi::Gles(None))
|
||||
.build(raw_window_handle);
|
||||
let not_current_gl_context = unsafe {
|
||||
gl_display
|
||||
.create_context(&gl_config, &context_attributes)
|
||||
.unwrap_or_else(|_| {
|
||||
tracing::debug!("failed to create gl_context with attributes: {:?}. retrying with fallback context attributes: {:?}",
|
||||
&context_attributes,
|
||||
&fallback_context_attributes);
|
||||
gl_config
|
||||
.display()
|
||||
.create_context(&gl_config, &fallback_context_attributes)
|
||||
.expect("failed to create context even with fallback attributes")
|
||||
})
|
||||
};
|
||||
|
||||
// this is where the window is created, if it has not been created while searching for suitable gl_config
|
||||
let window = window.take().unwrap_or_else(|| {
|
||||
tracing::debug!("window doesn't exist yet. creating one now with finalize_window");
|
||||
glutin_winit::finalize_window(event_loop, winit_window_builder.clone(), &gl_config)
|
||||
.expect("failed to finalize glutin window")
|
||||
});
|
||||
let (width, height): (u32, u32) = window.inner_size().into();
|
||||
let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap();
|
||||
let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap();
|
||||
let surface_attributes =
|
||||
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
|
||||
.build(window.raw_window_handle(), width, height);
|
||||
tracing::debug!(
|
||||
"creating surface with attributes: {:?}",
|
||||
&surface_attributes
|
||||
);
|
||||
let gl_surface = unsafe {
|
||||
gl_display
|
||||
.create_window_surface(&gl_config, &surface_attributes)
|
||||
.unwrap()
|
||||
};
|
||||
tracing::debug!("surface created successfully: {gl_surface:?}.making context current");
|
||||
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
|
||||
|
||||
gl_surface
|
||||
.set_swap_interval(
|
||||
&gl_context,
|
||||
glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
GlutinWindowContext {
|
||||
window,
|
||||
gl_context,
|
||||
gl_display,
|
||||
gl_surface,
|
||||
}
|
||||
}
|
||||
|
||||
fn window(&self) -> &winit::window::Window {
|
||||
&self.window
|
||||
}
|
||||
|
||||
fn resize(&self, physical_size: winit::dpi::PhysicalSize<u32>) {
|
||||
use glutin::surface::GlSurface;
|
||||
self.gl_surface.resize(
|
||||
&self.gl_context,
|
||||
physical_size.width.try_into().unwrap(),
|
||||
physical_size.height.try_into().unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
fn swap_buffers(&self) -> glutin::error::Result<()> {
|
||||
use glutin::surface::GlSurface;
|
||||
self.gl_surface.swap_buffers(&self.gl_context)
|
||||
}
|
||||
|
||||
fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void {
|
||||
use glutin::display::GlDisplay;
|
||||
self.gl_display.get_proc_address(addr)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut clear_color = [0.1, 0.1, 0.1];
|
||||
|
||||
let event_loop = winit::event_loop::EventLoopBuilder::with_user_event().build();
|
||||
let (gl_window, gl) = create_display(&event_loop);
|
||||
let gl = std::sync::Arc::new(gl);
|
||||
|
||||
let mut egui_glow = egui_glow::EguiGlow::new(&event_loop, gl.clone(), None);
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
let mut redraw = || {
|
||||
let mut quit = false;
|
||||
|
||||
let repaint_after = egui_glow.run(gl_window.window(), |egui_ctx| {
|
||||
egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| {
|
||||
ui.heading("Hello World!");
|
||||
if ui.button("Quit").clicked() {
|
||||
quit = true;
|
||||
}
|
||||
ui.color_edit_button_rgb(&mut clear_color);
|
||||
});
|
||||
});
|
||||
|
||||
*control_flow = if quit {
|
||||
winit::event_loop::ControlFlow::Exit
|
||||
} else if repaint_after.is_zero() {
|
||||
gl_window.window().request_redraw();
|
||||
winit::event_loop::ControlFlow::Poll
|
||||
} else if let Some(repaint_after_instant) =
|
||||
std::time::Instant::now().checked_add(repaint_after)
|
||||
{
|
||||
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
|
||||
} else {
|
||||
winit::event_loop::ControlFlow::Wait
|
||||
};
|
||||
|
||||
{
|
||||
unsafe {
|
||||
use glow::HasContext as _;
|
||||
gl.clear_color(clear_color[0], clear_color[1], clear_color[2], 1.0);
|
||||
gl.clear(glow::COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
// draw things behind egui here
|
||||
|
||||
egui_glow.paint(gl_window.window());
|
||||
|
||||
// draw things on top of egui here
|
||||
|
||||
gl_window.swap_buffers().unwrap();
|
||||
gl_window.window().set_visible(true);
|
||||
}
|
||||
};
|
||||
|
||||
match event {
|
||||
// Platform-dependent event handlers to workaround a winit bug
|
||||
// See: https://github.com/rust-windowing/winit/issues/987
|
||||
// See: https://github.com/rust-windowing/winit/issues/1619
|
||||
winit::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(),
|
||||
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(),
|
||||
|
||||
winit::event::Event::WindowEvent { event, .. } => {
|
||||
use winit::event::WindowEvent;
|
||||
if matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed) {
|
||||
*control_flow = winit::event_loop::ControlFlow::Exit;
|
||||
}
|
||||
|
||||
if let winit::event::WindowEvent::Resized(physical_size) = &event {
|
||||
gl_window.resize(*physical_size);
|
||||
} else if let winit::event::WindowEvent::ScaleFactorChanged {
|
||||
new_inner_size, ..
|
||||
} = &event
|
||||
{
|
||||
gl_window.resize(**new_inner_size);
|
||||
}
|
||||
|
||||
let event_response = egui_glow.on_event(&event);
|
||||
|
||||
if event_response.repaint {
|
||||
gl_window.window().request_redraw();
|
||||
}
|
||||
}
|
||||
winit::event::Event::LoopDestroyed => {
|
||||
egui_glow.destroy();
|
||||
}
|
||||
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {
|
||||
..
|
||||
}) => {
|
||||
gl_window.window().request_redraw();
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn create_display(
|
||||
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
|
||||
) -> (GlutinWindowContext, glow::Context) {
|
||||
let glutin_window_context = unsafe { GlutinWindowContext::new(event_loop) };
|
||||
let gl = unsafe {
|
||||
glow::Context::from_loader_function(|s| {
|
||||
let s = std::ffi::CString::new(s)
|
||||
.expect("failed to construct C string from string for gl proc address");
|
||||
|
||||
glutin_window_context.get_proc_address(&s)
|
||||
})
|
||||
};
|
||||
|
||||
(glutin_window_context, gl)
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
uniform sampler2D u_sampler;
|
||||
|
||||
#if NEW_SHADER_INTERFACE
|
||||
in vec4 v_rgba_in_gamma;
|
||||
in vec2 v_tc;
|
||||
out vec4 f_color;
|
||||
// a dirty hack applied to support webGL2
|
||||
#define gl_FragColor f_color
|
||||
#define texture2D texture
|
||||
#else
|
||||
varying vec4 v_rgba_in_gamma;
|
||||
varying vec2 v_tc;
|
||||
#endif
|
||||
|
||||
// 0-1 sRGB gamma from 0-1 linear
|
||||
vec3 srgb_gamma_from_linear(vec3 rgb) {
|
||||
bvec3 cutoff = lessThan(rgb, vec3(0.0031308));
|
||||
vec3 lower = rgb * vec3(12.92);
|
||||
vec3 higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055);
|
||||
return mix(higher, lower, vec3(cutoff));
|
||||
}
|
||||
|
||||
// 0-1 sRGBA gamma from 0-1 linear
|
||||
vec4 srgba_gamma_from_linear(vec4 rgba) {
|
||||
return vec4(srgb_gamma_from_linear(rgba.rgb), rgba.a);
|
||||
}
|
||||
|
||||
void main() {
|
||||
#if SRGB_TEXTURES
|
||||
vec4 texture_in_gamma = srgba_gamma_from_linear(texture2D(u_sampler, v_tc));
|
||||
#else
|
||||
vec4 texture_in_gamma = texture2D(u_sampler, v_tc);
|
||||
#endif
|
||||
|
||||
// We multiply the colors in gamma space, because that's the only way to get text to look right.
|
||||
gl_FragColor = v_rgba_in_gamma * texture_in_gamma;
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
#if NEW_SHADER_INTERFACE
|
||||
#define I in
|
||||
#define O out
|
||||
#define V(x) x
|
||||
#else
|
||||
#define I attribute
|
||||
#define O varying
|
||||
#define V(x) vec3(x)
|
||||
#endif
|
||||
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
uniform vec2 u_screen_size;
|
||||
I vec2 a_pos;
|
||||
I vec4 a_srgba; // 0-255 sRGB
|
||||
I vec2 a_tc;
|
||||
O vec4 v_rgba_in_gamma;
|
||||
O vec2 v_tc;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(
|
||||
2.0 * a_pos.x / u_screen_size.x - 1.0,
|
||||
1.0 - 2.0 * a_pos.y / u_screen_size.y,
|
||||
0.0,
|
||||
1.0);
|
||||
v_rgba_in_gamma = a_srgba / 255.0;
|
||||
v_tc = a_tc;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
`egui_web` used to be a standalone crate, but has now been moved into [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe).
|
|
@ -1,44 +0,0 @@
|
|||
[package]
|
||||
name = "emath"
|
||||
version = "0.21.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Minimal 2D math library for GUI work"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/crates/emath"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui/tree/master/crates/emath"
|
||||
categories = ["mathematics", "gui"]
|
||||
keywords = ["math", "gui"]
|
||||
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
## Enable additional checks if debug assertions are enabled (debug builds).
|
||||
extra_debug_asserts = []
|
||||
## Always enable additional checks.
|
||||
extra_asserts = []
|
||||
|
||||
|
||||
[dependencies]
|
||||
#! ### Optional dependencies
|
||||
|
||||
## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast `emath` types to `&[u8]`.
|
||||
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
|
||||
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
## [`mint`](https://docs.rs/mint) enables interopability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra).
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
|
||||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
|
@ -1,11 +0,0 @@
|
|||
# emath - egui math library
|
||||
|
||||
[](https://crates.io/crates/emath)
|
||||
[](https://docs.rs/emath)
|
||||
[](https://github.com/rust-secure-code/safety-dance/)
|
||||

|
||||

|
||||
|
||||
A bare-bones 2D math library with types and functions useful for GUI building.
|
||||
|
||||
Made for [`egui`](https://github.com/emilk/egui/).
|
|
@ -1,81 +0,0 @@
|
|||
# epaint changelog
|
||||
All notable changes to the epaint crate will be documented in this file.
|
||||
|
||||
|
||||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08
|
||||
* Improve the look of thin white lines ([#2437](https://github.com/emilk/egui/pull/2437)).
|
||||
* Don't render `\r` (Carriage Return) ([#2452](https://github.com/emilk/egui/pull/2452)).
|
||||
* Fix bug in `Mesh::split_to_u16` ([#2459](https://github.com/emilk/egui/pull/2459)).
|
||||
* Improve rendering of very thin rectangles.
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08
|
||||
* ⚠️ BREAKING: Fix text being too small ([#2069](https://github.com/emilk/egui/pull/2069)).
|
||||
* ⚠️ BREAKING: epaint now expects integrations to do all color blending in gamma space ([#2071](https://github.com/emilk/egui/pull/2071)).
|
||||
* Improve mixed CJK/Latin line-breaking ([#1986](https://github.com/emilk/egui/pull/1986)).
|
||||
* Added `Fonts::has_glyph(s)` for querying if a glyph is supported ([#2202](https://github.com/emilk/egui/pull/2202)).
|
||||
* Added support for [thin space](https://en.wikipedia.org/wiki/Thin_space).
|
||||
* Split out color into its own crate, `ecolor` ([#2399](https://github.com/emilk/egui/pull/2399)).
|
||||
|
||||
|
||||
## 0.19.0 - 2022-08-20
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.61.0` ([#1846](https://github.com/emilk/egui/pull/1846)).
|
||||
* Added `epaint::hex_color!` to create `Color32`'s from hex strings under the `color-hex` feature ([#1596](https://github.com/emilk/egui/pull/1596)).
|
||||
* Optimize tessellation of filled circles by 10x or more ([#1616](https://github.com/emilk/egui/pull/1616)).
|
||||
* Added opt-in feature `deadlock_detection` to detect double-lock of mutexes on the same thread ([#1619](https://github.com/emilk/egui/pull/1619)).
|
||||
* Texture loading now takes a `TextureOptions` with minification and magnification filters ([#2224](https://github.com/emilk/egui/pull/2224)).
|
||||
|
||||
|
||||
## 0.18.1 - 2022-05-01
|
||||
* Change `Shape::Callback` from `&dyn Any` to `&mut dyn Any` to support more backends.
|
||||
|
||||
|
||||
## 0.18.0 - 2022-04-30
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.60.0` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* Added `Shape::Callback` for backend-specific painting ([#1351](https://github.com/emilk/egui/pull/1351)).
|
||||
* Added more text wrapping options ([#1291](https://github.com/emilk/egui/pull/1291)):
|
||||
* Added `TextWrapping` struct containing all wrapping options.
|
||||
* Added `LayoutJob::wrap` field containing these options.
|
||||
* Moved `LayoutJob::wrap_width` to `TextWrapping::max_width`.
|
||||
* Added `TextWrapping::max_rows` to limit amount of rows the text should have.
|
||||
* Added `TextWrapping::break_anywhere` to control should the text break at appropriate places or not.
|
||||
* Added `TextWrapping::overflow_character` to specify what character should be used to represent clipped text.
|
||||
* 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)).
|
||||
* Renamed `AlphaImage` to `FontImage` to discourage any other use for it ([#1412](https://github.com/emilk/egui/pull/1412)).
|
||||
* Dark text is darker and more readable on bright backgrounds ([#1412](https://github.com/emilk/egui/pull/1412)).
|
||||
* Fixed panic when tessellating a `Shape::Vec` containing meshes with differing `TextureId`s ([#1445](https://github.com/emilk/egui/pull/1445)).
|
||||
* Added `Shape::galley_with_color` which adds the functionality of `Painter::galley_with_color` into the Shape enum ([#1461](https://github.com/emilk/egui/pull/1461)).
|
||||
* Renamed the feature `convert_bytemuck` to `bytemuck` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* Renamed the feature `serialize` to `serde` ([#1467](https://github.com/emilk/egui/pull/1467)).
|
||||
* Added line breaking rules for Japanese text ([#1498](https://github.com/emilk/egui/pull/1498)).
|
||||
* Optimize tessellation of circles and boxes with rounded corners ([#1547](https://github.com/emilk/egui/pull/1547)).
|
||||
|
||||
|
||||
## 0.17.0 - 2022-02-22
|
||||
* Much improved font selection ([#1154](https://github.com/emilk/egui/pull/1154)):
|
||||
* Replaced `TextStyle` with `FontId` which lets you pick any font size and font family.
|
||||
* Replaced `Fonts::font_image` with `font_image_delta` for partial font atlas updates.
|
||||
* Made the v-align and scale of user fonts tweakable ([#1241](https://github.com/emilk/egui/pull/1027)).
|
||||
* Added `ImageData` and `TextureManager` for loading images into textures ([#1110](https://github.com/emilk/egui/pull/1110)).
|
||||
* Added `Shape::dashed_line_many` ([#1027](https://github.com/emilk/egui/pull/1027)).
|
||||
* Replaced `corner_radius: f32` with `rounding: Rounding`, allowing per-corner rounding settings ([#1206](https://github.com/emilk/egui/pull/1206)).
|
||||
* Fixed anti-aliasing of filled paths with counter-clockwise winding order.
|
||||
* Improve the anti-aliasing of filled paths with sharp corners, at the cost of these corners sometimes becoming badly extruded instead (see https://github.com/emilk/egui/issues/1226).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
* Anti-alias path ends ([#893](https://github.com/emilk/egui/pull/893)).
|
||||
* `Rgba` now implements `Hash` ([#886](https://github.com/emilk/egui/pull/886)).
|
||||
* Renamed `Texture` to `FontImage`.
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
* `Fonts::layout_job`: New text layout engine allowing mixing fonts, colors and styles, with underlining and strikethrough.
|
||||
* New `CircleShape`, `PathShape`, `RectShape` and `TextShape` used in `enum Shape`.
|
||||
* Added support for rotated text (see `TextShape`).
|
||||
* Added `"convert_bytemuck"` feature.
|
|
@ -1,105 +0,0 @@
|
|||
[package]
|
||||
name = "epaint"
|
||||
version = "0.21.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Minimal 2D graphics library for GUI work"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/crates/epaint"
|
||||
license = "(MIT OR Apache-2.0) AND OFL-1.1 AND LicenseRef-UFL-1.0" # OFL and UFL used by default_fonts. See https://github.com/emilk/egui/issues/2321
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui/tree/master/crates/epaint"
|
||||
categories = ["graphics", "gui"]
|
||||
keywords = ["graphics", "gui", "egui"]
|
||||
include = [
|
||||
"../LICENSE-APACHE",
|
||||
"../LICENSE-MIT",
|
||||
"**/*.rs",
|
||||
"Cargo.toml",
|
||||
"fonts/*.ttf",
|
||||
"fonts/*.txt",
|
||||
"fonts/OFL.txt",
|
||||
"fonts/UFL.txt",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
|
||||
|
||||
[features]
|
||||
default = ["default_fonts"]
|
||||
|
||||
## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`Vertex`] to `&[u8]`.
|
||||
bytemuck = ["dep:bytemuck", "emath/bytemuck", "ecolor/bytemuck"]
|
||||
|
||||
## [`cint`](https://docs.rs/cint) enables interopability with other color libraries.
|
||||
cint = ["ecolor/cint"]
|
||||
|
||||
## Enable the [`hex_color`] macro.
|
||||
color-hex = ["ecolor/color-hex"]
|
||||
|
||||
## This will automatically detect deadlocks due to double-locking on the same thread.
|
||||
## If your app freezes, you may want to enable this!
|
||||
## Only affects [`mutex::RwLock`] (which epaint and egui uses a lot).
|
||||
deadlock_detection = ["dep:backtrace"]
|
||||
|
||||
## If set, epaint will use `include_bytes!` to bundle some fonts.
|
||||
## If you plan on specifying your own fonts you may disable this feature.
|
||||
default_fonts = []
|
||||
|
||||
## Enable additional checks if debug assertions are enabled (debug builds).
|
||||
extra_debug_asserts = [
|
||||
"emath/extra_debug_asserts",
|
||||
"ecolor/extra_debug_asserts",
|
||||
]
|
||||
## Always enable additional checks.
|
||||
extra_asserts = ["emath/extra_asserts", "ecolor/extra_asserts"]
|
||||
|
||||
## [`mint`](https://docs.rs/mint) enables interopability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra).
|
||||
mint = ["emath/mint"]
|
||||
|
||||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||
serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/serde"]
|
||||
|
||||
## Change Vertex layout to be compatible with unity
|
||||
unity = []
|
||||
|
||||
[dependencies]
|
||||
emath = { version = "0.21.0", path = "../emath" }
|
||||
ecolor = { version = "0.21.0", path = "../ecolor" }
|
||||
|
||||
ab_glyph = "0.2.11"
|
||||
ahash = { version = "0.8.1", default-features = false, features = [
|
||||
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
|
||||
"std",
|
||||
] }
|
||||
nohash-hasher = "0.2"
|
||||
|
||||
#! ### Optional dependencies
|
||||
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
|
||||
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
## Allow serialization using [`serde`](https://docs.rs/serde) .
|
||||
serde = { version = "1", optional = true, features = ["derive", "rc"] }
|
||||
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
backtrace = { version = "0.3", optional = true }
|
||||
parking_lot = "0.12" # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.
|
||||
|
||||
# web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
atomic_refcell = "0.1" # Used instead of parking_lot on on wasm. See https://github.com/emilk/egui/issues/1401
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.4", default-features = false }
|
||||
|
||||
|
||||
[[bench]]
|
||||
name = "benchmark"
|
||||
harness = false
|
|
@ -1,11 +0,0 @@
|
|||
# epaint - egui paint library
|
||||
|
||||
[](https://crates.io/crates/epaint)
|
||||
[](https://docs.rs/epaint)
|
||||
[](https://github.com/rust-secure-code/safety-dance/)
|
||||

|
||||

|
||||
|
||||
A bare-bones 2D graphics library for turning simple 2D shapes and text into textured triangles.
|
||||
|
||||
Made for [`egui`](https://github.com/emilk/egui/).
|
|
@ -1,81 +0,0 @@
|
|||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
use epaint::*;
|
||||
|
||||
fn single_dashed_lines(c: &mut Criterion) {
|
||||
c.bench_function("single_dashed_lines", move |b| {
|
||||
b.iter(|| {
|
||||
let mut v = Vec::new();
|
||||
|
||||
let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)];
|
||||
|
||||
for _ in 0..100 {
|
||||
v.extend(Shape::dashed_line(
|
||||
&line,
|
||||
Stroke::new(1.5, Color32::RED),
|
||||
10.0,
|
||||
2.5,
|
||||
));
|
||||
}
|
||||
|
||||
black_box(v);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn many_dashed_lines(c: &mut Criterion) {
|
||||
c.bench_function("many_dashed_lines", move |b| {
|
||||
b.iter(|| {
|
||||
let mut v = Vec::new();
|
||||
|
||||
let line = [pos2(0.0, 0.0), pos2(50.0, 0.0), pos2(100.0, 1.0)];
|
||||
|
||||
for _ in 0..100 {
|
||||
Shape::dashed_line_many(&line, Stroke::new(1.5, Color32::RED), 10.0, 2.5, &mut v);
|
||||
}
|
||||
|
||||
black_box(v);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn tessellate_circles(c: &mut Criterion) {
|
||||
c.bench_function("tessellate_circles_100k", move |b| {
|
||||
let radii: [f32; 10] = [1.0, 2.0, 3.6, 4.0, 5.7, 8.0, 10.0, 13.0, 15.0, 17.0];
|
||||
let mut clipped_shapes = vec![];
|
||||
for r in radii {
|
||||
for _ in 0..10_000 {
|
||||
let clip_rect = Rect::from_min_size(Pos2::ZERO, Vec2::splat(1024.0));
|
||||
let shape = Shape::circle_filled(Pos2::new(10.0, 10.0), r, Color32::WHITE);
|
||||
clipped_shapes.push(ClippedShape(clip_rect, shape));
|
||||
}
|
||||
}
|
||||
assert_eq!(clipped_shapes.len(), 100_000);
|
||||
|
||||
let pixels_per_point = 2.0;
|
||||
let options = TessellationOptions::default();
|
||||
|
||||
let atlas = TextureAtlas::new([4096, 256]);
|
||||
let font_tex_size = atlas.size();
|
||||
let prepared_discs = atlas.prepared_discs();
|
||||
|
||||
b.iter(|| {
|
||||
let clipped_primitive = tessellate_shapes(
|
||||
pixels_per_point,
|
||||
options,
|
||||
font_tex_size,
|
||||
prepared_discs.clone(),
|
||||
clipped_shapes.clone(),
|
||||
);
|
||||
black_box(clipped_primitive);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
single_dashed_lines,
|
||||
many_dashed_lines,
|
||||
tessellate_circles
|
||||
);
|
||||
criterion_main!(benches);
|
|
@ -1,579 +0,0 @@
|
|||
//! Helper module that wraps some Mutex types with different implementations.
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(debug_assertions))]
|
||||
mod mutex_impl {
|
||||
/// Provides interior mutability.
|
||||
///
|
||||
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||
#[derive(Default)]
|
||||
pub struct Mutex<T>(parking_lot::Mutex<T>);
|
||||
|
||||
/// The lock you get from [`Mutex`].
|
||||
pub use parking_lot::MutexGuard;
|
||||
|
||||
impl<T> Mutex<T> {
|
||||
#[inline(always)]
|
||||
pub fn new(val: T) -> Self {
|
||||
Self(parking_lot::Mutex::new(val))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn lock(&self) -> MutexGuard<'_, T> {
|
||||
self.0.lock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(debug_assertions)]
|
||||
mod mutex_impl {
|
||||
/// Provides interior mutability.
|
||||
///
|
||||
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||
#[derive(Default)]
|
||||
pub struct Mutex<T>(parking_lot::Mutex<T>);
|
||||
|
||||
/// The lock you get from [`Mutex`].
|
||||
pub struct MutexGuard<'a, T>(parking_lot::MutexGuard<'a, T>, *const ());
|
||||
|
||||
#[derive(Default)]
|
||||
struct HeldLocks(Vec<*const ()>);
|
||||
|
||||
impl HeldLocks {
|
||||
#[inline(always)]
|
||||
fn insert(&mut self, lock: *const ()) {
|
||||
// Very few locks will ever be held at the same time, so a linear search is fast
|
||||
assert!(
|
||||
!self.0.contains(&lock),
|
||||
"Recursively locking a Mutex in the same thread is not supported"
|
||||
);
|
||||
self.0.push(lock);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn remove(&mut self, lock: *const ()) {
|
||||
self.0.retain(|&ptr| ptr != lock);
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static HELD_LOCKS_TLS: std::cell::RefCell<HeldLocks> = Default::default();
|
||||
}
|
||||
|
||||
impl<T> Mutex<T> {
|
||||
#[inline(always)]
|
||||
pub fn new(val: T) -> Self {
|
||||
Self(parking_lot::Mutex::new(val))
|
||||
}
|
||||
|
||||
pub fn lock(&self) -> MutexGuard<'_, T> {
|
||||
// Detect if we are recursively taking out a lock on this mutex.
|
||||
|
||||
// use a pointer to the inner data as an id for this lock
|
||||
let ptr = (&self.0 as *const parking_lot::Mutex<_>).cast::<()>();
|
||||
|
||||
// Store it in thread local storage while we have a lock guard taken out
|
||||
HELD_LOCKS_TLS.with(|held_locks| {
|
||||
held_locks.borrow_mut().insert(ptr);
|
||||
});
|
||||
|
||||
MutexGuard(self.0.lock(), ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for MutexGuard<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
let ptr = self.1;
|
||||
HELD_LOCKS_TLS.with(|held_locks| {
|
||||
held_locks.borrow_mut().remove(ptr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for MutexGuard<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::DerefMut for MutexGuard<'_, T> {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(feature = "deadlock_detection"))]
|
||||
mod rw_lock_impl {
|
||||
/// The lock you get from [`RwLock::read`].
|
||||
pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard;
|
||||
|
||||
/// The lock you get from [`RwLock::write`].
|
||||
pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard;
|
||||
|
||||
/// Provides interior mutability.
|
||||
///
|
||||
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||
#[derive(Default)]
|
||||
pub struct RwLock<T>(parking_lot::RwLock<T>);
|
||||
|
||||
impl<T> RwLock<T> {
|
||||
#[inline(always)]
|
||||
pub fn new(val: T) -> Self {
|
||||
Self(parking_lot::RwLock::new(val))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read(&self) -> RwLockReadGuard<'_, T> {
|
||||
parking_lot::RwLockReadGuard::map(self.0.read(), |v| v)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn write(&self) -> RwLockWriteGuard<'_, T> {
|
||||
parking_lot::RwLockWriteGuard::map(self.0.write(), |v| v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
mod rw_lock_impl {
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
thread::ThreadId,
|
||||
};
|
||||
|
||||
use ahash::HashMap;
|
||||
use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard};
|
||||
|
||||
/// The lock you get from [`RwLock::read`].
|
||||
pub struct RwLockReadGuard<'a, T> {
|
||||
// The option is used only because we need to `take()` the guard out of self
|
||||
// when doing remappings (`map()`), i.e. it's used as a safe `ManuallyDrop`.
|
||||
guard: Option<MappedRwLockReadGuard<'a, T>>,
|
||||
holders: Arc<parking_lot::Mutex<HashMap<ThreadId, backtrace::Backtrace>>>,
|
||||
}
|
||||
|
||||
impl<'a, T> RwLockReadGuard<'a, T> {
|
||||
#[inline]
|
||||
pub fn map<U, F>(mut s: Self, f: F) -> RwLockReadGuard<'a, U>
|
||||
where
|
||||
F: FnOnce(&T) -> &U,
|
||||
{
|
||||
RwLockReadGuard {
|
||||
guard: s
|
||||
.guard
|
||||
.take()
|
||||
.map(|g| parking_lot::MappedRwLockReadGuard::map(g, f)),
|
||||
holders: Arc::clone(&s.holders),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for RwLockReadGuard<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.guard.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for RwLockReadGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
let tid = std::thread::current().id();
|
||||
self.holders.lock().remove(&tid);
|
||||
}
|
||||
}
|
||||
|
||||
/// The lock you get from [`RwLock::write`].
|
||||
pub struct RwLockWriteGuard<'a, T> {
|
||||
// The option is used only because we need to `take()` the guard out of self
|
||||
// when doing remappings (`map()`), i.e. it's used as a safe `ManuallyDrop`.
|
||||
guard: Option<MappedRwLockWriteGuard<'a, T>>,
|
||||
holders: Arc<parking_lot::Mutex<HashMap<ThreadId, backtrace::Backtrace>>>,
|
||||
}
|
||||
|
||||
impl<'a, T> RwLockWriteGuard<'a, T> {
|
||||
#[inline]
|
||||
pub fn map<U, F>(mut s: Self, f: F) -> RwLockWriteGuard<'a, U>
|
||||
where
|
||||
F: FnOnce(&mut T) -> &mut U,
|
||||
{
|
||||
RwLockWriteGuard {
|
||||
guard: s
|
||||
.guard
|
||||
.take()
|
||||
.map(|g| parking_lot::MappedRwLockWriteGuard::map(g, f)),
|
||||
holders: Arc::clone(&s.holders),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for RwLockWriteGuard<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.guard.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> DerefMut for RwLockWriteGuard<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.guard.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for RwLockWriteGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
let tid = std::thread::current().id();
|
||||
self.holders.lock().remove(&tid);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides interior mutability.
|
||||
///
|
||||
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||
#[derive(Default)]
|
||||
pub struct RwLock<T> {
|
||||
lock: parking_lot::RwLock<T>,
|
||||
// Technically we'd need a list of backtraces per thread-id since parking_lot's
|
||||
// read-locks are reentrant.
|
||||
// In practice it's not that useful to have the whole list though, so we only
|
||||
// keep track of the first backtrace for now.
|
||||
holders: Arc<parking_lot::Mutex<HashMap<ThreadId, backtrace::Backtrace>>>,
|
||||
}
|
||||
|
||||
impl<T> RwLock<T> {
|
||||
pub fn new(val: T) -> Self {
|
||||
Self {
|
||||
lock: parking_lot::RwLock::new(val),
|
||||
holders: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&self) -> RwLockReadGuard<'_, T> {
|
||||
let tid = std::thread::current().id();
|
||||
|
||||
// If it is write-locked, and we locked it (re-entrancy deadlock)
|
||||
let would_deadlock =
|
||||
self.lock.is_locked_exclusive() && self.holders.lock().contains_key(&tid);
|
||||
assert!(
|
||||
!would_deadlock,
|
||||
"{} DEAD-LOCK DETECTED ({:?})!\n\
|
||||
Trying to grab read-lock at:\n{}\n\
|
||||
which is already exclusively held by current thread at:\n{}\n\n",
|
||||
std::any::type_name::<Self>(),
|
||||
tid,
|
||||
format_backtrace(&mut make_backtrace()),
|
||||
format_backtrace(self.holders.lock().get_mut(&tid).unwrap())
|
||||
);
|
||||
|
||||
self.holders
|
||||
.lock()
|
||||
.entry(tid)
|
||||
.or_insert_with(make_backtrace);
|
||||
|
||||
RwLockReadGuard {
|
||||
guard: parking_lot::RwLockReadGuard::map(self.lock.read(), |v| v).into(),
|
||||
holders: Arc::clone(&self.holders),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self) -> RwLockWriteGuard<'_, T> {
|
||||
let tid = std::thread::current().id();
|
||||
|
||||
// If it is locked in any way, and we locked it (re-entrancy deadlock)
|
||||
let would_deadlock = self.lock.is_locked() && self.holders.lock().contains_key(&tid);
|
||||
assert!(
|
||||
!would_deadlock,
|
||||
"{} DEAD-LOCK DETECTED ({:?})!\n\
|
||||
Trying to grab write-lock at:\n{}\n\
|
||||
which is already held by current thread at:\n{}\n\n",
|
||||
std::any::type_name::<Self>(),
|
||||
tid,
|
||||
format_backtrace(&mut make_backtrace()),
|
||||
format_backtrace(self.holders.lock().get_mut(&tid).unwrap())
|
||||
);
|
||||
|
||||
self.holders
|
||||
.lock()
|
||||
.entry(tid)
|
||||
.or_insert_with(make_backtrace);
|
||||
|
||||
RwLockWriteGuard {
|
||||
guard: parking_lot::RwLockWriteGuard::map(self.lock.write(), |v| v).into(),
|
||||
holders: Arc::clone(&self.holders),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_backtrace() -> backtrace::Backtrace {
|
||||
backtrace::Backtrace::new_unresolved()
|
||||
}
|
||||
|
||||
fn format_backtrace(backtrace: &mut backtrace::Backtrace) -> String {
|
||||
backtrace.resolve();
|
||||
|
||||
let stacktrace = format!("{:?}", backtrace);
|
||||
|
||||
// Remove irrelevant parts of the stacktrace:
|
||||
let end_offset = stacktrace
|
||||
.find("std::sys_common::backtrace::__rust_begin_short_backtrace")
|
||||
.unwrap_or(stacktrace.len());
|
||||
let stacktrace = &stacktrace[..end_offset];
|
||||
|
||||
let first_interesting_function = "epaint::mutex::rw_lock_impl::make_backtrace\n";
|
||||
if let Some(start_offset) = stacktrace.find(first_interesting_function) {
|
||||
stacktrace[start_offset + first_interesting_function.len()..].to_owned()
|
||||
} else {
|
||||
stacktrace.to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod mutex_impl {
|
||||
// `atomic_refcell` will panic if multiple threads try to access the same value
|
||||
|
||||
/// Provides interior mutability.
|
||||
///
|
||||
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||
#[derive(Default)]
|
||||
pub struct Mutex<T>(atomic_refcell::AtomicRefCell<T>);
|
||||
|
||||
/// The lock you get from [`Mutex`].
|
||||
pub use atomic_refcell::AtomicRefMut as MutexGuard;
|
||||
|
||||
impl<T> Mutex<T> {
|
||||
#[inline(always)]
|
||||
pub fn new(val: T) -> Self {
|
||||
Self(atomic_refcell::AtomicRefCell::new(val))
|
||||
}
|
||||
|
||||
/// Panics if already locked.
|
||||
#[inline(always)]
|
||||
pub fn lock(&self) -> MutexGuard<'_, T> {
|
||||
self.0.borrow_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod rw_lock_impl {
|
||||
// `atomic_refcell` will panic if multiple threads try to access the same value
|
||||
|
||||
/// The lock you get from [`RwLock::read`].
|
||||
pub use atomic_refcell::AtomicRef as RwLockReadGuard;
|
||||
|
||||
/// The lock you get from [`RwLock::write`].
|
||||
pub use atomic_refcell::AtomicRefMut as RwLockWriteGuard;
|
||||
|
||||
/// Provides interior mutability.
|
||||
///
|
||||
/// Uses `parking_lot` crate on native targets, and `atomic_refcell` on `wasm32` targets.
|
||||
#[derive(Default)]
|
||||
pub struct RwLock<T>(atomic_refcell::AtomicRefCell<T>);
|
||||
|
||||
impl<T> RwLock<T> {
|
||||
#[inline(always)]
|
||||
pub fn new(val: T) -> Self {
|
||||
Self(atomic_refcell::AtomicRefCell::new(val))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn read(&self) -> RwLockReadGuard<'_, T> {
|
||||
self.0.borrow()
|
||||
}
|
||||
|
||||
/// Panics if already locked.
|
||||
#[inline(always)]
|
||||
pub fn write(&self) -> RwLockWriteGuard<'_, T> {
|
||||
self.0.borrow_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub use mutex_impl::{Mutex, MutexGuard};
|
||||
pub use rw_lock_impl::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
impl<T> Clone for Mutex<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self::new(self.lock().clone())
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::mutex::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn lock_two_different_mutexes_single_thread() {
|
||||
let one = Mutex::new(());
|
||||
let two = Mutex::new(());
|
||||
let _a = one.lock();
|
||||
let _b = two.lock();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn lock_reentry_single_thread() {
|
||||
let one = Mutex::new(());
|
||||
let _a = one.lock();
|
||||
let _a2 = one.lock(); // panics
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_multiple_threads() {
|
||||
use std::sync::Arc;
|
||||
let one = Arc::new(Mutex::new(()));
|
||||
let our_lock = one.lock();
|
||||
let other_thread = {
|
||||
let one = Arc::clone(&one);
|
||||
std::thread::spawn(move || {
|
||||
let _ = one.lock();
|
||||
})
|
||||
};
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
drop(our_lock);
|
||||
other_thread.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(feature = "deadlock_detection")]
|
||||
#[cfg(test)]
|
||||
mod tests_rwlock {
|
||||
use crate::mutex::RwLock;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn lock_two_different_rwlocks_single_thread() {
|
||||
let one = RwLock::new(());
|
||||
let two = RwLock::new(());
|
||||
let _a = one.write();
|
||||
let _b = two.write();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rwlock_multiple_threads() {
|
||||
use std::sync::Arc;
|
||||
let one = Arc::new(RwLock::new(()));
|
||||
let our_lock = one.write();
|
||||
let other_thread1 = {
|
||||
let one = Arc::clone(&one);
|
||||
std::thread::spawn(move || {
|
||||
let _ = one.write();
|
||||
})
|
||||
};
|
||||
let other_thread2 = {
|
||||
let one = Arc::clone(&one);
|
||||
std::thread::spawn(move || {
|
||||
let _ = one.read();
|
||||
})
|
||||
};
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
drop(our_lock);
|
||||
other_thread1.join().unwrap();
|
||||
other_thread2.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn rwlock_write_write_reentrancy() {
|
||||
let one = RwLock::new(());
|
||||
let _a1 = one.write();
|
||||
let _a2 = one.write(); // panics
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn rwlock_write_read_reentrancy() {
|
||||
let one = RwLock::new(());
|
||||
let _a1 = one.write();
|
||||
let _a2 = one.read(); // panics
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn rwlock_read_write_reentrancy() {
|
||||
let one = RwLock::new(());
|
||||
let _a1 = one.read();
|
||||
let _a2 = one.write(); // panics
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rwlock_read_read_reentrancy() {
|
||||
let one = RwLock::new(());
|
||||
let _a1 = one.read();
|
||||
// This is legal: this test suite specifically targets native, which relies
|
||||
// on parking_lot's rw-locks, which are reentrant.
|
||||
let _a2 = one.read();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rwlock_short_read_foreign_read_write_reentrancy() {
|
||||
use std::sync::Arc;
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
|
||||
// Thread #0 grabs a read lock
|
||||
let t0r0 = lock.read();
|
||||
|
||||
// Thread #1 grabs the same read lock
|
||||
let other_thread = {
|
||||
let lock = Arc::clone(&lock);
|
||||
std::thread::spawn(move || {
|
||||
let _t1r0 = lock.read();
|
||||
})
|
||||
};
|
||||
other_thread.join().unwrap();
|
||||
|
||||
// Thread #0 releases its read lock
|
||||
drop(t0r0);
|
||||
|
||||
// Thread #0 now grabs a write lock, which is legal
|
||||
let _t0w0 = lock.write();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn rwlock_read_foreign_read_write_reentrancy() {
|
||||
use std::sync::Arc;
|
||||
|
||||
let lock = Arc::new(RwLock::new(()));
|
||||
|
||||
// Thread #0 grabs a read lock
|
||||
let _t0r0 = lock.read();
|
||||
|
||||
// Thread #1 grabs the same read lock
|
||||
let other_thread = {
|
||||
let lock = Arc::clone(&lock);
|
||||
std::thread::spawn(move || {
|
||||
let _t1r0 = lock.read();
|
||||
})
|
||||
};
|
||||
other_thread.join().unwrap();
|
||||
|
||||
// Thread #0 now grabs a write lock, which should panic (read-write)
|
||||
let _t0w0 = lock.write(); // panics
|
||||
}
|
||||
}
|
63
deny.toml
63
deny.toml
|
@ -3,7 +3,6 @@
|
|||
targets = [
|
||||
{ triple = "aarch64-apple-darwin" },
|
||||
{ triple = "aarch64-linux-android" },
|
||||
{ triple = "wasm32-unknown-unknown" },
|
||||
{ triple = "x86_64-apple-darwin" },
|
||||
{ triple = "x86_64-pc-windows-msvc" },
|
||||
{ triple = "x86_64-unknown-linux-gnu" },
|
||||
|
@ -17,36 +16,28 @@ yanked = "deny"
|
|||
ignore = [
|
||||
"RUSTSEC-2020-0071", # https://rustsec.org/advisories/RUSTSEC-2020-0071 - chrono/time: Potential segfault in the time crate
|
||||
"RUSTSEC-2020-0159", # https://rustsec.org/advisories/RUSTSEC-2020-0159 - chrono/time: Potential segfault in localtime_r invocations
|
||||
"RUSTSEC-2021-0127", # https://rustsec.org/advisories/RUSTSEC-2021-0127 - https://github.com/bheisler/criterion.rs/issues/534
|
||||
"RUSTSEC-2021-0019", # https://rustsec.org/advisories/RUSTSEC-2021-0019 - xcb - is being worked on: https://github.com/rust-x-bindings/rust-xcb/issues/107
|
||||
]
|
||||
|
||||
[bans]
|
||||
multiple-versions = "deny"
|
||||
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
||||
deny = [
|
||||
{ name = "cmake" }, # Lord no
|
||||
{ name = "openssl-sys" }, # prefer rustls
|
||||
{ name = "openssl" }, # prefer rustls
|
||||
{ name = "openssl" }, # prefer rustls
|
||||
{ name = "openssl-sys" }, # prefer rustls
|
||||
]
|
||||
|
||||
skip = [
|
||||
{ name = "ahash" }, # old version via dark-light
|
||||
{ name = "arrayvec" }, # old version via tiny-skiaz
|
||||
{ name = "hashbrown" }, # old version via dark-light
|
||||
{ name = "nix" }, # old version via winit
|
||||
{ name = "time" }, # old version pulled in by unmaintianed crate 'chrono'
|
||||
{ name = "tiny-skia" }, # winit uses a different version from egui_extras (TODO(emilk): update egui_extras!)
|
||||
{ name = "ttf-parser" }, # different versions pulled in by ab_glyph and usvg
|
||||
{ name = "wayland-sys" }, # old version via winit
|
||||
{ name = "windows_x86_64_msvc" }, # old version via glutin
|
||||
{ name = "windows-sys" }, # old version via glutin
|
||||
{ name = "windows" }, # old version via accesskit
|
||||
{ name = "ahash" }, # old version via dark-light
|
||||
{ name = "arrayvec" }, # old version via tiny-skia
|
||||
{ name = "hashbrown" }, # old version via dark-light
|
||||
{ name = "time" }, # old version pulled in by unmaintianed crate 'chrono'
|
||||
{ name = "ttf-parser" }, # different versions pulled in by ab_glyph and usvg
|
||||
]
|
||||
skip-tree = [
|
||||
{ name = "criterion" }, # dev-dependency
|
||||
{ name = "darling" }, # old version via tts
|
||||
{ name = "foreign-types" }, # old version from wgpu
|
||||
{ name = "rfd" }, # example dependency
|
||||
{ name = "criterion" }, # dev-dependnecy
|
||||
{ name = "glium" }, # legacy crate, lots of old dependencies
|
||||
{ name = "glutin" }, # legacy crate, lots of old dependencies
|
||||
]
|
||||
|
||||
|
||||
|
@ -56,27 +47,29 @@ allow-osi-fsf-free = "neither"
|
|||
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
|
||||
copyleft = "deny"
|
||||
allow = [
|
||||
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
|
||||
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
|
||||
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
|
||||
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
|
||||
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
|
||||
"ISC", # https://tldrlegal.com/license/-isc-license
|
||||
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
|
||||
"MIT", # https://tldrlegal.com/license/mit-license
|
||||
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11
|
||||
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
|
||||
"OpenSSL", # https://www.openssl.org/source/license.html
|
||||
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
|
||||
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
|
||||
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
|
||||
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
|
||||
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
|
||||
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
|
||||
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
|
||||
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
|
||||
"ISC", # https://tldrlegal.com/license/-isc-license
|
||||
"MIT", # https://tldrlegal.com/license/mit-license
|
||||
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11
|
||||
"OpenSSL", # https://www.openssl.org/source/license.html
|
||||
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
|
||||
]
|
||||
|
||||
[[licenses.clarify]]
|
||||
name = "webpki"
|
||||
expression = "ISC"
|
||||
license-files = [{ path = "LICENSE", hash = 0x001c7e6c }]
|
||||
license-files = [
|
||||
{ path = "LICENSE", hash = 0x001c7e6c }
|
||||
]
|
||||
|
||||
[[licenses.clarify]]
|
||||
name = "ring"
|
||||
expression = "MIT AND ISC AND OpenSSL"
|
||||
license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]
|
||||
license-files = [
|
||||
{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||
]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue