Compare commits

..

No commits in common. "master" and "0.18.1" have entirely different histories.

387 changed files with 14541 additions and 28377 deletions

View file

@ -1,6 +1,84 @@
# 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_methods",
"-Wclippy::doc_markdown",
"-Wclippy::empty_enum",
"-Wclippy::enum_glob_use",
"-Wclippy::equatable_if_let",
"-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",
"-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::mut_mut",
"-Wclippy::mutex_integer",
"-Wclippy::needless_borrow",
"-Wclippy::needless_continue",
"-Wclippy::needless_for_each",
"-Wclippy::needless_pass_by_value",
"-Wclippy::option_option",
"-Wclippy::path_buf_push_overwrite",
"-Wclippy::ptr_as_ptr",
"-Wclippy::rc_mutex",
"-Wclippy::ref_option_ref",
"-Wclippy::rest_pat_in_fully_bound_structs",
"-Wclippy::same_functions_in_if_condition",
"-Wclippy::semicolon_if_nothing_returned",
"-Wclippy::single_match_else",
"-Wclippy::string_add_assign",
"-Wclippy::string_add",
"-Wclippy::string_lit_as_bytes",
"-Wclippy::string_to_string",
"-Wclippy::todo",
"-Wclippy::trait_duplication_in_bounds",
"-Wclippy::unimplemented",
"-Wclippy::unnested_or_patterns",
"-Wclippy::unused_self",
"-Wclippy::useless_transmute",
"-Wclippy::verbose_file_reads",
"-Wclippy::zero_sized_map_values",
"-Wfuture_incompatible",
"-Wnonstandard_style",
"-Wrust_2018_idioms",
"-Wrustdoc::missing_crate_level_docs",
]

View file

@ -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:

View file

@ -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!
-->

View file

@ -3,209 +3,181 @@ 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 eframe 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
RUSTFLAGS: --cfg=web_sys_unstable_apis
RUSTDOCFLAGS: -D warnings
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.60.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 libgtk-3-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.60.0
override: true
- run: sudo apt-get update && sudo apt-get install libspeechd-dev libgtk-3-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.60.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.60.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.60.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.60.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.60.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.60.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.60.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.60.0
override: true
- run: sudo apt-get update && sudo apt-get install libspeechd-dev
- run: cargo doc --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.60.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
View file

@ -1,7 +1,4 @@
.DS_Store
**/target
**/target_ra
**/target_wasm
/.*.json
/.vscode
/media/*

29
.vscode/settings.json vendored
View file

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

View file

@ -1,4 +1,4 @@
# 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.

View file

@ -1,153 +1,12 @@
# 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-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.
@ -177,7 +36,7 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
* `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)).
* Warnings will pe 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)).
@ -188,9 +47,9 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
* Fixed `ComboBox`es 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)).
* Fix 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)).
* Add 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)).
@ -326,7 +185,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,15 +244,15 @@ 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).
@ -407,14 +266,14 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
* 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 +298,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 +320,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 +333,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 +370,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 +393,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.
* Add `Response::on_disabled_hover_text` to show tooltip for disabled widgets.
* Zoom input: ctrl-scroll and (on `eframe` 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,
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 +436,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 +465,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 +484,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 +527,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 +540,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 +567,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 +585,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 +611,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 +654,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 +671,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

View file

@ -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

2897
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,33 @@
[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",
"egui_demo_app",
"egui_demo_lib",
"egui_extras",
"egui_glium",
"egui_glow",
"egui-winit",
"egui",
"emath",
"epaint",
"examples/*",
"examples/confirm_exit",
"examples/custom_3d_glow",
"examples/custom_3d_three-d",
"examples/custom_font",
"examples/custom_font_style",
"examples/custom_window_frame",
"examples/download_image",
"examples/file_dialog",
"examples/hello_world",
"examples/puffin_profiler",
"examples/retained_image",
"examples/svg",
]
[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 +36,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

View file

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

View file

@ -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
@ -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 [`eframe`](https://github.com/emilk/egui/tree/master/eframe).
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 -y libclang-dev libgtk-3-dev 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,35 @@ 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
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).
* [`eframe`](https://github.com/emilk/egui/tree/master/eframe) for compiling the same app to web/wasm and desktop/native. Uses `egui_glow` and `egui-winit`.
* [`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 rendering egui with [glow](https://github.com/grovesNL/glow) on native and web, and for making native apps.
* [`egui-winit`](https://github.com/emilk/egui/tree/master/egui-winit) for integrating with [winit](https://github.com/rust-windowing/winit).
### 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 +215,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) or [the `egui_glow` painter](https://github.com/emilk/egui/blob/master/egui_glow/src/painter.rs).
### Debugging your integration
@ -324,11 +315,11 @@ If you call `.await` in your GUI code, the UI will freeze, which is very bad UX.
* [`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.
@ -348,14 +339,14 @@ 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>
`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 +363,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.
@ -399,7 +388,6 @@ Notable contributions by:
* [@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 +400,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>

View file

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

View file

@ -1 +0,0 @@
doc-valid-idents = ["AccessKit", ".."]

View file

@ -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`

View file

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

View file

@ -1,11 +0,0 @@
# ecolor - egui color library
[![Latest version](https://img.shields.io/crates/v/ecolor.svg)](https://crates.io/crates/ecolor)
[![Documentation](https://docs.rs/ecolor/badge.svg)](https://docs.rs/ecolor)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
A simple color storage and conversion library.
Made for [`egui`](https://github.com/emilk/egui/).

View file

@ -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,
}
}
}

View file

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

View file

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

View file

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

View file

@ -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,
}
}
}

View file

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

View file

@ -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],
])
}
}

View file

@ -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`

View file

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

File diff suppressed because it is too large Load diff

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

@ -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>.

View file

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

View file

@ -1,10 +0,0 @@
# egui-wgpu
[![Latest version](https://img.shields.io/crates/v/egui-wgpu.svg)](https://crates.io/crates/egui-wgpu)
[![Documentation](https://docs.rs/egui-wgpu/badge.svg)](https://docs.rs/egui-wgpu)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
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

View file

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

View file

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

View file

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

View file

@ -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?
}
}

View file

@ -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`.

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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

View file

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

View file

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

View file

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

View file

@ -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.

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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).

View file

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

View file

@ -1,11 +0,0 @@
# emath - egui math library
[![Latest version](https://img.shields.io/crates/v/emath.svg)](https://crates.io/crates/emath)
[![Documentation](https://docs.rs/emath/badge.svg)](https://docs.rs/emath)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
A bare-bones 2D math library with types and functions useful for GUI building.
Made for [`egui`](https://github.com/emilk/egui/).

View file

@ -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

View file

@ -1,11 +0,0 @@
# epaint - egui paint library
[![Latest version](https://img.shields.io/crates/v/epaint.svg)](https://crates.io/crates/epaint)
[![Documentation](https://docs.rs/epaint/badge.svg)](https://docs.rs/epaint)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
A bare-bones 2D graphics library for turning simple 2D shapes and text into textured triangles.
Made for [`egui`](https://github.com/emilk/egui/).

View file

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

View file

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

View file

@ -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" },
@ -24,29 +23,20 @@ ignore = [
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-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-skiaz
{ 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
]
@ -56,19 +46,17 @@ 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]]

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -33,8 +33,6 @@
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
height: 100%;
width: 100%;
}
/* Position canvas in center-top: */
@ -133,11 +131,7 @@
console.debug("wasm loaded. starting app…");
// This call installs a bunch of callbacks and then returns:
const handle = wasm_bindgen.start("the_canvas_id");
// call `handle.stop_web()` to stop
// uncomment to quick result
// setTimeout(() => {handle.stop_web(); handle.free())}, 2000)
wasm_bindgen.start("the_canvas_id");
console.debug("app started.");
document.getElementById("center_text").remove();

View file

