Merge remote-tracking branch 'egui/master' into dynamic-grid
This commit is contained in:
commit
767357c468
100 changed files with 1905 additions and 1945 deletions
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -2,7 +2,7 @@
|
|||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
labels: feature-request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
|
18
.github/workflows/rust.yml
vendored
18
.github/workflows/rust.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.54.0
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: sudo apt-get update && sudo apt-get install libspeechd-dev
|
||||
- uses: actions-rs/cargo@v1
|
||||
|
@ -32,7 +32,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.54.0
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: sudo apt-get update && sudo apt-get install libspeechd-dev
|
||||
- uses: actions-rs/cargo@v1
|
||||
|
@ -48,7 +48,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.54.0
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions-rs/cargo@v1
|
||||
|
@ -64,7 +64,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.54.0
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions-rs/cargo@v1
|
||||
|
@ -80,7 +80,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.54.0
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: sudo apt-get update && sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libgtk-3-dev # libgtk-3-dev is used by rfd
|
||||
- uses: actions-rs/cargo@v1
|
||||
|
@ -96,7 +96,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.54.0
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
|
@ -112,7 +112,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.54.0
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: rustup component add clippy
|
||||
- run: sudo apt-get update && sudo apt-get install libspeechd-dev libgtk-3-dev # libgtk-3-dev is used by rfd
|
||||
|
@ -129,7 +129,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.54.0
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: sudo apt-get update && sudo apt-get install libspeechd-dev
|
||||
- run: cargo doc -p emath -p epaint -p egui -p eframe -p epi -p egui_web -p egui-winit -p egui_glium -p egui_glow --lib --no-deps --all-features
|
||||
|
@ -142,7 +142,7 @@ jobs:
|
|||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.54.0
|
||||
toolchain: 1.56.0
|
||||
override: true
|
||||
- run: sudo apt-get update && sudo apt-get install libspeechd-dev
|
||||
- run: rustup target add wasm32-unknown-unknown
|
||||
|
|
60
CHANGELOG.md
60
CHANGELOG.md
|
@ -8,28 +8,47 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
|||
## Unreleased
|
||||
|
||||
### Added ⭐
|
||||
* Add context menus: See `Ui::menu_button` and `Response::context_menu` ([#543](https://github.com/emilk/egui/pull/543)).
|
||||
* Added `Ui::add_visible` and `Ui::add_visible_ui`.
|
||||
|
||||
### Changed 🔧
|
||||
* Renamed `Ui::visible` to `Ui::is_visible`.
|
||||
|
||||
|
||||
## 0.16.1 - 2021-12-31 - Add back `CtxRef::begin_frame,end_frame`
|
||||
|
||||
### Added ⭐
|
||||
* Add back `CtxRef::begin_frame,end_frame` as an alternative to `CtxRef::run`.
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29 - Context menus and rich text
|
||||
|
||||
### Added ⭐
|
||||
* Added context menus: See `Ui::menu_button` and `Response::context_menu` ([#543](https://github.com/emilk/egui/pull/543)).
|
||||
* Most widgets containing text (`Label`, `Button` etc) now supports rich text ([#855](https://github.com/emilk/egui/pull/855)).
|
||||
* Plots:
|
||||
* Add bar charts and box plots ([#863](https://github.com/emilk/egui/pull/863)).
|
||||
* Added bar charts and box plots ([#863](https://github.com/emilk/egui/pull/863)).
|
||||
* You can now query information about the plot (e.g. get the mouse position in plot coordinates, or the plot
|
||||
bounds) while adding items. `Plot` ([#766](https://github.com/emilk/egui/pull/766) and
|
||||
[#892](https://github.com/emilk/egui/pull/892)).
|
||||
* You can now read and write the cursor of a `TextEdit` ([#848](https://github.com/emilk/egui/pull/848)).
|
||||
* When using a custom font you can now specify a font index ([#873](https://github.com/emilk/egui/pull/873)).
|
||||
* Add vertical sliders with `Slider::new(…).vertical()` ([#875](https://github.com/emilk/egui/pull/875)).
|
||||
* Add `Button::image_and_text` ([#832](https://github.com/emilk/egui/pull/832)).
|
||||
* Added vertical sliders with `Slider::new(…).vertical()` ([#875](https://github.com/emilk/egui/pull/875)).
|
||||
* Added `Button::image_and_text` ([#832](https://github.com/emilk/egui/pull/832)).
|
||||
* Added `CollapsingHeader::open` to control if it is open or collapsed ([#1006](https://github.com/emilk/egui/pull/1006)).
|
||||
* Added `egui::widgets::color_picker::color_picker_color32` to show the color picker.
|
||||
|
||||
### Changed 🔧
|
||||
* MSRV (Minimum Supported Rust Version) is now `1.56.0`.
|
||||
* `ui.add(Button::new("…").text_color(…))` is now `ui.button(RichText::new("…").color(…))` (same for `Label` )([#855](https://github.com/emilk/egui/pull/855)).
|
||||
* Plots now provide a `show` method that has to be used to add items to and show the plot ([#766](https://github.com/emilk/egui/pull/766)).
|
||||
* Replace `CtxRef::begin_frame` and `end_frame` with `CtxRef::run` ([#872](https://github.com/emilk/egui/pull/872)).
|
||||
* Replace `scroll_delta` and `zoom_delta` in `RawInput` with `Event::Scroll` and `Event::Zoom`.
|
||||
* Unifiy the four `Memory` data buckets (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `Memory::data`, with a new interface ([#836](https://github.com/emilk/egui/pull/836)).
|
||||
* Replace `Ui::__test` with `egui::__run_test_ui` ([#872](https://github.com/emilk/egui/pull/872)).
|
||||
* `menu::menu(ui, ...)` is now `ui.menu_button(...)` ([#543](https://github.com/emilk/egui/pull/543))
|
||||
* Replaced `CtxRef::begin_frame` and `end_frame` with `CtxRef::run` ([#872](https://github.com/emilk/egui/pull/872)).
|
||||
* Replaced `scroll_delta` and `zoom_delta` in `RawInput` with `Event::Scroll` and `Event::Zoom`.
|
||||
* Unified the four `Memory` data buckets (`data`, `data_temp`, `id_data` and `id_data_temp`) into a single `Memory::data`, with a new interface ([#836](https://github.com/emilk/egui/pull/836)).
|
||||
* Replaced `Ui::__test` with `egui::__run_test_ui` ([#872](https://github.com/emilk/egui/pull/872)).
|
||||
|
||||
### Fixed 🐛
|
||||
* Fix `ComboBox` and other popups getting clipped to parent window ([#885](https://github.com/emilk/egui/pull/885)).
|
||||
* Fixed `ComboBox` and other popups getting clipped to parent window ([#885](https://github.com/emilk/egui/pull/885)).
|
||||
* The color picker is now better att keeping the same hue even when saturation goes to zero ([#886](https://github.com/emilk/egui/pull/886)).
|
||||
|
||||
### Removed 🔥
|
||||
|
@ -37,16 +56,19 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
|
|||
* Removed `egui::paint` (use `egui::epaint` instead).
|
||||
|
||||
### Contributors 🙏
|
||||
* [5225225](https://github.com/5225225): ([#849](https://github.com/emilk/egui/pull/849)).
|
||||
* [B-Reif](https://github.com/B-Reif) ([#875](https://github.com/emilk/egui/pull/875)).
|
||||
* [Bromeon](https://github.com/Bromeon): ([#863](https://github.com/emilk/egui/pull/863)).
|
||||
* [d10sfan](https://github.com/d10sfan) ([#832](https://github.com/emilk/egui/pull/832)).
|
||||
* [EmbersArc](https://github.com/EmbersArc): ([#766](https://github.com/emilk/egui/pull/766), [#892](https://github.com/emilk/egui/pull/892)).
|
||||
* [Hperigo](https://github.com/Hperigo): ([#905](https://github.com/emilk/egui/pull/905)).
|
||||
* [mankinskin](https://github.com/mankinskin) ([#543](https://github.com/emilk/egui/pull/543)).
|
||||
* [niladic](https://github.com/niladic): ([#499](https://github.com/emilk/egui/pull/499), [#863](https://github.com/emilk/egui/pull/863)).
|
||||
* [sumibi-yakitori](https://github.com/sumibi-yakitori) ([#830](https://github.com/emilk/egui/pull/830)).
|
||||
* [t18b219k](https://github.com/t18b219k): ([#868](https://github.com/emilk/egui/pull/868), [#888](https://github.com/emilk/egui/pull/888)).
|
||||
* [5225225](https://github.com/5225225): [#849](https://github.com/emilk/egui/pull/849).
|
||||
* [aevyrie](https://github.com/aevyrie): [#966](https://github.com/emilk/egui/pull/966).
|
||||
* [B-Reif](https://github.com/B-Reif): [#875](https://github.com/emilk/egui/pull/875).
|
||||
* [Bromeon](https://github.com/Bromeon): [#863](https://github.com/emilk/egui/pull/863), [#918](https://github.com/emilk/egui/pull/918).
|
||||
* [d10sfan](https://github.com/d10sfan): [#832](https://github.com/emilk/egui/pull/832).
|
||||
* [EmbersArc](https://github.com/EmbersArc): [#766](https://github.com/emilk/egui/pull/766), [#892](https://github.com/emilk/egui/pull/892).
|
||||
* [Hperigo](https://github.com/Hperigo): [#905](https://github.com/emilk/egui/pull/905).
|
||||
* [isegal](https://github.com/isegal): [#934](https://github.com/emilk/egui/pull/934).
|
||||
* [mankinskin](https://github.com/mankinskin): [#543](https://github.com/emilk/egui/pull/543).
|
||||
* [niladic](https://github.com/niladic): [#499](https://github.com/emilk/egui/pull/499), [#863](https://github.com/emilk/egui/pull/863).
|
||||
* [singalen](https://github.com/singalen): [#973](https://github.com/emilk/egui/pull/973).
|
||||
* [sumibi-yakitori](https://github.com/sumibi-yakitori): [#830](https://github.com/emilk/egui/pull/830), [#870](https://github.com/emilk/egui/pull/870).
|
||||
* [t18b219k](https://github.com/t18b219k): [#868](https://github.com/emilk/egui/pull/868), [#888](https://github.com/emilk/egui/pull/888).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24 - Syntax highlighting and hscroll
|
||||
|
|
726
Cargo.lock
generated
726
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
13
README.md
13
README.md
|
@ -7,6 +7,7 @@
|
|||
[](https://github.com/emilk/egui/actions?workflow=CI)
|
||||

|
||||

|
||||
[](https://discord.gg/JFcEma9bJq)
|
||||
|
||||
|
||||
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).
|
||||
|
@ -31,11 +32,11 @@ Sections:
|
|||
|
||||
## Quick start
|
||||
|
||||
If you just want to write a GUI application in Rust (for the web or for native), go to <https://github.com/emilk/eframe_template/> and follow the instructions there!
|
||||
If you just want to write a GUI application in Rust (for the web or for native), go to <https://github.com/emilk/eframe_template/> and follow the instructions there! The official docs are at <https://docs.rs/egui>. For inspiration, check out the [the egui web demo](https://emilk.github.io/egui/index.html) and follow the links in it to its source code. There is also an excellent tutorial video at <https://www.youtube.com/watch?v=NtUkr_z7l84>.
|
||||
|
||||
If you want to integrate egui into an existing engine, go to the [Integrations](#integrations) section.
|
||||
|
||||
If you have questions, use [Discussions](https://github.com/emilk/egui/discussions). If you want to contribute to egui, please read the [Contributing Guidelines](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
|
||||
If you have questions, use [GitHub Discussions](https://github.com/emilk/egui/discussions). There is also [an egui discord server](https://discord.gg/JFcEma9bJq). If you want to contribute to egui, please read the [Contributing Guidelines](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md).
|
||||
|
||||
## Demo
|
||||
|
||||
|
@ -43,13 +44,13 @@ If you have questions, use [Discussions](https://github.com/emilk/egui/discussio
|
|||
|
||||
To test the demo app locally, run `cargo run --release -p egui_demo_app`.
|
||||
|
||||
The native backend is [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) (using [`glium`](https://github.com/glium/glium)) 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 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 speech-dispatcher-devel libxkbcommon-devel pkg-config openssl-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!
|
||||
|
||||
|
@ -321,7 +322,7 @@ On Linux and Mac, Firefox will copy the WebGL render target from GPU, to CPU and
|
|||
To alleviate the above mentioned performance issues the default max-width of an egui web app is 1024 points. You can change this by overriding the `fn max_size_points` of [`epi::App`](https://docs.rs/epi/latest/epi/trait.App.html).
|
||||
|
||||
### How do I render 3D stuff in an egui area?
|
||||
egui can't do 3D graphics itself, but if you use a 3D library (e.g. [`glium`](https://github.com/glium/glium) using [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium), or [`miniquad`](https://github.com/not-fl3/miniquad) using [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad)) you can render your 3D content to a texture, then display it using [`ui.image(…)`](https://docs.rs/egui/latest/egui/struct.Ui.html#method.image). You first need to convert the native texture to an [`egui::TextureId`](https://docs.rs/egui/latest/egui/enum.TextureId.html), and how to do this depends on the integration you use (e.g. [`register_glium_texture`](https://docs.rs/egui_glium/latest/egui_glium/struct.Painter.html#method.register_glium_texture)).
|
||||
egui can't do 3D graphics itself, but if you use a 3D library (e.g. [`glium`](https://github.com/glium/glium) using [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium), or [`miniquad`](https://github.com/not-fl3/miniquad) using [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad)) you can render your 3D content to a texture, then display it using [`ui.image(…)`](https://docs.rs/egui/latest/egui/struct.Ui.html#method.image). You first need to convert the native texture to an [`egui::TextureId`](https://docs.rs/egui/latest/egui/enum.TextureId.html), and how to do this depends on the integration you use (e.g. [`register_glium_texture`](https://docs.rs/epi/latest/epi/trait.NativeTexture.html#tymethod.register_native_texture)).
|
||||
|
||||
There is an example for showing a native glium texture in an egui window at <https://github.com/emilk/egui/blob/master/egui_glium/examples/native_texture.rs>.
|
||||
|
||||
|
@ -338,7 +339,7 @@ All colors have premultiplied alpha.
|
|||
|
||||
egui uses the builder pattern for construction widgets. For instance: `ui.add(Label::new("Hello").text_color(RED));` I am not a big fan of the builder pattern (it is quite verbose both in implementation and in use) but until Rust has named, default arguments it is the best we can do. To alleviate some of the verbosity there are common-case helper functions, like `ui.label("Hello");`.
|
||||
|
||||
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.
|
||||
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>.
|
||||
|
||||
### Inspiration
|
||||
|
||||
|
|
3
docs/README.md
Normal file
3
docs/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
This folder contains the files required for the egui web demo hosted at <https://emilk.github.io/egui/>.
|
||||
|
||||
The reason the folder is called "docs" is because that is the name that GitHub requires in order to host a web page from the `master` branch of a repository.
|
|
@ -213,47 +213,47 @@ function makeMutClosure(arg0, arg1, dtor, f) {
|
|||
return real;
|
||||
}
|
||||
function __wbg_adapter_30(arg0, arg1) {
|
||||
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22dce46016b364fe(arg0, arg1);
|
||||
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hedff1e3c1ca6c71c(arg0, arg1);
|
||||
}
|
||||
|
||||
function __wbg_adapter_33(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h04545aeee2637565(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1d2f25dc91669030(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_36(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h04545aeee2637565(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1d2f25dc91669030(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_39(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h04545aeee2637565(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1d2f25dc91669030(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_42(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h04545aeee2637565(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1d2f25dc91669030(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_45(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h04545aeee2637565(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1d2f25dc91669030(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_48(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h04545aeee2637565(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1d2f25dc91669030(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_51(arg0, arg1) {
|
||||
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h768f86e2936098f5(arg0, arg1);
|
||||
function __wbg_adapter_51(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1d2f25dc91669030(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_54(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h04545aeee2637565(arg0, arg1, addHeapObject(arg2));
|
||||
function __wbg_adapter_54(arg0, arg1) {
|
||||
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__he78f81eace0d019f(arg0, arg1);
|
||||
}
|
||||
|
||||
function __wbg_adapter_57(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h04545aeee2637565(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h1d2f25dc91669030(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_60(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hbf24353061fe7bc4(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h84e1489b515e3492(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function makeClosure(arg0, arg1, dtor, f) {
|
||||
|
@ -278,11 +278,11 @@ function makeClosure(arg0, arg1, dtor, f) {
|
|||
return real;
|
||||
}
|
||||
function __wbg_adapter_63(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h35a934a712e366af(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h72e381f5228369dd(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_66(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h35a934a712e366af(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h72e381f5228369dd(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -356,6 +356,15 @@ async function init(input) {
|
|||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbindgen_cb_drop = function(arg0) {
|
||||
const obj = takeObject(arg0).original;
|
||||
if (obj.cnt-- == 1) {
|
||||
obj.a = 0;
|
||||
return true;
|
||||
}
|
||||
var ret = false;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
var ret = typeof(obj) === 'string' ? obj : undefined;
|
||||
|
@ -368,30 +377,21 @@ async function init(input) {
|
|||
var ret = getStringFromWasm0(arg0, arg1);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_cb_drop = function(arg0) {
|
||||
const obj = takeObject(arg0).original;
|
||||
if (obj.cnt-- == 1) {
|
||||
obj.a = 0;
|
||||
return true;
|
||||
}
|
||||
var ret = false;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
var ret = typeof(obj) === 'number' ? obj : undefined;
|
||||
getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
|
||||
var ret = getObject(arg0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_boolean_get = function(arg0) {
|
||||
const v = getObject(arg0);
|
||||
var ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
|
||||
var ret = getObject(arg0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_WebGl2RenderingContext_56ad96bfac3f5531 = function(arg0) {
|
||||
var ret = getObject(arg0) instanceof WebGL2RenderingContext;
|
||||
return ret;
|
||||
|
@ -621,14 +621,9 @@ async function init(input) {
|
|||
var ret = getObject(arg0).setTimeout(getObject(arg1), arg2);
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_writeText_3b86a6dbc18b261b = function(arg0, arg1, arg2) {
|
||||
var ret = getObject(arg0).writeText(getStringFromWasm0(arg1, arg2));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_clipboardData_d717f7cf398c0dd9 = function(arg0) {
|
||||
var ret = getObject(arg0).clipboardData;
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_addEventListener_52721772cc0a7f30 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||
getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_matches_76fae292b8cd60a6 = function(arg0) {
|
||||
var ret = getObject(arg0).matches;
|
||||
return ret;
|
||||
|
@ -704,6 +699,82 @@ async function init(input) {
|
|||
var ret = getObject(arg0)[arg1 >>> 0];
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_type_7a49279491e15d0a = function(arg0, arg1) {
|
||||
var ret = getObject(arg1).type;
|
||||
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
imports.wbg.__wbg_preventDefault_9866c9fd51eecfb6 = function(arg0) {
|
||||
getObject(arg0).preventDefault();
|
||||
};
|
||||
imports.wbg.__wbg_stopPropagation_ae76be6b0f664ee8 = function(arg0) {
|
||||
getObject(arg0).stopPropagation();
|
||||
};
|
||||
imports.wbg.__wbg_length_a2870b8b80e120c3 = function(arg0) {
|
||||
var ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_get_b84d80d476cf15e4 = function(arg0, arg1) {
|
||||
var ret = getObject(arg0)[arg1 >>> 0];
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_speaking_6ee7e15af03f4ade = function(arg0) {
|
||||
var ret = getObject(arg0).speaking;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_cancel_db8fc22aeb60a627 = function(arg0) {
|
||||
getObject(arg0).cancel();
|
||||
};
|
||||
imports.wbg.__wbg_speak_a2c1dfdf7b0927fc = function(arg0, arg1) {
|
||||
getObject(arg0).speak(getObject(arg1));
|
||||
};
|
||||
imports.wbg.__wbg_length_1d27563e3515722e = function(arg0) {
|
||||
var ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_item_a23b382195352a8a = function(arg0, arg1) {
|
||||
var ret = getObject(arg0).item(arg1 >>> 0);
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_get_20b719b18767c76e = function(arg0, arg1) {
|
||||
var ret = getObject(arg0)[arg1 >>> 0];
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_name_6af1a38f3edc1522 = function(arg0, arg1) {
|
||||
var ret = getObject(arg1).name;
|
||||
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
imports.wbg.__wbg_lastModified_c61609c3c6a0bd88 = function(arg0) {
|
||||
var ret = getObject(arg0).lastModified;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_HtmlCanvasElement_25d964a0dde6717e = function(arg0) {
|
||||
var ret = getObject(arg0) instanceof HTMLCanvasElement;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_width_555f63ab09ba7d3f = function(arg0) {
|
||||
var ret = getObject(arg0).width;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_setwidth_c1a7061891b71f25 = function(arg0, arg1) {
|
||||
getObject(arg0).width = arg1 >>> 0;
|
||||
};
|
||||
imports.wbg.__wbg_height_7153faec70fbaf7b = function(arg0) {
|
||||
var ret = getObject(arg0).height;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_setheight_88894b05710ff752 = function(arg0, arg1) {
|
||||
getObject(arg0).height = arg1 >>> 0;
|
||||
};
|
||||
imports.wbg.__wbg_getContext_f701d0231ae22393 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
var ret = getObject(arg0).getContext(getStringFromWasm0(arg1, arg2));
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_keyCode_490ed69472addfdc = function(arg0) {
|
||||
var ret = getObject(arg0).keyCode;
|
||||
return ret;
|
||||
|
@ -735,82 +806,6 @@ async function init(input) {
|
|||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
imports.wbg.__wbg_speaking_6ee7e15af03f4ade = function(arg0) {
|
||||
var ret = getObject(arg0).speaking;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_cancel_db8fc22aeb60a627 = function(arg0) {
|
||||
getObject(arg0).cancel();
|
||||
};
|
||||
imports.wbg.__wbg_speak_a2c1dfdf7b0927fc = function(arg0, arg1) {
|
||||
getObject(arg0).speak(getObject(arg1));
|
||||
};
|
||||
imports.wbg.__wbg_type_7a49279491e15d0a = function(arg0, arg1) {
|
||||
var ret = getObject(arg1).type;
|
||||
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
imports.wbg.__wbg_preventDefault_9866c9fd51eecfb6 = function(arg0) {
|
||||
getObject(arg0).preventDefault();
|
||||
};
|
||||
imports.wbg.__wbg_stopPropagation_ae76be6b0f664ee8 = function(arg0) {
|
||||
getObject(arg0).stopPropagation();
|
||||
};
|
||||
imports.wbg.__wbg_name_6af1a38f3edc1522 = function(arg0, arg1) {
|
||||
var ret = getObject(arg1).name;
|
||||
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
imports.wbg.__wbg_lastModified_c61609c3c6a0bd88 = function(arg0) {
|
||||
var ret = getObject(arg0).lastModified;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_length_a2870b8b80e120c3 = function(arg0) {
|
||||
var ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_get_b84d80d476cf15e4 = function(arg0, arg1) {
|
||||
var ret = getObject(arg0)[arg1 >>> 0];
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_HtmlCanvasElement_25d964a0dde6717e = function(arg0) {
|
||||
var ret = getObject(arg0) instanceof HTMLCanvasElement;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_width_555f63ab09ba7d3f = function(arg0) {
|
||||
var ret = getObject(arg0).width;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_setwidth_c1a7061891b71f25 = function(arg0, arg1) {
|
||||
getObject(arg0).width = arg1 >>> 0;
|
||||
};
|
||||
imports.wbg.__wbg_height_7153faec70fbaf7b = function(arg0) {
|
||||
var ret = getObject(arg0).height;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_setheight_88894b05710ff752 = function(arg0, arg1) {
|
||||
getObject(arg0).height = arg1 >>> 0;
|
||||
};
|
||||
imports.wbg.__wbg_getContext_f701d0231ae22393 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
var ret = getObject(arg0).getContext(getStringFromWasm0(arg1, arg2));
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_length_1d27563e3515722e = function(arg0) {
|
||||
var ret = getObject(arg0).length;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_item_a23b382195352a8a = function(arg0, arg1) {
|
||||
var ret = getObject(arg0).item(arg1 >>> 0);
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_get_20b719b18767c76e = function(arg0, arg1) {
|
||||
var ret = getObject(arg0)[arg1 >>> 0];
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_clipboard_3dff7cff084c4be2 = function(arg0) {
|
||||
var ret = getObject(arg0).clipboard;
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
|
@ -838,6 +833,14 @@ async function init(input) {
|
|||
var ret = getObject(arg0).arrayBuffer();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_width_d55d3698a2514ec1 = function(arg0) {
|
||||
var ret = getObject(arg0).width;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_height_df08a93b45ce76ec = function(arg0) {
|
||||
var ret = getObject(arg0).height;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_top_3946f8347860b55c = function(arg0) {
|
||||
var ret = getObject(arg0).top;
|
||||
return ret;
|
||||
|
@ -873,6 +876,14 @@ async function init(input) {
|
|||
var ret = getObject(arg0).scrollLeft;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_clientWidth_4d9e01af2b5b9f21 = function(arg0) {
|
||||
var ret = getObject(arg0).clientWidth;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_clientHeight_87c209f0cacf2e97 = function(arg0) {
|
||||
var ret = getObject(arg0).clientHeight;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_getBoundingClientRect_2fba0402ea2a6ec4 = function(arg0) {
|
||||
var ret = getObject(arg0).getBoundingClientRect();
|
||||
return addHeapObject(ret);
|
||||
|
@ -980,6 +991,10 @@ async function init(input) {
|
|||
var ret = getObject(arg0).getExtension(getStringFromWasm0(arg1, arg2));
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_getParameter_6412bd2d0602696d = function() { return handleError(function (arg0, arg1) {
|
||||
var ret = getObject(arg0).getParameter(arg1 >>> 0);
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_getProgramInfoLog_b60e82d52c200cbd = function(arg0, arg1, arg2) {
|
||||
var ret = getObject(arg1).getProgramInfoLog(getObject(arg2));
|
||||
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
|
@ -1078,13 +1093,9 @@ async function init(input) {
|
|||
imports.wbg.__wbg_focus_00530e359f44fc6e = function() { return handleError(function (arg0) {
|
||||
getObject(arg0).focus();
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_data_dbff09eb89176161 = function(arg0, arg1) {
|
||||
var ret = getObject(arg1).data;
|
||||
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
imports.wbg.__wbg_setProperty_1460c660bc329763 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
getObject(arg0).setProperty(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_hash_0fff5255cf3c317c = function() { return handleError(function (arg0, arg1) {
|
||||
var ret = getObject(arg1).hash;
|
||||
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
|
@ -1122,9 +1133,13 @@ async function init(input) {
|
|||
imports.wbg.__wbg_setItem_b0c4561489dffecd = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_setProperty_1460c660bc329763 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
getObject(arg0).setProperty(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_data_dbff09eb89176161 = function(arg0, arg1) {
|
||||
var ret = getObject(arg1).data;
|
||||
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
imports.wbg.__wbg_type_a6fcda966902940d = function(arg0, arg1) {
|
||||
var ret = getObject(arg1).type;
|
||||
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
|
@ -1169,13 +1184,18 @@ async function init(input) {
|
|||
var ret = getObject(arg0).deltaMode;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_writeText_3b86a6dbc18b261b = function(arg0, arg1, arg2) {
|
||||
var ret = getObject(arg0).writeText(getStringFromWasm0(arg1, arg2));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_clipboardData_d717f7cf398c0dd9 = function(arg0) {
|
||||
var ret = getObject(arg0).clipboardData;
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_dataTransfer_ebba35c1049e694f = function(arg0) {
|
||||
var ret = getObject(arg0).dataTransfer;
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_addEventListener_52721772cc0a7f30 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
||||
getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_instanceof_Response_e1b11afbefa5b563 = function(arg0) {
|
||||
var ret = getObject(arg0) instanceof Response;
|
||||
return ret;
|
||||
|
@ -1371,56 +1391,56 @@ async function init(input) {
|
|||
var ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper1990 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_30);
|
||||
imports.wbg.__wbindgen_closure_wrapper1901 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_30);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper1991 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_33);
|
||||
imports.wbg.__wbindgen_closure_wrapper1902 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_33);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper1993 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_36);
|
||||
imports.wbg.__wbindgen_closure_wrapper1904 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_36);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper1995 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_39);
|
||||
imports.wbg.__wbindgen_closure_wrapper1906 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_39);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper1997 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_42);
|
||||
imports.wbg.__wbindgen_closure_wrapper1908 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_42);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper1999 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_45);
|
||||
imports.wbg.__wbindgen_closure_wrapper1911 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_45);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper2002 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_48);
|
||||
imports.wbg.__wbindgen_closure_wrapper1913 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_48);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper2004 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_51);
|
||||
imports.wbg.__wbindgen_closure_wrapper1915 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_51);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper2006 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_54);
|
||||
imports.wbg.__wbindgen_closure_wrapper1917 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_54);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper2008 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_57);
|
||||
imports.wbg.__wbindgen_closure_wrapper1919 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_57);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper2042 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 802, __wbg_adapter_60);
|
||||
imports.wbg.__wbindgen_closure_wrapper2113 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 823, __wbg_adapter_60);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper2109 = function(arg0, arg1, arg2) {
|
||||
var ret = makeClosure(arg0, arg1, 851, __wbg_adapter_63);
|
||||
imports.wbg.__wbindgen_closure_wrapper2179 = function(arg0, arg1, arg2) {
|
||||
var ret = makeClosure(arg0, arg1, 871, __wbg_adapter_63);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper2111 = function(arg0, arg1, arg2) {
|
||||
var ret = makeClosure(arg0, arg1, 851, __wbg_adapter_66);
|
||||
imports.wbg.__wbindgen_closure_wrapper2181 = function(arg0, arg1, arg2) {
|
||||
var ret = makeClosure(arg0, arg1, 871, __wbg_adapter_66);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
|
|
Binary file not shown.
BIN
docs/favicon.ico
Executable file
BIN
docs/favicon.ico
Executable file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -45,12 +45,59 @@
|
|||
left: 50%;
|
||||
transform: translate(-50%, 0%);
|
||||
}
|
||||
|
||||
.loading {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
font-family: Ubuntu-Light, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------- */
|
||||
/* 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 this dynamically -->
|
||||
<!-- The WASM code will resize the canvas dynamically -->
|
||||
<canvas id="the_canvas_id"></canvas>
|
||||
<div class="loading" id="loading">
|
||||
Loading…
|
||||
<div class="lds-dual-ring"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// The `--no-modules`-generated JS from `wasm-bindgen` attempts to use
|
||||
|
@ -77,9 +124,13 @@
|
|||
.catch(console.error);
|
||||
|
||||
function on_wasm_loaded() {
|
||||
// This call installs a bunch of callbacks and then returns.
|
||||
console.log("loaded wasm, starting egui app");
|
||||
console.log("loaded wasm, starting egui app…");
|
||||
|
||||
// This call installs a bunch of callbacks and then returns:
|
||||
wasm_bindgen.start("the_canvas_id");
|
||||
|
||||
console.log("egui app started.");
|
||||
document.getElementById("loading").remove();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -5,6 +5,14 @@ NOTE: [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.m
|
|||
|
||||
|
||||
## Unreleased
|
||||
* The default native backend is now `egui_glow` (instead of `egui_glium`) ([#1020](https://github.com/emilk/egui/pull/1020)).
|
||||
* The default web painter is now `egui_glow` (instead of WebGL) ([#1020](https://github.com/emilk/egui/pull/1020)).
|
||||
|
||||
|
||||
## 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)).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
[package]
|
||||
name = "eframe"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "egui framework - write GUI apps that compiles to web and/or natively"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/eframe"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
@ -23,25 +24,25 @@ all-features = true
|
|||
[lib]
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.15.0", path = "../egui", default-features = false }
|
||||
epi = { version = "0.15.0", path = "../epi" }
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false }
|
||||
epi = { version = "0.16.0", path = "../epi" }
|
||||
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
egui-winit = { version = "0.15.0", path = "../egui-winit", default-features = false }
|
||||
egui_glium = { version = "0.15.0", path = "../egui_glium", default-features = false, features = ["clipboard", "epi", "links"], optional = true }
|
||||
egui_glow = { version = "0.15.0", path = "../egui_glow", default-features = false, features = ["clipboard", "epi", "links", "winit"], optional = true }
|
||||
egui-winit = { version = "0.16.0", path = "../egui-winit", default-features = false }
|
||||
egui_glium = { version = "0.16.0", path = "../egui_glium", default-features = false, features = ["clipboard", "epi", "links"], optional = true }
|
||||
egui_glow = { version = "0.16.0", path = "../egui_glow", default-features = false, features = ["clipboard", "epi", "links", "winit"], optional = true }
|
||||
|
||||
# web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
egui_web = { version = "0.15.0", path = "../egui_web", default-features = false }
|
||||
egui_web = { version = "0.16.0", path = "../egui_web", default-features = false, features = ["glow"] }
|
||||
|
||||
[dev-dependencies]
|
||||
image = { version = "0.23", default-features = false, features = ["png"] }
|
||||
rfd = "0.5.0"
|
||||
rfd = "0.6"
|
||||
|
||||
[features]
|
||||
default = ["default_fonts", "egui_glium"]
|
||||
default = ["default_fonts", "egui_glow"]
|
||||
|
||||
# If set, egui will use `include_bytes!` to bundle some fonts.
|
||||
# If you plan on specifying your own fonts you may disable this feature.
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
|
||||
To get started, go to <https://github.com/emilk/eframe_template/> and follow the instructions there!
|
||||
|
||||
You can also take a look at [the `eframe` examples folder](https://github.com/emilk/egui/tree/master/eframe/examples). There is also an excellent tutorial video at <https://www.youtube.com/watch?v=NtUkr_z7l84>.
|
||||
|
||||
For how to use `egui`, see [the egui docs](https://docs.rs/egui).
|
||||
|
||||
---
|
||||
|
||||
`eframe` is a very thin crate that re-exports [`egui`](https://github.com/emilk/egui) and[`epi`](https://github.com/emilk/egui/tree/master/epi) with thin wrappers over the backends.
|
||||
|
@ -24,17 +28,17 @@ sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev lib
|
|||
|
||||
|
||||
## Alternatives
|
||||
The default native backend for `eframe` is currently [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium), but you can try out the new [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) backend by putting this in your `Cargo.toml`:
|
||||
The default native backend for `eframe` is currently [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow), but you can switch to the previous [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) backend by putting this in your `Cargo.toml`:
|
||||
|
||||
``` toml
|
||||
eframe = { version = "*", default-features = false, features = ["default_fonts", "egui_glow"] }
|
||||
eframe = { version = "*", default-features = false, features = ["default_fonts", "egui_glium"] }
|
||||
```
|
||||
|
||||
`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) and [`egui_sdl2_gl`](https://github.com/ArjunNair/egui_sdl2_gl).
|
||||
|
||||
|
||||
## Companion crates
|
||||
Not all rust crates work when compiles to WASM, but here are some useful crates have been designed to work well both natively and as WASM:
|
||||
Not all rust crates work when compiled to WASM, but here are some useful crates have been designed to work well both natively and as WASM:
|
||||
|
||||
* Audio: [`cpal`](https://github.com/RustAudio/cpal).
|
||||
* HTTP client: [`ehttp`](https://github.com/emilk/ehttp).
|
||||
|
|
65
eframe/examples/custom_font.rs
Normal file
65
eframe/examples/custom_font.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use eframe::{egui, epi};
|
||||
|
||||
struct MyApp {
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl Default for MyApp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
text: "Edit this text field if you want".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::App for MyApp {
|
||||
fn name(&self) -> &str {
|
||||
"egui example: custom font"
|
||||
}
|
||||
|
||||
fn setup(
|
||||
&mut self,
|
||||
ctx: &egui::CtxRef,
|
||||
_frame: &epi::Frame,
|
||||
_storage: Option<&dyn epi::Storage>,
|
||||
) {
|
||||
// Start with the default fonts (we will be adding to them rather than replacing them).
|
||||
let mut fonts = egui::FontDefinitions::default();
|
||||
|
||||
// Install my own font (maybe supporting non-latin characters).
|
||||
// .ttf and .otf files supported.
|
||||
fonts.font_data.insert(
|
||||
"my_font".to_owned(),
|
||||
egui::FontData::from_static(include_bytes!("../../epaint/fonts/Hack-Regular.ttf")),
|
||||
);
|
||||
|
||||
// Put my font first (highest priority) for proportional text:
|
||||
fonts
|
||||
.fonts_for_family
|
||||
.entry(egui::FontFamily::Proportional)
|
||||
.or_default()
|
||||
.insert(0, "my_font".to_owned());
|
||||
|
||||
// Put my font as last fallback for monospace:
|
||||
fonts
|
||||
.fonts_for_family
|
||||
.entry(egui::FontFamily::Monospace)
|
||||
.or_default()
|
||||
.push("my_font".to_owned());
|
||||
|
||||
// Tell egui to use these fonts:
|
||||
ctx.set_fonts(fonts);
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("egui using custom fonts");
|
||||
ui.text_edit_multiline(&mut self.text);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions::default();
|
||||
eframe::run_native(Box::new(MyApp::default()), options);
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -11,13 +13,11 @@ impl epi::App for MyApp {
|
|||
"Native file dialogs and drag-and-drop files"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.label("Drag-and-drop files onto the window!");
|
||||
|
||||
if cfg!(target_os = "macos") {
|
||||
// Awaiting fix of winit bug: https://github.com/rust-windowing/winit/pull/2027
|
||||
} else if ui.button("Open file…").clicked() {
|
||||
if ui.button("Open file…").clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
||||
self.picked_path = Some(path.display().to_string());
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
|
||||
struct MyApp {
|
||||
|
@ -19,7 +21,7 @@ impl epi::App for MyApp {
|
|||
"My egui App"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
let Self { name, age } = self;
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::{egui, epi};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -10,26 +12,20 @@ impl epi::App for MyApp {
|
|||
"Show an image with eframe/egui"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
if self.texture.is_none() {
|
||||
// Load the image:
|
||||
let image_data = include_bytes!("rust-logo-256x256.png");
|
||||
use image::GenericImageView;
|
||||
let image = image::load_from_memory(image_data).expect("Failed to load image");
|
||||
let image_buffer = image.to_rgba8();
|
||||
let size = (image.width() as usize, image.height() as usize);
|
||||
let size = [image.width() as usize, image.height() as usize];
|
||||
let pixels = image_buffer.into_vec();
|
||||
assert_eq!(size.0 * size.1 * 4, pixels.len());
|
||||
let pixels: Vec<_> = pixels
|
||||
.chunks_exact(4)
|
||||
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||
.collect();
|
||||
let image = epi::Image::from_rgba_unmultiplied(size, &pixels);
|
||||
|
||||
// Allocate a texture:
|
||||
let texture = frame
|
||||
.tex_allocator()
|
||||
.alloc_srgba_premultiplied(size, &pixels);
|
||||
let size = egui::Vec2::new(size.0 as f32, size.1 as f32);
|
||||
let texture = frame.alloc_texture(image);
|
||||
let size = egui::Vec2::new(size[0] as f32, size[1] as f32);
|
||||
self.texture = Some((size, texture));
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
//!
|
||||
//! To get started, look at <https://github.com/emilk/eframe_template>.
|
||||
//!
|
||||
//! You can also take a look at [the `eframe` examples folder](https://github.com/emilk/egui/tree/master/eframe/examples).
|
||||
//!
|
||||
//! You write your application code for [`epi`] (implementing [`epi::App`]) and then
|
||||
//! call from [`crate::run_native`] your `main.rs`, and/or call `eframe::start_web` from your `lib.rs`.
|
||||
//!
|
||||
|
@ -24,7 +26,7 @@
|
|||
//! "My egui App"
|
||||
//! }
|
||||
//!
|
||||
//! fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
//! fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
//! egui::CentralPanel::default().show(ctx, |ui| {
|
||||
//! ui.heading("Hello World!");
|
||||
//! });
|
||||
|
@ -55,7 +57,12 @@
|
|||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(clippy::all, missing_crate_level_docs, missing_docs, rust_2018_idioms)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
missing_docs,
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
|
||||
pub use {egui, epi};
|
||||
|
@ -110,7 +117,7 @@ pub fn start_web(canvas_id: &str, app: Box<dyn epi::App>) -> Result<(), wasm_bin
|
|||
// ----------------------------------------------------------------------------
|
||||
// When compiling natively
|
||||
|
||||
/// Call from `fn main` like this: `
|
||||
/// Call from `fn main` like this:
|
||||
/// ``` no_run
|
||||
/// use eframe::{epi, egui};
|
||||
///
|
||||
|
@ -122,7 +129,7 @@ pub fn start_web(canvas_id: &str, app: Box<dyn epi::App>) -> Result<(), wasm_bin
|
|||
/// "My egui App"
|
||||
/// }
|
||||
///
|
||||
/// fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
/// fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||
/// ui.heading("Hello World!");
|
||||
/// });
|
||||
|
@ -141,8 +148,25 @@ pub fn run_native(app: Box<dyn epi::App>, native_options: epi::NativeOptions) ->
|
|||
egui_glium::run(app, &native_options)
|
||||
}
|
||||
|
||||
/// Call from `fn main` like this: `
|
||||
/// Call from `fn main` like this:
|
||||
/// ``` no_run
|
||||
/// use eframe::{epi, egui};
|
||||
///
|
||||
/// #[derive(Default)]
|
||||
/// struct MyEguiApp {}
|
||||
///
|
||||
/// impl epi::App for MyEguiApp {
|
||||
/// fn name(&self) -> &str {
|
||||
/// "My egui App"
|
||||
/// }
|
||||
///
|
||||
/// fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||
/// ui.heading("Hello World!");
|
||||
/// });
|
||||
/// }
|
||||
///}
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = MyEguiApp::default();
|
||||
/// let native_options = eframe::NativeOptions::default();
|
||||
|
|
|
@ -4,9 +4,15 @@ All notable changes to the `egui-winit` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Add helper `EpiIntegration` ([#871](https://github.com/emilk/egui/pull/871)).
|
||||
* Fix shift key getting stuck enabled with the X11 option `shift:both_capslock` enabled ([#849](https://github.com/emilk/egui/pull/849)).
|
||||
* Remove `State::is_quit_event` and `State::is_quit_shortcut` ([#881](https://github.com/emilk/egui/pull/881)).
|
||||
* Replaced `std::time::Instant` with `instant::Instant` for WebAssembly compatability ([#1023](https://github.com/emilk/egui/pull/1023))
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
* Added helper `EpiIntegration` ([#871](https://github.com/emilk/egui/pull/871)).
|
||||
* Fixed shift key getting stuck enabled with the X11 option `shift:both_capslock` enabled ([#849](https://github.com/emilk/egui/pull/849)).
|
||||
* Removed `State::is_quit_event` and `State::is_quit_shortcut` ([#881](https://github.com/emilk/egui/pull/881)).
|
||||
* Updated `winit` to 0.26 ([#930](https://github.com/emilk/egui/pull/930)).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
First stand-alone release. Previously part of `egui_glium`.
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
[package]
|
||||
name = "egui-winit"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Bindings for using egui with winit"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/egui-winit"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
@ -21,17 +22,18 @@ include = [
|
|||
all-features = true
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.15.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
||||
winit = "0.25"
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
||||
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
||||
winit = "0.26"
|
||||
|
||||
epi = { version = "0.15.0", path = "../epi", optional = true }
|
||||
epi = { version = "0.16.0", path = "../epi", optional = true }
|
||||
|
||||
copypasta = { version = "0.7", optional = true }
|
||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||
webbrowser = { version = "0.5", optional = true }
|
||||
|
||||
# feature screen_reader
|
||||
tts = { version = "0.17", optional = true }
|
||||
tts = { version = "0.19", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["clipboard", "links"]
|
||||
|
|
|
@ -55,9 +55,10 @@ pub fn handle_app_output(
|
|||
window: &winit::window::Window,
|
||||
current_pixels_per_point: f32,
|
||||
app_output: epi::backend::AppOutput,
|
||||
) {
|
||||
) -> epi::backend::TexAllocationData {
|
||||
let epi::backend::AppOutput {
|
||||
quit: _,
|
||||
tex_allocation_data,
|
||||
window_size,
|
||||
window_title,
|
||||
decorated,
|
||||
|
@ -85,6 +86,8 @@ pub fn handle_app_output(
|
|||
if drag_window {
|
||||
let _ = window.drag_window();
|
||||
}
|
||||
|
||||
tex_allocation_data
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -92,7 +95,7 @@ pub fn handle_app_output(
|
|||
/// For loading/saving app state and/or egui memory to disk.
|
||||
pub struct Persistence {
|
||||
storage: Option<Box<dyn epi::Storage>>,
|
||||
last_auto_save: std::time::Instant,
|
||||
last_auto_save: instant::Instant,
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_self)]
|
||||
|
@ -113,7 +116,7 @@ impl Persistence {
|
|||
|
||||
Self {
|
||||
storage: create_storage(app_name),
|
||||
last_auto_save: std::time::Instant::now(),
|
||||
last_auto_save: instant::Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,7 +177,7 @@ impl Persistence {
|
|||
egui_ctx: &egui::Context,
|
||||
window: &winit::window::Window,
|
||||
) {
|
||||
let now = std::time::Instant::now();
|
||||
let now = instant::Instant::now();
|
||||
if now - self.last_auto_save > app.auto_save_interval() {
|
||||
self.save(app, egui_ctx, window);
|
||||
self.last_auto_save = now;
|
||||
|
@ -186,13 +189,11 @@ impl Persistence {
|
|||
|
||||
/// Everything needed to make a winit-based integration for [`epi`].
|
||||
pub struct EpiIntegration {
|
||||
integration_name: &'static str,
|
||||
frame: epi::Frame,
|
||||
persistence: crate::epi::Persistence,
|
||||
repaint_signal: std::sync::Arc<dyn epi::RepaintSignal>,
|
||||
pub egui_ctx: egui::CtxRef,
|
||||
egui_winit: crate::State,
|
||||
pub app: Box<dyn epi::App>,
|
||||
latest_frame_time: Option<f32>,
|
||||
/// When set, it is time to quit
|
||||
quit: bool,
|
||||
}
|
||||
|
@ -201,8 +202,7 @@ impl EpiIntegration {
|
|||
pub fn new(
|
||||
integration_name: &'static str,
|
||||
window: &winit::window::Window,
|
||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
||||
repaint_signal: std::sync::Arc<dyn epi::RepaintSignal>,
|
||||
repaint_signal: std::sync::Arc<dyn epi::backend::RepaintSignal>,
|
||||
persistence: crate::epi::Persistence,
|
||||
app: Box<dyn epi::App>,
|
||||
) -> Self {
|
||||
|
@ -210,54 +210,50 @@ impl EpiIntegration {
|
|||
|
||||
*egui_ctx.memory() = persistence.load_memory().unwrap_or_default();
|
||||
|
||||
let mut slf = Self {
|
||||
integration_name,
|
||||
persistence,
|
||||
let frame = epi::Frame::new(epi::backend::FrameData {
|
||||
info: epi::IntegrationInfo {
|
||||
name: integration_name,
|
||||
web_info: None,
|
||||
prefer_dark_mode: None, // TODO: figure out system default
|
||||
cpu_usage: None,
|
||||
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
|
||||
},
|
||||
output: Default::default(),
|
||||
repaint_signal,
|
||||
});
|
||||
|
||||
let mut slf = Self {
|
||||
frame,
|
||||
persistence,
|
||||
egui_ctx,
|
||||
egui_winit: crate::State::new(window),
|
||||
app,
|
||||
latest_frame_time: None,
|
||||
quit: false,
|
||||
};
|
||||
|
||||
slf.setup(window, tex_allocator);
|
||||
slf.setup(window);
|
||||
if slf.app.warm_up_enabled() {
|
||||
slf.warm_up(window, tex_allocator);
|
||||
slf.warm_up(window);
|
||||
}
|
||||
|
||||
slf
|
||||
}
|
||||
|
||||
fn setup(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
||||
) {
|
||||
let mut app_output = epi::backend::AppOutput::default();
|
||||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: integration_info(self.integration_name, window, None),
|
||||
tex_allocator,
|
||||
output: &mut app_output,
|
||||
repaint_signal: self.repaint_signal.clone(),
|
||||
}
|
||||
.build();
|
||||
fn setup(&mut self, window: &winit::window::Window) {
|
||||
self.app
|
||||
.setup(&self.egui_ctx, &mut frame, self.persistence.storage());
|
||||
|
||||
.setup(&self.egui_ctx, &self.frame, self.persistence.storage());
|
||||
let app_output = self.frame.take_app_output();
|
||||
self.quit |= app_output.quit;
|
||||
|
||||
let tex_alloc_data =
|
||||
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
self.frame.lock().output.tex_allocation_data = tex_alloc_data; // Do it later
|
||||
}
|
||||
|
||||
fn warm_up(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
||||
) {
|
||||
fn warm_up(&mut self, window: &winit::window::Window) {
|
||||
let saved_memory = self.egui_ctx.memory().clone();
|
||||
self.egui_ctx.memory().set_everything_is_visible(true);
|
||||
self.update(window, tex_allocator);
|
||||
let (_, tex_alloc_data, _) = self.update(window);
|
||||
self.frame.lock().output.tex_allocation_data = tex_alloc_data; // 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();
|
||||
}
|
||||
|
@ -277,38 +273,31 @@ impl EpiIntegration {
|
|||
pub fn update(
|
||||
&mut self,
|
||||
window: &winit::window::Window,
|
||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
||||
) -> (bool, Vec<egui::epaint::ClippedShape>) {
|
||||
let frame_start = std::time::Instant::now();
|
||||
) -> (
|
||||
bool,
|
||||
epi::backend::TexAllocationData,
|
||||
Vec<egui::epaint::ClippedShape>,
|
||||
) {
|
||||
let frame_start = instant::Instant::now();
|
||||
|
||||
let raw_input = self.egui_winit.take_egui_input(window);
|
||||
|
||||
let mut app_output = epi::backend::AppOutput::default();
|
||||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: integration_info(self.integration_name, window, self.latest_frame_time),
|
||||
tex_allocator,
|
||||
output: &mut app_output,
|
||||
repaint_signal: self.repaint_signal.clone(),
|
||||
}
|
||||
.build();
|
||||
|
||||
let app = &mut self.app; // TODO: remove when we update MSVR to 1.56
|
||||
let (egui_output, shapes) = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
app.update(egui_ctx, &mut frame);
|
||||
self.app.update(egui_ctx, &self.frame);
|
||||
});
|
||||
|
||||
let needs_repaint = egui_output.needs_repaint;
|
||||
self.egui_winit
|
||||
.handle_output(window, &self.egui_ctx, egui_output);
|
||||
|
||||
let app_output = self.frame.take_app_output();
|
||||
self.quit |= app_output.quit;
|
||||
|
||||
let tex_allocation_data =
|
||||
crate::epi::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.latest_frame_time = Some(frame_time);
|
||||
let frame_time = (instant::Instant::now() - frame_start).as_secs_f64() as f32;
|
||||
self.frame.lock().info.cpu_usage = Some(frame_time);
|
||||
|
||||
(needs_repaint, shapes)
|
||||
(needs_repaint, tex_allocation_data, shapes)
|
||||
}
|
||||
|
||||
pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
|
||||
|
@ -322,17 +311,3 @@ impl EpiIntegration {
|
|||
.save(&mut *self.app, &self.egui_ctx, window);
|
||||
}
|
||||
}
|
||||
|
||||
fn integration_info(
|
||||
integration_name: &'static str,
|
||||
window: &winit::window::Window,
|
||||
previous_frame_time: Option<f32>,
|
||||
) -> epi::IntegrationInfo {
|
||||
epi::IntegrationInfo {
|
||||
name: integration_name,
|
||||
web_info: None,
|
||||
prefer_dark_mode: None, // TODO: figure out system default
|
||||
cpu_usage: previous_frame_time,
|
||||
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,9 +76,9 @@
|
|||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
missing_crate_level_docs,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
@ -105,7 +105,7 @@ pub fn screen_size_in_pixels(window: &winit::window::Window) -> egui::Vec2 {
|
|||
|
||||
/// Handles the integration between egui and winit.
|
||||
pub struct State {
|
||||
start_time: std::time::Instant,
|
||||
start_time: instant::Instant,
|
||||
egui_input: egui::RawInput,
|
||||
pointer_pos_in_points: Option<egui::Pos2>,
|
||||
any_pointer_button_down: bool,
|
||||
|
@ -137,7 +137,7 @@ impl State {
|
|||
/// Initialize with a given dpi scaling.
|
||||
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
|
||||
Self {
|
||||
start_time: std::time::Instant::now(),
|
||||
start_time: instant::Instant::now(),
|
||||
egui_input: egui::RawInput {
|
||||
pixels_per_point: Some(pixels_per_point),
|
||||
..Default::default()
|
||||
|
@ -458,6 +458,9 @@ impl State {
|
|||
// https://github.com/rust-windowing/winit/issues/1695 being closed
|
||||
delta.x *= -1.0;
|
||||
}
|
||||
if cfg!(target_os = "windows") {
|
||||
delta.x *= -1.0; // until https://github.com/rust-windowing/winit/pull/2101 is merged
|
||||
}
|
||||
|
||||
if self.egui_input.modifiers.ctrl || self.egui_input.modifiers.command {
|
||||
// Treat as zoom instead:
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
[package]
|
||||
name = "egui"
|
||||
version = "0.15.0"
|
||||
version = "0.16.1"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Simple, portable immediate mode GUI library for Rust"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
homepage = "https://github.com/emilk/egui"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "../README.md"
|
||||
|
@ -23,7 +24,7 @@ all-features = true
|
|||
[lib]
|
||||
|
||||
[dependencies]
|
||||
epaint = { version = "0.15.0", path = "../epaint", default-features = false }
|
||||
epaint = { version = "0.16.0", path = "../epaint", default-features = false }
|
||||
|
||||
ahash = "0.7"
|
||||
nohash-hasher = "0.2"
|
||||
|
|
5
egui/examples/README.md
Normal file
5
egui/examples/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
There are no stand-alone egui examples, because egui is not stand-alone!
|
||||
|
||||
There are plenty of examples in [the online demo](https://emilk.github.io/egui/). You can find the source code for it at <https://github.com/emilk/egui/tree/master/egui_demo_lib>.
|
||||
|
||||
If you are using `eframe`, check out [the `eframe` examples](https://github.com/emilk/egui/tree/master/eframe/examples) and [the `eframe` template repository](https://github.com/emilk/eframe_template/).
|
|
@ -144,6 +144,7 @@ pub(crate) fn paint_icon(ui: &mut Ui, openness: f32, response: &Response) {
|
|||
pub struct CollapsingHeader {
|
||||
text: WidgetText,
|
||||
default_open: bool,
|
||||
open: Option<bool>,
|
||||
id_source: Id,
|
||||
enabled: bool,
|
||||
selectable: bool,
|
||||
|
@ -164,6 +165,7 @@ impl CollapsingHeader {
|
|||
Self {
|
||||
text,
|
||||
default_open: false,
|
||||
open: None,
|
||||
id_source,
|
||||
enabled: true,
|
||||
selectable: false,
|
||||
|
@ -179,6 +181,16 @@ impl CollapsingHeader {
|
|||
self
|
||||
}
|
||||
|
||||
/// Calling `.open(Some(true))` will make the collapsing header open this frame (or stay open).
|
||||
///
|
||||
/// Calling `.open(Some(false))` will make the collapsing header close this frame (or stay closed).
|
||||
///
|
||||
/// Calling `.open(None)` has no effect (default).
|
||||
pub fn open(mut self, open: Option<bool>) -> Self {
|
||||
self.open = open;
|
||||
self
|
||||
}
|
||||
|
||||
/// Explicitly set the source of the `Id` of this widget, instead of using title label.
|
||||
/// This is useful if the title label is dynamic or not unique.
|
||||
pub fn id_source(mut self, id_source: impl Hash) -> Self {
|
||||
|
@ -186,12 +198,6 @@ impl CollapsingHeader {
|
|||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: CollapsingHeader::new(RichText::new(text).text_style(…))"]
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
self.text = self.text.text_style(text_style);
|
||||
self
|
||||
}
|
||||
|
||||
/// If you set this to `false`, the `CollapsingHeader` will be grayed out and un-clickable.
|
||||
///
|
||||
/// This is a convenience for [`Ui::set_enabled`].
|
||||
|
@ -256,6 +262,7 @@ impl CollapsingHeader {
|
|||
let Self {
|
||||
text,
|
||||
default_open,
|
||||
open,
|
||||
id_source,
|
||||
enabled: _,
|
||||
selectable: _,
|
||||
|
@ -291,10 +298,16 @@ impl CollapsingHeader {
|
|||
);
|
||||
|
||||
let mut state = State::from_memory_with_default_open(ui.ctx(), id, default_open);
|
||||
if header_response.clicked() {
|
||||
if let Some(open) = open {
|
||||
if open != state.open {
|
||||
state.toggle(ui);
|
||||
header_response.mark_changed();
|
||||
}
|
||||
} else if header_response.clicked() {
|
||||
state.toggle(ui);
|
||||
header_response.mark_changed();
|
||||
}
|
||||
|
||||
header_response
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text()));
|
||||
|
||||
|
|
|
@ -733,13 +733,16 @@ impl Prepared {
|
|||
state.offset = state.offset.min(available_offset);
|
||||
state.offset = state.offset.max(Vec2::ZERO);
|
||||
|
||||
// Is scroll handle at end of content? If so enter sticky mode.
|
||||
// Is scroll handle at end of content, or is there no scrollbar
|
||||
// yet (not enough content), but sticking is requested? If so, enter sticky mode.
|
||||
// Only has an effect if stick_to_end is enabled but we save in
|
||||
// state anyway so that entering sticky mode at an arbitrary time
|
||||
// has appropriate effect.
|
||||
state.scroll_stuck_to_end = [
|
||||
state.offset[0] == available_offset[0],
|
||||
state.offset[1] == available_offset[1],
|
||||
(state.offset[0] == available_offset[0])
|
||||
|| (self.stick_to_end[0] && available_offset[0] < 0.),
|
||||
(state.offset[1] == available_offset[1])
|
||||
|| (self.stick_to_end[1] && available_offset[1] < 0.),
|
||||
];
|
||||
|
||||
state.show_scroll = show_scroll_this_frame;
|
||||
|
|
|
@ -26,6 +26,9 @@ use epaint::{stats::*, text::Fonts, *};
|
|||
///
|
||||
/// [`CtxRef`] is cheap to clone, and any clones refers to the same mutable data.
|
||||
///
|
||||
/// A [`CtxRef`] is only valid for the duration of a frame, and so you should not store a [`CtxRef`] between frames.
|
||||
/// A new [`CtxRef`] is created each frame by calling [`Self::run`].
|
||||
///
|
||||
/// # Example:
|
||||
///
|
||||
/// ``` no_run
|
||||
|
@ -49,7 +52,6 @@ use epaint::{stats::*, text::Fonts, *};
|
|||
/// paint(clipped_meshes);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
#[derive(Clone)]
|
||||
pub struct CtxRef(std::sync::Arc<Context>);
|
||||
|
||||
|
@ -97,19 +99,54 @@ impl CtxRef {
|
|||
///
|
||||
/// This will modify the internal reference to point to a new generation of [`Context`].
|
||||
/// Any old clones of this [`CtxRef`] will refer to the old [`Context`], which will not get new input.
|
||||
///
|
||||
/// You can alternatively run [`Self::begin_frame`] and [`Self::end_frame`].
|
||||
///
|
||||
/// ``` rust
|
||||
/// // One egui context that you keep reusing:
|
||||
/// let mut ctx = egui::CtxRef::default();
|
||||
///
|
||||
/// // Each frame:
|
||||
/// let input = egui::RawInput::default();
|
||||
/// let (output, shapes) = ctx.run(input, |ctx| {
|
||||
/// egui::CentralPanel::default().show(&ctx, |ui| {
|
||||
/// ui.label("Hello egui!");
|
||||
/// });
|
||||
/// });
|
||||
/// // handle output, paint shapes
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn run(
|
||||
&mut self,
|
||||
new_input: RawInput,
|
||||
run_ui: impl FnOnce(&CtxRef),
|
||||
) -> (Output, Vec<ClippedShape>) {
|
||||
self.begin_frame(new_input);
|
||||
run_ui(self);
|
||||
self.end_frame()
|
||||
}
|
||||
|
||||
/// An alternative to calling [`Self::run`].
|
||||
///
|
||||
/// ``` rust
|
||||
/// // One egui context that you keep reusing:
|
||||
/// let mut ctx = egui::CtxRef::default();
|
||||
///
|
||||
/// // Each frame:
|
||||
/// let input = egui::RawInput::default();
|
||||
/// ctx.begin_frame(input);
|
||||
///
|
||||
/// egui::CentralPanel::default().show(&ctx, |ui| {
|
||||
/// ui.label("Hello egui!");
|
||||
/// });
|
||||
///
|
||||
/// let (output, shapes) = ctx.end_frame();
|
||||
/// // handle output, paint shapes
|
||||
/// ```
|
||||
pub fn begin_frame(&mut self, new_input: RawInput) {
|
||||
let mut self_: Context = (*self.0).clone();
|
||||
self_.begin_frame_mut(new_input);
|
||||
*self = Self(Arc::new(self_));
|
||||
|
||||
run_ui(self);
|
||||
|
||||
self.end_frame()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
@ -299,6 +336,11 @@ impl CtxRef {
|
|||
memory.surrender_focus(id);
|
||||
}
|
||||
|
||||
if response.dragged() && !memory.has_focus(response.id) {
|
||||
// e.g.: remove focus from a widget when you drag something else
|
||||
memory.stop_text_input();
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
|
@ -311,30 +353,21 @@ impl CtxRef {
|
|||
pub fn debug_painter(&self) -> Painter {
|
||||
Self::layer_painter(self, LayerId::debug())
|
||||
}
|
||||
|
||||
/// Respond to secondary clicks (right-clicks) by showing the given menu.
|
||||
pub(crate) fn show_context_menu(
|
||||
&self,
|
||||
response: &Response,
|
||||
add_contents: impl FnOnce(&mut Ui),
|
||||
) {
|
||||
self.context_menu_system()
|
||||
.context_menu(response, add_contents);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// This is the first thing you need when working with egui. Create using [`CtxRef`].
|
||||
/// Your handle to egui.
|
||||
///
|
||||
/// This is the first thing you need when working with egui.
|
||||
/// Use [`CtxRef`] to create and refer to a [`Context`].
|
||||
///
|
||||
/// Contains the [`InputState`], [`Memory`], [`Output`], and more.
|
||||
///
|
||||
/// Your handle to Egui.
|
||||
///
|
||||
/// Almost all methods are marked `&self`, `Context` has interior mutability (protected by mutexes).
|
||||
/// Almost all methods are marked `&self`, [`Context`] has interior mutability (protected by mutexes).
|
||||
/// Multi-threaded access to a [`Context`] is behind the feature flag `multi_threaded`.
|
||||
/// Normally you'd always do all ui work on one thread, or perhaps use multiple contexts,
|
||||
/// but if you really want to access the same Context from multiple threads, it *SHOULD* be fine,
|
||||
/// but if you really want to access the same [`Context`] from multiple threads, it *SHOULD* be fine,
|
||||
/// but you are likely the first person to try it.
|
||||
#[derive(Default)]
|
||||
pub struct Context {
|
||||
|
@ -343,7 +376,7 @@ pub struct Context {
|
|||
// This means everything else needs to be behind an Arc.
|
||||
// We can probably come up with a nicer design.
|
||||
//
|
||||
/// None until first call to `begin_frame`.
|
||||
/// `None` until the start of the first frame.
|
||||
fonts: Option<Arc<Fonts>>,
|
||||
memory: Arc<Mutex<Memory>>,
|
||||
animation_manager: Arc<Mutex<AnimationManager>>,
|
||||
|
@ -435,11 +468,12 @@ impl Context {
|
|||
.expect("No fonts available until first call to CtxRef::run()")
|
||||
}
|
||||
|
||||
/// The egui texture, containing font characters etc.
|
||||
/// The egui font image, containing font characters etc.
|
||||
///
|
||||
/// Not valid until first call to [`CtxRef::run()`].
|
||||
/// That's because since we don't know the proper `pixels_per_point` until then.
|
||||
pub fn texture(&self) -> Arc<epaint::Texture> {
|
||||
self.fonts().texture()
|
||||
pub fn font_image(&self) -> Arc<epaint::FontImage> {
|
||||
self.fonts().font_image()
|
||||
}
|
||||
|
||||
/// Tell `egui` which fonts to use.
|
||||
|
@ -626,7 +660,7 @@ impl Context {
|
|||
/// Returns what has happened this frame [`crate::Output`] as well as what you need to paint.
|
||||
/// You can transform the returned shapes into triangles with a call to [`Context::tessellate`].
|
||||
#[must_use]
|
||||
fn end_frame(&self) -> (Output, Vec<ClippedShape>) {
|
||||
pub fn end_frame(&self) -> (Output, Vec<ClippedShape>) {
|
||||
if self.input.wants_repaint() {
|
||||
self.request_repaint();
|
||||
}
|
||||
|
@ -664,7 +698,7 @@ impl Context {
|
|||
let clipped_meshes = tessellator::tessellate_shapes(
|
||||
shapes,
|
||||
tessellation_options,
|
||||
self.fonts().texture().size(),
|
||||
self.fonts().font_image().size(),
|
||||
);
|
||||
*self.paint_stats.lock() = paint_stats.with_clipped_meshes(&clipped_meshes);
|
||||
clipped_meshes
|
||||
|
@ -774,8 +808,15 @@ impl Context {
|
|||
/// Calling this with `value = false` will always yield a number less than one, quickly going towards zero.
|
||||
///
|
||||
/// The function will call [`Self::request_repaint()`] when appropriate.
|
||||
///
|
||||
/// The animation time is taken from [`Style::animation_time`].
|
||||
pub fn animate_bool(&self, id: Id, value: bool) -> f32 {
|
||||
let animation_time = self.style().animation_time;
|
||||
self.animate_bool_with_time(id, value, animation_time)
|
||||
}
|
||||
|
||||
/// Like [`Self::animate_bool`] but allows you to control the animation time.
|
||||
pub fn animate_bool_with_time(&self, id: Id, value: bool, animation_time: f32) -> f32 {
|
||||
let animated_value =
|
||||
self.animation_manager
|
||||
.lock()
|
||||
|
@ -808,7 +849,7 @@ impl Context {
|
|||
.show(ui, |ui| {
|
||||
let mut font_definitions = self.fonts().definitions().clone();
|
||||
font_definitions.ui(ui);
|
||||
self.fonts().texture().ui(ui);
|
||||
self.fonts().font_image().ui(ui);
|
||||
self.set_fonts(font_definitions);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! uis for egui types.
|
||||
use crate::*;
|
||||
|
||||
impl Widget for &epaint::Texture {
|
||||
impl Widget for &epaint::FontImage {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
use epaint::Mesh;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
//! Try the live web demo: <https://emilk.github.io/egui/index.html>. Read more about egui at <https://github.com/emilk/egui>.
|
||||
//!
|
||||
//! `egui` is in heavy development, with each new version having breaking changes.
|
||||
//! You need to have rust 1.54.0 or later to use `egui`.
|
||||
//! You need to have rust 1.56.0 or later to use `egui`.
|
||||
//!
|
||||
//! To quickly get started with egui, you can take a look at [`eframe_template`](https://github.com/emilk/eframe_template)
|
||||
//! which uses [`eframe`](https://docs.rs/eframe).
|
||||
|
@ -349,9 +349,9 @@
|
|||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
missing_crate_level_docs,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
@ -386,7 +386,7 @@ pub use emath::{lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos
|
|||
pub use epaint::{
|
||||
color, mutex,
|
||||
text::{FontData, FontDefinitions, FontFamily, TextStyle},
|
||||
ClippedMesh, Color32, Rgba, Shape, Stroke, Texture, TextureId,
|
||||
ClippedMesh, Color32, FontImage, Rgba, Shape, Stroke, TextureId,
|
||||
};
|
||||
|
||||
pub mod text {
|
||||
|
|
|
@ -327,13 +327,16 @@ impl Memory {
|
|||
self.interaction.focus.id
|
||||
}
|
||||
|
||||
pub(crate) fn lock_focus(&mut self, id: Id, lock_focus: bool) {
|
||||
/// Prevent keyboard focus from moving away from this widget even if users presses the tab key.
|
||||
/// You must first give focus to the widget before calling this.
|
||||
pub fn lock_focus(&mut self, id: Id, lock_focus: bool) {
|
||||
if self.had_focus_last_frame(id) && self.has_focus(id) {
|
||||
self.interaction.focus.is_focus_locked = lock_focus;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn has_lock_focus(&mut self, id: Id) -> bool {
|
||||
/// Is the keyboard focus locked on this widget? If so the focus won't move even if the user presses the tab key.
|
||||
pub fn has_lock_focus(&mut self, id: Id) -> bool {
|
||||
if self.had_focus_last_frame(id) && self.has_focus(id) {
|
||||
self.interaction.focus.is_focus_locked
|
||||
} else {
|
||||
|
|
|
@ -42,7 +42,7 @@ impl BarState {
|
|||
ctx.memory().data.insert_temp(bar_id, self);
|
||||
}
|
||||
|
||||
/// Show a menu at pointer if right-clicked response.
|
||||
/// Show a menu at pointer if primary-clicked response.
|
||||
/// Should be called from [`Context`] on a [`Response`]
|
||||
pub fn bar_menu<R>(
|
||||
&mut self,
|
||||
|
@ -87,8 +87,11 @@ pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResp
|
|||
add_contents(ui)
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct a top level menu in a menu bar. This would be e.g. "File", "Edit" etc.
|
||||
///
|
||||
/// Responds to primary clicks.
|
||||
///
|
||||
/// Returns `None` if the menu is not open.
|
||||
pub fn menu_button<R>(
|
||||
ui: &mut Ui,
|
||||
|
@ -97,8 +100,11 @@ pub fn menu_button<R>(
|
|||
) -> InnerResponse<Option<R>> {
|
||||
stationary_menu_impl(ui, title, Box::new(add_contents))
|
||||
}
|
||||
|
||||
/// Construct a nested sub menu in another menu.
|
||||
///
|
||||
/// Opens on hover.
|
||||
///
|
||||
/// Returns `None` if the menu is not open.
|
||||
pub(crate) fn submenu_button<R>(
|
||||
ui: &mut Ui,
|
||||
|
@ -150,7 +156,9 @@ pub(crate) fn menu_ui<'c, R>(
|
|||
inner_response
|
||||
}
|
||||
|
||||
/// build a top level menu with a button
|
||||
/// Build a top level menu with a button.
|
||||
///
|
||||
/// Responds to primary clicks.
|
||||
fn stationary_menu_impl<'c, R>(
|
||||
ui: &mut Ui,
|
||||
title: impl Into<WidgetText>,
|
||||
|
@ -275,7 +283,10 @@ impl MenuRoot {
|
|||
}
|
||||
(MenuResponse::Stay, None)
|
||||
}
|
||||
/// interaction with a stationary menu, i.e. fixed in another Ui
|
||||
|
||||
/// Interaction with a stationary menu, i.e. fixed in another Ui.
|
||||
///
|
||||
/// Responds to primary clicks.
|
||||
fn stationary_interaction(
|
||||
response: &Response,
|
||||
root: &mut MenuRootManager,
|
||||
|
@ -310,7 +321,8 @@ impl MenuRoot {
|
|||
}
|
||||
MenuResponse::Stay
|
||||
}
|
||||
/// interaction with a context menu
|
||||
|
||||
/// Interaction with a context menu (secondary clicks).
|
||||
fn context_interaction(
|
||||
response: &Response,
|
||||
root: &mut Option<MenuRoot>,
|
||||
|
@ -328,10 +340,9 @@ impl MenuRoot {
|
|||
destroy = root.id == response.id;
|
||||
}
|
||||
if !in_old_menu {
|
||||
let in_target = response.hovered();
|
||||
if in_target && pointer.secondary_down() {
|
||||
if response.hovered() && pointer.secondary_down() {
|
||||
return MenuResponse::Create(pos, id);
|
||||
} else if (in_target && pointer.primary_down()) || destroy {
|
||||
} else if (response.hovered() && pointer.primary_down()) || destroy {
|
||||
return MenuResponse::Close;
|
||||
}
|
||||
}
|
||||
|
@ -339,6 +350,7 @@ impl MenuRoot {
|
|||
}
|
||||
MenuResponse::Stay
|
||||
}
|
||||
|
||||
fn handle_menu_response(root: &mut MenuRootManager, menu_response: MenuResponse) {
|
||||
match menu_response {
|
||||
MenuResponse::Create(pos, id) => {
|
||||
|
@ -348,11 +360,14 @@ impl MenuRoot {
|
|||
MenuResponse::Stay => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Respond to secondary (right) clicks.
|
||||
pub fn context_click_interaction(response: &Response, root: &mut MenuRootManager, id: Id) {
|
||||
let menu_response = Self::context_interaction(response, root, id);
|
||||
Self::handle_menu_response(root, menu_response);
|
||||
}
|
||||
|
||||
// Responds to primary clicks.
|
||||
pub fn stationary_click_interaction(response: &Response, root: &mut MenuRootManager, id: Id) {
|
||||
let menu_response = Self::stationary_interaction(response, root, id);
|
||||
Self::handle_menu_response(root, menu_response);
|
||||
|
|
|
@ -66,7 +66,7 @@ impl Painter {
|
|||
self.fade_to_color = fade_to_color;
|
||||
}
|
||||
|
||||
pub(crate) fn visible(&self) -> bool {
|
||||
pub(crate) fn is_visible(&self) -> bool {
|
||||
self.fade_to_color != Some(Color32::TRANSPARENT)
|
||||
}
|
||||
|
||||
|
|
|
@ -437,13 +437,12 @@ impl Response {
|
|||
/// Move the scroll to this UI with the specified alignment.
|
||||
///
|
||||
/// ```
|
||||
/// # use egui::Align;
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
/// for i in 0..1000 {
|
||||
/// let response = ui.button(format!("Button {}", i));
|
||||
/// let response = ui.button("Scroll to me");
|
||||
/// if response.clicked() {
|
||||
/// response.scroll_to_me(Align::Center);
|
||||
/// response.scroll_to_me(egui::Align::Center);
|
||||
/// }
|
||||
/// }
|
||||
/// });
|
||||
|
@ -490,8 +489,12 @@ impl Response {
|
|||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// See also: [`Ui::menu_button`] and [`Ui::close_menu`].
|
||||
pub fn context_menu(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
|
||||
self.ctx.show_context_menu(&self, add_contents);
|
||||
self.ctx
|
||||
.context_menu_system()
|
||||
.context_menu(&self, add_contents);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,10 +154,10 @@ impl Spacing {
|
|||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct Interaction {
|
||||
/// Mouse must be the close to the side of a window to resize
|
||||
/// Mouse must be this close to the side of a window to resize
|
||||
pub resize_grab_radius_side: f32,
|
||||
|
||||
/// Mouse must be the close to the corner of a window to resize
|
||||
/// Mouse must be this close to the corner of a window to resize
|
||||
pub resize_grab_radius_corner: f32,
|
||||
|
||||
/// If `false`, tooltips will show up anytime you hover anything, even is mouse is still moving
|
||||
|
|
|
@ -119,6 +119,8 @@ impl Ui {
|
|||
}
|
||||
|
||||
/// Style options for this `Ui` and its children.
|
||||
///
|
||||
/// Note that this may be a different [`Style`] than that of [`Context::style`].
|
||||
#[inline]
|
||||
pub fn style(&self) -> &std::sync::Arc<Style> {
|
||||
&self.style
|
||||
|
@ -234,7 +236,7 @@ impl Ui {
|
|||
/// ```
|
||||
pub fn set_enabled(&mut self, enabled: bool) {
|
||||
self.enabled &= enabled;
|
||||
if !self.enabled && self.visible() {
|
||||
if !self.enabled && self.is_visible() {
|
||||
self.painter
|
||||
.set_fade_to_color(Some(self.visuals().window_fill()));
|
||||
}
|
||||
|
@ -242,8 +244,13 @@ impl Ui {
|
|||
|
||||
/// If `false`, any widgets added to the `Ui` will be invisible and non-interactive.
|
||||
#[inline]
|
||||
pub fn is_visible(&self) -> bool {
|
||||
self.painter.is_visible()
|
||||
}
|
||||
|
||||
#[deprecated = "Renamed is_visible"]
|
||||
pub fn visible(&self) -> bool {
|
||||
self.painter.visible()
|
||||
self.painter.is_visible()
|
||||
}
|
||||
|
||||
/// Calling `set_visible(false)` will cause all further widgets to be invisible,
|
||||
|
@ -349,7 +356,7 @@ impl Ui {
|
|||
|
||||
/// Can be used for culling: if `false`, then no part of `rect` will be visible on screen.
|
||||
pub fn is_rect_visible(&self, rect: Rect) -> bool {
|
||||
self.visible() && rect.intersects(self.clip_rect())
|
||||
self.is_visible() && rect.intersects(self.clip_rect())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -935,7 +942,7 @@ impl Ui {
|
|||
|
||||
/// Add a single[`Widget`] that is possibly disabled, i.e. greyed out and non-interactive.
|
||||
///
|
||||
/// If you call `add_enabled` from within an already disabled UI,
|
||||
/// If you call `add_enabled` from within an already disabled `Ui`,
|
||||
/// the widget will always be disabled, even if the `enabled` argument is true.
|
||||
///
|
||||
/// See also [`Self::add_enabled_ui`] and [`Self::is_enabled`].
|
||||
|
@ -946,21 +953,21 @@ impl Ui {
|
|||
/// # });
|
||||
/// ```
|
||||
pub fn add_enabled(&mut self, enabled: bool, widget: impl Widget) -> Response {
|
||||
if enabled || !self.is_enabled() {
|
||||
self.add(widget)
|
||||
} else {
|
||||
if self.is_enabled() && !enabled {
|
||||
let old_painter = self.painter.clone();
|
||||
self.set_enabled(false);
|
||||
let response = self.add(widget);
|
||||
self.enabled = true;
|
||||
self.painter = old_painter;
|
||||
response
|
||||
} else {
|
||||
self.add(widget)
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a section that is possibly disabled, i.e. greyed out and non-interactive.
|
||||
///
|
||||
/// If you call `add_enabled_ui` from within an already disabled UI,
|
||||
/// If you call `add_enabled_ui` from within an already disabled `Ui`,
|
||||
/// the result will always be disabled, even if the `enabled` argument is true.
|
||||
///
|
||||
/// See also [`Self::add_enabled`] and [`Self::is_enabled`].
|
||||
|
@ -988,6 +995,63 @@ impl Ui {
|
|||
})
|
||||
}
|
||||
|
||||
/// Add a single[`Widget`] that is possibly invisible.
|
||||
///
|
||||
/// An invisible widget still takes up the same space as if it were visible.
|
||||
///
|
||||
/// If you call `add_visible` from within an already invisible `Ui`,
|
||||
/// the widget will always be invisible, even if the `visible` argument is true.
|
||||
///
|
||||
/// See also [`Self::add_visible_ui`], [`Self::set_visible`] and [`Self::is_visible`].
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// ui.add_visible(false, egui::Label::new("You won't see me!"));
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn add_visible(&mut self, visible: bool, widget: impl Widget) -> Response {
|
||||
if self.is_visible() && !visible {
|
||||
// temporary make us invisible:
|
||||
let old_painter = self.painter.clone();
|
||||
self.set_visible(false);
|
||||
let response = self.add(widget);
|
||||
self.painter = old_painter;
|
||||
response
|
||||
} else {
|
||||
self.add(widget)
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a section that is possibly invisible, i.e. greyed out and non-interactive.
|
||||
///
|
||||
/// An invisible ui still takes up the same space as if it were visible.
|
||||
///
|
||||
/// If you call `add_visible_ui` from within an already invisible `Ui`,
|
||||
/// the result will always be invisible, even if the `visible` argument is true.
|
||||
///
|
||||
/// See also [`Self::add_visible`], [`Self::set_visible`] and [`Self::is_visible`].
|
||||
///
|
||||
/// ### Example
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// # let mut visible = true;
|
||||
/// ui.checkbox(&mut visible, "Show subsection");
|
||||
/// ui.add_visible_ui(visible, |ui| {
|
||||
/// ui.label("Maybe you see this, maybe you don't!");
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn add_visible_ui<R>(
|
||||
&mut self,
|
||||
visible: bool,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
self.scope(|ui| {
|
||||
ui.set_visible(visible);
|
||||
add_contents(ui)
|
||||
})
|
||||
}
|
||||
|
||||
/// Add extra space before the next widget.
|
||||
///
|
||||
/// The direction is dependent on the layout.
|
||||
|
@ -1761,7 +1825,9 @@ impl Ui {
|
|||
result
|
||||
}
|
||||
|
||||
/// Close menu (with submenus), if any.
|
||||
/// Close the menu we are in (including submenus), if any.
|
||||
///
|
||||
/// See also: [`Self::menu_button`] and [`Response::context_menu`].
|
||||
pub fn close_menu(&mut self) {
|
||||
if let Some(menu_state) = &mut self.menu_state {
|
||||
menu_state.write().close();
|
||||
|
@ -1778,7 +1844,9 @@ impl Ui {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
/// Create a menu button. Creates a button for a sub-menu when the `Ui` is inside a menu.
|
||||
/// Create a menu button that when clicked will show the given menu.
|
||||
///
|
||||
/// If called from within a menu this will instead create a button for a sub-menu.
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
|
@ -1791,6 +1859,8 @@ impl Ui {
|
|||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// See also: [`Self::close_menu`] and [`Response::context_menu`].
|
||||
pub fn menu_button<R>(
|
||||
&mut self,
|
||||
title: impl Into<WidgetText>,
|
||||
|
|
|
@ -80,18 +80,6 @@ impl Button {
|
|||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: Button::new(RichText::new(text).color(…))"]
|
||||
pub fn text_color(mut self, text_color: Color32) -> Self {
|
||||
self.text = self.text.color(text_color);
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: Button::new(RichText::new(text).text_style(…))"]
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
self.text = self.text.text_style(text_style);
|
||||
self
|
||||
}
|
||||
|
||||
/// Override background fill color. Note that this will override any on-hover effects.
|
||||
/// Calling this will also turn on the frame.
|
||||
pub fn fill(mut self, fill: impl Into<Color32>) -> Self {
|
||||
|
@ -241,18 +229,6 @@ impl<'a> Checkbox<'a> {
|
|||
text: text.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: Checkbox::new(RichText::new(text).color(…))"]
|
||||
pub fn text_color(mut self, text_color: Color32) -> Self {
|
||||
self.text = self.text.color(text_color);
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: Checkbox::new(RichText::new(text).text_style(…))"]
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
self.text = self.text.text_style(text_style);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Checkbox<'a> {
|
||||
|
@ -347,18 +323,6 @@ impl RadioButton {
|
|||
text: text.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: RadioButton::new(RichText::new(text).color(…))"]
|
||||
pub fn text_color(mut self, text_color: Color32) -> Self {
|
||||
self.text = self.text.color(text_color);
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: RadioButton::new(RichText::new(text).text_style(…))"]
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
self.text = self.text.text_style(text_style);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for RadioButton {
|
||||
|
|
|
@ -44,6 +44,7 @@ fn background_checkers(painter: &Painter, rect: Rect) {
|
|||
painter.add(Shape::mesh(mesh));
|
||||
}
|
||||
|
||||
/// Show a color with background checkers to demonstrate transparency (if any).
|
||||
pub fn show_color(ui: &mut Ui, color: impl Into<Hsva>, desired_size: Vec2) -> Response {
|
||||
show_hsva(ui, color.into(), desired_size)
|
||||
}
|
||||
|
@ -322,13 +323,15 @@ fn color_picker_hsva_2d(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Shows a color picker where the user can change the given color.
|
||||
///
|
||||
/// Returns `true` on change.
|
||||
pub fn color_picker_color32(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) -> bool {
|
||||
let mut hsva = color_cache_get(ui.ctx(), *srgba);
|
||||
let response = color_picker_hsva_2d(ui, &mut hsva, alpha);
|
||||
let changed = color_picker_hsva_2d(ui, &mut hsva, alpha);
|
||||
*srgba = Color32::from(hsva);
|
||||
color_cache_set(ui.ctx(), *srgba, hsva);
|
||||
response
|
||||
changed
|
||||
}
|
||||
|
||||
pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response {
|
||||
|
|
|
@ -6,8 +6,13 @@ use crate::*;
|
|||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// // These are equivalent:
|
||||
/// ui.hyperlink("https://github.com/emilk/egui");
|
||||
/// ui.add(egui::Hyperlink::new("https://github.com/emilk/egui").text("My favorite repo").small());
|
||||
/// ui.add(egui::Hyperlink::new("https://github.com/emilk/egui"));
|
||||
///
|
||||
/// // These are equivalent:
|
||||
/// ui.hyperlink_to("My favorite repo", "https://github.com/emilk/egui");
|
||||
/// ui.add(egui::Hyperlink::from_label_and_url("My favorite repo", "https://github.com/emilk/egui"));
|
||||
/// # });
|
||||
/// ```
|
||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||
|
@ -33,25 +38,6 @@ impl Hyperlink {
|
|||
text: text.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated = "Use Hyperlink::from_label_and_url instead"]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn text(mut self, text: impl ToString) -> Self {
|
||||
self.text = text.to_string().into();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Use Hyperlink::from_label_and_url instead"]
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
self.text = self.text.text_style(text_style);
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Use Hyperlink::from_label_and_url instead"]
|
||||
pub fn small(mut self) -> Self {
|
||||
self.text = self.text.text_style(TextStyle::Small);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Hyperlink {
|
||||
|
|
|
@ -43,90 +43,6 @@ impl Label {
|
|||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).text_style(…))"]
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
self.text = self.text.text_style(text_style);
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).heading())"]
|
||||
pub fn heading(mut self) -> Self {
|
||||
self.text = self.text.heading();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).monospace())"]
|
||||
pub fn monospace(mut self) -> Self {
|
||||
self.text = self.text.monospace();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).code())"]
|
||||
pub fn code(mut self) -> Self {
|
||||
self.text = self.text.code();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).strong())"]
|
||||
pub fn strong(mut self) -> Self {
|
||||
self.text = self.text.strong();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).weak())"]
|
||||
pub fn weak(mut self) -> Self {
|
||||
self.text = self.text.weak();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).underline())"]
|
||||
pub fn underline(mut self) -> Self {
|
||||
self.text = self.text.underline();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).strikethrough())"]
|
||||
pub fn strikethrough(mut self) -> Self {
|
||||
self.text = self.text.strikethrough();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).italics())"]
|
||||
pub fn italics(mut self) -> Self {
|
||||
self.text = self.text.italics();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).small())"]
|
||||
pub fn small(mut self) -> Self {
|
||||
self.text = self.text.small();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).small_raised())"]
|
||||
pub fn small_raised(mut self) -> Self {
|
||||
self.text = self.text.small_raised();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).raised())"]
|
||||
pub fn raised(mut self) -> Self {
|
||||
self.text = self.text.raised();
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).background_color(…))"]
|
||||
pub fn background_color(mut self, background_color: impl Into<Color32>) -> Self {
|
||||
self.text = self.text.background_color(background_color);
|
||||
self
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by Label::new(RichText::new(…).text_color())"]
|
||||
pub fn text_color(mut self, text_color: impl Into<Color32>) -> Self {
|
||||
self.text = self.text.color(text_color);
|
||||
self
|
||||
}
|
||||
|
||||
/// Make the label respond to clicks and/or drags.
|
||||
///
|
||||
/// By default, a label is inert and does not respond to click or drags.
|
||||
|
|
|
@ -17,6 +17,7 @@ mod progress_bar;
|
|||
mod selected_label;
|
||||
mod separator;
|
||||
mod slider;
|
||||
mod spinner;
|
||||
pub mod text_edit;
|
||||
|
||||
pub use button::*;
|
||||
|
@ -28,6 +29,7 @@ pub use progress_bar::ProgressBar;
|
|||
pub use selected_label::SelectableLabel;
|
||||
pub use separator::Separator;
|
||||
pub use slider::*;
|
||||
pub use spinner::*;
|
||||
pub use text_edit::{TextBuffer, TextEdit};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
@ -7,7 +7,7 @@ use epaint::Mesh;
|
|||
|
||||
use crate::*;
|
||||
|
||||
use super::{PlotBounds, ScreenTransform};
|
||||
use super::{CustomLabelFuncRef, PlotBounds, ScreenTransform};
|
||||
use rect_elem::*;
|
||||
use values::*;
|
||||
|
||||
|
@ -61,7 +61,13 @@ pub(super) trait PlotItem {
|
|||
}
|
||||
}
|
||||
|
||||
fn on_hover(&self, elem: ClosestElem, shapes: &mut Vec<Shape>, plot: &PlotConfig<'_>) {
|
||||
fn on_hover(
|
||||
&self,
|
||||
elem: ClosestElem,
|
||||
shapes: &mut Vec<Shape>,
|
||||
plot: &PlotConfig<'_>,
|
||||
custom_label_func: &CustomLabelFuncRef,
|
||||
) {
|
||||
let points = match self.geometry() {
|
||||
PlotGeometry::Points(points) => points,
|
||||
PlotGeometry::None => {
|
||||
|
@ -83,7 +89,7 @@ pub(super) trait PlotItem {
|
|||
let pointer = plot.transform.position_from_value(&value);
|
||||
shapes.push(Shape::circle_filled(pointer, 3.0, line_color));
|
||||
|
||||
rulers_at_value(pointer, value, self.name(), plot, shapes);
|
||||
rulers_at_value(pointer, value, self.name(), plot, shapes, custom_label_func);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1365,7 +1371,13 @@ impl PlotItem for BarChart {
|
|||
find_closest_rect(&self.bars, point, transform)
|
||||
}
|
||||
|
||||
fn on_hover(&self, elem: ClosestElem, shapes: &mut Vec<Shape>, plot: &PlotConfig<'_>) {
|
||||
fn on_hover(
|
||||
&self,
|
||||
elem: ClosestElem,
|
||||
shapes: &mut Vec<Shape>,
|
||||
plot: &PlotConfig<'_>,
|
||||
_: &CustomLabelFuncRef,
|
||||
) {
|
||||
let bar = &self.bars[elem.index];
|
||||
|
||||
bar.add_shapes(plot.transform, true, shapes);
|
||||
|
@ -1501,7 +1513,13 @@ impl PlotItem for BoxPlot {
|
|||
find_closest_rect(&self.boxes, point, transform)
|
||||
}
|
||||
|
||||
fn on_hover(&self, elem: ClosestElem, shapes: &mut Vec<Shape>, plot: &PlotConfig<'_>) {
|
||||
fn on_hover(
|
||||
&self,
|
||||
elem: ClosestElem,
|
||||
shapes: &mut Vec<Shape>,
|
||||
plot: &PlotConfig<'_>,
|
||||
_: &CustomLabelFuncRef,
|
||||
) {
|
||||
let box_plot = &self.boxes[elem.index];
|
||||
|
||||
box_plot.add_shapes(plot.transform, true, shapes);
|
||||
|
@ -1619,6 +1637,7 @@ pub(super) fn rulers_at_value(
|
|||
name: &str,
|
||||
plot: &PlotConfig<'_>,
|
||||
shapes: &mut Vec<Shape>,
|
||||
custom_label_func: &CustomLabelFuncRef,
|
||||
) {
|
||||
let line_color = rulers_color(plot.ui);
|
||||
if plot.show_x {
|
||||
|
@ -1638,7 +1657,9 @@ pub(super) fn rulers_at_value(
|
|||
let scale = plot.transform.dvalue_dpos();
|
||||
let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).at_most(6);
|
||||
let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).at_most(6);
|
||||
if plot.show_x && plot.show_y {
|
||||
if let Some(custom_label) = custom_label_func {
|
||||
custom_label(name, &value)
|
||||
} else if plot.show_x && plot.show_y {
|
||||
format!(
|
||||
"{}x = {:.*}\ny = {:.*}",
|
||||
prefix, x_decimals, value.x, y_decimals, value.y
|
||||
|
|
|
@ -18,6 +18,9 @@ mod items;
|
|||
mod legend;
|
||||
mod transform;
|
||||
|
||||
type CustomLabelFunc = dyn Fn(&str, &Value) -> String;
|
||||
type CustomLabelFuncRef = Option<Box<CustomLabelFunc>>;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Information about the plot that has to persist between frames.
|
||||
|
@ -76,6 +79,7 @@ pub struct Plot {
|
|||
|
||||
show_x: bool,
|
||||
show_y: bool,
|
||||
custom_label_func: CustomLabelFuncRef,
|
||||
legend_config: Option<Legend>,
|
||||
show_background: bool,
|
||||
show_axes: [bool; 2],
|
||||
|
@ -102,6 +106,7 @@ impl Plot {
|
|||
|
||||
show_x: true,
|
||||
show_y: true,
|
||||
custom_label_func: None,
|
||||
legend_config: None,
|
||||
show_background: true,
|
||||
show_axes: [true; 2],
|
||||
|
@ -182,6 +187,35 @@ impl Plot {
|
|||
self
|
||||
}
|
||||
|
||||
/// Provide a function to customize the on-hovel label for the x and y axis
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// use egui::plot::{Line, Plot, Value, Values};
|
||||
/// let sin = (0..1000).map(|i| {
|
||||
/// let x = i as f64 * 0.01;
|
||||
/// Value::new(x, x.sin())
|
||||
/// });
|
||||
/// let line = Line::new(Values::from_values_iter(sin));
|
||||
/// Plot::new("my_plot").view_aspect(2.0)
|
||||
/// .custom_label_func(|name, value| {
|
||||
/// if !name.is_empty() {
|
||||
/// format!("{}: {:.*}%", name, 1, value.y).to_string()
|
||||
/// } else {
|
||||
/// "".to_string()
|
||||
/// }
|
||||
/// })
|
||||
/// .show(ui, |plot_ui| plot_ui.line(line));
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn custom_label_func<F: 'static + Fn(&str, &Value) -> String>(
|
||||
mut self,
|
||||
custom_lebel_func: F,
|
||||
) -> Self {
|
||||
self.custom_label_func = Some(Box::new(custom_lebel_func));
|
||||
self
|
||||
}
|
||||
|
||||
/// Expand bounds to include the given x value.
|
||||
/// For instance, to always show the y axis, call `plot.include_x(0.0)`.
|
||||
pub fn include_x(mut self, x: impl Into<f64>) -> Self {
|
||||
|
@ -235,6 +269,7 @@ impl Plot {
|
|||
view_aspect,
|
||||
mut show_x,
|
||||
mut show_y,
|
||||
custom_label_func,
|
||||
legend_config,
|
||||
show_background,
|
||||
show_axes,
|
||||
|
@ -406,6 +441,7 @@ impl Plot {
|
|||
items,
|
||||
show_x,
|
||||
show_y,
|
||||
custom_label_func,
|
||||
show_axes,
|
||||
transform: transform.clone(),
|
||||
};
|
||||
|
@ -613,6 +649,7 @@ struct PreparedPlot {
|
|||
items: Vec<Box<dyn PlotItem>>,
|
||||
show_x: bool,
|
||||
show_y: bool,
|
||||
custom_label_func: CustomLabelFuncRef,
|
||||
show_axes: [bool; 2],
|
||||
transform: ScreenTransform,
|
||||
}
|
||||
|
@ -731,6 +768,7 @@ impl PreparedPlot {
|
|||
transform,
|
||||
show_x,
|
||||
show_y,
|
||||
custom_label_func,
|
||||
items,
|
||||
..
|
||||
} = self;
|
||||
|
@ -760,10 +798,10 @@ impl PreparedPlot {
|
|||
};
|
||||
|
||||
if let Some((item, elem)) = closest {
|
||||
item.on_hover(elem, shapes, &plot);
|
||||
item.on_hover(elem, shapes, &plot, custom_label_func);
|
||||
} else {
|
||||
let value = transform.value_from_position(pointer);
|
||||
items::rulers_at_value(pointer, value, "", &plot, shapes);
|
||||
items::rulers_at_value(pointer, value, "", &plot, shapes, custom_label_func);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,12 +34,6 @@ impl SelectableLabel {
|
|||
text: text.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated = "Replaced by: Button::new(RichText::new(text).text_style(…))"]
|
||||
pub fn text_style(mut self, text_style: TextStyle) -> Self {
|
||||
self.text = self.text.text_style(text_style);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for SelectableLabel {
|
||||
|
|
56
egui/src/widgets/spinner.rs
Normal file
56
egui/src/widgets/spinner.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use epaint::{emath::lerp, vec2, Pos2, Shape, Stroke};
|
||||
|
||||
use crate::{Response, Sense, Ui, Widget};
|
||||
|
||||
/// A spinner widget used to indicate loading.
|
||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||
#[derive(Default)]
|
||||
pub struct Spinner {
|
||||
/// Uses the style's `interact_size` if `None`.
|
||||
size: Option<f32>,
|
||||
}
|
||||
|
||||
impl Spinner {
|
||||
/// Create a new spinner that uses the style's `interact_size` unless changed.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Sets the spinner's size. The size sets both the height and width, as the spinner is always
|
||||
/// square. If the size isn't set explicitly, the active style's `interact_size` is used.
|
||||
pub fn size(mut self, size: f32) -> Self {
|
||||
self.size = Some(size);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Spinner {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let size = self
|
||||
.size
|
||||
.unwrap_or_else(|| ui.style().spacing.interact_size.y);
|
||||
let (rect, response) = ui.allocate_exact_size(vec2(size, size), Sense::hover());
|
||||
|
||||
if ui.is_rect_visible(rect) {
|
||||
ui.ctx().request_repaint();
|
||||
|
||||
let radius = (rect.height() / 2.0) - 2.0;
|
||||
let n_points = 20;
|
||||
let start_angle = ui.input().time as f64 * 360f64.to_radians();
|
||||
let end_angle = start_angle + 240f64.to_radians() * ui.input().time.sin();
|
||||
let points: Vec<Pos2> = (0..n_points)
|
||||
.map(|i| {
|
||||
let angle = lerp(start_angle..=end_angle, i as f64 / n_points as f64);
|
||||
let (sin, cos) = angle.sin_cos();
|
||||
rect.center() + radius * vec2(cos as f32, sin as f32)
|
||||
})
|
||||
.collect();
|
||||
ui.painter().add(Shape::line(
|
||||
points,
|
||||
Stroke::new(3.0, ui.visuals().strong_text_color()),
|
||||
));
|
||||
}
|
||||
|
||||
response
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
[package]
|
||||
name = "egui_datepicker"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
egui = { version = "0.15.0", path = "../egui", default-features = false }
|
||||
egui_dynamic_grid = { version = "0.15.0", path = "../egui_dynamic_grid" }
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false }
|
||||
egui_dynamic_grid = { version = "0.16.0", path = "../egui_dynamic_grid" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
[package]
|
||||
name = "egui_demo_app"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
eframe = { version = "0.15.0", path = "../eframe" }
|
||||
# eframe = { version = "0.15.0", path = "../eframe", default-features = false, features = ["default_fonts", "egui_glow"] }
|
||||
eframe = { version = "0.16.0", path = "../eframe" }
|
||||
|
||||
egui_demo_lib = { version = "0.15.0", path = "../egui_demo_lib", features = ["extra_debug_asserts"] }
|
||||
# To use the old glium backend instead:
|
||||
# eframe = { version = "0.16.0", path = "../eframe", default-features = false, features = ["default_fonts", "egui_glium"] }
|
||||
|
||||
egui_demo_lib = { version = "0.16.0", path = "../egui_demo_lib", features = ["extra_debug_asserts"] }
|
||||
|
||||
[features]
|
||||
default = ["persistence"]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
|
@ -8,7 +10,7 @@ fn main() {
|
|||
let app = egui_demo_lib::WrapApp::default();
|
||||
let options = eframe::NativeOptions {
|
||||
// Let's show off that we support transparent windows
|
||||
transparent: true,
|
||||
// transparent: true,
|
||||
drag_and_drop_support: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
[package]
|
||||
name = "egui_demo_lib"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Example library for egui"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/egui_demo_lib"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
@ -18,10 +19,10 @@ all-features = true
|
|||
[lib]
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.15.0", path = "../egui", default-features = false }
|
||||
epi = { version = "0.15.0", path = "../epi" }
|
||||
egui_dynamic_grid = { version = "0.15.0", path = "../egui_dynamic_grid" }
|
||||
egui_datepicker = { version = "0.15.0", path = "../egui_datepicker", optional = true }
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false }
|
||||
epi = { version = "0.16.0", path = "../epi" }
|
||||
egui_dynamic_grid = { version = "0.16.0", path = "../egui_dynamic_grid" }
|
||||
egui_datepicker = { version = "0.16.0", path = "../egui_datepicker", optional = true }
|
||||
|
||||
chrono = { version = "0.4", features = ["js-sys", "wasmbind"], optional = true }
|
||||
enum-map = { version = "1", features = ["serde"] }
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
|
||||
This crate contains example code for [`egui`](https://github.com/emilk/egui).
|
||||
|
||||
It is in a separate crate for two reasons:
|
||||
The demo library is a separate crate for three reasons:
|
||||
|
||||
* To ensure it only uses the public `egui` api.
|
||||
* To remove the amount of code in `egui` proper.
|
||||
* To make it easy for other integrations to use the egui demos a test.
|
||||
|
|
|
@ -97,7 +97,11 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
|||
let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
|
||||
c.bench_function("tessellate_text", |b| {
|
||||
b.iter(|| {
|
||||
tessellator.tessellate_text(fonts.texture().size(), text_shape.clone(), &mut mesh);
|
||||
tessellator.tessellate_text(
|
||||
fonts.font_image().size(),
|
||||
text_shape.clone(),
|
||||
&mut mesh,
|
||||
);
|
||||
mesh.clear();
|
||||
})
|
||||
});
|
||||
|
|
|
@ -34,7 +34,7 @@ impl epi::App for ColorTest {
|
|||
"🎨 Color test"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if frame.is_web() {
|
||||
ui.label(
|
||||
|
@ -43,18 +43,14 @@ impl epi::App for ColorTest {
|
|||
ui.separator();
|
||||
}
|
||||
ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| {
|
||||
self.ui(ui, &mut Some(frame.tex_allocator()));
|
||||
self.ui(ui, Some(frame));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorTest {
|
||||
pub fn ui(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
mut tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
|
||||
) {
|
||||
pub fn ui(&mut self, ui: &mut Ui, tex_allocator: Option<&dyn epi::TextureAllocator>) {
|
||||
ui.set_max_width(680.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
|
@ -105,10 +101,10 @@ impl ColorTest {
|
|||
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
|
||||
self.tex_gradient(ui, tex_allocator, "Ground truth (texture)", WHITE, &g);
|
||||
}
|
||||
if let Some(tex_allocator) = &mut tex_allocator {
|
||||
if let Some(tex_allocator) = tex_allocator {
|
||||
ui.horizontal(|ui| {
|
||||
let g = Gradient::one_color(Color32::from(tex_color));
|
||||
let tex = self.tex_mngr.get(*tex_allocator, &g);
|
||||
let tex = self.tex_mngr.get(tex_allocator, &g);
|
||||
let texel_offset = 0.5 / (g.0.len() as f32);
|
||||
let uv =
|
||||
Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
||||
|
@ -167,7 +163,7 @@ impl ColorTest {
|
|||
fn show_gradients(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
|
||||
tex_allocator: Option<&dyn epi::TextureAllocator>,
|
||||
bg_fill: Color32,
|
||||
(left, right): (Color32, Color32),
|
||||
) {
|
||||
|
@ -261,7 +257,7 @@ impl ColorTest {
|
|||
fn tex_gradient(
|
||||
&mut self,
|
||||
ui: &mut Ui,
|
||||
tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
|
||||
tex_allocator: Option<&dyn epi::TextureAllocator>,
|
||||
label: &str,
|
||||
bg_fill: Color32,
|
||||
gradient: &Gradient,
|
||||
|
@ -271,7 +267,7 @@ impl ColorTest {
|
|||
}
|
||||
if let Some(tex_allocator) = tex_allocator {
|
||||
ui.horizontal(|ui| {
|
||||
let tex = self.tex_mngr.get(*tex_allocator, gradient);
|
||||
let tex = self.tex_mngr.get(tex_allocator, gradient);
|
||||
let texel_offset = 0.5 / (gradient.0.len() as f32);
|
||||
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
|
||||
ui.add(Image::new(tex, GRADIENT_SIZE).bg_fill(bg_fill).uv(uv))
|
||||
|
@ -391,16 +387,15 @@ impl Gradient {
|
|||
struct TextureManager(HashMap<Gradient, TextureId>);
|
||||
|
||||
impl TextureManager {
|
||||
fn get(
|
||||
&mut self,
|
||||
tex_allocator: &mut dyn epi::TextureAllocator,
|
||||
gradient: &Gradient,
|
||||
) -> TextureId {
|
||||
fn get(&mut self, tex_allocator: &dyn epi::TextureAllocator, gradient: &Gradient) -> TextureId {
|
||||
*self.0.entry(gradient.clone()).or_insert_with(|| {
|
||||
let pixels = gradient.to_pixel_row();
|
||||
let width = pixels.len();
|
||||
let height = 1;
|
||||
tex_allocator.alloc_srgba_premultiplied((width, height), &pixels)
|
||||
tex_allocator.alloc(epi::Image {
|
||||
size: [width, height],
|
||||
pixels,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ impl epi::App for DemoApp {
|
|||
fn setup(
|
||||
&mut self,
|
||||
_ctx: &egui::CtxRef,
|
||||
_frame: &mut epi::Frame<'_>,
|
||||
_frame: &epi::Frame,
|
||||
_storage: Option<&dyn epi::Storage>,
|
||||
) {
|
||||
#[cfg(feature = "persistence")]
|
||||
|
@ -31,7 +31,7 @@ impl epi::App for DemoApp {
|
|||
epi::set_value(storage, epi::APP_KEY, self);
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||
self.demo_windows.ui(ctx);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ impl DemoWindows {
|
|||
|
||||
egui::SidePanel::right("egui_demo_panel")
|
||||
.min_width(150.0)
|
||||
.default_width(190.0)
|
||||
.default_width(180.0)
|
||||
.show(ctx, |ui| {
|
||||
egui::trace!(ui);
|
||||
ui.vertical_centered(|ui| {
|
||||
|
|
|
@ -347,8 +347,8 @@ impl Widget for &mut ItemsDemo {
|
|||
TextureId::Egui,
|
||||
Value::new(0.0, 10.0),
|
||||
[
|
||||
ui.fonts().texture().width as f32 / 100.0,
|
||||
ui.fonts().texture().height as f32 / 100.0,
|
||||
ui.fonts().font_image().width as f32 / 100.0,
|
||||
ui.fonts().font_image().height as f32 / 100.0,
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
@ -239,6 +239,10 @@ impl WidgetGallery {
|
|||
This toggle switch is just 15 lines of code.",
|
||||
);
|
||||
ui.end_row();
|
||||
|
||||
ui.add(doc_link_label("Spinner", "spinner"));
|
||||
ui.add(egui::Spinner::new());
|
||||
ui.end_row();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ impl epi::App for FractalClock {
|
|||
"🕑 Fractal Clock"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||
egui::CentralPanel::default()
|
||||
.frame(Frame::dark_canvas(&ctx.style()))
|
||||
.show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight()));
|
||||
|
|
|
@ -7,7 +7,7 @@ struct Resource {
|
|||
text: Option<String>,
|
||||
|
||||
/// If set, the response was an image.
|
||||
image: Option<Image>,
|
||||
image: Option<epi::Image>,
|
||||
|
||||
/// If set, the response was text with some supported syntax highlighting (e.g. ".rs" or ".md").
|
||||
colored_text: Option<ColoredText>,
|
||||
|
@ -17,7 +17,7 @@ impl Resource {
|
|||
fn from_response(ctx: &egui::Context, response: ehttp::Response) -> Self {
|
||||
let content_type = response.content_type().unwrap_or_default();
|
||||
let image = if content_type.starts_with("image/") {
|
||||
Image::decode(&response.bytes)
|
||||
decode_image(&response.bytes)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -67,7 +67,7 @@ impl epi::App for HttpApp {
|
|||
"⬇ HTTP"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
if let Some(receiver) = &mut self.in_progress {
|
||||
// Are we there yet?
|
||||
if let Ok(result) = receiver.try_recv() {
|
||||
|
@ -95,13 +95,13 @@ impl epi::App for HttpApp {
|
|||
|
||||
if trigger_fetch {
|
||||
let request = ehttp::Request::get(&self.url);
|
||||
let repaint_signal = frame.repaint_signal();
|
||||
let frame = frame.clone();
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
self.in_progress = Some(receiver);
|
||||
|
||||
ehttp::fetch(request, move |response| {
|
||||
sender.send(response).ok();
|
||||
repaint_signal.request_repaint();
|
||||
frame.request_repaint();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ impl epi::App for HttpApp {
|
|||
}
|
||||
}
|
||||
|
||||
fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> bool {
|
||||
fn ui_url(ui: &mut egui::Ui, frame: &epi::Frame, url: &mut String) -> bool {
|
||||
let mut trigger_fetch = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
|
@ -160,12 +160,7 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> bo
|
|||
trigger_fetch
|
||||
}
|
||||
|
||||
fn ui_resource(
|
||||
ui: &mut egui::Ui,
|
||||
frame: &mut epi::Frame<'_>,
|
||||
tex_mngr: &mut TexMngr,
|
||||
resource: &Resource,
|
||||
) {
|
||||
fn ui_resource(ui: &mut egui::Ui, frame: &epi::Frame, tex_mngr: &mut TexMngr, resource: &Resource) {
|
||||
let Resource {
|
||||
response,
|
||||
text,
|
||||
|
@ -218,7 +213,7 @@ fn ui_resource(
|
|||
|
||||
if let Some(image) = image {
|
||||
if let Some(texture_id) = tex_mngr.texture(frame, &response.url, image) {
|
||||
let mut size = egui::Vec2::new(image.size.0 as f32, image.size.1 as f32);
|
||||
let mut size = egui::Vec2::new(image.size[0] as f32, image.size[1] as f32);
|
||||
size *= (ui.available_width() / size.x).min(1.0);
|
||||
ui.image(texture_id, size);
|
||||
}
|
||||
|
@ -304,44 +299,27 @@ struct TexMngr {
|
|||
impl TexMngr {
|
||||
fn texture(
|
||||
&mut self,
|
||||
frame: &mut epi::Frame<'_>,
|
||||
frame: &epi::Frame,
|
||||
url: &str,
|
||||
image: &Image,
|
||||
image: &epi::Image,
|
||||
) -> Option<egui::TextureId> {
|
||||
if self.loaded_url != url {
|
||||
if let Some(texture_id) = self.texture_id.take() {
|
||||
frame.tex_allocator().free(texture_id);
|
||||
frame.free_texture(texture_id);
|
||||
}
|
||||
|
||||
self.texture_id = Some(
|
||||
frame
|
||||
.tex_allocator()
|
||||
.alloc_srgba_premultiplied(image.size, &image.pixels),
|
||||
);
|
||||
self.texture_id = Some(frame.alloc_texture(image.clone()));
|
||||
self.loaded_url = url.to_owned();
|
||||
}
|
||||
self.texture_id
|
||||
}
|
||||
}
|
||||
|
||||
struct Image {
|
||||
size: (usize, usize),
|
||||
pixels: Vec<egui::Color32>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
fn decode(bytes: &[u8]) -> Option<Image> {
|
||||
fn decode_image(bytes: &[u8]) -> Option<epi::Image> {
|
||||
use image::GenericImageView;
|
||||
let image = image::load_from_memory(bytes).ok()?;
|
||||
let image_buffer = image.to_rgba8();
|
||||
let size = (image.width() as usize, image.height() as usize);
|
||||
let size = [image.width() as usize, image.height() as usize];
|
||||
let pixels = image_buffer.into_vec();
|
||||
assert_eq!(size.0 * size.1 * 4, pixels.len());
|
||||
let pixels = pixels
|
||||
.chunks(4)
|
||||
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||
.collect();
|
||||
|
||||
Some(Image { size, pixels })
|
||||
}
|
||||
Some(epi::Image::from_rgba_unmultiplied(size, &pixels))
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ impl Default for BackendPanel {
|
|||
}
|
||||
|
||||
impl BackendPanel {
|
||||
pub fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
pub fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
self.frame_history
|
||||
.on_new_frame(ctx.input().time, frame.info().cpu_usage);
|
||||
|
||||
|
@ -92,7 +92,7 @@ impl BackendPanel {
|
|||
self.egui_windows.windows(ctx);
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) {
|
||||
egui::trace!(ui);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading("💻 Backend");
|
||||
|
@ -147,13 +147,12 @@ impl BackendPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
|
||||
fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) {
|
||||
if frame.is_web() {
|
||||
ui.label("egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
|
||||
ui.label(
|
||||
"Everything you see is rendered as textured triangles. There is no DOM and no HTML elements. \
|
||||
This is the web page, reinvented with game tech.");
|
||||
ui.label("This is also work in progress, and not ready for production… yet :)");
|
||||
ui.hyperlink("https://github.com/emilk/egui");
|
||||
|
||||
ui.separator();
|
||||
|
@ -170,13 +169,13 @@ impl BackendPanel {
|
|||
}
|
||||
}
|
||||
|
||||
show_integration_name(ui, frame.info());
|
||||
show_integration_name(ui, &frame.info());
|
||||
|
||||
// For instance: `egui_web` sets `pixels_per_point` every frame to force
|
||||
// egui to use the same scale as the web zoom factor.
|
||||
let integration_controls_pixels_per_point = ui.input().raw.pixels_per_point.is_some();
|
||||
if !integration_controls_pixels_per_point {
|
||||
if let Some(new_pixels_per_point) = self.pixels_per_point_ui(ui, frame.info()) {
|
||||
if let Some(new_pixels_per_point) = self.pixels_per_point_ui(ui, &frame.info()) {
|
||||
ui.ctx().set_pixels_per_point(new_pixels_per_point);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ impl epi::App for EasyMarkEditor {
|
|||
"🖹 EasyMark editor"
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
|
||||
egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {
|
||||
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
||||
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {
|
||||
|
|
|
@ -77,9 +77,9 @@
|
|||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
missing_crate_level_docs,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
|
|
@ -45,7 +45,7 @@ impl epi::App for WrapApp {
|
|||
fn setup(
|
||||
&mut self,
|
||||
_ctx: &egui::CtxRef,
|
||||
_frame: &mut epi::Frame<'_>,
|
||||
_frame: &epi::Frame,
|
||||
_storage: Option<&dyn epi::Storage>,
|
||||
) {
|
||||
#[cfg(feature = "persistence")]
|
||||
|
@ -72,7 +72,7 @@ impl epi::App for WrapApp {
|
|||
cfg!(not(debug_assertions))
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut epi::Frame<'_>) {
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
|
||||
if let Some(web_info) = frame.info().web_info.as_ref() {
|
||||
if let Some(anchor) = web_info.web_location_hash.strip_prefix('#') {
|
||||
self.selected_anchor = anchor.to_owned();
|
||||
|
@ -126,7 +126,7 @@ impl epi::App for WrapApp {
|
|||
}
|
||||
|
||||
impl WrapApp {
|
||||
fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
|
||||
fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) {
|
||||
// A menu-bar is a horizontal layout with some special styles applied.
|
||||
// egui::menu::bar(ui, |ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "egui_dynamic_grid"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
edition = "2018"
|
||||
description = "Dynamic grid and table for egui"
|
||||
authors = [
|
||||
|
@ -16,4 +16,4 @@ keywords = ["glium", "egui", "gui", "gamedev"]
|
|||
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.15.0", path = "../egui", default-features = false }
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false }
|
||||
|
|
|
@ -3,8 +3,13 @@ All notable changes to the `egui_glium` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Simplify `EguiGlium` interface ([#871](https://github.com/emilk/egui/pull/871)).
|
||||
* Remove `EguiGlium::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
* Simplified `EguiGlium` interface ([#871](https://github.com/emilk/egui/pull/871)).
|
||||
* Removed `EguiGlium::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)).
|
||||
* Updated `glium` to 0.31 ([#930](https://github.com/emilk/egui/pull/930)).
|
||||
* Changed the `Painter` interface slightly ([#999](https://github.com/emilk/egui/pull/999)).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
[package]
|
||||
name = "egui_glium"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Bindings for using egui natively using the glium library"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/egui_glium"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
@ -22,11 +23,11 @@ include = [
|
|||
all-features = true
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.15.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
||||
egui-winit = { version = "0.15.0", path = "../egui-winit", default-features = false, features = ["epi"] }
|
||||
epi = { version = "0.15.0", path = "../epi", optional = true }
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
||||
egui-winit = { version = "0.16.0", path = "../egui-winit", default-features = false, features = ["epi"] }
|
||||
epi = { version = "0.16.0", path = "../epi", optional = true }
|
||||
|
||||
glium = "0.30"
|
||||
glium = "0.31"
|
||||
|
||||
[dev-dependencies]
|
||||
image = { version = "0.23", default-features = false, features = ["png"] }
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
//! Example how to use [epi::NativeTexture] with glium.
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use epi::NativeTexture;
|
||||
use glium::glutin;
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
//! Example how to use pure `egui_glium` without [`epi`].
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use glium::glutin;
|
||||
|
||||
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
|
||||
|
|
|
@ -1,22 +1,6 @@
|
|||
use crate::*;
|
||||
use egui::Color32;
|
||||
use glium::glutin;
|
||||
|
||||
impl epi::TextureAllocator for Painter {
|
||||
fn alloc_srgba_premultiplied(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[Color32],
|
||||
) -> egui::TextureId {
|
||||
let id = self.alloc_user_texture();
|
||||
self.set_user_texture(id, size, srgba_pixels);
|
||||
id
|
||||
}
|
||||
|
||||
fn free(&mut self, id: egui::TextureId) {
|
||||
self.free_user_texture(id);
|
||||
}
|
||||
}
|
||||
use crate::*;
|
||||
|
||||
struct RequestRepaintEvent;
|
||||
|
||||
|
@ -24,7 +8,7 @@ struct GliumRepaintSignal(
|
|||
std::sync::Mutex<glutin::event_loop::EventLoopProxy<RequestRepaintEvent>>,
|
||||
);
|
||||
|
||||
impl epi::RepaintSignal for GliumRepaintSignal {
|
||||
impl epi::backend::RepaintSignal for GliumRepaintSignal {
|
||||
fn request_repaint(&self) {
|
||||
self.0.lock().unwrap().send_event(RequestRepaintEvent).ok();
|
||||
}
|
||||
|
@ -64,7 +48,6 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
let mut integration = egui_winit::epi::EpiIntegration::new(
|
||||
"egui_glium",
|
||||
display.gl_window().window(),
|
||||
&mut painter,
|
||||
repaint_signal,
|
||||
persistence,
|
||||
app,
|
||||
|
@ -83,10 +66,15 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
|
||||
let (needs_repaint, shapes) =
|
||||
integration.update(display.gl_window().window(), &mut painter);
|
||||
let (needs_repaint, mut tex_allocation_data, shapes) =
|
||||
integration.update(display.gl_window().window());
|
||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
||||
|
||||
for (id, image) in tex_allocation_data.creations {
|
||||
painter.set_texture(&display, id, &image);
|
||||
}
|
||||
|
||||
// paint:
|
||||
{
|
||||
use glium::Surface as _;
|
||||
let mut target = display.draw();
|
||||
|
@ -98,12 +86,16 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
&mut target,
|
||||
integration.egui_ctx.pixels_per_point(),
|
||||
clipped_meshes,
|
||||
&integration.egui_ctx.texture(),
|
||||
&integration.egui_ctx.font_image(),
|
||||
);
|
||||
|
||||
target.finish().unwrap();
|
||||
}
|
||||
|
||||
for id in tex_allocation_data.destructions.drain(..) {
|
||||
painter.free_texture(id);
|
||||
}
|
||||
|
||||
{
|
||||
*control_flow = if integration.should_quit() {
|
||||
glutin::event_loop::ControlFlow::Exit
|
||||
|
|
|
@ -80,9 +80,9 @@
|
|||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
missing_crate_level_docs,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
@ -153,7 +153,7 @@ impl EguiGlium {
|
|||
target,
|
||||
self.egui_ctx.pixels_per_point(),
|
||||
clipped_meshes,
|
||||
&self.egui_ctx.texture(),
|
||||
&self.egui_ctx.font_image(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use {
|
|||
uniform,
|
||||
uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
|
||||
},
|
||||
std::rc::Rc,
|
||||
std::{collections::HashMap, rc::Rc},
|
||||
};
|
||||
|
||||
pub struct Painter {
|
||||
|
@ -22,19 +22,11 @@ pub struct Painter {
|
|||
egui_texture: Option<SrgbTexture2d>,
|
||||
egui_texture_version: Option<u64>,
|
||||
|
||||
/// `None` means unallocated (freed) slot.
|
||||
user_textures: Vec<Option<UserTexture>>,
|
||||
}
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, Rc<SrgbTexture2d>>,
|
||||
|
||||
#[derive(Default)]
|
||||
struct UserTexture {
|
||||
/// Pending upload (will be emptied later).
|
||||
/// This is the format glium likes.
|
||||
pixels: Vec<Vec<(u8, u8, u8, u8)>>,
|
||||
|
||||
/// Lazily uploaded from [`Self::pixels`],
|
||||
/// or owned by the user via `register_native_texture`.
|
||||
gl_texture: Option<Rc<SrgbTexture2d>>,
|
||||
#[cfg(feature = "epi")]
|
||||
next_native_tex_id: u64, // TODO: 128-bit texture space?
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
|
@ -65,21 +57,23 @@ impl Painter {
|
|||
egui_texture: None,
|
||||
egui_texture_version: None,
|
||||
user_textures: Default::default(),
|
||||
#[cfg(feature = "epi")]
|
||||
next_native_tex_id: 1 << 32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upload_egui_texture(
|
||||
&mut self,
|
||||
facade: &dyn glium::backend::Facade,
|
||||
texture: &egui::Texture,
|
||||
font_image: &egui::FontImage,
|
||||
) {
|
||||
if self.egui_texture_version == Some(texture.version) {
|
||||
if self.egui_texture_version == Some(font_image.version) {
|
||||
return; // No change
|
||||
}
|
||||
|
||||
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = texture
|
||||
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = font_image
|
||||
.pixels
|
||||
.chunks(texture.width as usize)
|
||||
.chunks(font_image.width as usize)
|
||||
.map(|row| {
|
||||
row.iter()
|
||||
.map(|&a| Color32::from_white_alpha(a).to_tuple())
|
||||
|
@ -91,7 +85,7 @@ impl Painter {
|
|||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||
self.egui_texture =
|
||||
Some(SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap());
|
||||
self.egui_texture_version = Some(texture.version);
|
||||
self.egui_texture_version = Some(font_image.version);
|
||||
}
|
||||
|
||||
/// Main entry-point for painting a frame.
|
||||
|
@ -103,10 +97,9 @@ impl Painter {
|
|||
target: &mut T,
|
||||
pixels_per_point: f32,
|
||||
cipped_meshes: Vec<egui::ClippedMesh>,
|
||||
egui_texture: &egui::Texture,
|
||||
font_image: &egui::FontImage,
|
||||
) {
|
||||
self.upload_egui_texture(display, egui_texture);
|
||||
self.upload_pending_user_textures(display);
|
||||
self.upload_egui_texture(display, font_image);
|
||||
|
||||
for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes {
|
||||
self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh);
|
||||
|
@ -114,7 +107,7 @@ impl Painter {
|
|||
}
|
||||
|
||||
#[inline(never)] // Easier profiling
|
||||
pub fn paint_mesh<T: glium::Surface>(
|
||||
fn paint_mesh<T: glium::Surface>(
|
||||
&mut self,
|
||||
target: &mut T,
|
||||
display: &glium::Display,
|
||||
|
@ -229,82 +222,41 @@ impl Painter {
|
|||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// user textures: this is an experimental feature.
|
||||
// No need to implement this in your egui integration!
|
||||
|
||||
pub fn alloc_user_texture(&mut self) -> egui::TextureId {
|
||||
for (i, tex) in self.user_textures.iter_mut().enumerate() {
|
||||
if tex.is_none() {
|
||||
*tex = Some(Default::default());
|
||||
return egui::TextureId::User(i as u64);
|
||||
}
|
||||
}
|
||||
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
||||
self.user_textures.push(Some(Default::default()));
|
||||
id
|
||||
}
|
||||
|
||||
pub fn set_user_texture(
|
||||
#[cfg(feature = "epi")]
|
||||
pub fn set_texture(
|
||||
&mut self,
|
||||
id: egui::TextureId,
|
||||
size: (usize, usize),
|
||||
pixels: &[Color32],
|
||||
facade: &dyn glium::backend::Facade,
|
||||
tex_id: u64,
|
||||
image: &epi::Image,
|
||||
) {
|
||||
assert_eq!(
|
||||
size.0 * size.1,
|
||||
pixels.len(),
|
||||
image.size[0] * image.size[1],
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = pixels
|
||||
.chunks(size.0 as usize)
|
||||
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = image
|
||||
.pixels
|
||||
.chunks(image.size[0] as usize)
|
||||
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
|
||||
.collect();
|
||||
|
||||
*user_texture = UserTexture {
|
||||
pixels,
|
||||
gl_texture: None,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_user_texture(&mut self, id: egui::TextureId) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
let index = id as usize;
|
||||
if index < self.user_textures.len() {
|
||||
self.user_textures[index] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
|
||||
match texture_id {
|
||||
egui::TextureId::Egui => self.egui_texture.as_ref(),
|
||||
egui::TextureId::User(id) => self
|
||||
.user_textures
|
||||
.get(id as usize)?
|
||||
.as_ref()?
|
||||
.gl_texture
|
||||
.as_ref()
|
||||
.map(|rc| rc.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upload_pending_user_textures(&mut self, facade: &dyn glium::backend::Facade) {
|
||||
for user_texture in self.user_textures.iter_mut().flatten() {
|
||||
if user_texture.gl_texture.is_none() {
|
||||
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||
let format = texture::SrgbFormat::U8U8U8U8;
|
||||
let mipmaps = texture::MipmapsOption::NoMipmap;
|
||||
user_texture.gl_texture = Some(
|
||||
SrgbTexture2d::with_format(facade, pixels, format, mipmaps)
|
||||
.unwrap()
|
||||
.into(),
|
||||
);
|
||||
let gl_texture = SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap();
|
||||
|
||||
self.user_textures.insert(tex_id, gl_texture.into());
|
||||
}
|
||||
|
||||
pub fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
|
||||
match texture_id {
|
||||
egui::TextureId::Egui => self.egui_texture.as_ref(),
|
||||
egui::TextureId::User(id) => self.user_textures.get(&id).map(|rc| rc.as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -314,26 +266,15 @@ impl epi::NativeTexture for Painter {
|
|||
type Texture = Rc<SrgbTexture2d>;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
let id = self.alloc_user_texture();
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
*user_texture = UserTexture {
|
||||
pixels: vec![],
|
||||
gl_texture: Some(native),
|
||||
}
|
||||
}
|
||||
}
|
||||
id
|
||||
let id = self.next_native_tex_id;
|
||||
self.next_native_tex_id += 1;
|
||||
self.user_textures.insert(id, native);
|
||||
egui::TextureId::User(id as u64)
|
||||
}
|
||||
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
*user_texture = UserTexture {
|
||||
pixels: vec![],
|
||||
gl_texture: Some(replacing),
|
||||
};
|
||||
}
|
||||
self.user_textures.insert(id, replacing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,14 @@ All notable changes to the `egui_glow` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Make winit/glutin an optional dependency ([#868](https://github.com/emilk/egui/pull/868)).
|
||||
* Simplify `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)).
|
||||
* Remove `EguiGlow::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
* Made winit/glutin an optional dependency ([#868](https://github.com/emilk/egui/pull/868)).
|
||||
* Simplified `EguiGlow` interface ([#871](https://github.com/emilk/egui/pull/871)).
|
||||
* Removed `EguiGlow::is_quit_event` ([#881](https://github.com/emilk/egui/pull/881)).
|
||||
* Updated `glutin` to 0.28 ([#930](https://github.com/emilk/egui/pull/930)).
|
||||
* Changed the `Painter` interface slightly ([#999](https://github.com/emilk/egui/pull/999)).
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
[package]
|
||||
name = "egui_glow"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Bindings for using egui natively using the glow library"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/egui_glow"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
@ -22,15 +23,15 @@ include = [
|
|||
all-features = true
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.15.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
||||
|
||||
epi = { version = "0.15.0", path = "../epi", optional = true }
|
||||
epi = { version = "0.16.0", path = "../epi", optional = true }
|
||||
glow = "0.11"
|
||||
memoffset = "0.6"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
egui-winit = { version = "0.15.0", path = "../egui-winit", default-features = false, features = ["epi"], optional = true }
|
||||
glutin = { version = "0.27.0", optional = true }
|
||||
egui-winit = { version = "0.16.0", path = "../egui-winit", default-features = false, features = ["epi"], optional = true }
|
||||
glutin = { version = "0.28.0", optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
web-sys = { version = "0.3", features=["console"] }
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Example how to use pure `egui_glow` without [`epi`].
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
fn create_display(
|
||||
event_loop: &glutin::event_loop::EventLoop<()>,
|
||||
) -> (
|
||||
|
|
|
@ -4,7 +4,7 @@ struct RequestRepaintEvent;
|
|||
|
||||
struct GlowRepaintSignal(std::sync::Mutex<glutin::event_loop::EventLoopProxy<RequestRepaintEvent>>);
|
||||
|
||||
impl epi::RepaintSignal for GlowRepaintSignal {
|
||||
impl epi::backend::RepaintSignal for GlowRepaintSignal {
|
||||
fn request_repaint(&self) {
|
||||
self.0.lock().unwrap().send_event(RequestRepaintEvent).ok();
|
||||
}
|
||||
|
@ -64,7 +64,6 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
let mut integration = egui_winit::epi::EpiIntegration::new(
|
||||
"egui_glow",
|
||||
gl_window.window(),
|
||||
&mut painter,
|
||||
repaint_signal,
|
||||
persistence,
|
||||
app,
|
||||
|
@ -83,9 +82,15 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
}
|
||||
|
||||
let (needs_repaint, shapes) = integration.update(gl_window.window(), &mut painter);
|
||||
let (needs_repaint, mut tex_allocation_data, shapes) =
|
||||
integration.update(gl_window.window());
|
||||
let clipped_meshes = integration.egui_ctx.tessellate(shapes);
|
||||
|
||||
for (id, image) in tex_allocation_data.creations {
|
||||
painter.set_texture(&gl, id, &image);
|
||||
}
|
||||
|
||||
// paint:
|
||||
{
|
||||
let color = integration.app.clear_color();
|
||||
unsafe {
|
||||
|
@ -94,10 +99,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
gl.clear_color(color[0], color[1], color[2], color[3]);
|
||||
gl.clear(glow::COLOR_BUFFER_BIT);
|
||||
}
|
||||
painter.upload_egui_texture(&gl, &integration.egui_ctx.texture());
|
||||
painter.upload_egui_texture(&gl, &integration.egui_ctx.font_image());
|
||||
painter.paint_meshes(
|
||||
gl_window.window().inner_size().into(),
|
||||
&gl,
|
||||
gl_window.window().inner_size().into(),
|
||||
integration.egui_ctx.pixels_per_point(),
|
||||
clipped_meshes,
|
||||
);
|
||||
|
@ -105,6 +110,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
|
|||
gl_window.swap_buffers().unwrap();
|
||||
}
|
||||
|
||||
for id in tex_allocation_data.destructions.drain(..) {
|
||||
painter.free_texture(id);
|
||||
}
|
||||
|
||||
{
|
||||
*control_flow = if integration.should_quit() {
|
||||
glutin::event_loop::ControlFlow::Exit
|
||||
|
|
|
@ -80,9 +80,9 @@
|
|||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
missing_crate_level_docs,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
@ -164,10 +164,10 @@ impl EguiGlow {
|
|||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||
let dimensions: [u32; 2] = gl_window.window().inner_size().into();
|
||||
self.painter
|
||||
.upload_egui_texture(gl, &self.egui_ctx.texture());
|
||||
.upload_egui_texture(gl, &self.egui_ctx.font_image());
|
||||
self.painter.paint_meshes(
|
||||
dimensions,
|
||||
gl,
|
||||
dimensions,
|
||||
self.egui_ctx.pixels_per_point(),
|
||||
clipped_meshes,
|
||||
);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#![allow(unsafe_code)]
|
||||
use glow::HasContext;
|
||||
use std::option::Option::Some;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
pub(crate) fn srgbtexture2d(
|
||||
gl: &glow::Context,
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
#![allow(unsafe_code)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use egui::{
|
||||
emath::Rect,
|
||||
epaint::{Color32, Mesh, Vertex},
|
||||
};
|
||||
pub use glow::Context;
|
||||
|
||||
use memoffset::offset_of;
|
||||
|
||||
use glow::HasContext;
|
||||
use memoffset::offset_of;
|
||||
|
||||
use crate::misc_util::{
|
||||
as_u8_slice, compile_shader, glow_debug_print, link_program, srgbtexture2d,
|
||||
|
@ -17,6 +16,8 @@ use crate::post_process::PostProcess;
|
|||
use crate::shader_version::ShaderVersion;
|
||||
use crate::vao_emulate;
|
||||
|
||||
pub use glow::Context;
|
||||
|
||||
const VERT_SRC: &str = include_str!("shader/vertex.glsl");
|
||||
const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
|
||||
|
||||
|
@ -34,29 +35,23 @@ pub struct Painter {
|
|||
is_embedded: bool,
|
||||
vertex_array: crate::misc_util::VAO,
|
||||
srgb_support: bool,
|
||||
/// `None` means unallocated (freed) slot.
|
||||
pub(crate) user_textures: Vec<Option<UserTexture>>,
|
||||
post_process: Option<PostProcess>,
|
||||
vertex_buffer: glow::Buffer,
|
||||
element_array_buffer: glow::Buffer,
|
||||
|
||||
// Stores outdated OpenGL textures that are yet to be deleted
|
||||
old_textures: Vec<glow::Texture>,
|
||||
// Only used in debug builds, to make sure we are destroyed correctly.
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, glow::Texture>,
|
||||
|
||||
#[cfg(feature = "epi")]
|
||||
next_native_tex_id: u64, // TODO: 128-bit texture space?
|
||||
|
||||
/// Stores outdated OpenGL textures that are yet to be deleted
|
||||
textures_to_destroy: Vec<glow::Texture>,
|
||||
|
||||
/// Used to make sure we are destroyed correctly.
|
||||
destroyed: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct UserTexture {
|
||||
/// Pending upload (will be emptied later).
|
||||
/// This is the format glow likes.
|
||||
pub(crate) data: Vec<u8>,
|
||||
pub(crate) size: (usize, usize),
|
||||
|
||||
/// Lazily uploaded
|
||||
pub(crate) gl_texture: Option<glow::Texture>,
|
||||
}
|
||||
|
||||
impl Painter {
|
||||
/// Create painter.
|
||||
///
|
||||
|
@ -195,20 +190,22 @@ impl Painter {
|
|||
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
|
||||
vertex_array,
|
||||
srgb_support,
|
||||
user_textures: Default::default(),
|
||||
post_process,
|
||||
vertex_buffer,
|
||||
element_array_buffer,
|
||||
old_textures: Vec::new(),
|
||||
user_textures: Default::default(),
|
||||
#[cfg(feature = "epi")]
|
||||
next_native_tex_id: 1 << 32,
|
||||
textures_to_destroy: Vec::new(),
|
||||
destroyed: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upload_egui_texture(&mut self, gl: &glow::Context, texture: &egui::Texture) {
|
||||
pub fn upload_egui_texture(&mut self, gl: &glow::Context, font_image: &egui::FontImage) {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
if self.egui_texture_version == Some(texture.version) {
|
||||
if self.egui_texture_version == Some(font_image.version) {
|
||||
return; // No change
|
||||
}
|
||||
let gamma = if self.is_embedded && self.post_process.is_none() {
|
||||
|
@ -216,7 +213,7 @@ impl Painter {
|
|||
} else {
|
||||
1.0
|
||||
};
|
||||
let pixels: Vec<u8> = texture
|
||||
let pixels: Vec<u8> = font_image
|
||||
.srgba_pixels(gamma)
|
||||
.flat_map(|a| Vec::from(a.to_array()))
|
||||
.collect();
|
||||
|
@ -228,15 +225,15 @@ impl Painter {
|
|||
self.is_webgl_1,
|
||||
self.srgb_support,
|
||||
&pixels,
|
||||
texture.width,
|
||||
texture.height,
|
||||
font_image.width,
|
||||
font_image.height,
|
||||
)),
|
||||
) {
|
||||
unsafe {
|
||||
gl.delete_texture(old_tex);
|
||||
}
|
||||
}
|
||||
self.egui_texture_version = Some(texture.version);
|
||||
self.egui_texture_version = Some(font_image.version);
|
||||
}
|
||||
|
||||
unsafe fn prepare_painting(
|
||||
|
@ -298,15 +295,13 @@ impl Painter {
|
|||
/// of the effects your program might have on this code. Look at the source if in doubt.
|
||||
pub fn paint_meshes(
|
||||
&mut self,
|
||||
inner_size: [u32; 2],
|
||||
gl: &glow::Context,
|
||||
inner_size: [u32; 2],
|
||||
pixels_per_point: f32,
|
||||
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||
) {
|
||||
//chimera of egui_glow and egui_web
|
||||
self.assert_not_destroyed();
|
||||
|
||||
self.upload_pending_user_textures(gl);
|
||||
if let Some(ref mut post_process) = self.post_process {
|
||||
unsafe {
|
||||
post_process.begin(gl, inner_size[0] as i32, inner_size[1] as i32);
|
||||
|
@ -393,128 +388,48 @@ impl Painter {
|
|||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// user textures: this is an experimental feature.
|
||||
// No need to implement this in your egui integration!
|
||||
|
||||
pub fn alloc_user_texture(&mut self) -> egui::TextureId {
|
||||
#[cfg(feature = "epi")]
|
||||
pub fn set_texture(&mut self, gl: &glow::Context, tex_id: u64, image: &epi::Image) {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
for (i, tex) in self.user_textures.iter_mut().enumerate() {
|
||||
if tex.is_none() {
|
||||
*tex = Some(Default::default());
|
||||
return egui::TextureId::User(i as u64);
|
||||
}
|
||||
}
|
||||
let id = egui::TextureId::User(self.user_textures.len() as u64);
|
||||
self.user_textures.push(Some(Default::default()));
|
||||
id
|
||||
}
|
||||
|
||||
/// register glow texture as egui texture
|
||||
/// Usable for render to image rectangle
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn register_glow_texture(&mut self, texture: glow::Texture) -> egui::TextureId {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
let id = self.alloc_user_texture();
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
if let UserTexture {
|
||||
gl_texture: Some(old_tex),
|
||||
..
|
||||
} = std::mem::replace(
|
||||
user_texture,
|
||||
UserTexture {
|
||||
data: vec![],
|
||||
size: (0, 0),
|
||||
gl_texture: Some(texture),
|
||||
},
|
||||
) {
|
||||
self.old_textures.push(old_tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
pub fn set_user_texture(
|
||||
&mut self,
|
||||
id: egui::TextureId,
|
||||
size: (usize, usize),
|
||||
pixels: &[Color32],
|
||||
) {
|
||||
self.assert_not_destroyed();
|
||||
assert_eq!(
|
||||
size.0 * size.1,
|
||||
pixels.len(),
|
||||
"Mismatch between size and texel count"
|
||||
image.size[0] * image.size[1],
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
let data: Vec<u8> = pixels
|
||||
// TODO: optimize
|
||||
let pixels: Vec<u8> = image
|
||||
.pixels
|
||||
.iter()
|
||||
.flat_map(|srgba| Vec::from(srgba.to_array()))
|
||||
.collect();
|
||||
|
||||
if let UserTexture {
|
||||
gl_texture: Some(old_tex),
|
||||
..
|
||||
} = std::mem::replace(
|
||||
user_texture,
|
||||
UserTexture {
|
||||
data,
|
||||
size,
|
||||
gl_texture: None,
|
||||
},
|
||||
) {
|
||||
self.old_textures.push(old_tex);
|
||||
}
|
||||
}
|
||||
let gl_texture = srgbtexture2d(
|
||||
gl,
|
||||
self.is_webgl_1,
|
||||
self.srgb_support,
|
||||
&pixels,
|
||||
image.size[0],
|
||||
image.size[1],
|
||||
);
|
||||
|
||||
if let Some(old_tex) = self.user_textures.insert(tex_id, gl_texture) {
|
||||
self.textures_to_destroy.push(old_tex);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_user_texture(&mut self, id: egui::TextureId) {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
if let egui::TextureId::User(id) = id {
|
||||
let index = id as usize;
|
||||
if index < self.user_textures.len() {
|
||||
self.user_textures[index] = None;
|
||||
}
|
||||
}
|
||||
pub fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
match texture_id {
|
||||
egui::TextureId::Egui => self.egui_texture,
|
||||
egui::TextureId::User(id) => self.user_textures.get(id as usize)?.as_ref()?.gl_texture,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn upload_pending_user_textures(&mut self, gl: &glow::Context) {
|
||||
self.assert_not_destroyed();
|
||||
|
||||
for user_texture in self.user_textures.iter_mut().flatten() {
|
||||
if user_texture.gl_texture.is_none() {
|
||||
let data = std::mem::take(&mut user_texture.data);
|
||||
user_texture.gl_texture = Some(srgbtexture2d(
|
||||
gl,
|
||||
self.is_webgl_1,
|
||||
self.srgb_support,
|
||||
&data,
|
||||
user_texture.size.0,
|
||||
user_texture.size.1,
|
||||
));
|
||||
user_texture.size = (0, 0);
|
||||
}
|
||||
}
|
||||
for t in self.old_textures.drain(..) {
|
||||
unsafe {
|
||||
gl.delete_texture(t);
|
||||
}
|
||||
egui::TextureId::User(id) => self.user_textures.get(&id).copied(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -523,14 +438,12 @@ impl Painter {
|
|||
if let Some(tex) = self.egui_texture {
|
||||
gl.delete_texture(tex);
|
||||
}
|
||||
for tex in self.user_textures.iter().flatten() {
|
||||
if let Some(t) = tex.gl_texture {
|
||||
gl.delete_texture(t);
|
||||
}
|
||||
for tex in self.user_textures.values() {
|
||||
gl.delete_texture(*tex);
|
||||
}
|
||||
gl.delete_buffer(self.vertex_buffer);
|
||||
gl.delete_buffer(self.element_array_buffer);
|
||||
for t in &self.old_textures {
|
||||
for t in &self.textures_to_destroy {
|
||||
gl.delete_texture(*t);
|
||||
}
|
||||
}
|
||||
|
@ -539,7 +452,7 @@ impl Painter {
|
|||
/// that should be deleted.
|
||||
|
||||
pub fn destroy(&mut self, gl: &glow::Context) {
|
||||
debug_assert!(!self.destroyed, "Only destroy once!");
|
||||
if !self.destroyed {
|
||||
unsafe {
|
||||
self.destroy_gl(gl);
|
||||
if let Some(ref post_process) = self.post_process {
|
||||
|
@ -548,9 +461,10 @@ impl Painter {
|
|||
}
|
||||
self.destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_not_destroyed(&self) {
|
||||
debug_assert!(!self.destroyed, "the egui glow has already been destroyed!");
|
||||
assert!(!self.destroyed, "the egui glow has already been destroyed!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -573,27 +487,11 @@ pub fn clear(gl: &glow::Context, dimension: [u32; 2], clear_color: egui::Rgba) {
|
|||
|
||||
impl Drop for Painter {
|
||||
fn drop(&mut self) {
|
||||
debug_assert!(
|
||||
self.destroyed,
|
||||
"Make sure to call destroy() before dropping to avoid leaking OpenGL objects!"
|
||||
if !self.destroyed {
|
||||
eprintln!(
|
||||
"You forgot to call destroy() on the egui glow painter. Resources will leak!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "epi")]
|
||||
impl epi::TextureAllocator for Painter {
|
||||
fn alloc_srgba_premultiplied(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[Color32],
|
||||
) -> egui::TextureId {
|
||||
let id = self.alloc_user_texture();
|
||||
self.set_user_texture(id, size, srgba_pixels);
|
||||
id
|
||||
}
|
||||
|
||||
fn free(&mut self, id: egui::TextureId) {
|
||||
self.free_user_texture(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -602,17 +500,20 @@ impl epi::NativeTexture for Painter {
|
|||
type Texture = glow::Texture;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
self.register_glow_texture(native)
|
||||
self.assert_not_destroyed();
|
||||
|
||||
let id = self.next_native_tex_id;
|
||||
self.next_native_tex_id += 1;
|
||||
|
||||
self.user_textures.insert(id, native);
|
||||
|
||||
egui::TextureId::User(id as u64)
|
||||
}
|
||||
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
*user_texture = UserTexture {
|
||||
data: vec![],
|
||||
gl_texture: Some(replacing),
|
||||
size: (0, 0),
|
||||
};
|
||||
if let Some(old_tex) = self.user_textures.insert(id, replacing) {
|
||||
self.textures_to_destroy.push(old_tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -221,6 +221,7 @@ impl PostProcess {
|
|||
gl.bind_texture(glow::TEXTURE_2D, None);
|
||||
gl.use_program(None);
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn destroy(&self, gl: &glow::Context) {
|
||||
gl.delete_buffer(self.pos_buffer);
|
||||
gl.delete_buffer(self.index_buffer);
|
||||
|
|
|
@ -4,8 +4,14 @@ All notable changes to the `egui_web` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Fix [dark rendering in WebKitGTK](https://github.com/emilk/egui/issues/794) ([#888](https://github.com/emilk/egui/pull/888/)).
|
||||
* Add feature `glow` to switch to a [`glow`](https://github.com/grovesNL/glow) based painter ([#868](https://github.com/emilk/egui/pull/868)).
|
||||
* The default painter is now glow instead of WebGL ([#1020](https://github.com/emilk/egui/pull/1020)).
|
||||
* Made the WebGL painter opt-in ([#1020](https://github.com/emilk/egui/pull/1020)).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
* 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
|
||||
### Added
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
[package]
|
||||
name = "egui_web"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Bindings for compiling egui code to WASM for a web page"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/egui_web"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui/tree/master/egui_web"
|
||||
|
@ -25,26 +26,31 @@ all-features = true
|
|||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.15.0", path = "../egui", default-features = false, features = [
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false, features = [
|
||||
"single_threaded",
|
||||
] }
|
||||
egui_glow = { version = "0.15.0",path = "../egui_glow", default-features = false, optional = true }
|
||||
epi = { version = "0.15.0", path = "../epi" }
|
||||
egui_glow = { version = "0.16.0",path = "../egui_glow", default-features = false, optional = true }
|
||||
epi = { version = "0.16.0", path = "../epi" }
|
||||
js-sys = "0.3"
|
||||
ron = { version = "0.7", optional = true }
|
||||
serde = { version = "1", optional = true }
|
||||
tts = { version = "0.17", optional = true } # feature screen_reader
|
||||
tts = { version = "0.19", optional = true } # feature screen_reader
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
|
||||
[features]
|
||||
default = ["default_fonts"]
|
||||
default = ["default_fonts", "glow"]
|
||||
|
||||
# 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 as the renderer
|
||||
|
||||
# Use glow as the renderer.
|
||||
glow = ["egui_glow", "egui_glow/epi"]
|
||||
|
||||
# Alternative to the glow renderer.
|
||||
webgl = []
|
||||
|
||||
persistence = ["egui/persistence", "ron", "serde"]
|
||||
screen_reader = ["tts"] # experimental
|
||||
|
||||
|
|
|
@ -5,11 +5,13 @@ pub use egui::{pos2, Color32};
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
fn create_painter(canvas_id: &str) -> Result<Box<dyn Painter>, JsValue> {
|
||||
#[cfg(feature = "glow")]
|
||||
// Glow takes precedence:
|
||||
#[cfg(all(feature = "glow"))]
|
||||
return Ok(Box::new(crate::glow_wrapping::WrappedGlowPainter::new(
|
||||
canvas_id,
|
||||
)));
|
||||
#[cfg(not(feature = "glow"))]
|
||||
|
||||
#[cfg(all(feature = "webgl", not(feature = "glow")))]
|
||||
if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) {
|
||||
console_log("Using WebGL2 backend");
|
||||
Ok(Box::new(webgl2_painter))
|
||||
|
@ -18,6 +20,9 @@ fn create_painter(canvas_id: &str) -> Result<Box<dyn Painter>, JsValue> {
|
|||
let webgl1_painter = webgl1::WebGlPainter::new(canvas_id)?;
|
||||
Ok(Box::new(webgl1_painter))
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "webgl"), not(feature = "glow")))]
|
||||
compile_error!("Either the 'glow' or 'webgl' feature of egui_web must be enabled!");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -67,7 +72,7 @@ impl NeedRepaint {
|
|||
}
|
||||
}
|
||||
|
||||
impl epi::RepaintSignal for NeedRepaint {
|
||||
impl epi::backend::RepaintSignal for NeedRepaint {
|
||||
fn request_repaint(&self) {
|
||||
self.0.store(true, SeqCst);
|
||||
}
|
||||
|
@ -76,28 +81,44 @@ impl epi::RepaintSignal for NeedRepaint {
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct AppRunner {
|
||||
frame: epi::Frame,
|
||||
egui_ctx: egui::CtxRef,
|
||||
painter: Box<dyn Painter>,
|
||||
previous_frame_time: Option<f32>,
|
||||
pub(crate) input: WebInput,
|
||||
app: Box<dyn epi::App>,
|
||||
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
||||
storage: LocalStorage,
|
||||
prefer_dark_mode: Option<bool>,
|
||||
last_save_time: f64,
|
||||
screen_reader: crate::screen_reader::ScreenReader,
|
||||
pub(crate) text_cursor_pos: Option<egui::Pos2>,
|
||||
pub(crate) mutable_text_under_cursor: bool,
|
||||
pending_texture_destructions: Vec<u64>,
|
||||
}
|
||||
|
||||
impl AppRunner {
|
||||
pub fn new(canvas_id: &str, app: Box<dyn epi::App>) -> Result<Self, JsValue> {
|
||||
let egui_ctx = egui::CtxRef::default();
|
||||
|
||||
load_memory(&egui_ctx);
|
||||
let painter = create_painter(canvas_id)?;
|
||||
|
||||
let prefer_dark_mode = crate::prefer_dark_mode();
|
||||
|
||||
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
|
||||
|
||||
let frame = epi::Frame::new(epi::backend::FrameData {
|
||||
info: epi::IntegrationInfo {
|
||||
name: painter.name(),
|
||||
web_info: Some(epi::WebInfo {
|
||||
web_location_hash: location_hash().unwrap_or_default(),
|
||||
}),
|
||||
prefer_dark_mode,
|
||||
cpu_usage: None,
|
||||
native_pixels_per_point: Some(native_pixels_per_point()),
|
||||
},
|
||||
output: Default::default(),
|
||||
repaint_signal: needs_repaint.clone(),
|
||||
});
|
||||
|
||||
let egui_ctx = egui::CtxRef::default();
|
||||
load_memory(&egui_ctx);
|
||||
if prefer_dark_mode == Some(true) {
|
||||
egui_ctx.set_visuals(egui::Visuals::dark());
|
||||
} else {
|
||||
|
@ -107,32 +128,24 @@ impl AppRunner {
|
|||
let storage = LocalStorage::default();
|
||||
|
||||
let mut runner = Self {
|
||||
frame,
|
||||
egui_ctx,
|
||||
painter: create_painter(canvas_id)?,
|
||||
previous_frame_time: None,
|
||||
painter,
|
||||
input: Default::default(),
|
||||
app,
|
||||
needs_repaint: Default::default(),
|
||||
needs_repaint,
|
||||
storage,
|
||||
prefer_dark_mode,
|
||||
last_save_time: now_sec(),
|
||||
screen_reader: Default::default(),
|
||||
text_cursor_pos: None,
|
||||
mutable_text_under_cursor: false,
|
||||
pending_texture_destructions: Default::default(),
|
||||
};
|
||||
|
||||
{
|
||||
let mut app_output = epi::backend::AppOutput::default();
|
||||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: runner.integration_info(),
|
||||
tex_allocator: runner.painter.as_tex_allocator(),
|
||||
output: &mut app_output,
|
||||
repaint_signal: runner.needs_repaint.clone(),
|
||||
}
|
||||
.build();
|
||||
runner
|
||||
.app
|
||||
.setup(&runner.egui_ctx, &mut frame, Some(&runner.storage));
|
||||
.setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage));
|
||||
}
|
||||
|
||||
Ok(runner)
|
||||
|
@ -170,18 +183,6 @@ impl AppRunner {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn integration_info(&self) -> epi::IntegrationInfo {
|
||||
epi::IntegrationInfo {
|
||||
name: self.painter.name(),
|
||||
web_info: Some(epi::WebInfo {
|
||||
web_location_hash: location_hash().unwrap_or_default(),
|
||||
}),
|
||||
prefer_dark_mode: self.prefer_dark_mode,
|
||||
cpu_usage: self.previous_frame_time,
|
||||
native_pixels_per_point: Some(native_pixels_per_point()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn logic(&mut self) -> Result<(egui::Output, Vec<egui::ClippedMesh>), JsValue> {
|
||||
let frame_start = now_sec();
|
||||
|
||||
|
@ -189,42 +190,44 @@ impl AppRunner {
|
|||
let canvas_size = canvas_size_in_points(self.canvas_id());
|
||||
let raw_input = self.input.new_frame(canvas_size);
|
||||
|
||||
let mut app_output = epi::backend::AppOutput::default();
|
||||
let mut frame = epi::backend::FrameBuilder {
|
||||
info: self.integration_info(),
|
||||
tex_allocator: self.painter.as_tex_allocator(),
|
||||
output: &mut app_output,
|
||||
repaint_signal: self.needs_repaint.clone(),
|
||||
}
|
||||
.build();
|
||||
|
||||
let app = &mut self.app; // TODO: remove when we bump MSRV to 1.56
|
||||
let (egui_output, shapes) = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
app.update(egui_ctx, &mut frame);
|
||||
self.app.update(egui_ctx, &self.frame);
|
||||
});
|
||||
let clipped_meshes = self.egui_ctx.tessellate(shapes);
|
||||
|
||||
self.handle_egui_output(&egui_output);
|
||||
|
||||
{
|
||||
let app_output = self.frame.take_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
|
||||
tex_allocation_data,
|
||||
} = app_output;
|
||||
|
||||
for (id, image) in tex_allocation_data.creations {
|
||||
self.painter.set_texture(id, image);
|
||||
}
|
||||
self.pending_texture_destructions = tex_allocation_data.destructions;
|
||||
}
|
||||
|
||||
self.previous_frame_time = Some((now_sec() - frame_start) as f32);
|
||||
self.frame.lock().info.cpu_usage = Some((now_sec() - frame_start) as f32);
|
||||
Ok((egui_output, clipped_meshes))
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, clipped_meshes: Vec<egui::ClippedMesh>) -> Result<(), JsValue> {
|
||||
self.painter.upload_egui_texture(&self.egui_ctx.texture());
|
||||
self.painter
|
||||
.upload_egui_texture(&self.egui_ctx.font_image());
|
||||
self.painter.clear(self.app.clear_color());
|
||||
self.painter
|
||||
.paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point())
|
||||
.paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point())?;
|
||||
for id in self.pending_texture_destructions.drain(..) {
|
||||
self.painter.free_texture(id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_egui_output(&mut self, output: &egui::Output) {
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use crate::web_sys::WebGl2RenderingContext;
|
||||
use crate::web_sys::WebGlRenderingContext;
|
||||
use crate::{canvas_element_or_die, console_error};
|
||||
use egui::{ClippedMesh, Rgba, Texture};
|
||||
use egui::{ClippedMesh, FontImage, Rgba};
|
||||
use egui_glow::glow;
|
||||
use epi::TextureAllocator;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use web_sys::WebGl2RenderingContext;
|
||||
use web_sys::WebGlRenderingContext;
|
||||
|
||||
pub(crate) struct WrappedGlowPainter {
|
||||
pub(crate) gl_ctx: glow::Context,
|
||||
|
@ -63,12 +62,16 @@ fn requires_brightening(canvas: &web_sys::HtmlCanvasElement) -> bool {
|
|||
.dyn_into::<WebGlRenderingContext>()
|
||||
.unwrap();
|
||||
let user_agent = web_sys::window().unwrap().navigator().user_agent().unwrap();
|
||||
crate::webgl1::is_safari_and_webkit_gtk(&gl) && !user_agent.contains("Mac OS X")
|
||||
crate::is_safari_and_webkit_gtk(&gl) && !user_agent.contains("Mac OS X")
|
||||
}
|
||||
|
||||
impl crate::Painter for WrappedGlowPainter {
|
||||
fn as_tex_allocator(&mut self) -> &mut dyn TextureAllocator {
|
||||
&mut self.painter
|
||||
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
|
||||
self.painter.set_texture(&self.gl_ctx, tex_id, &image);
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: u64) {
|
||||
self.painter.free_texture(tex_id);
|
||||
}
|
||||
|
||||
fn debug_info(&self) -> String {
|
||||
|
@ -83,8 +86,8 @@ impl crate::Painter for WrappedGlowPainter {
|
|||
&self.canvas_id
|
||||
}
|
||||
|
||||
fn upload_egui_texture(&mut self, texture: &Texture) {
|
||||
self.painter.upload_egui_texture(&self.gl_ctx, texture)
|
||||
fn upload_egui_texture(&mut self, font_image: &FontImage) {
|
||||
self.painter.upload_egui_texture(&self.gl_ctx, font_image)
|
||||
}
|
||||
|
||||
fn clear(&mut self, clear_color: Rgba) {
|
||||
|
@ -99,8 +102,8 @@ impl crate::Painter for WrappedGlowPainter {
|
|||
) -> Result<(), JsValue> {
|
||||
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
||||
self.painter.paint_meshes(
|
||||
canvas_dimension,
|
||||
&self.gl_ctx,
|
||||
canvas_dimension,
|
||||
pixels_per_point,
|
||||
clipped_meshes,
|
||||
);
|
||||
|
@ -113,33 +116,29 @@ impl crate::Painter for WrappedGlowPainter {
|
|||
}
|
||||
|
||||
pub fn init_glow_context_from_canvas(canvas: &HtmlCanvasElement) -> glow::Context {
|
||||
let ctx = canvas.get_context("webgl2");
|
||||
if let Ok(ctx) = ctx {
|
||||
crate::console_log("webgl found");
|
||||
if let Some(ctx) = ctx {
|
||||
crate::console_log("webgl 2 selected");
|
||||
let gl_ctx = ctx.dyn_into::<web_sys::WebGl2RenderingContext>().unwrap();
|
||||
glow::Context::from_webgl2_context(gl_ctx)
|
||||
} else {
|
||||
let ctx = canvas.get_context("webgl");
|
||||
if let Ok(ctx) = ctx {
|
||||
crate::console_log("falling back to webgl1");
|
||||
if let Some(ctx) = ctx {
|
||||
crate::console_log("webgl1 selected");
|
||||
let gl2_ctx = canvas
|
||||
.get_context("webgl2")
|
||||
.expect("Failed to query about WebGL2 context");
|
||||
|
||||
let gl_ctx = ctx.dyn_into::<web_sys::WebGlRenderingContext>().unwrap();
|
||||
crate::console_log("success");
|
||||
glow::Context::from_webgl1_context(gl_ctx)
|
||||
if let Some(gl2_ctx) = gl2_ctx {
|
||||
crate::console_log("WebGL2 found");
|
||||
let gl2_ctx = gl2_ctx
|
||||
.dyn_into::<web_sys::WebGl2RenderingContext>()
|
||||
.unwrap();
|
||||
glow::Context::from_webgl2_context(gl2_ctx)
|
||||
} else {
|
||||
panic!("tried webgl1 but can't get context");
|
||||
}
|
||||
let gl1 = canvas
|
||||
.get_context("webgl")
|
||||
.expect("Failed to query about WebGL1 context");
|
||||
|
||||
if let Some(gl1) = gl1 {
|
||||
crate::console_log("WebGL2 not available - falling back to WebGL2");
|
||||
let gl1_ctx = gl1.dyn_into::<web_sys::WebGlRenderingContext>().unwrap();
|
||||
glow::Context::from_webgl1_context(gl1_ctx)
|
||||
} else {
|
||||
panic!("tried webgl1 but can't get context");
|
||||
panic!("Failed to get WebGL context.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("tried webgl2 but something went wrong");
|
||||
}
|
||||
}
|
||||
|
||||
trait DummyWebGLConstructor {
|
||||
|
|
|
@ -12,14 +12,17 @@
|
|||
// Forbid warnings in release builds:
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(clippy::all, missing_crate_level_docs, rust_2018_idioms)]
|
||||
#![warn(clippy::all, rustdoc::missing_crate_level_docs, rust_2018_idioms)]
|
||||
|
||||
pub mod backend;
|
||||
#[cfg(feature = "glow")]
|
||||
mod glow_wrapping;
|
||||
mod painter;
|
||||
pub mod screen_reader;
|
||||
|
||||
#[cfg(feature = "webgl")]
|
||||
pub mod webgl1;
|
||||
#[cfg(feature = "webgl")]
|
||||
pub mod webgl2;
|
||||
|
||||
pub use backend::*;
|
||||
|
@ -1234,3 +1237,23 @@ fn move_text_cursor(cursor: &Option<egui::Pos2>, canvas_id: &str) -> Option<()>
|
|||
style.set_property("left", "0px").ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// detecting Safari and webkitGTK.
|
||||
///
|
||||
/// Safari and webkitGTK use unmasked renderer :Apple GPU
|
||||
///
|
||||
/// If we detect safari or webkitGTK returns true.
|
||||
///
|
||||
/// This function used to avoid displaying linear color with `sRGB` supported systems.
|
||||
pub(crate) fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool {
|
||||
if let Ok(renderer) = gl.get_parameter(web_sys::WebglDebugRendererInfo::UNMASKED_RENDERER_WEBGL)
|
||||
{
|
||||
if let Some(renderer) = renderer.as_string() {
|
||||
if renderer.contains("Apple") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
use wasm_bindgen::prelude::JsValue;
|
||||
|
||||
pub trait Painter {
|
||||
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator;
|
||||
fn set_texture(&mut self, tex_id: u64, image: epi::Image);
|
||||
|
||||
fn free_texture(&mut self, tex_id: u64);
|
||||
|
||||
fn debug_info(&self) -> String;
|
||||
|
||||
/// id of the canvas html element containing the rendering
|
||||
fn canvas_id(&self) -> &str;
|
||||
|
||||
fn upload_egui_texture(&mut self, texture: &egui::Texture);
|
||||
fn upload_egui_texture(&mut self, font_image: &egui::FontImage);
|
||||
|
||||
fn clear(&mut self, clear_color: egui::Rgba);
|
||||
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use {
|
||||
js_sys::WebAssembly,
|
||||
wasm_bindgen::{prelude::*, JsCast},
|
||||
web_sys::{
|
||||
ExtSRgb, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlRenderingContext, WebGlShader,
|
||||
WebGlTexture, WebglDebugRendererInfo,
|
||||
WebGlTexture,
|
||||
},
|
||||
};
|
||||
|
||||
use egui::{
|
||||
emath::vec2,
|
||||
epaint::{Color32, Texture},
|
||||
epaint::{Color32, FontImage},
|
||||
};
|
||||
|
||||
type Gl = WebGlRenderingContext;
|
||||
|
@ -29,19 +31,10 @@ pub struct WebGlPainter {
|
|||
egui_texture: WebGlTexture,
|
||||
egui_texture_version: Option<u64>,
|
||||
|
||||
/// `None` means unallocated (freed) slot.
|
||||
user_textures: Vec<Option<UserTexture>>,
|
||||
}
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, WebGlTexture>,
|
||||
|
||||
#[derive(Default)]
|
||||
struct UserTexture {
|
||||
size: (usize, usize),
|
||||
|
||||
/// Pending upload (will be emptied later).
|
||||
pixels: Vec<u8>,
|
||||
|
||||
/// Lazily uploaded
|
||||
gl_texture: Option<WebGlTexture>,
|
||||
next_native_tex_id: u64, // TODO: 128-bit texture space?
|
||||
}
|
||||
|
||||
impl WebGlPainter {
|
||||
|
@ -111,109 +104,14 @@ impl WebGlPainter {
|
|||
egui_texture,
|
||||
egui_texture_version: None,
|
||||
user_textures: Default::default(),
|
||||
next_native_tex_id: 1 << 32,
|
||||
})
|
||||
}
|
||||
|
||||
fn alloc_user_texture_index(&mut self) -> usize {
|
||||
for (index, tex) in self.user_textures.iter_mut().enumerate() {
|
||||
if tex.is_none() {
|
||||
*tex = Some(Default::default());
|
||||
return index;
|
||||
}
|
||||
}
|
||||
let index = self.user_textures.len();
|
||||
self.user_textures.push(Some(Default::default()));
|
||||
index
|
||||
}
|
||||
|
||||
fn alloc_user_texture(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[Color32],
|
||||
) -> egui::TextureId {
|
||||
let index = self.alloc_user_texture_index();
|
||||
assert_eq!(
|
||||
size.0 * size.1,
|
||||
srgba_pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(index) {
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(srgba_pixels.len() * 4);
|
||||
for srgba in srgba_pixels {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
pixels.push(srgba.a());
|
||||
}
|
||||
|
||||
*user_texture = UserTexture {
|
||||
size,
|
||||
pixels,
|
||||
gl_texture: None,
|
||||
};
|
||||
}
|
||||
|
||||
egui::TextureId::User(index as u64)
|
||||
}
|
||||
|
||||
fn free_user_texture(&mut self, id: egui::TextureId) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
let index = id as usize;
|
||||
if index < self.user_textures.len() {
|
||||
self.user_textures[index] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||
match texture_id {
|
||||
egui::TextureId::Egui => Some(&self.egui_texture),
|
||||
egui::TextureId::User(id) => self
|
||||
.user_textures
|
||||
.get(id as usize)?
|
||||
.as_ref()?
|
||||
.gl_texture
|
||||
.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_user_textures(&mut self) {
|
||||
let gl = &self.gl;
|
||||
|
||||
for user_texture in self.user_textures.iter_mut().flatten() {
|
||||
if user_texture.gl_texture.is_none() {
|
||||
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||
|
||||
let gl_texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
||||
let level = 0;
|
||||
let internal_format = self.texture_format;
|
||||
let border = 0;
|
||||
let src_format = self.texture_format;
|
||||
let src_type = Gl::UNSIGNED_BYTE;
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as i32,
|
||||
user_texture.size.0 as i32,
|
||||
user_texture.size.1 as i32,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
user_texture.gl_texture = Some(gl_texture);
|
||||
}
|
||||
egui::TextureId::User(id) => self.user_textures.get(&id),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -338,51 +236,75 @@ impl WebGlPainter {
|
|||
}
|
||||
}
|
||||
|
||||
impl epi::TextureAllocator for WebGlPainter {
|
||||
fn alloc_srgba_premultiplied(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[egui::Color32],
|
||||
) -> egui::TextureId {
|
||||
self.alloc_user_texture(size, srgba_pixels)
|
||||
}
|
||||
|
||||
fn free(&mut self, id: egui::TextureId) {
|
||||
self.free_user_texture(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::NativeTexture for WebGlPainter {
|
||||
type Texture = WebGlTexture;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
let id = self.alloc_user_texture_index();
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id) {
|
||||
*user_texture = UserTexture {
|
||||
size: (0, 0),
|
||||
pixels: vec![],
|
||||
gl_texture: Some(native),
|
||||
}
|
||||
}
|
||||
let id = self.next_native_tex_id;
|
||||
self.next_native_tex_id += 1;
|
||||
self.user_textures.insert(id, native);
|
||||
egui::TextureId::User(id as u64)
|
||||
}
|
||||
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
*user_texture = UserTexture {
|
||||
size: (0, 0),
|
||||
pixels: vec![],
|
||||
gl_texture: Some(replacing),
|
||||
}
|
||||
if let Some(user_texture) = self.user_textures.get_mut(&id) {
|
||||
*user_texture = replacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Painter for WebGlPainter {
|
||||
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator {
|
||||
self
|
||||
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
|
||||
assert_eq!(
|
||||
image.size[0] * image.size[1],
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
// TODO: optimize
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(image.pixels.len() * 4);
|
||||
for srgba in image.pixels {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
pixels.push(srgba.a());
|
||||
}
|
||||
|
||||
let gl = &self.gl;
|
||||
let gl_texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
||||
let level = 0;
|
||||
let internal_format = self.texture_format;
|
||||
let border = 0;
|
||||
let src_format = self.texture_format;
|
||||
let src_type = Gl::UNSIGNED_BYTE;
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as _,
|
||||
image.size[0] as _,
|
||||
image.size[1] as _,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.user_textures.insert(tex_id, gl_texture);
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
fn debug_info(&self) -> String {
|
||||
|
@ -401,8 +323,8 @@ impl crate::Painter for WebGlPainter {
|
|||
&self.canvas_id
|
||||
}
|
||||
|
||||
fn upload_egui_texture(&mut self, texture: &Texture) {
|
||||
if self.egui_texture_version == Some(texture.version) {
|
||||
fn upload_egui_texture(&mut self, font_image: &FontImage) {
|
||||
if self.egui_texture_version == Some(font_image.version) {
|
||||
return; // No change
|
||||
}
|
||||
|
||||
|
@ -411,8 +333,8 @@ impl crate::Painter for WebGlPainter {
|
|||
} else {
|
||||
1.0 // post process enables linear blending
|
||||
};
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(texture.pixels.len() * 4);
|
||||
for srgba in texture.srgba_pixels(gamma) {
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(font_image.pixels.len() * 4);
|
||||
for srgba in font_image.srgba_pixels(gamma) {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
|
@ -431,8 +353,8 @@ impl crate::Painter for WebGlPainter {
|
|||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as i32,
|
||||
texture.width as i32,
|
||||
texture.height as i32,
|
||||
font_image.width as i32,
|
||||
font_image.height as i32,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
|
@ -440,7 +362,7 @@ impl crate::Painter for WebGlPainter {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
self.egui_texture_version = Some(texture.version);
|
||||
self.egui_texture_version = Some(font_image.version);
|
||||
}
|
||||
|
||||
fn clear(&mut self, clear_color: egui::Rgba) {
|
||||
|
@ -467,8 +389,6 @@ impl crate::Painter for WebGlPainter {
|
|||
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||
pixels_per_point: f32,
|
||||
) -> Result<(), JsValue> {
|
||||
self.upload_user_textures();
|
||||
|
||||
let gl = &self.gl;
|
||||
|
||||
if let Some(ref mut post_process) = self.post_process {
|
||||
|
@ -559,7 +479,7 @@ fn requires_brightening(gl: &web_sys::WebGlRenderingContext) -> bool {
|
|||
// See https://github.com/emilk/egui/issues/794
|
||||
|
||||
let user_agent = web_sys::window().unwrap().navigator().user_agent().unwrap();
|
||||
crate::webgl1::is_safari_and_webkit_gtk(gl) && !user_agent.contains("Mac OS X")
|
||||
crate::is_safari_and_webkit_gtk(gl) && !user_agent.contains("Mac OS X")
|
||||
}
|
||||
|
||||
impl PostProcess {
|
||||
|
@ -768,26 +688,3 @@ fn link_program<'a, T: IntoIterator<Item = &'a WebGlShader>>(
|
|||
.unwrap_or_else(|| "Unknown error creating program object".into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// detecting Safari and webkitGTK.
|
||||
///
|
||||
/// Safari and webkitGTK use unmasked renderer :Apple GPU
|
||||
///
|
||||
/// If we detect safari or webkitGTK returns true.
|
||||
///
|
||||
/// This function used to avoid displaying linear color with `sRGB` supported systems.
|
||||
pub(crate) fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool {
|
||||
if gl
|
||||
.get_extension("WEBGL_debug_renderer_info")
|
||||
.unwrap()
|
||||
.is_some()
|
||||
{
|
||||
let renderer: JsValue = gl
|
||||
.get_parameter(WebglDebugRendererInfo::UNMASKED_RENDERER_WEBGL)
|
||||
.unwrap();
|
||||
if renderer.as_string().unwrap().contains("Apple") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
//! Mostly a carbon-copy of `webgl1.rs`.
|
||||
use std::collections::HashMap;
|
||||
|
||||
use {
|
||||
js_sys::WebAssembly,
|
||||
|
@ -11,7 +12,7 @@ use {
|
|||
|
||||
use egui::{
|
||||
emath::vec2,
|
||||
epaint::{Color32, Texture},
|
||||
epaint::{Color32, FontImage},
|
||||
};
|
||||
|
||||
type Gl = WebGl2RenderingContext;
|
||||
|
@ -30,19 +31,10 @@ pub struct WebGl2Painter {
|
|||
egui_texture: WebGlTexture,
|
||||
egui_texture_version: Option<u64>,
|
||||
|
||||
/// `None` means unallocated (freed) slot.
|
||||
user_textures: Vec<Option<UserTexture>>,
|
||||
}
|
||||
/// Index is the same as in [`egui::TextureId::User`].
|
||||
user_textures: HashMap<u64, WebGlTexture>,
|
||||
|
||||
#[derive(Default)]
|
||||
struct UserTexture {
|
||||
size: (usize, usize),
|
||||
|
||||
/// Pending upload (will be emptied later).
|
||||
pixels: Vec<u8>,
|
||||
|
||||
/// Lazily uploaded
|
||||
gl_texture: Option<WebGlTexture>,
|
||||
next_native_tex_id: u64, // TODO: 128-bit texture space?
|
||||
}
|
||||
|
||||
impl WebGl2Painter {
|
||||
|
@ -96,109 +88,14 @@ impl WebGl2Painter {
|
|||
egui_texture,
|
||||
egui_texture_version: None,
|
||||
user_textures: Default::default(),
|
||||
next_native_tex_id: 1 << 32,
|
||||
})
|
||||
}
|
||||
|
||||
fn alloc_user_texture_index(&mut self) -> usize {
|
||||
for (index, tex) in self.user_textures.iter_mut().enumerate() {
|
||||
if tex.is_none() {
|
||||
*tex = Some(Default::default());
|
||||
return index;
|
||||
}
|
||||
}
|
||||
let index = self.user_textures.len();
|
||||
self.user_textures.push(Some(Default::default()));
|
||||
index
|
||||
}
|
||||
|
||||
fn alloc_user_texture(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[Color32],
|
||||
) -> egui::TextureId {
|
||||
let index = self.alloc_user_texture_index();
|
||||
assert_eq!(
|
||||
size.0 * size.1,
|
||||
srgba_pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(index) {
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(srgba_pixels.len() * 4);
|
||||
for srgba in srgba_pixels {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
pixels.push(srgba.a());
|
||||
}
|
||||
|
||||
*user_texture = UserTexture {
|
||||
size,
|
||||
pixels,
|
||||
gl_texture: None,
|
||||
};
|
||||
}
|
||||
|
||||
egui::TextureId::User(index as u64)
|
||||
}
|
||||
|
||||
fn free_user_texture(&mut self, id: egui::TextureId) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
let index = id as usize;
|
||||
if index < self.user_textures.len() {
|
||||
self.user_textures[index] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||
fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
|
||||
match texture_id {
|
||||
egui::TextureId::Egui => Some(&self.egui_texture),
|
||||
egui::TextureId::User(id) => self
|
||||
.user_textures
|
||||
.get(id as usize)?
|
||||
.as_ref()?
|
||||
.gl_texture
|
||||
.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_user_textures(&mut self) {
|
||||
let gl = &self.gl;
|
||||
for user_texture in self.user_textures.iter_mut().flatten() {
|
||||
if user_texture.gl_texture.is_none() {
|
||||
let pixels = std::mem::take(&mut user_texture.pixels);
|
||||
|
||||
let gl_texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as i32);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as i32);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
||||
let level = 0;
|
||||
let internal_format = Gl::SRGB8_ALPHA8;
|
||||
let border = 0;
|
||||
let src_format = Gl::RGBA;
|
||||
let src_type = Gl::UNSIGNED_BYTE;
|
||||
gl.pixel_storei(Gl::UNPACK_ALIGNMENT, 1);
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as i32,
|
||||
user_texture.size.0 as i32,
|
||||
user_texture.size.1 as i32,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
user_texture.gl_texture = Some(gl_texture);
|
||||
}
|
||||
egui::TextureId::User(id) => self.user_textures.get(&id),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,51 +220,75 @@ impl WebGl2Painter {
|
|||
}
|
||||
}
|
||||
|
||||
impl epi::TextureAllocator for WebGl2Painter {
|
||||
fn alloc_srgba_premultiplied(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[egui::Color32],
|
||||
) -> egui::TextureId {
|
||||
self.alloc_user_texture(size, srgba_pixels)
|
||||
}
|
||||
|
||||
fn free(&mut self, id: egui::TextureId) {
|
||||
self.free_user_texture(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::NativeTexture for WebGl2Painter {
|
||||
type Texture = WebGlTexture;
|
||||
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
|
||||
let id = self.alloc_user_texture_index();
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id) {
|
||||
*user_texture = UserTexture {
|
||||
size: (0, 0),
|
||||
pixels: vec![],
|
||||
gl_texture: Some(native),
|
||||
}
|
||||
}
|
||||
let id = self.next_native_tex_id;
|
||||
self.next_native_tex_id += 1;
|
||||
self.user_textures.insert(id, native);
|
||||
egui::TextureId::User(id as u64)
|
||||
}
|
||||
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) {
|
||||
*user_texture = UserTexture {
|
||||
size: (0, 0),
|
||||
pixels: vec![],
|
||||
gl_texture: Some(replacing),
|
||||
}
|
||||
if let Some(user_texture) = self.user_textures.get_mut(&id) {
|
||||
*user_texture = replacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Painter for WebGl2Painter {
|
||||
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator {
|
||||
self
|
||||
fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
|
||||
assert_eq!(
|
||||
image.size[0] * image.size[1],
|
||||
image.pixels.len(),
|
||||
"Mismatch between texture size and texel count"
|
||||
);
|
||||
|
||||
// TODO: optimize
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(image.pixels.len() * 4);
|
||||
for srgba in image.pixels {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
pixels.push(srgba.a());
|
||||
}
|
||||
|
||||
let gl = &self.gl;
|
||||
let gl_texture = gl.create_texture().unwrap();
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_S, Gl::CLAMP_TO_EDGE as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_WRAP_T, Gl::CLAMP_TO_EDGE as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MIN_FILTER, Gl::LINEAR as _);
|
||||
gl.tex_parameteri(Gl::TEXTURE_2D, Gl::TEXTURE_MAG_FILTER, Gl::LINEAR as _);
|
||||
|
||||
gl.bind_texture(Gl::TEXTURE_2D, Some(&gl_texture));
|
||||
|
||||
let level = 0;
|
||||
let internal_format = Gl::SRGB8_ALPHA8;
|
||||
let border = 0;
|
||||
let src_format = Gl::RGBA;
|
||||
let src_type = Gl::UNSIGNED_BYTE;
|
||||
gl.tex_image_2d_with_i32_and_i32_and_i32_and_format_and_type_and_opt_u8_array(
|
||||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as _,
|
||||
image.size[0] as _,
|
||||
image.size[1] as _,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
Some(&pixels),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
self.user_textures.insert(tex_id, gl_texture);
|
||||
}
|
||||
|
||||
fn free_texture(&mut self, tex_id: u64) {
|
||||
self.user_textures.remove(&tex_id);
|
||||
}
|
||||
|
||||
fn debug_info(&self) -> String {
|
||||
|
@ -386,13 +307,13 @@ impl crate::Painter for WebGl2Painter {
|
|||
&self.canvas_id
|
||||
}
|
||||
|
||||
fn upload_egui_texture(&mut self, texture: &Texture) {
|
||||
if self.egui_texture_version == Some(texture.version) {
|
||||
fn upload_egui_texture(&mut self, font_image: &FontImage) {
|
||||
if self.egui_texture_version == Some(font_image.version) {
|
||||
return; // No change
|
||||
}
|
||||
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(texture.pixels.len() * 4);
|
||||
for srgba in texture.srgba_pixels(1.0) {
|
||||
let mut pixels: Vec<u8> = Vec::with_capacity(font_image.pixels.len() * 4);
|
||||
for srgba in font_image.srgba_pixels(1.0) {
|
||||
pixels.push(srgba.r());
|
||||
pixels.push(srgba.g());
|
||||
pixels.push(srgba.b());
|
||||
|
@ -412,8 +333,8 @@ impl crate::Painter for WebGl2Painter {
|
|||
Gl::TEXTURE_2D,
|
||||
level,
|
||||
internal_format as i32,
|
||||
texture.width as i32,
|
||||
texture.height as i32,
|
||||
font_image.width as i32,
|
||||
font_image.height as i32,
|
||||
border,
|
||||
src_format,
|
||||
src_type,
|
||||
|
@ -421,7 +342,7 @@ impl crate::Painter for WebGl2Painter {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
self.egui_texture_version = Some(texture.version);
|
||||
self.egui_texture_version = Some(font_image.version);
|
||||
}
|
||||
|
||||
fn clear(&mut self, clear_color: egui::Rgba) {
|
||||
|
@ -448,8 +369,6 @@ impl crate::Painter for WebGl2Painter {
|
|||
clipped_meshes: Vec<egui::ClippedMesh>,
|
||||
pixels_per_point: f32,
|
||||
) -> Result<(), JsValue> {
|
||||
self.upload_user_textures();
|
||||
|
||||
let gl = &self.gl;
|
||||
|
||||
self.post_process
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
[package]
|
||||
name = "emath"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Minimal 2D math library for GUI work"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/emath"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
|
|
@ -84,9 +84,9 @@
|
|||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
missing_crate_level_docs,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
|
|
@ -4,8 +4,12 @@ All notable changes to the epaint crate will be documented in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* `Rgba` now implements `Hash` ([#886](https://github.com/emilk/egui/pull/886)).
|
||||
|
||||
|
||||
## 0.16.0 - 2021-12-29
|
||||
* Anti-alias path ends ([#893](https://github.com/emilk/egui/pull/893)).
|
||||
* `Rgba` now implements `Hash` ([#886](https://github.com/emilk/egui/pull/886)).
|
||||
* Renamed `Texture` to `FontImage`.
|
||||
|
||||
|
||||
## 0.15.0 - 2021-10-24
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
[package]
|
||||
name = "epaint"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Minimal 2D graphics library for GUI work"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/epaint"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
@ -25,7 +26,7 @@ all-features = true
|
|||
[lib]
|
||||
|
||||
[dependencies]
|
||||
emath = { version = "0.15.0", path = "../emath" }
|
||||
emath = { version = "0.16.0", path = "../emath" }
|
||||
|
||||
ab_glyph = "0.2.11"
|
||||
ahash = { version = "0.7", features = ["std"], default-features = false }
|
||||
|
|
|
@ -80,9 +80,9 @@
|
|||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
missing_crate_level_docs,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
@ -109,7 +109,7 @@ pub use {
|
|||
stroke::Stroke,
|
||||
tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
|
||||
text::{Fonts, Galley, TextStyle},
|
||||
texture_atlas::{Texture, TextureAtlas},
|
||||
texture_atlas::{FontImage, TextureAtlas},
|
||||
};
|
||||
|
||||
pub use emath::{pos2, vec2, Pos2, Rect, Vec2};
|
||||
|
|
|
@ -64,7 +64,6 @@ impl Mesh {
|
|||
|
||||
/// Are all indices within the bounds of the contained vertices?
|
||||
pub fn is_valid(&self) -> bool {
|
||||
use std::convert::TryFrom;
|
||||
if let Ok(n) = u32::try_from(self.vertices.len()) {
|
||||
self.indices.iter().all(|&i| i < n)
|
||||
} else {
|
||||
|
@ -223,7 +222,6 @@ impl Mesh {
|
|||
MAX_SIZE
|
||||
);
|
||||
|
||||
use std::convert::TryFrom;
|
||||
let mesh = Mesh16 {
|
||||
indices: self.indices[span_start..index_cursor]
|
||||
.iter()
|
||||
|
@ -267,7 +265,6 @@ pub struct Mesh16 {
|
|||
impl Mesh16 {
|
||||
/// Are all indices within the bounds of the contained vertices?
|
||||
pub fn is_valid(&self) -> bool {
|
||||
use std::convert::TryFrom;
|
||||
if let Ok(n) = u16::try_from(self.vertices.len()) {
|
||||
self.indices.iter().all(|&i| i < n)
|
||||
} else {
|
||||
|
|
|
@ -377,7 +377,7 @@ fn allocate_glyph(
|
|||
} else {
|
||||
let glyph_pos = atlas.allocate((glyph_width, glyph_height));
|
||||
|
||||
let texture = atlas.texture_mut();
|
||||
let texture = atlas.image_mut();
|
||||
glyph.draw(|x, y, v| {
|
||||
if v > 0.0 {
|
||||
let px = glyph_pos.0 + x as usize;
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
font::{Font, FontImpl},
|
||||
Galley, LayoutJob,
|
||||
},
|
||||
Texture, TextureAtlas,
|
||||
FontImage, TextureAtlas,
|
||||
};
|
||||
|
||||
// TODO: rename
|
||||
|
@ -56,7 +56,7 @@ pub enum FontFamily {
|
|||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct FontData {
|
||||
/// The data of a `.ttf` or `.otf` file.
|
||||
/// The content of a `.ttf` or `.otf` file.
|
||||
pub font: std::borrow::Cow<'static, [u8]>,
|
||||
/// Which font face in the file to use.
|
||||
/// When in doubt, use `0`.
|
||||
|
@ -233,9 +233,10 @@ pub struct Fonts {
|
|||
definitions: FontDefinitions,
|
||||
fonts: BTreeMap<TextStyle, Font>,
|
||||
atlas: Arc<Mutex<TextureAtlas>>,
|
||||
/// Copy of the texture in the texture atlas.
|
||||
|
||||
/// Copy of the font image in the texture atlas.
|
||||
/// This is so we can return a reference to it (the texture atlas is behind a lock).
|
||||
buffered_texture: Mutex<Arc<Texture>>,
|
||||
buffered_font_image: Mutex<Arc<FontImage>>,
|
||||
|
||||
galley_cache: Mutex<GalleyCache>,
|
||||
}
|
||||
|
@ -258,7 +259,7 @@ impl Fonts {
|
|||
// Make the top left pixel fully white:
|
||||
let pos = atlas.allocate((1, 1));
|
||||
assert_eq!(pos, (0, 0));
|
||||
atlas.texture_mut()[pos] = 255;
|
||||
atlas.image_mut()[pos] = 255;
|
||||
}
|
||||
|
||||
let atlas = Arc::new(Mutex::new(atlas));
|
||||
|
@ -284,7 +285,7 @@ impl Fonts {
|
|||
|
||||
{
|
||||
let mut atlas = atlas.lock();
|
||||
let texture = atlas.texture_mut();
|
||||
let texture = atlas.image_mut();
|
||||
// Make sure we seed the texture version with something unique based on the default characters:
|
||||
texture.version = crate::util::hash(&texture.pixels);
|
||||
}
|
||||
|
@ -294,7 +295,7 @@ impl Fonts {
|
|||
definitions,
|
||||
fonts,
|
||||
atlas,
|
||||
buffered_texture: Default::default(), //atlas.lock().texture().clone();
|
||||
buffered_font_image: Default::default(), //atlas.lock().texture().clone();
|
||||
galley_cache: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -319,11 +320,11 @@ impl Fonts {
|
|||
}
|
||||
|
||||
/// Call each frame to get the latest available font texture data.
|
||||
pub fn texture(&self) -> Arc<Texture> {
|
||||
pub fn font_image(&self) -> Arc<FontImage> {
|
||||
let atlas = self.atlas.lock();
|
||||
let mut buffered_texture = self.buffered_texture.lock();
|
||||
if buffered_texture.version != atlas.texture().version {
|
||||
*buffered_texture = Arc::new(atlas.texture().clone());
|
||||
let mut buffered_texture = self.buffered_font_image.lock();
|
||||
if buffered_texture.version != atlas.image().version {
|
||||
*buffered_texture = Arc::new(atlas.image().clone());
|
||||
}
|
||||
|
||||
buffered_texture.clone()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// TODO: `TextureData` or similar?
|
||||
/// An 8-bit texture containing font data.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Texture {
|
||||
pub struct FontImage {
|
||||
/// e.g. a hash of the data. Use this to detect changes!
|
||||
/// If the texture changes, this too will change.
|
||||
pub version: u64,
|
||||
|
@ -11,7 +10,7 @@ pub struct Texture {
|
|||
pub pixels: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Texture {
|
||||
impl FontImage {
|
||||
pub fn size(&self) -> [usize; 2] {
|
||||
[self.width, self.height]
|
||||
}
|
||||
|
@ -36,7 +35,7 @@ impl Texture {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<(usize, usize)> for Texture {
|
||||
impl std::ops::Index<(usize, usize)> for FontImage {
|
||||
type Output = u8;
|
||||
|
||||
#[inline]
|
||||
|
@ -47,7 +46,7 @@ impl std::ops::Index<(usize, usize)> for Texture {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<(usize, usize)> for Texture {
|
||||
impl std::ops::IndexMut<(usize, usize)> for FontImage {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut u8 {
|
||||
assert!(x < self.width);
|
||||
|
@ -61,7 +60,7 @@ impl std::ops::IndexMut<(usize, usize)> for Texture {
|
|||
/// More characters can be added, possibly expanding the texture.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct TextureAtlas {
|
||||
texture: Texture,
|
||||
image: FontImage,
|
||||
|
||||
/// Used for when allocating new rectangles.
|
||||
cursor: (usize, usize),
|
||||
|
@ -71,7 +70,7 @@ pub struct TextureAtlas {
|
|||
impl TextureAtlas {
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
texture: Texture {
|
||||
image: FontImage {
|
||||
version: 0,
|
||||
width,
|
||||
height,
|
||||
|
@ -81,13 +80,13 @@ impl TextureAtlas {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn texture(&self) -> &Texture {
|
||||
&self.texture
|
||||
pub fn image(&self) -> &FontImage {
|
||||
&self.image
|
||||
}
|
||||
|
||||
pub fn texture_mut(&mut self) -> &mut Texture {
|
||||
self.texture.version += 1;
|
||||
&mut self.texture
|
||||
pub fn image_mut(&mut self) -> &mut FontImage {
|
||||
self.image.version += 1;
|
||||
&mut self.image
|
||||
}
|
||||
|
||||
/// Returns the coordinates of where the rect ended up.
|
||||
|
@ -98,12 +97,12 @@ impl TextureAtlas {
|
|||
const PADDING: usize = 1;
|
||||
|
||||
assert!(
|
||||
w <= self.texture.width,
|
||||
w <= self.image.width,
|
||||
"Tried to allocate a {} wide glyph in a {} wide texture atlas",
|
||||
w,
|
||||
self.texture.width
|
||||
self.image.width
|
||||
);
|
||||
if self.cursor.0 + w > self.texture.width {
|
||||
if self.cursor.0 + w > self.image.width {
|
||||
// New row:
|
||||
self.cursor.0 = 0;
|
||||
self.cursor.1 += self.row_height + PADDING;
|
||||
|
@ -111,19 +110,19 @@ impl TextureAtlas {
|
|||
}
|
||||
|
||||
self.row_height = self.row_height.max(h);
|
||||
while self.cursor.1 + self.row_height >= self.texture.height {
|
||||
self.texture.height *= 2;
|
||||
while self.cursor.1 + self.row_height >= self.image.height {
|
||||
self.image.height *= 2;
|
||||
}
|
||||
|
||||
if self.texture.width * self.texture.height > self.texture.pixels.len() {
|
||||
self.texture
|
||||
if self.image.width * self.image.height > self.image.pixels.len() {
|
||||
self.image
|
||||
.pixels
|
||||
.resize(self.texture.width * self.texture.height, 0);
|
||||
.resize(self.image.width * self.image.height, 0);
|
||||
}
|
||||
|
||||
let pos = self.cursor;
|
||||
self.cursor.0 += w + PADDING;
|
||||
self.texture.version += 1;
|
||||
self.image.version += 1;
|
||||
(pos.0 as usize, pos.1 as usize)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
[package]
|
||||
name = "epi"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Backend-agnostic interface for writing apps using egui"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/epi"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
|
@ -23,7 +24,7 @@ all-features = true
|
|||
[lib]
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.15.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
||||
egui = { version = "0.16.0", path = "../egui", default-features = false, features = ["single_threaded"] }
|
||||
|
||||
directories-next = { version = "2", optional = true }
|
||||
ron = { version = "0.7", optional = true }
|
||||
|
|
224
epi/src/lib.rs
224
epi/src/lib.rs
|
@ -81,9 +81,9 @@
|
|||
clippy::verbose_file_reads,
|
||||
clippy::zero_sized_map_values,
|
||||
future_incompatible,
|
||||
missing_crate_level_docs,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms
|
||||
rust_2018_idioms,
|
||||
rustdoc::missing_crate_level_docs
|
||||
)]
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
@ -95,6 +95,8 @@ pub mod file_storage;
|
|||
|
||||
pub use egui; // Re-export for user convenience
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Implement this trait to write apps that can be compiled both natively using the [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) crate,
|
||||
|
@ -104,8 +106,11 @@ pub trait App {
|
|||
///
|
||||
/// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
|
||||
///
|
||||
/// To force a repaint, call either [`egui::Context::request_repaint`] or use [`Frame::repaint_signal`].
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut Frame<'_>);
|
||||
/// The given [`egui::CtxRef`] is only valid for the duration of this call.
|
||||
/// The [`Frame`] however can be cloned and saved.
|
||||
///
|
||||
/// To force a repaint, call either [`egui::Context::request_repaint`] or [`Frame::request_repaint`].
|
||||
fn update(&mut self, ctx: &egui::CtxRef, frame: &Frame);
|
||||
|
||||
/// Called once before the first frame.
|
||||
///
|
||||
|
@ -113,13 +118,7 @@ pub trait App {
|
|||
/// [`egui::Context::set_visuals`] etc.
|
||||
///
|
||||
/// Also allows you to restore state, if there is a storage (required the "persistence" feature).
|
||||
fn setup(
|
||||
&mut self,
|
||||
_ctx: &egui::CtxRef,
|
||||
_frame: &mut Frame<'_>,
|
||||
_storage: Option<&dyn Storage>,
|
||||
) {
|
||||
}
|
||||
fn setup(&mut self, _ctx: &egui::CtxRef, _frame: &Frame, _storage: Option<&dyn Storage>) {}
|
||||
|
||||
/// If `true` a warm-up call to [`Self::update`] will be issued where
|
||||
/// `ctx.memory().everything_is_visible()` will be set to `true`.
|
||||
|
@ -188,7 +187,9 @@ pub trait App {
|
|||
}
|
||||
}
|
||||
|
||||
/// Options controlling the behavior of a native window
|
||||
/// 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.
|
||||
|
@ -240,7 +241,7 @@ impl Default for NativeOptions {
|
|||
/// Image data for the icon.
|
||||
#[derive(Clone)]
|
||||
pub struct IconData {
|
||||
/// RGBA pixels.
|
||||
/// RGBA pixels, unmultiplied.
|
||||
pub rgba: Vec<u8>,
|
||||
|
||||
/// Image width. This should be a multiple of 4.
|
||||
|
@ -254,57 +255,99 @@ pub struct IconData {
|
|||
///
|
||||
/// It provides methods to inspect the surroundings (are we on the web?),
|
||||
/// allocate textures, and change settings (e.g. window size).
|
||||
pub struct Frame<'a>(backend::FrameBuilder<'a>);
|
||||
///
|
||||
/// [`Frame`] is cheap to clone and is safe to pass to other threads.
|
||||
#[derive(Clone)]
|
||||
pub struct Frame(pub Arc<Mutex<backend::FrameData>>);
|
||||
|
||||
impl Frame {
|
||||
/// Create a `Frame` - called by the integration.
|
||||
#[doc(hidden)]
|
||||
pub fn new(frame_data: backend::FrameData) -> Self {
|
||||
Self(Arc::new(Mutex::new(frame_data)))
|
||||
}
|
||||
|
||||
/// Convenience to access the underlying `backend::FrameData`.
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
pub fn lock(&self) -> std::sync::MutexGuard<'_, backend::FrameData> {
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
impl<'a> Frame<'a> {
|
||||
/// True if you are in a web environment.
|
||||
pub fn is_web(&self) -> bool {
|
||||
self.info().web_info.is_some()
|
||||
self.lock().info.web_info.is_some()
|
||||
}
|
||||
|
||||
/// Information about the integration.
|
||||
pub fn info(&self) -> &IntegrationInfo {
|
||||
&self.0.info
|
||||
}
|
||||
|
||||
/// A way to allocate textures.
|
||||
pub fn tex_allocator(&mut self) -> &mut dyn TextureAllocator {
|
||||
self.0.tex_allocator
|
||||
pub fn info(&self) -> IntegrationInfo {
|
||||
self.lock().info.clone()
|
||||
}
|
||||
|
||||
/// 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.0.output.quit = true;
|
||||
pub fn quit(&self) {
|
||||
self.lock().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.0.output.window_size = Some(size);
|
||||
pub fn set_window_size(&self, size: egui::Vec2) {
|
||||
self.lock().output.window_size = Some(size);
|
||||
}
|
||||
|
||||
/// Set the desired title of the window.
|
||||
pub fn set_window_title(&mut self, title: &str) {
|
||||
self.0.output.window_title = Some(title.to_owned());
|
||||
pub fn set_window_title(&self, title: &str) {
|
||||
self.lock().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.0.output.decorated = Some(decorated);
|
||||
pub fn set_decorations(&self, decorated: bool) {
|
||||
self.lock().output.decorated = Some(decorated);
|
||||
}
|
||||
|
||||
/// 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, and works badly on Mac.
|
||||
pub fn drag_window(&mut self) {
|
||||
self.0.output.drag_window = true;
|
||||
/// Does not work on the web.
|
||||
pub fn drag_window(&self) {
|
||||
self.lock().output.drag_window = true;
|
||||
}
|
||||
|
||||
/// If you need to request a repaint from another thread, clone this and send it to that other thread.
|
||||
pub fn repaint_signal(&self) -> std::sync::Arc<dyn RepaintSignal> {
|
||||
self.0.repaint_signal.clone()
|
||||
/// This signals the [`egui`] integration that a repaint is required.
|
||||
///
|
||||
/// Call this e.g. when a background process finishes in an async context and/or background thread.
|
||||
pub fn request_repaint(&self) {
|
||||
self.lock().repaint_signal.request_repaint();
|
||||
}
|
||||
|
||||
/// for integrations only: call once per frame
|
||||
pub fn take_app_output(&self) -> crate::backend::AppOutput {
|
||||
let mut lock = self.lock();
|
||||
let next_id = lock.output.tex_allocation_data.next_id;
|
||||
let app_output = std::mem::take(&mut lock.output);
|
||||
lock.output.tex_allocation_data.next_id = next_id;
|
||||
app_output
|
||||
}
|
||||
|
||||
/// Allocate a texture. Free it again with [`Self::free_texture`].
|
||||
pub fn alloc_texture(&self, image: Image) -> egui::TextureId {
|
||||
self.lock().output.tex_allocation_data.alloc(image)
|
||||
}
|
||||
|
||||
/// Free a texture that has been previously allocated with [`Self::alloc_texture`]. Idempotent.
|
||||
pub fn free_texture(&self, id: egui::TextureId) {
|
||||
self.lock().output.tex_allocation_data.free(id);
|
||||
}
|
||||
}
|
||||
|
||||
impl TextureAllocator for Frame {
|
||||
fn alloc(&self, image: Image) -> egui::TextureId {
|
||||
self.lock().output.tex_allocation_data.alloc(image)
|
||||
}
|
||||
|
||||
fn free(&self, id: egui::TextureId) {
|
||||
self.lock().output.tex_allocation_data.free(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -344,14 +387,33 @@ pub trait TextureAllocator {
|
|||
///
|
||||
/// There is no way to change a texture.
|
||||
/// Instead allocate a new texture and free the previous one with [`Self::free`].
|
||||
fn alloc_srgba_premultiplied(
|
||||
&mut self,
|
||||
size: (usize, usize),
|
||||
srgba_pixels: &[egui::Color32],
|
||||
) -> egui::TextureId;
|
||||
fn alloc(&self, image: Image) -> egui::TextureId;
|
||||
|
||||
/// Free the given texture.
|
||||
fn free(&mut self, id: egui::TextureId);
|
||||
fn free(&self, id: egui::TextureId);
|
||||
}
|
||||
|
||||
/// A 2D color image in RAM.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Image {
|
||||
/// width, height
|
||||
pub size: [usize; 2],
|
||||
/// The pixels, row by row, from top to bottom.
|
||||
pub pixels: Vec<egui::Color32>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
/// Create an `Image` from flat RGBA data.
|
||||
/// Panics unless `size[0] * size[1] * 4 == rgba.len()`.
|
||||
/// This is usually what you want to use after having loaded an image.
|
||||
pub fn from_rgba_unmultiplied(size: [usize; 2], rgba: &[u8]) -> Self {
|
||||
assert_eq!(size[0] * size[1] * 4, rgba.len());
|
||||
let pixels = rgba
|
||||
.chunks_exact(4)
|
||||
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
|
||||
.collect();
|
||||
Self { size, pixels }
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstraction for platform dependent texture reference
|
||||
|
@ -359,21 +421,13 @@ pub trait NativeTexture {
|
|||
/// The native texture type.
|
||||
type Texture;
|
||||
|
||||
/// Bind native texture to egui texture
|
||||
/// Bind native texture to an egui texture id.
|
||||
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId;
|
||||
|
||||
/// Change id's actual pointing texture
|
||||
/// only for user texture
|
||||
/// Change what texture the given id refers to.
|
||||
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture);
|
||||
}
|
||||
|
||||
/// How to signal the [`egui`] integration that a repaint is required.
|
||||
pub trait RepaintSignal: Send + Sync {
|
||||
/// This signals the [`egui`] integration that a repaint is required.
|
||||
/// This is meant to be called when a background process finishes in an async context and/or background thread.
|
||||
fn request_repaint(&self);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// A place where you can store custom data in a way that persists when you restart the app.
|
||||
|
@ -423,29 +477,68 @@ pub const APP_KEY: &str = "app";
|
|||
|
||||
/// You only need to look here if you are writing a backend for `epi`.
|
||||
pub mod backend {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// How to signal the [`egui`] integration that a repaint is required.
|
||||
pub trait RepaintSignal: Send + Sync {
|
||||
/// This signals the [`egui`] integration that a repaint is required.
|
||||
///
|
||||
/// Call this e.g. when a background process finishes in an async context and/or background thread.
|
||||
fn request_repaint(&self);
|
||||
}
|
||||
|
||||
/// The data required by [`Frame`] each frame.
|
||||
pub struct FrameBuilder<'a> {
|
||||
pub struct FrameData {
|
||||
/// Information about the integration.
|
||||
pub info: IntegrationInfo,
|
||||
/// A way to allocate textures (on integrations that support it).
|
||||
pub tex_allocator: &'a mut dyn TextureAllocator,
|
||||
/// Where the app can issue commands back to the integration.
|
||||
pub output: &'a mut AppOutput,
|
||||
pub output: AppOutput,
|
||||
/// If you need to request a repaint from another thread, clone this and send it to that other thread.
|
||||
pub repaint_signal: std::sync::Arc<dyn RepaintSignal>,
|
||||
}
|
||||
|
||||
impl<'a> FrameBuilder<'a> {
|
||||
/// Wrap us in a [`Frame`] to send to [`App::update`].
|
||||
pub fn build(self) -> Frame<'a> {
|
||||
Frame(self)
|
||||
/// The data needed in order to allocate and free textures/images.
|
||||
#[derive(Default)]
|
||||
#[must_use]
|
||||
pub struct TexAllocationData {
|
||||
/// We allocate texture id linearly.
|
||||
pub(crate) next_id: u64,
|
||||
/// New creations this frame
|
||||
pub creations: HashMap<u64, Image>,
|
||||
/// destructions this frame.
|
||||
pub destructions: Vec<u64>,
|
||||
}
|
||||
|
||||
impl TexAllocationData {
|
||||
/// Should only be used by integrations
|
||||
pub fn take(&mut self) -> Self {
|
||||
let next_id = self.next_id;
|
||||
let ret = std::mem::take(self);
|
||||
self.next_id = next_id;
|
||||
ret
|
||||
}
|
||||
|
||||
/// Allocate a new texture.
|
||||
pub fn alloc(&mut self, image: Image) -> egui::TextureId {
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
self.creations.insert(id, image);
|
||||
egui::TextureId::User(id)
|
||||
}
|
||||
|
||||
/// Free an existing texture.
|
||||
pub fn free(&mut self, id: egui::TextureId) {
|
||||
if let egui::TextureId::User(id) = id {
|
||||
self.destructions.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Action that can be taken by the user app.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
#[derive(Default)]
|
||||
#[must_use]
|
||||
pub struct AppOutput {
|
||||
/// Set to `true` to stop the app.
|
||||
/// This does nothing for web apps.
|
||||
|
@ -457,10 +550,13 @@ pub mod backend {
|
|||
/// 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
|
||||
/// Set to some bool to change window decorations.
|
||||
pub decorated: Option<bool>,
|
||||
|
||||
/// Set to true to drap window
|
||||
/// Set to true to drag window while primary mouse button is down.
|
||||
pub drag_window: bool,
|
||||
|
||||
/// A way to allocate textures (on integrations that support it).
|
||||
pub tex_allocation_data: TexAllocationData,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@
|
|||
# to the user in the error, instead of "error: invalid channel name '[toolchain]'".
|
||||
|
||||
[toolchain]
|
||||
channel = "1.54.0"
|
||||
channel = "1.56.0"
|
||||
components = [ "rustfmt", "clippy" ]
|
||||
targets = [ "wasm32-unknown-unknown" ]
|
||||
|
|
|
@ -50,9 +50,12 @@ cargo build \
|
|||
--no-default-features \
|
||||
--features ${FEATURES}
|
||||
|
||||
# Get the output directory (in the workspace it is in another location)
|
||||
TARGET=`cargo metadata --format-version=1 | jq --raw-output .target_directory`
|
||||
|
||||
echo "Generating JS bindings for wasm…"
|
||||
TARGET_NAME="${CRATE_NAME}.wasm"
|
||||
wasm-bindgen "target/wasm32-unknown-unknown/$BUILD/$TARGET_NAME" \
|
||||
wasm-bindgen "${TARGET}/wasm32-unknown-unknown/$BUILD/$TARGET_NAME" \
|
||||
--out-dir docs --no-modules --no-typescript
|
||||
|
||||
# to get wasm-strip: apt/brew/dnf install wabt
|
||||
|
|
|
@ -26,7 +26,7 @@ cargo doc -p egui_web --target wasm32-unknown-unknown --lib --no-deps --all-feat
|
|||
(cd egui && cargo check --no-default-features --features "multi_threaded,serialize")
|
||||
(cd eframe && cargo check --no-default-features --features "egui_glow")
|
||||
(cd epi && cargo check --no-default-features)
|
||||
(cd egui_web && cargo check --no-default-features)
|
||||
# (cd egui_web && cargo check --no-default-features) # we need to pick webgl or glow backend
|
||||
# (cd egui-winit && cargo check --no-default-features) # we don't pick singlethreaded or multithreaded
|
||||
(cd egui_glium && cargo check --no-default-features)
|
||||
(cd egui_glow && cargo check --no-default-features)
|
||||
|
|
|
@ -5,5 +5,5 @@ cd "$script_path/.."
|
|||
|
||||
# Pre-requisites:
|
||||
rustup target add wasm32-unknown-unknown
|
||||
cargo install -f wasm-bindgen-cli
|
||||
cargo install wasm-bindgen-cli
|
||||
cargo update -p wasm-bindgen
|
||||
|
|
Loading…
Reference in a new issue