@ -1,200 +0,0 @@
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!-- Disable zooming: -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<head>
<title>egui An immediate mode GUI written in Rust</title>
<style>
html {
/* Remove touch delay: */
touch-action: manipulation;
}
body {
/* Light mode background color for what is not covered by the egui canvas,
or where the egui canvas is translucent. */
background: #909090;
display:flex;
}
.canvas_wrap{
/* height: 200px; */
width: 400px;
}
@media (prefers-color-scheme: dark) {
body {
/* Dark mode background color for what is not covered by the egui canvas,
or where the egui canvas is translucent. */
background: #404040;
}
}
/* Allow canvas to fill entire web page: */
html,
body {
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
height: 100%;
width: 100%;
}
/* Position canvas in center-top: */
canvas {
/* margin-right: auto;
margin-left: auto; */
/* display: block;
position: absolute;
top: 0%;
left: 50%;
transform: translate(-50%, 0%); */
width:90%;
height:90%;
}
.centered {
margin-right: auto;
margin-left: auto;
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #f0f0f0;
font-size: 24px;
font-family: Ubuntu-Light, Helvetica, sans-serif;
text-align: center;
}
/* ---------------------------------------------- */
/* Loading animation from https://loading.io/css/ */
.lds-dual-ring {
display: inline-block;
width: 24px;
height: 24px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 24px;
height: 24px;
margin: 0px;
border-radius: 50%;
border: 3px solid #fff;
border-color: #fff transparent #fff transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<!-- The WASM code will resize the canvas dynamically -->
<div>controls</div>
<button class="stop_one">
stop
</button>
<div class="canvas_wrap one">
<canvas id="the_canvas_id_one"></canvas>
</div>
<div class="canvas_wrap two">
<canvas id="the_canvas_id_two"></canvas>
</div>
<div class="centered" id="center_text">
<p style="font-size:16px">
Loading…
</p>
<div class="lds-dual-ring"></div>
</div>
<script>
// The `--no-modules`-generated JS from `wasm-bindgen` attempts to use
// `WebAssembly.instantiateStreaming` to instantiate the wasm module,
// but this doesn't work with `file://` urls. This example is frequently
// viewed by simply opening `index.html` in a browser (with a `file://`
// url), so it would fail if we were to call this function!
//
// Work around this for now by deleting the function to ensure that the
// `no_modules.js` script doesn't have access to it. You won't need this
// hack when deploying over HTTP.
delete WebAssembly.instantiateStreaming;
</script>
<!-- this is the JS generated by the `wasm-bindgen` CLI tool -->
<script src="egui_demo_app.js"></script>
<script>
// We'll defer our execution until the wasm is ready to go.
// Here we tell bindgen the path to the wasm file so it can start
// initialization and return to us a promise when it's done.
console.debug("loading wasm…");
wasm_bindgen("./egui_demo_app_bg.wasm")
.then(on_wasm_loaded)
.catch(on_wasm_error);
function on_wasm_loaded() {
console.debug("wasm loaded. starting app…");
// This call installs a bunch of callbacks and then returns:
wasm_bindgen.init_wasm_hooks()
const handle_one = wasm_bindgen.start_separate("the_canvas_id_one");
const handle_two = wasm_bindgen.start_separate("the_canvas_id_two");
const button = document.getElementsByClassName("stop_one")[0]
button.addEventListener("click", ()=>{
handle_one.stop_web()
handle_one.free()
});
// call `handle.stop_web()` to stop
// uncomment to quick result
// setTimeout(() => {handle.stop_web()}, 2000)
console.debug("app started.");
document.getElementById("center_text").remove();
}
function on_wasm_error(error) {
console.error("Failed to start: " + error);
document.getElementById("center_text").innerHTML = `
<p>
An error occurred during loading:
</p>
<p style="font-family:Courier New">
${error}
</p>
<p style="font-size:14px">
Make sure you use a modern browser with WebGL and WASM enabled.
</p>`;
}
</script>
</body>
</html>
<!-- Powered by egui: https://github.com/emilk/egui/ -->

142
eframe/CHANGELOG.md Normal file
View file

@ -0,0 +1,142 @@
# Changelog for eframe
All notable changes to the `eframe` crate.
NOTE: [`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.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`.
* Add `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)).
* Fix 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
* `Frame` now provides `set_decorations` to set whether to show window decorations.
* Remove "http" feature (use https://github.com/emilk/ehttp instead!).
* Add `App::persist_native_window` and `App::persist_egui_memory` to control what gets persisted.
#### Desktop/Native:
* Increase native scroll speed.
* Add 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.
* Fix multiline paste.
* Fix painting with non-opaque backgrounds.
* Improve text input on mobile and for IME.
## 0.14.0 - 2021-08-24
* Add 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
* Fix `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`.
* Add 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
* [Add 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`

131
eframe/Cargo.toml Normal file
View file

@ -0,0 +1,131 @@
[package]
name = "eframe"
version = "0.18.0"
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.60"
homepage = "https://github.com/emilk/egui/tree/master/eframe"
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/emilk/egui/tree/master/eframe"
categories = ["gui", "game-development"]
keywords = ["egui", "gui", "gamedev"]
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
[package.metadata.docs.rs]
all-features = true
[lib]
[features]
default = ["default_fonts"]
# detect dark mode system preference
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"]
# Enable saving app state to disk.
persistence = [
"directories-next",
"egui-winit/serde",
"egui/persistence",
"ron",
"serde",
]
# Enable profiling with the puffin crate: https://github.com/EmbarkStudios/puffin
# 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"]
# enable screen reader support (requires `ctx.options().screen_reader = true;`)
screen_reader = [
"egui-winit/screen_reader",
"tts",
]
[dependencies]
egui = { version = "0.18.0", path = "../egui", default-features = false, features = [
"bytemuck",
"tracing",
] }
egui_glow = { version = "0.18.0", path = "../egui_glow", default-features = false }
glow = "0.11"
tracing = "0.1"
# optional:
ron = { version = "0.7", optional = true }
serde = { version = "1", optional = true }
# -------------------------------------------
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
dark-light = { version = "0.2.1", optional = true }
egui-winit = { version = "0.18.0", path = "../egui-winit", default-features = false, features = ["clipboard", "links"] }
glutin = { version = "0.28.0" }
winit = "0.26.1"
# optional:
puffin = { version = "0.13", optional = true }
directories-next = { version = "2", optional = true }
# -------------------------------------------
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
bytemuck = "1.7"
js-sys = "0.3"
percent-encoding = "2.1"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3.52", 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:
# feature screen_reader
tts = { version = "0.20", optional = true } # stuck on old version due to compilation problems on linux

View file

@ -17,24 +17,20 @@ 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).
`eframe` uses [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) for rendering, and on native it uses [`egui-winit`](https://github.com/emilk/egui/tree/master/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
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-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`.
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.
## 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.
@ -45,6 +41,7 @@ You can also use `egui_glow` and [`winit`](https://github.com/rust-windowing/win
* 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.
* On Linux and Mac, Firefox will copy the WebGL render target from GPU, to CPU and then back again (https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0), slowing down egui.
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).

491
eframe/src/epi.rs Normal file
View file

@ -0,0 +1,491 @@
//! Platform-agnostic interface for writing apps using [`egui`] (epi = egui programming interface).
//!
//! `epi` provides interfaces for window management and serialization.
//!
//! Start by looking at the [`App`] trait, and implement [`App::update`].
#![warn(missing_docs)] // Let's keep `epi` well-documented.
/// This is how your app is created.
///
/// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc.
pub type AppCreator = Box<dyn FnOnce(&CreationContext<'_>) -> Box<dyn App>>;
/// Data that is passed to [`AppCreator`] that can be used to setup and initialize your app.
pub struct CreationContext<'s> {
/// The egui Context.
///
/// You can use this to customize the look of egui, e.g to call [`egui::Context::set_fonts`],
/// [`egui::Context::set_visuals`] etc.
pub egui_ctx: egui::Context,
/// Information about the surrounding environment.
pub integration_info: IntegrationInfo,
/// You can use the storage to restore app state(requires the "persistence" feature).
pub storage: Option<&'s dyn Storage>,
/// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that
/// you might want to use later from a [`egui::PaintCallback`].
pub gl: std::rc::Rc<glow::Context>,
}
// ----------------------------------------------------------------------------
/// Implement this trait to write apps that can be compiled for both web/wasm and desktop/native using [`eframe`](https://github.com/emilk/egui/tree/master/eframe).
pub trait App {
/// Called each time the UI needs repainting, which may be many times per second.
///
/// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
///
/// The [`egui::Context`] can be cloned and saved if you like.
///
/// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame);
/// Called on shutdown, and perhaps at regular intervals. Allows you to save state.
///
/// Only called when the "persistence" feature is enabled.
///
/// On web the state is stored to "Local Storage".
/// On native the path is picked using [`directories_next::ProjectDirs::data_dir`](https://docs.rs/directories-next/2.0.0/directories_next/struct.ProjectDirs.html#method.data_dir) which is:
/// * Linux: `/home/UserName/.local/share/APPNAME`
/// * macOS: `/Users/UserName/Library/Application Support/APPNAME`
/// * Windows: `C:\Users\UserName\AppData\Roaming\APPNAME`
///
/// where `APPNAME` is what is given to `eframe::run_native`.
fn save(&mut self, _storage: &mut dyn Storage) {}
/// Called before an exit that can be aborted.
/// By returning `false` the exit will be aborted. To continue the exit return `true`.
///
/// A scenario where this method will be run is after pressing the close button on a native
/// window, which allows you to ask the user whether they want to do something before exiting.
/// See the example at <https://github.com/emilk/egui/blob/master/examples/confirm_exit/> for practical usage.
///
/// It will _not_ be called on the web or when the window is forcefully closed.
fn on_exit_event(&mut self) -> bool {
true
}
/// Called once on shutdown, after [`Self::save`].
///
/// If you need to abort an exit use [`Self::on_exit_event`].
fn on_exit(&mut self, _gl: &glow::Context) {}
// ---------
// Settings:
/// Time between automatic calls to [`Self::save`]
fn auto_save_interval(&self) -> std::time::Duration {
std::time::Duration::from_secs(30)
}
/// The size limit of the web app canvas.
///
/// By default the max size is [`egui::Vec2::INFINITY`], i.e. unlimited.
///
/// A large canvas can lead to bad frame rates on some older browsers on some platforms
/// (see <https://bugzilla.mozilla.org/show_bug.cgi?id=1010527#c0>).
fn max_size_points(&self) -> egui::Vec2 {
egui::Vec2::INFINITY
}
/// Background color for the app, e.g. what is sent to `gl.clearColor`.
/// This is the background of your windows if you don't set a central panel.
fn clear_color(&self, _visuals: &egui::Visuals) -> egui::Rgba {
// NOTE: a bright gray makes the shadows of the windows look weird.
// We use a bit of transparency so that if the user switches on the
// `transparent()` option they get immediate results.
egui::Color32::from_rgba_unmultiplied(12, 12, 12, 180).into()
// _visuals.window_fill() would also be a natural choice
}
/// Controls whether or not the native window position and size will be
/// persisted (only if the "persistence" feature is enabled).
fn persist_native_window(&self) -> bool {
true
}
/// Controls whether or not the egui memory (window positions etc) will be
/// persisted (only if the "persistence" feature is enabled).
fn persist_egui_memory(&self) -> bool {
true
}
/// If `true` a warm-up call to [`Self::update`] will be issued where
/// `ctx.memory().everything_is_visible()` will be set to `true`.
///
/// This can help pre-caching resources loaded by different parts of the UI, preventing stutter later on.
///
/// In this warm-up call, all painted shapes will be ignored.
///
/// The default is `false`, and it is unlikely you will want to change this.
fn warm_up_enabled(&self) -> bool {
false
}
}
/// Options controlling the behavior of a native window.
///
/// Only a single native window is currently supported.
#[derive(Clone)]
pub struct NativeOptions {
/// Sets whether or not the window will always be on top of other windows.
pub always_on_top: bool,
/// Show window in maximized mode
pub maximized: bool,
/// On desktop: add window decorations (i.e. a frame around your app)?
/// If false it will be difficult to move and resize the app.
pub decorated: bool,
/// On Windows: enable drag and drop support. Drag and drop can
/// not be disabled on other platforms.
///
/// See [winit's documentation][drag_and_drop] for information on why you
/// might want to disable this on windows.
///
/// [drag_and_drop]: https://docs.rs/winit/latest/x86_64-pc-windows-msvc/winit/platform/windows/trait.WindowBuilderExtWindows.html#tymethod.with_drag_and_drop
pub drag_and_drop_support: bool,
/// The application icon, e.g. in the Windows task bar etc.
pub icon_data: Option<IconData>,
/// The initial (inner) position of the native window in points (logical pixels).
pub initial_window_pos: Option<egui::Pos2>,
/// The initial inner size of the native window in points (logical pixels).
pub initial_window_size: Option<egui::Vec2>,
/// The minimum inner window size
pub min_window_size: Option<egui::Vec2>,
/// The maximum inner window size
pub max_window_size: Option<egui::Vec2>,
/// Should the app window be resizable?
pub resizable: bool,
/// On desktop: make the window transparent.
/// You control the transparency with [`App::clear_color()`].
/// You should avoid having a [`egui::CentralPanel`], or make sure its frame is also transparent.
pub transparent: bool,
/// Turn on vertical syncing, limiting the FPS to the display refresh rate.
///
/// The default is `true`.
pub vsync: bool,
/// Set the level of the multisampling anti-aliasing (MSAA).
///
/// Must be a power-of-two. Higher = more smooth 3D.
///
/// A value of `0` turns it off (default).
///
/// `egui` already performs anti-aliasing via "feathering"
/// (controlled by [`egui::epaint::TessellationOptions`]),
/// but if you are embedding 3D in egui you may want to turn on multisampling.
pub multisampling: u16,
/// Sets the number of bits in the depth buffer.
///
/// `egui` doesn't need the depth buffer, so the default value is 0.
pub depth_buffer: u8,
/// Sets the number of bits in the stencil buffer.
///
/// `egui` doesn't need the stencil buffer, so the default value is 0.
pub stencil_buffer: u8,
}
impl Default for NativeOptions {
fn default() -> Self {
Self {
always_on_top: false,
maximized: false,
decorated: true,
drag_and_drop_support: true,
icon_data: None,
initial_window_pos: None,
initial_window_size: None,
min_window_size: None,
max_window_size: None,
resizable: true,
transparent: false,
vsync: true,
multisampling: 0,
depth_buffer: 0,
stencil_buffer: 0,
}
}
}
/// Image data for an application icon.
#[derive(Clone)]
pub struct IconData {
/// RGBA pixels, unmultiplied.
pub rgba: Vec<u8>,
/// Image width. This should be a multiple of 4.
pub width: u32,
/// Image height. This should be a multiple of 4.
pub height: u32,
}
/// Represents the surroundings of your app.
///
/// It provides methods to inspect the surroundings (are we on the web?),
/// allocate textures, and change settings (e.g. window size).
pub struct Frame {
/// Information about the integration.
#[doc(hidden)]
pub info: IntegrationInfo,
/// Where the app can issue commands back to the integration.
#[doc(hidden)]
pub output: backend::AppOutput,
/// A place where you can store custom data in a way that persists when you restart the app.
#[doc(hidden)]
pub storage: Option<Box<dyn Storage>>,
/// A reference to the underlying [`glow`] (OpenGL) context.
#[doc(hidden)]
pub gl: std::rc::Rc<glow::Context>,
}
impl Frame {
/// True if you are in a web environment.
pub fn is_web(&self) -> bool {
self.info.web_info.is_some()
}
/// Information about the integration.
pub fn info(&self) -> IntegrationInfo {
self.info.clone()
}
/// A place where you can store custom data in a way that persists when you restart the app.
pub fn storage(&self) -> Option<&dyn Storage> {
self.storage.as_deref()
}
/// A place where you can store custom data in a way that persists when you restart the app.
pub fn storage_mut(&mut self) -> Option<&mut (dyn Storage + 'static)> {
self.storage.as_deref_mut()
}
/// A reference to the underlying [`glow`] (OpenGL) context.
///
/// This can be used, for instance, to:
/// * Render things to offscreen buffers.
/// * Read the pixel buffer from the previous frame (`glow::Context::read_pixels`).
/// * Render things behind the egui windows.
///
/// Note that all egui painting is deferred to after the call to [`App::update`]
/// ([`egui`] only collects [`egui::Shape`]s and then eframe paints them all in one go later on).
pub fn gl(&self) -> &std::rc::Rc<glow::Context> {
&self.gl
}
/// Signal the app to stop/exit/quit the app (only works for native apps, not web apps).
/// The framework will not quit immediately, but at the end of the this frame.
pub fn quit(&mut self) {
self.output.quit = true;
}
/// Set the desired inner size of the window (in egui points).
pub fn set_window_size(&mut self, size: egui::Vec2) {
self.output.window_size = Some(size);
}
/// Set the desired title of the window.
pub fn set_window_title(&mut self, title: &str) {
self.output.window_title = Some(title.to_owned());
}
/// Set whether to show window decorations (i.e. a frame around you app).
/// If false it will be difficult to move and resize the app.
pub fn set_decorations(&mut self, decorated: bool) {
self.output.decorated = Some(decorated);
}
/// set the position of the outer window
pub fn set_window_pos(&mut self, pos: egui::Pos2) {
self.output.window_pos = Some(pos);
}
/// When called, the native window will follow the
/// movement of the cursor while the primary mouse button is down.
///
/// Does not work on the web.
pub fn drag_window(&mut self) {
self.output.drag_window = true;
}
/// for integrations only: call once per frame
#[doc(hidden)]
pub fn take_app_output(&mut self) -> backend::AppOutput {
std::mem::take(&mut self.output)
}
}
/// Information about the web environment (if applicable).
#[derive(Clone, Debug)]
pub struct WebInfo {
/// Information about the URL.
pub location: Location,
}
/// Information about the URL.
///
/// Everything has been percent decoded (`%20` -> ` ` etc).
#[derive(Clone, Debug)]
pub struct Location {
/// The full URL (`location.href`) without the hash.
///
/// Example: `"http://www.example.com:80/index.html?foo=bar"`.
pub url: String,
/// `location.protocol`
///
/// Example: `"http:"`.
pub protocol: String,
/// `location.host`
///
/// Example: `"example.com:80"`.
pub host: String,
/// `location.hostname`
///
/// Example: `"example.com"`.
pub hostname: String,
/// `location.port`
///
/// Example: `"80"`.
pub port: String,
/// The "#fragment" part of "www.example.com/index.html?query#fragment".
///
/// Note that the leading `#` is included in the string.
/// Also known as "hash-link" or "anchor".
pub hash: String,
/// The "query" part of "www.example.com/index.html?query#fragment".
///
/// Note that the leading `?` is NOT included in the string.
///
/// Use [`Self::web_query_map]` to get the parsed version of it.
pub query: String,
/// The parsed "query" part of "www.example.com/index.html?query#fragment".
///
/// "foo=42&bar%20" is parsed as `{"foo": "42", "bar ": ""}`
pub query_map: std::collections::BTreeMap<String, String>,
/// `location.origin`
///
/// Example: `"http://www.example.com:80"`.
pub origin: String,
}
/// Information about the integration passed to the use app each frame.
#[derive(Clone, Debug)]
pub struct IntegrationInfo {
/// If the app is running in a Web context, this returns information about the environment.
pub web_info: Option<WebInfo>,
/// Does the system prefer dark mode (over light mode)?
/// `None` means "don't know".
pub prefer_dark_mode: Option<bool>,
/// Seconds of cpu usage (in seconds) of UI code on the previous frame.
/// `None` if this is the first frame.
pub cpu_usage: Option<f32>,
/// The OS native pixels-per-point
pub native_pixels_per_point: Option<f32>,
}
// ----------------------------------------------------------------------------
/// A place where you can store custom data in a way that persists when you restart the app.
///
/// On the web this is backed by [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
/// On desktop this is backed by the file system.
///
/// See [`CreationContext::storage`] and [`App::save`].
pub trait Storage {
/// Get the value for the given key.
fn get_string(&self, key: &str) -> Option<String>;
/// Set the value for the given key.
fn set_string(&mut self, key: &str, value: String);
/// write-to-disk or similar
fn flush(&mut self);
}
/// Stores nothing.
#[derive(Clone, Default)]
pub(crate) struct DummyStorage {}
impl Storage for DummyStorage {
fn get_string(&self, _key: &str) -> Option<String> {
None
}
fn set_string(&mut self, _key: &str, _value: String) {}
fn flush(&mut self) {}
}
/// Get and deserialize the [RON](https://github.com/ron-rs/ron) stored at the given key.
#[cfg(feature = "ron")]
pub fn get_value<T: serde::de::DeserializeOwned>(storage: &dyn Storage, key: &str) -> Option<T> {
storage
.get_string(key)
.and_then(|value| ron::from_str(&value).ok())
}
/// Serialize the given value as [RON](https://github.com/ron-rs/ron) and store with the given key.
#[cfg(feature = "ron")]
pub fn set_value<T: serde::Serialize>(storage: &mut dyn Storage, key: &str, value: &T) {
storage.set_string(key, ron::ser::to_string(value).unwrap());
}
/// [`Storage`] key used for app
pub const APP_KEY: &str = "app";
// ----------------------------------------------------------------------------
/// You only need to look here if you are writing a backend for `epi`.
#[doc(hidden)]
pub mod backend {
/// Action that can be taken by the user app.
#[derive(Clone, Debug, Default)]
#[must_use]
pub struct AppOutput {
/// Set to `true` to stop the app.
/// This does nothing for web apps.
pub quit: bool,
/// Set to some size to resize the outer window (e.g. glium window) to this size.
pub window_size: Option<egui::Vec2>,
/// Set to some string to rename the outer window (e.g. glium window) to this title.
pub window_title: Option<String>,
/// Set to some bool to change window decorations.
pub decorated: Option<bool>,
/// Set to true to drag window while primary mouse button is down.
pub drag_window: bool,
/// Set to some position to move the outer window (e.g. glium window) to this position
pub window_pos: Option<egui::Pos2>,
}
}

View file

@ -48,26 +48,15 @@
//! /// 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
//! pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
//! eframe::start_web(canvas_id, Box::new(|cc| Box::new(MyApp::new(cc))))
//! }
//! ```
//!
//! ## 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};
pub use {egui, egui::emath, egui::epaint, glow};
mod epi;
@ -78,14 +67,11 @@ pub use epi::*;
// When compiling for web
#[cfg(target_arch = "wasm32")]
pub mod web;
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;
@ -97,47 +83,25 @@ pub use web_sys;
/// use wasm_bindgen::prelude::*;
///
/// /// This is the entry-point for all the web-assembly.
/// /// This is called from the HTML.
/// /// This is called once 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 })
/// pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
/// eframe::start_web(canvas_id, Box::new(|cc| Box::new(MyEguiApp::new(cc))))
/// }
/// ```
///
/// # 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)
pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bindgen::JsValue> {
web::start(canvas_id, app_creator)?;
Ok(())
}
// ----------------------------------------------------------------------------
// 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.
@ -175,88 +139,32 @@ mod native;
/// }
/// }
/// ```
///
/// # 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)
}
}
pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! {
native::run(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>;
// ---------------------------------------------------------------------------
/// Profiling macro for feature "puffin"
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
mod profiling_scopes {
/// Profiling macro for feature "puffin"
macro_rules! profile_function {
macro_rules! profile_function {
($($arg: tt)*) => {
#[cfg(feature = "puffin")]
puffin::profile_function!($($arg)*);
};
}
pub(crate) use profile_function;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use profile_function;
/// Profiling macro for feature "puffin"
macro_rules! profile_scope {
/// Profiling macro for feature "puffin"
#[cfg(not(target_arch = "wasm32"))]
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::*;
pub(crate) use profile_scope;

View file

@ -0,0 +1,347 @@
use crate::epi;
use egui_winit::{native_pixels_per_point, WindowSettings};
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 window_builder(
native_options: &epi::NativeOptions,
window_settings: &Option<WindowSettings>,
) -> winit::window::WindowBuilder {
let epi::NativeOptions {
always_on_top,
maximized,
decorated,
drag_and_drop_support,
icon_data,
initial_window_pos,
initial_window_size,
min_window_size,
max_window_size,
resizable,
transparent,
vsync: _, // used in `fn create_display`
multisampling: _, // used in `fn create_display`
depth_buffer: _, // used in `fn create_display`
stencil_buffer: _, // used in `fn create_display`
} = native_options;
let window_icon = icon_data.clone().and_then(load_icon);
let mut window_builder = winit::window::WindowBuilder::new()
.with_always_on_top(*always_on_top)
.with_maximized(*maximized)
.with_decorations(*decorated)
.with_resizable(*resizable)
.with_transparent(*transparent)
.with_window_icon(window_icon);
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);
if let Some(window_settings) = window_settings {
window_builder = window_settings.initialize_window(window_builder);
} else {
if let Some(pos) = *initial_window_pos {
window_builder = window_builder.with_position(winit::dpi::PhysicalPosition {
x: pos.x as f64,
y: pos.y as f64,
});
}
if let Some(initial_window_size) = *initial_window_size {
window_builder = window_builder.with_inner_size(points_to_size(initial_window_size));
}
}
window_builder
}
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,
) {
let epi::backend::AppOutput {
quit: _,
window_size,
window_title,
decorated,
drag_window,
window_pos,
} = 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(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();
}
}
// ----------------------------------------------------------------------------
/// 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 quit
quit: bool,
can_drag_window: bool,
}
impl EpiIntegration {
pub fn new(
gl: std::rc::Rc<glow::Context>,
max_texture_side: usize,
window: &winit::window::Window,
storage: Option<Box<dyn epi::Storage>>,
) -> Self {
let egui_ctx = egui::Context::default();
*egui_ctx.memory() = load_egui_memory(storage.as_deref()).unwrap_or_default();
let prefer_dark_mode = prefer_dark_mode();
let frame = epi::Frame {
info: epi::IntegrationInfo {
web_info: None,
prefer_dark_mode,
cpu_usage: None,
native_pixels_per_point: Some(native_pixels_per_point(window)),
},
output: Default::default(),
storage,
gl,
};
if prefer_dark_mode == Some(true) {
egui_ctx.set_visuals(egui::Visuals::dark());
} else {
egui_ctx.set_visuals(egui::Visuals::light());
}
Self {
frame,
last_auto_save: std::time::Instant::now(),
egui_ctx,
egui_winit: egui_winit::State::new(max_texture_side, window),
pending_full_output: Default::default(),
quit: false,
can_drag_window: false,
}
}
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().clone();
self.egui_ctx.memory().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() = saved_memory; // We don't want to remember that windows were huge.
self.egui_ctx.clear_animations();
}
/// If `true`, it is time to shut down.
pub fn should_quit(&self) -> bool {
self.quit
}
pub fn on_event(&mut self, app: &mut dyn epi::App, event: &winit::event::WindowEvent<'_>) {
use winit::event::{ElementState, MouseButton, WindowEvent};
match event {
WindowEvent::CloseRequested => self.quit = app.on_exit_event(),
WindowEvent::Destroyed => self.quit = true,
WindowEvent::MouseInput {
button: MouseButton::Left,
state: ElementState::Pressed,
..
} => self.can_drag_window = true,
_ => {}
}
self.egui_winit.on_event(&self.egui_ctx, event);
}
pub fn update(
&mut self,
app: &mut dyn epi::App,
window: &winit::window::Window,
) -> egui::FullOutput {
let frame_start = std::time::Instant::now();
let raw_input = self.egui_winit.take_egui_input(window);
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.quit {
self.quit = app.on_exit_event();
}
handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
}
let frame_time = (std::time::Instant::now() - frame_start).as_secs_f64() as f32;
self.frame.info.cpu_usage = Some(frame_time);
full_output
}
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");
epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, &*self.egui_ctx.memory());
}
{
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
}
#[cfg(feature = "dark-light")]
fn prefer_dark_mode() -> Option<bool> {
match dark_light::detect() {
dark_light::Mode::Dark => Some(true),
dark_light::Mode::Light => Some(false),
}
}
#[cfg(not(feature = "dark-light"))]
fn prefer_dark_mode() -> Option<bool> {
None
}

View file

@ -26,7 +26,6 @@ impl FileStorage {
/// Store the state in this .ron file.
pub fn from_ron_filepath(ron_filepath: impl Into<PathBuf>) -> Self {
let ron_filepath: PathBuf = ron_filepath.into();
tracing::debug!("Loading app state from {:?}…", ron_filepath);
Self {
kv: read_ron(&ron_filepath).unwrap_or_default(),
ron_filepath,

View file

@ -1,6 +1,8 @@
mod epi_integration;
pub mod run;
mod run;
/// File storage which can be used by native backends.
#[cfg(feature = "persistence")]
pub mod file_storage;
pub use run::run;

184
eframe/src/native/run.rs Normal file
View file

@ -0,0 +1,184 @@
use super::epi_integration;
use crate::epi;
use egui_winit::winit;
struct RequestRepaintEvent;
#[allow(unsafe_code)]
fn create_display(
native_options: &NativeOptions,
window_builder: winit::window::WindowBuilder,
event_loop: &winit::event_loop::EventLoop<RequestRepaintEvent>,
) -> (
glutin::WindowedContext<glutin::PossiblyCurrent>,
glow::Context,
) {
crate::profile_function!();
let gl_window = unsafe {
glutin::ContextBuilder::new()
.with_depth_buffer(native_options.depth_buffer)
.with_multisampling(native_options.multisampling)
.with_srgb(true)
.with_stencil_buffer(native_options.stencil_buffer)
.with_vsync(native_options.vsync)
.build_windowed(window_builder, event_loop)
.unwrap()
.make_current()
.unwrap()
};
let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) };
(gl_window, gl)
}
// ----------------------------------------------------------------------------
pub use epi::NativeOptions;
/// Run an egui app
#[allow(unsafe_code)]
pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator) -> ! {
let storage = epi_integration::create_storage(app_name);
let window_settings = epi_integration::load_window_settings(storage.as_deref());
let window_builder =
epi_integration::window_builder(native_options, &window_settings).with_title(app_name);
let event_loop = winit::event_loop::EventLoop::with_user_event();
let (gl_window, gl) = create_display(native_options, window_builder, &event_loop);
let gl = std::rc::Rc::new(gl);
let mut painter = egui_glow::Painter::new(gl.clone(), None, "")
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
let mut integration = epi_integration::EpiIntegration::new(
gl.clone(),
painter.max_texture_side(),
gl_window.window(),
storage,
);
{
let event_loop_proxy = egui::mutex::Mutex::new(event_loop.create_proxy());
integration.egui_ctx.set_request_repaint_callback(move || {
event_loop_proxy.lock().send_event(RequestRepaintEvent).ok();
});
}
let mut app = app_creator(&epi::CreationContext {
egui_ctx: integration.egui_ctx.clone(),
integration_info: integration.frame.info(),
storage: integration.frame.storage(),
gl: gl.clone(),
});
if app.warm_up_enabled() {
integration.warm_up(app.as_mut(), gl_window.window());
}
let mut is_focused = true;
event_loop.run(move |event, _, control_flow| {
let mut redraw = || {
#[cfg(feature = "puffin")]
puffin::GlobalProfiler::lock().new_frame();
if !is_focused {
// On Mac, a minimized Window uses up all CPU: https://github.com/emilk/egui/issues/325
// We can't know if we are minimized: https://github.com/rust-windowing/winit/issues/208
// But we know if we are focused (in foreground). When minimized, we are not focused.
// However, a user may want an egui with an animation in the background,
// so we still need to repaint quite fast.
crate::profile_scope!("bg_sleep");
std::thread::sleep(std::time::Duration::from_millis(10));
}
crate::profile_scope!("frame");
let screen_size_in_pixels: [u32; 2] = gl_window.window().inner_size().into();
egui_glow::painter::clear(
&gl,
screen_size_in_pixels,
app.clear_color(&integration.egui_ctx.style().visuals),
);
let egui::FullOutput {
platform_output,
needs_repaint,
textures_delta,
shapes,
} = integration.update(app.as_mut(), gl_window.window());
integration.handle_platform_output(gl_window.window(), platform_output);
let clipped_primitives = {
crate::profile_scope!("tessellate");
integration.egui_ctx.tessellate(shapes)
};
painter.paint_and_update_textures(
screen_size_in_pixels,
integration.egui_ctx.pixels_per_point(),
&clipped_primitives,
&textures_delta,
);
{
crate::profile_scope!("swap_buffers");
gl_window.swap_buffers().unwrap();
}
{
*control_flow = if integration.should_quit() {
winit::event_loop::ControlFlow::Exit
} else if needs_repaint {
gl_window.window().request_redraw();
winit::event_loop::ControlFlow::Poll
} else {
winit::event_loop::ControlFlow::Wait
};
}
integration.maybe_autosave(app.as_mut(), gl_window.window());
};
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, .. } => {
if let winit::event::WindowEvent::Focused(new_focused) = event {
is_focused = new_focused;
}
if let winit::event::WindowEvent::Resized(physical_size) = &event {
gl_window.resize(*physical_size);
} else if let glutin::event::WindowEvent::ScaleFactorChanged {
new_inner_size,
..
} = &event
{
gl_window.resize(**new_inner_size);
}
integration.on_event(app.as_mut(), &event);
if integration.should_quit() {
*control_flow = winit::event_loop::ControlFlow::Exit;
}
gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead
}
winit::event::Event::LoopDestroyed => {
integration.save(&mut *app, gl_window.window());
app.on_exit(&gl);
painter.destroy();
}
winit::event::Event::UserEvent(RequestRepaintEvent) => {
gl_window.window().request_redraw();
}
_ => (),
}
});
}

View file

@ -1,11 +1,9 @@
use egui::{
mutex::{Mutex, MutexGuard},
TexturesDelta,
};
use super::{glow_wrapping::WrappedGlowPainter, *};
use crate::{epi, App};
use crate::epi;
use super::{web_painter::WebPainter, *};
use egui::TexturesDelta;
pub use egui::{pos2, Color32};
// ----------------------------------------------------------------------------
@ -36,48 +34,17 @@ impl WebInput {
use std::sync::atomic::Ordering::SeqCst;
/// Stores when to do the next repaint.
pub struct NeedRepaint(Mutex<f64>);
pub struct NeedRepaint(std::sync::atomic::AtomicBool);
impl Default for NeedRepaint {
fn default() -> Self {
Self(Mutex::new(f64::NEG_INFINITY)) // start with a repaint
Self(true.into())
}
}
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 fetch_and_clear(&self) -> bool {
self.0.swap(false, SeqCst)
}
pub fn set_true(&self) {
@ -87,10 +54,6 @@ impl IsDestroyed {
// ----------------------------------------------------------------------------
fn user_agent() -> Option<String> {
web_sys::window()?.navigator().user_agent().ok()
}
fn web_location() -> epi::Location {
let location = web_sys::window().unwrap().location();
@ -105,7 +68,7 @@ fn web_location() -> epi::Location {
let query_map = parse_query_map(&query)
.iter()
.map(|(k, v)| ((*k).to_owned(), (*v).to_owned()))
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect();
epi::Location {
@ -165,94 +128,60 @@ fn test_parse_query() {
pub struct AppRunner {
pub(crate) frame: epi::Frame,
egui_ctx: egui::Context,
painter: ActiveWebPainter,
painter: WrappedGlowPainter,
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?;
pub fn new(canvas_id: &str, app_creator: epi::AppCreator) -> Result<Self, JsValue> {
let painter = WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?; // fail early
let system_theme = if web_options.follow_system_theme {
super::system_theme()
} else {
None
};
let prefer_dark_mode = super::prefer_dark_mode();
let info = epi::IntegrationInfo {
web_info: epi::WebInfo {
user_agent: user_agent().unwrap_or_default(),
web_info: Some(epi::WebInfo {
location: web_location(),
},
system_theme,
}),
prefer_dark_mode,
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());
if prefer_dark_mode == Some(true) {
egui_ctx.set_visuals(egui::Visuals::dark());
} else {
egui_ctx.set_visuals(egui::Visuals::light());
}
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,
gl: painter.painter.gl().clone(),
});
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,
gl: painter.gl().clone(),
};
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();
needs_repaint.0.store(true, SeqCst);
});
}
@ -263,13 +192,11 @@ impl AppRunner {
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());
@ -281,17 +208,6 @@ impl AppRunner {
&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;
@ -313,38 +229,19 @@ impl AppRunner {
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));
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
self.egui_ctx.memory().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.memory() = 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.
/// Returns `true` if egui requests a repaint.
///
/// Call [`Self::paint`] later to paint
pub fn logic(&mut self) -> Result<(std::time::Duration, Vec<egui::ClippedPrimitive>), JsValue> {
pub fn logic(&mut self) -> Result<(bool, Vec<egui::ClippedPrimitive>), JsValue> {
let frame_start = now_sec();
resize_canvas_to_screen_size(self.canvas_id(), self.app.max_size_points());
@ -356,7 +253,7 @@ impl AppRunner {
});
let egui::FullOutput {
platform_output,
repaint_after,
needs_repaint,
textures_delta,
shapes,
} = full_output;
@ -367,11 +264,23 @@ impl AppRunner {
{
let app_output = self.frame.take_app_output();
let epi::backend::AppOutput {} = app_output;
let epi::backend::AppOutput {
quit: _, // Can't quit a web page
window_size: _, // Can't resize a web page
window_title: _, // TODO: change title of window
decorated: _, // Can't toggle decorations
drag_window: _, // Can't be dragged
window_pos: _, // Can't set position of a web page
} = app_output;
}
self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32);
Ok((repaint_after, clipped_primitives))
Ok((needs_repaint, clipped_primitives))
}
pub fn clear_color_buffer(&self) {
self.painter
.clear(self.app.clear_color(&self.egui_ctx.style().visuals));
}
/// Paint the results of the last call to [`Self::logic`].
@ -379,7 +288,6 @@ impl AppRunner {
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,
@ -389,7 +297,7 @@ impl AppRunner {
}
fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
if self.egui_ctx.options(|o| o.screen_reader) {
if self.egui_ctx.options().screen_reader {
self.screen_reader
.speak(&platform_output.events_description());
}
@ -401,8 +309,6 @@ impl AppRunner {
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);
@ -431,60 +337,24 @@ impl AppRunner {
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,
&self,
target: &EventTarget,
event_name: &'static str,
mut closure: impl FnMut(E, MutexGuard<'_, AppRunner>) + 'static,
) -> Result<(), JsValue> {
use wasm_bindgen::JsCast;
// Create a JS closure based on the FnMut provided
let closure = Closure::wrap({
// Clone atomics
@ -499,19 +369,14 @@ impl AppRunnerContainer {
closure(event, runner_ref.lock());
}
}) as Box<dyn FnMut(web_sys::Event)>
}) as Box<dyn FnMut(_)>
});
// 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));
// Bypass closure drop so that event handler can call the closure
closure.forget();
Ok(())
}
@ -521,17 +386,8 @@ impl AppRunnerContainer {
/// 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?;
pub fn start(canvas_id: &str, app_creator: epi::AppCreator) -> Result<AppRunnerRef, JsValue> {
let mut runner = AppRunner::new(canvas_id, app_creator)?;
runner.warm_up()?;
start_runner(runner)
}
@ -539,26 +395,24 @@ pub async fn start(
/// 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 {
let 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::install_canvas_events(&runner_container)?;
super::events::install_document_events(&runner_container)?;
text_agent::install_text_agent(&runner_container)?;
super::events::repaint_every_ms(&runner_container, 1000)?; // just in case. TODO: make it a parameter
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;
let panicked = runner_container.panicked;
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);
panicked.store(true, SeqCst);
// Propagate panic info to the previously registered panic hook
previous_hook(panic_info);
@ -576,10 +430,8 @@ 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) {}
}

View file

@ -1,36 +1,30 @@
use std::sync::atomic::{AtomicBool, Ordering};
use egui::Key;
use super::*;
struct IsDestroyed(pub bool);
use std::sync::atomic::{AtomicBool, Ordering};
pub fn paint_and_schedule(
runner_ref: &AppRunnerRef,
panicked: Arc<AtomicBool>,
) -> Result<(), JsValue> {
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<IsDestroyed, JsValue> {
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
let mut runner_lock = runner_ref.lock();
let is_destroyed = runner_lock.is_destroyed.fetch();
if !is_destroyed && runner_lock.needs_repaint.when_to_repaint() <= now_sec() {
runner_lock.needs_repaint.clear();
let (repaint_after, clipped_primitives) = runner_lock.logic()?;
if runner_lock.needs_repaint.fetch_and_clear() {
runner_lock.clear_color_buffer();
let (needs_repaint, clipped_primitives) = runner_lock.logic()?;
runner_lock.paint(&clipped_primitives)?;
runner_lock
.needs_repaint
.repaint_after(repaint_after.as_secs_f64());
if needs_repaint {
runner_lock.needs_repaint.set_true();
}
runner_lock.auto_save();
}
Ok(IsDestroyed(is_destroyed))
Ok(())
}
fn request_animation_frame(
runner_ref: AppRunnerRef,
panicked: Arc<AtomicBool>,
) -> Result<(), JsValue> {
use wasm_bindgen::JsCast;
let window = web_sys::window().unwrap();
let closure = Closure::once(move || paint_and_schedule(&runner_ref, panicked));
window.request_animation_frame(closure.as_ref().unchecked_ref())?;
@ -40,16 +34,14 @@ pub fn paint_and_schedule(
// Only paint and schedule if there has been no panic
if !panicked.load(Ordering::SeqCst) {
let is_destroyed = paint_if_needed(runner_ref)?;
if !is_destroyed.0 {
request_animation_frame(runner_ref.clone(), panicked)?;
}
paint_if_needed(runner_ref)?;
request_animation_frame(runner_ref.clone(), panicked)?;
}
Ok(())
}
pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
pub fn install_document_events(runner_container: &AppRunnerContainer) -> Result<(), JsValue> {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
@ -58,7 +50,7 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
"keydown",
|event: web_sys::KeyboardEvent, mut runner_lock| {
if event.is_composing() || event.key_code() == 229 {
// https://web.archive.org/web/20200526195704/https://www.fxsitecompat.dev/en-CA/docs/2018/keydown-and-keyup-events-are-now-fired-during-ime-composition/
// https://www.fxsitecompat.dev/en-CA/docs/2018/keydown-and-keyup-events-are-now-fired-during-ime-composition/
return;
}
@ -66,13 +58,11 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
runner_lock.input.raw.modifiers = modifiers;
let key = event.key();
let egui_key = translate_key(&key);
if let Some(key) = egui_key {
if let Some(key) = translate_key(&key) {
runner_lock.input.raw.events.push(egui::Event::Key {
key,
pressed: true,
repeat: false, // egui will fill this in for us!
modifiers,
});
}
@ -84,22 +74,14 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
{
runner_lock.input.raw.events.push(egui::Event::Text(key));
}
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
let egui_wants_keyboard = runner_lock.egui_ctx().wants_keyboard_input();
#[allow(clippy::if_same_then_else)]
let prevent_default = if egui_key == Some(Key::Tab) {
let prevent_default = if matches!(event.key().as_str(), "Tab") {
// Always prevent moving cursor to url bar.
// egui wants to use tab to move to the next text field.
true
} else if egui_key == Some(Key::P) {
#[allow(clippy::needless_bool)]
if modifiers.ctrl || modifiers.command || modifiers.mac_cmd {
true // Prevent ctrl-P opening the print dialog. Users may want to use it for a command palette.
} else {
false // let normal P:s through
}
} else if egui_wants_keyboard {
matches!(
event.key().as_str(),
@ -123,7 +105,6 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
if prevent_default {
event.prevent_default();
// event.stop_propagation();
}
},
)?;
@ -138,11 +119,10 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
runner_lock.input.raw.events.push(egui::Event::Key {
key,
pressed: false,
repeat: false,
modifiers,
});
}
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
},
)?;
@ -156,7 +136,7 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
let text = text.replace("\r\n", "\n");
if !text.is_empty() {
runner_lock.input.raw.events.push(egui::Event::Paste(text));
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
}
event.stop_propagation();
event.prevent_default();
@ -171,7 +151,7 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
"cut",
|_: web_sys::ClipboardEvent, mut runner_lock| {
runner_lock.input.raw.events.push(egui::Event::Cut);
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
},
)?;
@ -181,7 +161,7 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
"copy",
|_: web_sys::ClipboardEvent, mut runner_lock| {
runner_lock.input.raw.events.push(egui::Event::Copy);
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
},
)?;
@ -190,7 +170,7 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
&window,
event_name,
|_: web_sys::Event, runner_lock| {
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
},
)?;
}
@ -200,40 +180,66 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
"hashchange",
|_: web_sys::Event, mut runner_lock| {
// `epi::Frame::info(&self)` clones `epi::IntegrationInfo`, but we need to modify the original here
runner_lock.frame.info.web_info.location.hash = location_hash();
if let Some(web_info) = &mut runner_lock.frame.info.web_info {
web_info.location.hash = location_hash();
}
},
)?;
Ok(())
}
pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
/// Repaint at least every `ms` milliseconds.
pub fn repaint_every_ms(
runner_container: &AppRunnerContainer,
milliseconds: i32,
) -> Result<(), JsValue> {
assert!(milliseconds >= 0);
use wasm_bindgen::JsCast;
let window = web_sys::window().unwrap();
let closure = Closure::wrap(Box::new({
let runner = runner_container.runner.clone();
let panicked = runner_container.panicked.clone();
move || {
// Do not lock the runner if the code has panicked
if !panicked.load(Ordering::SeqCst) {
runner.lock().needs_repaint.set_true();
}
}
}) as Box<dyn FnMut()>);
window.set_interval_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
milliseconds,
)?;
closure.forget();
Ok(())
}
pub fn install_canvas_events(runner_container: &AppRunnerContainer) -> Result<(), JsValue> {
use wasm_bindgen::JsCast;
let canvas = canvas_element(runner_container.runner.lock().canvas_id()).unwrap();
let prevent_default_events = [
{
// By default, right-clicks open a context menu.
// We don't want to do that (right clicks is handled by egui):
"contextmenu",
// Allow users to use ctrl-p for e.g. a command palette
"afterprint",
];
for event_name in prevent_default_events {
let closure =
move |event: web_sys::MouseEvent,
mut _runner_lock: egui::mutex::MutexGuard<'_, AppRunner>| {
event.prevent_default();
// event.stop_propagation();
// tracing::debug!("Preventing event {:?}", event_name);
};
runner_container.add_event_listener(&canvas, event_name, closure)?;
let event_name = "contextmenu";
let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
event.prevent_default();
}) as Box<dyn FnMut(_)>);
canvas.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?;
closure.forget();
}
runner_container.add_event_listener(
&canvas,
"mousedown",
|event: web_sys::MouseEvent, mut runner_lock: egui::mutex::MutexGuard<'_, AppRunner>| {
|event: web_sys::MouseEvent, mut runner_lock| {
if let Some(button) = button_from_mouse_event(&event) {
let pos = pos_from_mouse_event(runner_lock.canvas_id(), &event);
let modifiers = runner_lock.input.raw.modifiers;
@ -247,7 +253,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
pressed: true,
modifiers,
});
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
}
event.stop_propagation();
// Note: prevent_default breaks VSCode tab focusing, hence why we don't call it here.
@ -264,7 +270,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
.raw
.events
.push(egui::Event::PointerMoved(pos));
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
},
@ -287,7 +293,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
pressed: false,
modifiers,
});
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
text_agent::update_text_agent(runner_lock);
}
@ -301,7 +307,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
"mouseleave",
|event: web_sys::MouseEvent, mut runner_lock| {
runner_lock.input.raw.events.push(egui::Event::PointerGone);
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
},
@ -328,8 +334,8 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
modifiers,
});
push_touches(&mut runner_lock, egui::TouchPhase::Start, &event);
runner_lock.needs_repaint.repaint_asap();
push_touches(&mut *runner_lock, egui::TouchPhase::Start, &event);
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
},
@ -350,8 +356,8 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
.events
.push(egui::Event::PointerMoved(pos));
push_touches(&mut runner_lock, egui::TouchPhase::Move, &event);
runner_lock.needs_repaint.repaint_asap();
push_touches(&mut *runner_lock, egui::TouchPhase::Move, &event);
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
},
@ -377,8 +383,8 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
// Then remove hover effect:
runner_lock.input.raw.events.push(egui::Event::PointerGone);
push_touches(&mut runner_lock, egui::TouchPhase::End, &event);
runner_lock.needs_repaint.repaint_asap();
push_touches(&mut *runner_lock, egui::TouchPhase::End, &event);
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
}
@ -408,7 +414,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
}
web_sys::WheelEvent::DOM_DELTA_LINE => {
#[allow(clippy::let_and_return)]
let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in winit.
let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in egui_glium / winit.
points_per_scroll_line
}
_ => 1.0, // DOM_DELTA_PIXEL
@ -437,7 +443,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
.push(egui::Event::Scroll(delta));
}
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
},
@ -457,7 +463,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
});
}
}
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
}
@ -469,7 +475,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
"dragleave",
|event: web_sys::DragEvent, mut runner_lock| {
runner_lock.input.raw.hovered_files.clear();
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
event.stop_propagation();
event.prevent_default();
},
@ -481,7 +487,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
move |event: web_sys::DragEvent, mut runner_lock| {
if let Some(data_transfer) = event.data_transfer() {
runner_lock.input.raw.hovered_files.clear();
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
// Unlock the runner so it can be locked after a future await point
drop(runner_lock);
@ -517,7 +523,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
..Default::default()
},
);
runner_lock.needs_repaint.repaint_asap();
runner_lock.needs_repaint.set_true();
}
Err(err) => {
tracing::error!("Failed to read file: {:?}", err);

View file

@ -0,0 +1,158 @@
use egui::{ClippedPrimitive, Rgba};
use egui_glow::glow;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web_sys::HtmlCanvasElement;
#[cfg(not(target_arch = "wasm32"))]
use web_sys::{WebGl2RenderingContext, WebGlRenderingContext};
pub(crate) struct WrappedGlowPainter {
pub(crate) canvas: HtmlCanvasElement,
pub(crate) canvas_id: String,
pub(crate) painter: egui_glow::Painter,
}
impl WrappedGlowPainter {
pub fn new(canvas_id: &str) -> Result<Self, String> {
let canvas = super::canvas_element_or_die(canvas_id);
let (gl, shader_prefix) = init_glow_context_from_canvas(&canvas)?;
let gl = std::rc::Rc::new(gl);
let dimension = [canvas.width() as i32, canvas.height() as i32];
let painter = egui_glow::Painter::new(gl, Some(dimension), shader_prefix)
.map_err(|error| format!("Error starting glow painter: {}", error))?;
Ok(Self {
canvas,
canvas_id: canvas_id.to_owned(),
painter,
})
}
}
impl WrappedGlowPainter {
pub fn gl(&self) -> &std::rc::Rc<glow::Context> {
self.painter.gl()
}
pub fn max_texture_side(&self) -> usize {
self.painter.max_texture_side()
}
pub fn canvas_id(&self) -> &str {
&self.canvas_id
}
pub fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
self.painter.set_texture(tex_id, delta);
}
pub fn free_texture(&mut self, tex_id: egui::TextureId) {
self.painter.free_texture(tex_id);
}
pub fn clear(&self, clear_color: Rgba) {
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color);
}
pub fn paint_primitives(
&mut self,
clipped_primitives: &[ClippedPrimitive],
pixels_per_point: f32,
) -> Result<(), JsValue> {
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
self.painter
.paint_primitives(canvas_dimension, pixels_per_point, clipped_primitives);
Ok(())
}
pub fn paint_and_update_textures(
&mut self,
clipped_primitives: &[egui::ClippedPrimitive],
pixels_per_point: f32,
textures_delta: &egui::TexturesDelta,
) -> Result<(), JsValue> {
for (id, image_delta) in &textures_delta.set {
self.set_texture(*id, image_delta);
}
self.paint_primitives(clipped_primitives, pixels_per_point)?;
for &id in &textures_delta.free {
self.free_texture(id);
}
Ok(())
}
}
/// Returns glow context and shader prefix.
fn init_glow_context_from_canvas(
canvas: &HtmlCanvasElement,
) -> Result<(glow::Context, &'static str), String> {
const BEST_FIRST: bool = true;
let result = if BEST_FIRST {
// Trying WebGl2 first
init_webgl2(canvas).or_else(|| init_webgl1(canvas))
} else {
// Trying WebGl1 first (useful for testing).
tracing::warn!("Looking for WebGL1 first");
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 super::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))
}
trait DummyWebGLConstructor {
fn from_webgl1_context(context: web_sys::WebGlRenderingContext) -> Self;
fn from_webgl2_context(context: web_sys::WebGl2RenderingContext) -> Self;
}

View file

@ -14,8 +14,6 @@ pub fn button_from_mouse_event(event: &web_sys::MouseEvent) -> Option<egui::Poin
0 => Some(egui::PointerButton::Primary),
1 => Some(egui::PointerButton::Middle),
2 => Some(egui::PointerButton::Secondary),
3 => Some(egui::PointerButton::Extra1),
4 => Some(egui::PointerButton::Extra2),
_ => None,
}
}
@ -54,8 +52,8 @@ pub fn pos_from_touch_event(
fn pos_from_touch(canvas_origin: egui::Pos2, touch: &web_sys::Touch) -> egui::Pos2 {
egui::Pos2 {
x: touch.page_x() as f32 - canvas_origin.x,
y: touch.page_y() as f32 - canvas_origin.y,
x: touch.page_x() as f32 - canvas_origin.x as f32,
y: touch.page_y() as f32 - canvas_origin.y as f32,
}
}
@ -113,88 +111,62 @@ pub fn should_ignore_key(key: &str) -> bool {
/// Web sends all all keys as strings, so it is up to us to figure out if it is
/// a real text input or the name of a key.
pub fn translate_key(key: &str) -> Option<egui::Key> {
use egui::Key;
match key {
"ArrowDown" => Some(Key::ArrowDown),
"ArrowLeft" => Some(Key::ArrowLeft),
"ArrowRight" => Some(Key::ArrowRight),
"ArrowUp" => Some(Key::ArrowUp),
"ArrowDown" => Some(egui::Key::ArrowDown),
"ArrowLeft" => Some(egui::Key::ArrowLeft),
"ArrowRight" => Some(egui::Key::ArrowRight),
"ArrowUp" => Some(egui::Key::ArrowUp),
"Esc" | "Escape" => Some(Key::Escape),
"Tab" => Some(Key::Tab),
"Backspace" => Some(Key::Backspace),
"Enter" => Some(Key::Enter),
"Space" | " " => Some(Key::Space),
"Esc" | "Escape" => Some(egui::Key::Escape),
"Tab" => Some(egui::Key::Tab),
"Backspace" => Some(egui::Key::Backspace),
"Enter" => Some(egui::Key::Enter),
"Space" | " " => Some(egui::Key::Space),
"Help" | "Insert" => Some(Key::Insert),
"Delete" => Some(Key::Delete),
"Home" => Some(Key::Home),
"End" => Some(Key::End),
"PageUp" => Some(Key::PageUp),
"PageDown" => Some(Key::PageDown),
"Help" | "Insert" => Some(egui::Key::Insert),
"Delete" => Some(egui::Key::Delete),
"Home" => Some(egui::Key::Home),
"End" => Some(egui::Key::End),
"PageUp" => Some(egui::Key::PageUp),
"PageDown" => Some(egui::Key::PageDown),
"-" => Some(Key::Minus),
"+" | "=" => Some(Key::PlusEquals),
"0" => Some(egui::Key::Num0),
"1" => Some(egui::Key::Num1),
"2" => Some(egui::Key::Num2),
"3" => Some(egui::Key::Num3),
"4" => Some(egui::Key::Num4),
"5" => Some(egui::Key::Num5),
"6" => Some(egui::Key::Num6),
"7" => Some(egui::Key::Num7),
"8" => Some(egui::Key::Num8),
"9" => Some(egui::Key::Num9),
"0" => Some(Key::Num0),
"1" => Some(Key::Num1),
"2" => Some(Key::Num2),
"3" => Some(Key::Num3),
"4" => Some(Key::Num4),
"5" => Some(Key::Num5),
"6" => Some(Key::Num6),
"7" => Some(Key::Num7),
"8" => Some(Key::Num8),
"9" => Some(Key::Num9),
"a" | "A" => Some(Key::A),
"b" | "B" => Some(Key::B),
"c" | "C" => Some(Key::C),
"d" | "D" => Some(Key::D),
"e" | "E" => Some(Key::E),
"f" | "F" => Some(Key::F),
"g" | "G" => Some(Key::G),
"h" | "H" => Some(Key::H),
"i" | "I" => Some(Key::I),
"j" | "J" => Some(Key::J),
"k" | "K" => Some(Key::K),
"l" | "L" => Some(Key::L),
"m" | "M" => Some(Key::M),
"n" | "N" => Some(Key::N),
"o" | "O" => Some(Key::O),
"p" | "P" => Some(Key::P),
"q" | "Q" => Some(Key::Q),
"r" | "R" => Some(Key::R),
"s" | "S" => Some(Key::S),
"t" | "T" => Some(Key::T),
"u" | "U" => Some(Key::U),
"v" | "V" => Some(Key::V),
"w" | "W" => Some(Key::W),
"x" | "X" => Some(Key::X),
"y" | "Y" => Some(Key::Y),
"z" | "Z" => Some(Key::Z),
"F1" => Some(Key::F1),
"F2" => Some(Key::F2),
"F3" => Some(Key::F3),
"F4" => Some(Key::F4),
"F5" => Some(Key::F5),
"F6" => Some(Key::F6),
"F7" => Some(Key::F7),
"F8" => Some(Key::F8),
"F9" => Some(Key::F9),
"F10" => Some(Key::F10),
"F11" => Some(Key::F11),
"F12" => Some(Key::F12),
"F13" => Some(Key::F13),
"F14" => Some(Key::F14),
"F15" => Some(Key::F15),
"F16" => Some(Key::F16),
"F17" => Some(Key::F17),
"F18" => Some(Key::F18),
"F19" => Some(Key::F19),
"F20" => Some(Key::F20),
"a" | "A" => Some(egui::Key::A),
"b" | "B" => Some(egui::Key::B),
"c" | "C" => Some(egui::Key::C),
"d" | "D" => Some(egui::Key::D),
"e" | "E" => Some(egui::Key::E),
"f" | "F" => Some(egui::Key::F),
"g" | "G" => Some(egui::Key::G),
"h" | "H" => Some(egui::Key::H),
"i" | "I" => Some(egui::Key::I),
"j" | "J" => Some(egui::Key::J),
"k" | "K" => Some(egui::Key::K),
"l" | "L" => Some(egui::Key::L),
"m" | "M" => Some(egui::Key::M),
"n" | "N" => Some(egui::Key::N),
"o" | "O" => Some(egui::Key::O),
"p" | "P" => Some(egui::Key::P),
"q" | "Q" => Some(egui::Key::Q),
"r" | "R" => Some(egui::Key::R),
"s" | "S" => Some(egui::Key::S),
"t" | "T" => Some(egui::Key::T),
"u" | "U" => Some(egui::Key::U),
"v" | "V" => Some(egui::Key::V),
"w" | "W" => Some(egui::Key::W),
"x" | "X" => Some(egui::Key::X),
"y" | "Y" => Some(egui::Key::Y),
"z" | "Z" => Some(egui::Key::Z),
_ => None,
}

View file

@ -4,26 +4,12 @@
pub mod backend;
mod events;
mod glow_wrapping;
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::*;
@ -34,19 +20,15 @@ use std::sync::{
Arc,
};
use egui::Vec2;
use egui::mutex::{Mutex, MutexGuard};
use wasm_bindgen::prelude::*;
use web_sys::EventTarget;
use input::*;
use crate::Theme;
// ----------------------------------------------------------------------------
/// Current time in seconds (since undefined point in time).
///
/// Monotonically increasing.
/// Current time in seconds (since undefined point in time)
pub fn now_sec() -> f64 {
web_sys::window()
.expect("should have a Window")
@ -56,7 +38,6 @@ pub fn now_sec() -> f64 {
/ 1000.0
}
#[allow(dead_code)]
pub fn screen_size_in_native_points() -> Option<egui::Vec2> {
let window = web_sys::window()?;
Some(egui::vec2(
@ -74,15 +55,17 @@ pub fn native_pixels_per_point() -> f32 {
}
}
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 prefer_dark_mode() -> Option<bool> {
Some(
web_sys::window()?
.match_media("(prefers-color-scheme: dark)")
.ok()??
.matches(),
)
}
pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
use wasm_bindgen::JsCast;
let document = web_sys::window()?.document()?;
let canvas = document.get_element_by_id(canvas_id)?;
canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
@ -90,14 +73,14 @@ pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
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))
.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)
egui::Pos2::new(rect.left() as f32, rect.top() as f32)
}
pub fn canvas_size_in_points(canvas_id: &str) -> egui::Vec2 {
@ -111,25 +94,13 @@ pub fn canvas_size_in_points(canvas_id: &str) -> egui::Vec2 {
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 screen_size_points = screen_size_in_native_points()?;
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 = pixels_per_point * screen_size_points;
let canvas_size_pixels = canvas_size_pixels.min(max_size_pixels);
let canvas_size_points = canvas_size_pixels / pixels_per_point;
@ -256,3 +227,47 @@ pub fn percent_decode(s: &str) -> String {
.decode_utf8_lossy()
.to_string()
}
// ----------------------------------------------------------------------------
pub(crate) 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: 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
}

View file

@ -1,9 +1,9 @@
pub struct ScreenReader {
#[cfg(feature = "tts")]
#[cfg(feature = "screen_reader")]
tts: Option<tts::Tts>,
}
#[cfg(not(feature = "tts"))]
#[cfg(not(feature = "screen_reader"))]
#[allow(clippy::derivable_impls)] // False positive
impl Default for ScreenReader {
fn default() -> Self {
@ -11,7 +11,7 @@ impl Default for ScreenReader {
}
}
#[cfg(feature = "tts")]
#[cfg(feature = "screen_reader")]
impl Default for ScreenReader {
fn default() -> Self {
let tts = match tts::Tts::default() {
@ -29,11 +29,11 @@ impl Default for ScreenReader {
}
impl ScreenReader {
#[cfg(not(feature = "tts"))]
#[cfg(not(feature = "screen_reader"))]
#[allow(clippy::unused_self)]
pub fn speak(&mut self, _text: &str) {}
#[cfg(feature = "tts")]
#[cfg(feature = "screen_reader")]
pub fn speak(&mut self, text: &str) {
if text.is_empty() {
return;

Some files were not shown because too many files have changed in this diff Show more