Merge remote-tracking branch 'egui/master' into dynamic-grid

This commit is contained in:
René Rössler 2022-01-04 12:28:13 +01:00
commit 767357c468
100 changed files with 1905 additions and 1945 deletions

View file

@ -2,7 +2,7 @@
name: Feature request name: Feature request
about: Suggest an idea for this project about: Suggest an idea for this project
title: '' title: ''
labels: enhancement labels: feature-request
assignees: '' assignees: ''
--- ---

View file

@ -17,7 +17,7 @@ jobs:
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: 1.54.0 toolchain: 1.56.0
override: true override: true
- run: sudo apt-get update && sudo apt-get install libspeechd-dev - run: sudo apt-get update && sudo apt-get install libspeechd-dev
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
@ -32,7 +32,7 @@ jobs:
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: 1.54.0 toolchain: 1.56.0
override: true override: true
- run: sudo apt-get update && sudo apt-get install libspeechd-dev - run: sudo apt-get update && sudo apt-get install libspeechd-dev
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
@ -48,7 +48,7 @@ jobs:
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: 1.54.0 toolchain: 1.56.0
override: true override: true
- run: rustup target add wasm32-unknown-unknown - run: rustup target add wasm32-unknown-unknown
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
@ -64,7 +64,7 @@ jobs:
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: 1.54.0 toolchain: 1.56.0
override: true override: true
- run: rustup target add wasm32-unknown-unknown - run: rustup target add wasm32-unknown-unknown
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
@ -80,7 +80,7 @@ jobs:
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: 1.54.0 toolchain: 1.56.0
override: true 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 - 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 - uses: actions-rs/cargo@v1
@ -96,7 +96,7 @@ jobs:
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: 1.54.0 toolchain: 1.56.0
override: true override: true
- run: rustup component add rustfmt - run: rustup component add rustfmt
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
@ -112,7 +112,7 @@ jobs:
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: 1.54.0 toolchain: 1.56.0
override: true override: true
- run: rustup component add clippy - 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 - 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 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: 1.54.0 toolchain: 1.56.0
override: true override: true
- run: sudo apt-get update && sudo apt-get install libspeechd-dev - 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 - 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 - uses: actions-rs/toolchain@v1
with: with:
profile: minimal profile: minimal
toolchain: 1.54.0 toolchain: 1.56.0
override: true override: true
- run: sudo apt-get update && sudo apt-get install libspeechd-dev - run: sudo apt-get update && sudo apt-get install libspeechd-dev
- run: rustup target add wasm32-unknown-unknown - run: rustup target add wasm32-unknown-unknown

View file

@ -8,28 +8,47 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
## Unreleased ## Unreleased
### Added ⭐ ### 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)). * Most widgets containing text (`Label`, `Button` etc) now supports rich text ([#855](https://github.com/emilk/egui/pull/855)).
* Plots: * 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 * 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 bounds) while adding items. `Plot` ([#766](https://github.com/emilk/egui/pull/766) and
[#892](https://github.com/emilk/egui/pull/892)). [#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)). * 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)). * 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)). * Added 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 `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 🔧 ### 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)). * `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)). * 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)). * `menu::menu(ui, ...)` is now `ui.menu_button(...)` ([#543](https://github.com/emilk/egui/pull/543))
* Replace `scroll_delta` and `zoom_delta` in `RawInput` with `Event::Scroll` and `Event::Zoom`. * Replaced `CtxRef::begin_frame` and `end_frame` with `CtxRef::run` ([#872](https://github.com/emilk/egui/pull/872)).
* 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)). * Replaced `scroll_delta` and `zoom_delta` in `RawInput` with `Event::Scroll` and `Event::Zoom`.
* Replace `Ui::__test` with `egui::__run_test_ui` ([#872](https://github.com/emilk/egui/pull/872)). * 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 🐛 ### 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)). * 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 🔥 ### Removed 🔥
@ -37,16 +56,19 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
* Removed `egui::paint` (use `egui::epaint` instead). * Removed `egui::paint` (use `egui::epaint` instead).
### Contributors 🙏 ### Contributors 🙏
* [5225225](https://github.com/5225225): ([#849](https://github.com/emilk/egui/pull/849)). * [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)). * [aevyrie](https://github.com/aevyrie): [#966](https://github.com/emilk/egui/pull/966).
* [Bromeon](https://github.com/Bromeon): ([#863](https://github.com/emilk/egui/pull/863)). * [B-Reif](https://github.com/B-Reif): [#875](https://github.com/emilk/egui/pull/875).
* [d10sfan](https://github.com/d10sfan) ([#832](https://github.com/emilk/egui/pull/832)). * [Bromeon](https://github.com/Bromeon): [#863](https://github.com/emilk/egui/pull/863), [#918](https://github.com/emilk/egui/pull/918).
* [EmbersArc](https://github.com/EmbersArc): ([#766](https://github.com/emilk/egui/pull/766), [#892](https://github.com/emilk/egui/pull/892)). * [d10sfan](https://github.com/d10sfan): [#832](https://github.com/emilk/egui/pull/832).
* [Hperigo](https://github.com/Hperigo): ([#905](https://github.com/emilk/egui/pull/905)). * [EmbersArc](https://github.com/EmbersArc): [#766](https://github.com/emilk/egui/pull/766), [#892](https://github.com/emilk/egui/pull/892).
* [mankinskin](https://github.com/mankinskin) ([#543](https://github.com/emilk/egui/pull/543)). * [Hperigo](https://github.com/Hperigo): [#905](https://github.com/emilk/egui/pull/905).
* [niladic](https://github.com/niladic): ([#499](https://github.com/emilk/egui/pull/499), [#863](https://github.com/emilk/egui/pull/863)). * [isegal](https://github.com/isegal): [#934](https://github.com/emilk/egui/pull/934).
* [sumibi-yakitori](https://github.com/sumibi-yakitori) ([#830](https://github.com/emilk/egui/pull/830)). * [mankinskin](https://github.com/mankinskin): [#543](https://github.com/emilk/egui/pull/543).
* [t18b219k](https://github.com/t18b219k): ([#868](https://github.com/emilk/egui/pull/868), [#888](https://github.com/emilk/egui/pull/888)). * [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 ## 0.15.0 - 2021-10-24 - Syntax highlighting and hscroll

726
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,7 @@
[![Build Status](https://github.com/emilk/egui/workflows/CI/badge.svg)](https://github.com/emilk/egui/actions?workflow=CI) [![Build Status](https://github.com/emilk/egui/workflows/CI/badge.svg)](https://github.com/emilk/egui/actions?workflow=CI)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg) ![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
[![Discord](https://img.shields.io/discord/900275882684477440?label=egui%20discord)](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). 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 ## 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 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 ## 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`. 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` `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: 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! **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). To alleviate the above mentioned performance issues the default max-width of an egui web app is 1024 points. You can change this by overriding the `fn max_size_points` of [`epi::App`](https://docs.rs/epi/latest/epi/trait.App.html).
### How do I render 3D stuff in an egui area? ### How do I render 3D stuff in an egui area?
egui can't do 3D graphics itself, but if you use a 3D library (e.g. [`glium`](https://github.com/glium/glium) using [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium), or [`miniquad`](https://github.com/not-fl3/miniquad) using [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad)) you can render your 3D content to a texture, then display it using [`ui.image(…)`](https://docs.rs/egui/latest/egui/struct.Ui.html#method.image). You first need to convert the native texture to an [`egui::TextureId`](https://docs.rs/egui/latest/egui/enum.TextureId.html), and how to do this depends on the integration you use (e.g. [`register_glium_texture`](https://docs.rs/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>. 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");`. 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 ### Inspiration

3
docs/README.md Normal file
View 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.

View file

@ -213,47 +213,47 @@ function makeMutClosure(arg0, arg1, dtor, f) {
return real; return real;
} }
function __wbg_adapter_30(arg0, arg1) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { function __wbg_adapter_51(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h768f86e2936098f5(arg0, arg1); 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) { function __wbg_adapter_54(arg0, arg1) {
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_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__he78f81eace0d019f(arg0, arg1);
} }
function __wbg_adapter_57(arg0, arg1, arg2) { 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) { 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) { function makeClosure(arg0, arg1, dtor, f) {
@ -278,11 +278,11 @@ function makeClosure(arg0, arg1, dtor, f) {
return real; return real;
} }
function __wbg_adapter_63(arg0, arg1, arg2) { 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) { 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) { imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(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) { imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = getObject(arg1); const obj = getObject(arg1);
var ret = typeof(obj) === 'string' ? obj : undefined; var ret = typeof(obj) === 'string' ? obj : undefined;
@ -368,30 +377,21 @@ async function init(input) {
var ret = getStringFromWasm0(arg0, arg1); var ret = getStringFromWasm0(arg0, arg1);
return addHeapObject(ret); 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) { imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
const obj = getObject(arg1); const obj = getObject(arg1);
var ret = typeof(obj) === 'number' ? obj : undefined; var ret = typeof(obj) === 'number' ? obj : undefined;
getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret; getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(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) { imports.wbg.__wbindgen_boolean_get = function(arg0) {
const v = getObject(arg0); const v = getObject(arg0);
var ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; var ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
return ret; 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) { imports.wbg.__wbg_instanceof_WebGl2RenderingContext_56ad96bfac3f5531 = function(arg0) {
var ret = getObject(arg0) instanceof WebGL2RenderingContext; var ret = getObject(arg0) instanceof WebGL2RenderingContext;
return ret; return ret;
@ -621,14 +621,9 @@ async function init(input) {
var ret = getObject(arg0).setTimeout(getObject(arg1), arg2); var ret = getObject(arg0).setTimeout(getObject(arg1), arg2);
return ret; return ret;
}, arguments) }; }, arguments) };
imports.wbg.__wbg_writeText_3b86a6dbc18b261b = function(arg0, arg1, arg2) { imports.wbg.__wbg_addEventListener_52721772cc0a7f30 = function() { return handleError(function (arg0, arg1, arg2, arg3) {
var ret = getObject(arg0).writeText(getStringFromWasm0(arg1, arg2)); getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));
return addHeapObject(ret); }, arguments) };
};
imports.wbg.__wbg_clipboardData_d717f7cf398c0dd9 = function(arg0) {
var ret = getObject(arg0).clipboardData;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_matches_76fae292b8cd60a6 = function(arg0) { imports.wbg.__wbg_matches_76fae292b8cd60a6 = function(arg0) {
var ret = getObject(arg0).matches; var ret = getObject(arg0).matches;
return ret; return ret;
@ -704,6 +699,82 @@ async function init(input) {
var ret = getObject(arg0)[arg1 >>> 0]; var ret = getObject(arg0)[arg1 >>> 0];
return isLikeNone(ret) ? 0 : addHeapObject(ret); 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) { imports.wbg.__wbg_keyCode_490ed69472addfdc = function(arg0) {
var ret = getObject(arg0).keyCode; var ret = getObject(arg0).keyCode;
return ret; return ret;
@ -735,82 +806,6 @@ async function init(input) {
getInt32Memory0()[arg0 / 4 + 1] = len0; getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0; 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) { imports.wbg.__wbg_clipboard_3dff7cff084c4be2 = function(arg0) {
var ret = getObject(arg0).clipboard; var ret = getObject(arg0).clipboard;
return isLikeNone(ret) ? 0 : addHeapObject(ret); return isLikeNone(ret) ? 0 : addHeapObject(ret);
@ -838,6 +833,14 @@ async function init(input) {
var ret = getObject(arg0).arrayBuffer(); var ret = getObject(arg0).arrayBuffer();
return addHeapObject(ret); 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) { imports.wbg.__wbg_top_3946f8347860b55c = function(arg0) {
var ret = getObject(arg0).top; var ret = getObject(arg0).top;
return ret; return ret;
@ -873,6 +876,14 @@ async function init(input) {
var ret = getObject(arg0).scrollLeft; var ret = getObject(arg0).scrollLeft;
return ret; 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) { imports.wbg.__wbg_getBoundingClientRect_2fba0402ea2a6ec4 = function(arg0) {
var ret = getObject(arg0).getBoundingClientRect(); var ret = getObject(arg0).getBoundingClientRect();
return addHeapObject(ret); return addHeapObject(ret);
@ -980,6 +991,10 @@ async function init(input) {
var ret = getObject(arg0).getExtension(getStringFromWasm0(arg1, arg2)); var ret = getObject(arg0).getExtension(getStringFromWasm0(arg1, arg2));
return isLikeNone(ret) ? 0 : addHeapObject(ret); return isLikeNone(ret) ? 0 : addHeapObject(ret);
}, arguments) }; }, 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) { imports.wbg.__wbg_getProgramInfoLog_b60e82d52c200cbd = function(arg0, arg1, arg2) {
var ret = getObject(arg1).getProgramInfoLog(getObject(arg2)); var ret = getObject(arg1).getProgramInfoLog(getObject(arg2));
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 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) { imports.wbg.__wbg_focus_00530e359f44fc6e = function() { return handleError(function (arg0) {
getObject(arg0).focus(); getObject(arg0).focus();
}, arguments) }; }, arguments) };
imports.wbg.__wbg_data_dbff09eb89176161 = function(arg0, arg1) { imports.wbg.__wbg_setProperty_1460c660bc329763 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
var ret = getObject(arg1).data; getObject(arg0).setProperty(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); }, arguments) };
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_hash_0fff5255cf3c317c = function() { return handleError(function (arg0, arg1) { imports.wbg.__wbg_hash_0fff5255cf3c317c = function() { return handleError(function (arg0, arg1) {
var ret = getObject(arg1).hash; var ret = getObject(arg1).hash;
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); 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) { imports.wbg.__wbg_setItem_b0c4561489dffecd = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
}, arguments) }; }, arguments) };
imports.wbg.__wbg_setProperty_1460c660bc329763 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { imports.wbg.__wbg_data_dbff09eb89176161 = function(arg0, arg1) {
getObject(arg0).setProperty(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); var ret = getObject(arg1).data;
}, arguments) }; 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) { imports.wbg.__wbg_type_a6fcda966902940d = function(arg0, arg1) {
var ret = getObject(arg1).type; var ret = getObject(arg1).type;
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@ -1169,13 +1184,18 @@ async function init(input) {
var ret = getObject(arg0).deltaMode; var ret = getObject(arg0).deltaMode;
return ret; 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) { imports.wbg.__wbg_dataTransfer_ebba35c1049e694f = function(arg0) {
var ret = getObject(arg0).dataTransfer; var ret = getObject(arg0).dataTransfer;
return isLikeNone(ret) ? 0 : addHeapObject(ret); 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) { imports.wbg.__wbg_instanceof_Response_e1b11afbefa5b563 = function(arg0) {
var ret = getObject(arg0) instanceof Response; var ret = getObject(arg0) instanceof Response;
return ret; return ret;
@ -1371,56 +1391,56 @@ async function init(input) {
var ret = wasm.memory; var ret = wasm.memory;
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper1990 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper1901 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_30); var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_30);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper1991 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper1902 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_33); var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_33);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper1993 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper1904 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_36); var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_36);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper1995 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper1906 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_39); var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_39);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper1997 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper1908 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_42); var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_42);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper1999 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper1911 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_45); var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_45);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper2002 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper1913 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_48); var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_48);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper2004 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper1915 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_51); var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_51);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper2006 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper1917 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_54); var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_54);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper2008 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper1919 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 769, __wbg_adapter_57); var ret = makeMutClosure(arg0, arg1, 720, __wbg_adapter_57);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper2042 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper2113 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 802, __wbg_adapter_60); var ret = makeMutClosure(arg0, arg1, 823, __wbg_adapter_60);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper2109 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper2179 = function(arg0, arg1, arg2) {
var ret = makeClosure(arg0, arg1, 851, __wbg_adapter_63); var ret = makeClosure(arg0, arg1, 871, __wbg_adapter_63);
return addHeapObject(ret); return addHeapObject(ret);
}; };
imports.wbg.__wbindgen_closure_wrapper2111 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper2181 = function(arg0, arg1, arg2) {
var ret = makeClosure(arg0, arg1, 851, __wbg_adapter_66); var ret = makeClosure(arg0, arg1, 871, __wbg_adapter_66);
return addHeapObject(ret); return addHeapObject(ret);
}; };

Binary file not shown.

BIN
docs/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -45,12 +45,59 @@
left: 50%; left: 50%;
transform: translate(-50%, 0%); 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> </style>
</head> </head>
<body> <body>
<!-- The WASM code will resize this dynamically --> <!-- The WASM code will resize the canvas dynamically -->
<canvas id="the_canvas_id"></canvas> <canvas id="the_canvas_id"></canvas>
<div class="loading" id="loading">
Loading…&nbsp;&nbsp;
<div class="lds-dual-ring"></div>
</div>
<script> <script>
// The `--no-modules`-generated JS from `wasm-bindgen` attempts to use // The `--no-modules`-generated JS from `wasm-bindgen` attempts to use
@ -77,9 +124,13 @@
.catch(console.error); .catch(console.error);
function on_wasm_loaded() { 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"); wasm_bindgen.start("the_canvas_id");
console.log("egui app started.");
document.getElementById("loading").remove();
} }
</script> </script>
</body> </body>

View file

@ -5,6 +5,14 @@ NOTE: [`egui_web`](egui_web/CHANGELOG.md), [`egui-winit`](egui-winit/CHANGELOG.m
## Unreleased ## 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 ## 0.15.0 - 2021-10-24

View file

@ -1,9 +1,10 @@
[package] [package]
name = "eframe" name = "eframe"
version = "0.15.0" version = "0.16.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"] authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "egui framework - write GUI apps that compiles to web and/or natively" 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" homepage = "https://github.com/emilk/egui/tree/master/eframe"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
readme = "README.md" readme = "README.md"
@ -23,25 +24,25 @@ all-features = true
[lib] [lib]
[dependencies] [dependencies]
egui = { version = "0.15.0", path = "../egui", default-features = false } egui = { version = "0.16.0", path = "../egui", default-features = false }
epi = { version = "0.15.0", path = "../epi" } epi = { version = "0.16.0", path = "../epi" }
# native: # native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
egui-winit = { version = "0.15.0", path = "../egui-winit", default-features = false } egui-winit = { version = "0.16.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_glium = { version = "0.16.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_glow = { version = "0.16.0", path = "../egui_glow", default-features = false, features = ["clipboard", "epi", "links", "winit"], optional = true }
# web: # web:
[target.'cfg(target_arch = "wasm32")'.dependencies] [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] [dev-dependencies]
image = { version = "0.23", default-features = false, features = ["png"] } image = { version = "0.23", default-features = false, features = ["png"] }
rfd = "0.5.0" rfd = "0.6"
[features] [features]
default = ["default_fonts", "egui_glium"] default = ["default_fonts", "egui_glow"]
# If set, egui will use `include_bytes!` to bundle some fonts. # If set, egui will use `include_bytes!` to bundle some fonts.
# If you plan on specifying your own fonts you may disable this feature. # If you plan on specifying your own fonts you may disable this feature.

View file

@ -10,6 +10,10 @@
To get started, go to <https://github.com/emilk/eframe_template/> and follow the instructions there! 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. `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 ## 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 ``` 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). `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 ## 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). * Audio: [`cpal`](https://github.com/RustAudio/cpal).
* HTTP client: [`ehttp`](https://github.com/emilk/ehttp). * HTTP client: [`ehttp`](https://github.com/emilk/ehttp).

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

View file

@ -1,3 +1,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::{egui, epi}; use eframe::{egui, epi};
#[derive(Default)] #[derive(Default)]
@ -11,13 +13,11 @@ impl epi::App for MyApp {
"Native file dialogs and drag-and-drop files" "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| { egui::CentralPanel::default().show(ctx, |ui| {
ui.label("Drag-and-drop files onto the window!"); ui.label("Drag-and-drop files onto the window!");
if cfg!(target_os = "macos") { if ui.button("Open file…").clicked() {
// Awaiting fix of winit bug: https://github.com/rust-windowing/winit/pull/2027
} else if ui.button("Open file…").clicked() {
if let Some(path) = rfd::FileDialog::new().pick_file() { if let Some(path) = rfd::FileDialog::new().pick_file() {
self.picked_path = Some(path.display().to_string()); self.picked_path = Some(path.display().to_string());
} }

View file

@ -1,3 +1,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::{egui, epi}; use eframe::{egui, epi};
struct MyApp { struct MyApp {
@ -19,7 +21,7 @@ impl epi::App for MyApp {
"My egui App" "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; let Self { name, age } = self;
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {

View file

@ -1,3 +1,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::{egui, epi}; use eframe::{egui, epi};
#[derive(Default)] #[derive(Default)]
@ -10,26 +12,20 @@ impl epi::App for MyApp {
"Show an image with eframe/egui" "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() { if self.texture.is_none() {
// Load the image: // Load the image:
let image_data = include_bytes!("rust-logo-256x256.png"); let image_data = include_bytes!("rust-logo-256x256.png");
use image::GenericImageView; use image::GenericImageView;
let image = image::load_from_memory(image_data).expect("Failed to load image"); let image = image::load_from_memory(image_data).expect("Failed to load image");
let image_buffer = image.to_rgba8(); 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(); let pixels = image_buffer.into_vec();
assert_eq!(size.0 * size.1 * 4, pixels.len()); let image = epi::Image::from_rgba_unmultiplied(size, &pixels);
let pixels: Vec<_> = pixels
.chunks_exact(4)
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
.collect();
// Allocate a texture: // Allocate a texture:
let texture = frame let texture = frame.alloc_texture(image);
.tex_allocator() let size = egui::Vec2::new(size[0] as f32, size[1] as f32);
.alloc_srgba_premultiplied(size, &pixels);
let size = egui::Vec2::new(size.0 as f32, size.1 as f32);
self.texture = Some((size, texture)); self.texture = Some((size, texture));
} }

View file

@ -6,6 +6,8 @@
//! //!
//! To get started, look at <https://github.com/emilk/eframe_template>. //! 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 //! 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`. //! 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" //! "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| { //! egui::CentralPanel::default().show(ctx, |ui| {
//! ui.heading("Hello World!"); //! ui.heading("Hello World!");
//! }); //! });
@ -55,7 +57,12 @@
// Forbid warnings in release builds: // Forbid warnings in release builds:
#![cfg_attr(not(debug_assertions), deny(warnings))] #![cfg_attr(not(debug_assertions), deny(warnings))]
#![forbid(unsafe_code)] #![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)] #![allow(clippy::needless_doctest_main)]
pub use {egui, epi}; 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 // When compiling natively
/// Call from `fn main` like this: ` /// Call from `fn main` like this:
/// ``` no_run /// ``` no_run
/// use eframe::{epi, egui}; /// 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" /// "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| { /// egui::CentralPanel::default().show(ctx, |ui| {
/// ui.heading("Hello World!"); /// 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) egui_glium::run(app, &native_options)
} }
/// Call from `fn main` like this: ` /// Call from `fn main` like this:
/// ``` no_run /// ``` 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() { /// fn main() {
/// let app = MyEguiApp::default(); /// let app = MyEguiApp::default();
/// let native_options = eframe::NativeOptions::default(); /// let native_options = eframe::NativeOptions::default();

View file

@ -4,9 +4,15 @@ All notable changes to the `egui-winit` integration will be noted in this file.
## Unreleased ## Unreleased
* Add helper `EpiIntegration` ([#871](https://github.com/emilk/egui/pull/871)). * Replaced `std::time::Instant` with `instant::Instant` for WebAssembly compatability ([#1023](https://github.com/emilk/egui/pull/1023))
* 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)).
## 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 ## 0.15.0 - 2021-10-24
First stand-alone release. Previously part of `egui_glium`. First stand-alone release. Previously part of `egui_glium`.

View file

@ -1,9 +1,10 @@
[package] [package]
name = "egui-winit" name = "egui-winit"
version = "0.15.0" version = "0.16.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"] authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for using egui with winit" 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" homepage = "https://github.com/emilk/egui/tree/master/egui-winit"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
readme = "README.md" readme = "README.md"
@ -21,17 +22,18 @@ include = [
all-features = true all-features = true
[dependencies] [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"] }
winit = "0.25" 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 } copypasta = { version = "0.7", optional = true }
serde = { version = "1.0", optional = true, features = ["derive"] } serde = { version = "1.0", optional = true, features = ["derive"] }
webbrowser = { version = "0.5", optional = true } webbrowser = { version = "0.5", optional = true }
# feature screen_reader # feature screen_reader
tts = { version = "0.17", optional = true } tts = { version = "0.19", optional = true }
[features] [features]
default = ["clipboard", "links"] default = ["clipboard", "links"]

View file

@ -55,9 +55,10 @@ pub fn handle_app_output(
window: &winit::window::Window, window: &winit::window::Window,
current_pixels_per_point: f32, current_pixels_per_point: f32,
app_output: epi::backend::AppOutput, app_output: epi::backend::AppOutput,
) { ) -> epi::backend::TexAllocationData {
let epi::backend::AppOutput { let epi::backend::AppOutput {
quit: _, quit: _,
tex_allocation_data,
window_size, window_size,
window_title, window_title,
decorated, decorated,
@ -85,6 +86,8 @@ pub fn handle_app_output(
if drag_window { if drag_window {
let _ = window.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. /// For loading/saving app state and/or egui memory to disk.
pub struct Persistence { pub struct Persistence {
storage: Option<Box<dyn epi::Storage>>, storage: Option<Box<dyn epi::Storage>>,
last_auto_save: std::time::Instant, last_auto_save: instant::Instant,
} }
#[allow(clippy::unused_self)] #[allow(clippy::unused_self)]
@ -113,7 +116,7 @@ impl Persistence {
Self { Self {
storage: create_storage(app_name), 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, egui_ctx: &egui::Context,
window: &winit::window::Window, 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() { if now - self.last_auto_save > app.auto_save_interval() {
self.save(app, egui_ctx, window); self.save(app, egui_ctx, window);
self.last_auto_save = now; self.last_auto_save = now;
@ -186,13 +189,11 @@ impl Persistence {
/// Everything needed to make a winit-based integration for [`epi`]. /// Everything needed to make a winit-based integration for [`epi`].
pub struct EpiIntegration { pub struct EpiIntegration {
integration_name: &'static str, frame: epi::Frame,
persistence: crate::epi::Persistence, persistence: crate::epi::Persistence,
repaint_signal: std::sync::Arc<dyn epi::RepaintSignal>,
pub egui_ctx: egui::CtxRef, pub egui_ctx: egui::CtxRef,
egui_winit: crate::State, egui_winit: crate::State,
pub app: Box<dyn epi::App>, pub app: Box<dyn epi::App>,
latest_frame_time: Option<f32>,
/// When set, it is time to quit /// When set, it is time to quit
quit: bool, quit: bool,
} }
@ -201,8 +202,7 @@ impl EpiIntegration {
pub fn new( pub fn new(
integration_name: &'static str, integration_name: &'static str,
window: &winit::window::Window, window: &winit::window::Window,
tex_allocator: &mut dyn epi::TextureAllocator, repaint_signal: std::sync::Arc<dyn epi::backend::RepaintSignal>,
repaint_signal: std::sync::Arc<dyn epi::RepaintSignal>,
persistence: crate::epi::Persistence, persistence: crate::epi::Persistence,
app: Box<dyn epi::App>, app: Box<dyn epi::App>,
) -> Self { ) -> Self {
@ -210,54 +210,50 @@ impl EpiIntegration {
*egui_ctx.memory() = persistence.load_memory().unwrap_or_default(); *egui_ctx.memory() = persistence.load_memory().unwrap_or_default();
let mut slf = Self { let frame = epi::Frame::new(epi::backend::FrameData {
integration_name, info: epi::IntegrationInfo {
persistence, 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, repaint_signal,
});
let mut slf = Self {
frame,
persistence,
egui_ctx, egui_ctx,
egui_winit: crate::State::new(window), egui_winit: crate::State::new(window),
app, app,
latest_frame_time: None,
quit: false, quit: false,
}; };
slf.setup(window, tex_allocator); slf.setup(window);
if slf.app.warm_up_enabled() { if slf.app.warm_up_enabled() {
slf.warm_up(window, tex_allocator); slf.warm_up(window);
} }
slf slf
} }
fn setup( fn setup(&mut self, window: &winit::window::Window) {
&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();
self.app 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; self.quit |= app_output.quit;
let tex_alloc_data =
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
self.frame.lock().output.tex_allocation_data = tex_alloc_data; // Do it later
} }
fn warm_up( fn warm_up(&mut self, window: &winit::window::Window) {
&mut self,
window: &winit::window::Window,
tex_allocator: &mut dyn epi::TextureAllocator,
) {
let saved_memory = self.egui_ctx.memory().clone(); let saved_memory = self.egui_ctx.memory().clone();
self.egui_ctx.memory().set_everything_is_visible(true); 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.memory() = saved_memory; // We don't want to remember that windows were huge.
self.egui_ctx.clear_animations(); self.egui_ctx.clear_animations();
} }
@ -277,38 +273,31 @@ impl EpiIntegration {
pub fn update( pub fn update(
&mut self, &mut self,
window: &winit::window::Window, window: &winit::window::Window,
tex_allocator: &mut dyn epi::TextureAllocator, ) -> (
) -> (bool, Vec<egui::epaint::ClippedShape>) { bool,
let frame_start = std::time::Instant::now(); epi::backend::TexAllocationData,
Vec<egui::epaint::ClippedShape>,
) {
let frame_start = instant::Instant::now();
let raw_input = self.egui_winit.take_egui_input(window); let raw_input = self.egui_winit.take_egui_input(window);
let 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| { 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; let needs_repaint = egui_output.needs_repaint;
self.egui_winit self.egui_winit
.handle_output(window, &self.egui_ctx, egui_output); .handle_output(window, &self.egui_ctx, egui_output);
let app_output = self.frame.take_app_output();
self.quit |= app_output.quit; self.quit |= app_output.quit;
let tex_allocation_data =
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output); let frame_time = (instant::Instant::now() - frame_start).as_secs_f64() as f32;
self.frame.lock().info.cpu_usage = Some(frame_time);
let frame_time = (std::time::Instant::now() - frame_start).as_secs_f64() as f32; (needs_repaint, tex_allocation_data, shapes)
self.latest_frame_time = Some(frame_time);
(needs_repaint, shapes)
} }
pub fn maybe_autosave(&mut self, window: &winit::window::Window) { pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
@ -322,17 +311,3 @@ impl EpiIntegration {
.save(&mut *self.app, &self.egui_ctx, window); .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)),
}
}

View file

@ -76,9 +76,9 @@
clippy::verbose_file_reads, clippy::verbose_file_reads,
clippy::zero_sized_map_values, clippy::zero_sized_map_values,
future_incompatible, future_incompatible,
missing_crate_level_docs,
nonstandard_style, nonstandard_style,
rust_2018_idioms rust_2018_idioms,
rustdoc::missing_crate_level_docs
)] )]
#![allow(clippy::float_cmp)] #![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]
@ -105,7 +105,7 @@ pub fn screen_size_in_pixels(window: &winit::window::Window) -> egui::Vec2 {
/// Handles the integration between egui and winit. /// Handles the integration between egui and winit.
pub struct State { pub struct State {
start_time: std::time::Instant, start_time: instant::Instant,
egui_input: egui::RawInput, egui_input: egui::RawInput,
pointer_pos_in_points: Option<egui::Pos2>, pointer_pos_in_points: Option<egui::Pos2>,
any_pointer_button_down: bool, any_pointer_button_down: bool,
@ -137,7 +137,7 @@ impl State {
/// Initialize with a given dpi scaling. /// Initialize with a given dpi scaling.
pub fn from_pixels_per_point(pixels_per_point: f32) -> Self { pub fn from_pixels_per_point(pixels_per_point: f32) -> Self {
Self { Self {
start_time: std::time::Instant::now(), start_time: instant::Instant::now(),
egui_input: egui::RawInput { egui_input: egui::RawInput {
pixels_per_point: Some(pixels_per_point), pixels_per_point: Some(pixels_per_point),
..Default::default() ..Default::default()
@ -458,6 +458,9 @@ impl State {
// https://github.com/rust-windowing/winit/issues/1695 being closed // https://github.com/rust-windowing/winit/issues/1695 being closed
delta.x *= -1.0; 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 { if self.egui_input.modifiers.ctrl || self.egui_input.modifiers.command {
// Treat as zoom instead: // Treat as zoom instead:

View file

@ -1,9 +1,10 @@
[package] [package]
name = "egui" name = "egui"
version = "0.15.0" version = "0.16.1"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"] authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Simple, portable immediate mode GUI library for Rust" description = "Simple, portable immediate mode GUI library for Rust"
edition = "2018" edition = "2021"
rust-version = "1.56"
homepage = "https://github.com/emilk/egui" homepage = "https://github.com/emilk/egui"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
readme = "../README.md" readme = "../README.md"
@ -23,7 +24,7 @@ all-features = true
[lib] [lib]
[dependencies] [dependencies]
epaint = { version = "0.15.0", path = "../epaint", default-features = false } epaint = { version = "0.16.0", path = "../epaint", default-features = false }
ahash = "0.7" ahash = "0.7"
nohash-hasher = "0.2" nohash-hasher = "0.2"

5
egui/examples/README.md Normal file
View 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/).

View file

@ -144,6 +144,7 @@ pub(crate) fn paint_icon(ui: &mut Ui, openness: f32, response: &Response) {
pub struct CollapsingHeader { pub struct CollapsingHeader {
text: WidgetText, text: WidgetText,
default_open: bool, default_open: bool,
open: Option<bool>,
id_source: Id, id_source: Id,
enabled: bool, enabled: bool,
selectable: bool, selectable: bool,
@ -164,6 +165,7 @@ impl CollapsingHeader {
Self { Self {
text, text,
default_open: false, default_open: false,
open: None,
id_source, id_source,
enabled: true, enabled: true,
selectable: false, selectable: false,
@ -179,6 +181,16 @@ impl CollapsingHeader {
self 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. /// 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. /// This is useful if the title label is dynamic or not unique.
pub fn id_source(mut self, id_source: impl Hash) -> Self { pub fn id_source(mut self, id_source: impl Hash) -> Self {
@ -186,12 +198,6 @@ impl CollapsingHeader {
self 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. /// If you set this to `false`, the `CollapsingHeader` will be grayed out and un-clickable.
/// ///
/// This is a convenience for [`Ui::set_enabled`]. /// This is a convenience for [`Ui::set_enabled`].
@ -256,6 +262,7 @@ impl CollapsingHeader {
let Self { let Self {
text, text,
default_open, default_open,
open,
id_source, id_source,
enabled: _, enabled: _,
selectable: _, selectable: _,
@ -291,10 +298,16 @@ impl CollapsingHeader {
); );
let mut state = State::from_memory_with_default_open(ui.ctx(), id, default_open); 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); state.toggle(ui);
header_response.mark_changed(); header_response.mark_changed();
} }
header_response header_response
.widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text())); .widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text()));

View file

@ -733,13 +733,16 @@ impl Prepared {
state.offset = state.offset.min(available_offset); state.offset = state.offset.min(available_offset);
state.offset = state.offset.max(Vec2::ZERO); 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 // 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 // state anyway so that entering sticky mode at an arbitrary time
// has appropriate effect. // has appropriate effect.
state.scroll_stuck_to_end = [ state.scroll_stuck_to_end = [
state.offset[0] == available_offset[0], (state.offset[0] == available_offset[0])
state.offset[1] == available_offset[1], || (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; state.show_scroll = show_scroll_this_frame;

View file

@ -26,6 +26,9 @@ use epaint::{stats::*, text::Fonts, *};
/// ///
/// [`CtxRef`] is cheap to clone, and any clones refers to the same mutable data. /// [`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: /// # Example:
/// ///
/// ``` no_run /// ``` no_run
@ -49,7 +52,6 @@ use epaint::{stats::*, text::Fonts, *};
/// paint(clipped_meshes); /// paint(clipped_meshes);
/// } /// }
/// ``` /// ```
///
#[derive(Clone)] #[derive(Clone)]
pub struct CtxRef(std::sync::Arc<Context>); 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`]. /// 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. /// 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] #[must_use]
pub fn run( pub fn run(
&mut self, &mut self,
new_input: RawInput, new_input: RawInput,
run_ui: impl FnOnce(&CtxRef), run_ui: impl FnOnce(&CtxRef),
) -> (Output, Vec<ClippedShape>) { ) -> (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(); let mut self_: Context = (*self.0).clone();
self_.begin_frame_mut(new_input); self_.begin_frame_mut(new_input);
*self = Self(Arc::new(self_)); *self = Self(Arc::new(self_));
run_ui(self);
self.end_frame()
} }
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -299,6 +336,11 @@ impl CtxRef {
memory.surrender_focus(id); 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 response
} }
@ -311,30 +353,21 @@ impl CtxRef {
pub fn debug_painter(&self) -> Painter { pub fn debug_painter(&self) -> Painter {
Self::layer_painter(self, LayerId::debug()) 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. /// 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`. /// 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, /// 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. /// but you are likely the first person to try it.
#[derive(Default)] #[derive(Default)]
pub struct Context { pub struct Context {
@ -343,7 +376,7 @@ pub struct Context {
// This means everything else needs to be behind an Arc. // This means everything else needs to be behind an Arc.
// We can probably come up with a nicer design. // 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>>, fonts: Option<Arc<Fonts>>,
memory: Arc<Mutex<Memory>>, memory: Arc<Mutex<Memory>>,
animation_manager: Arc<Mutex<AnimationManager>>, animation_manager: Arc<Mutex<AnimationManager>>,
@ -435,11 +468,12 @@ impl Context {
.expect("No fonts available until first call to CtxRef::run()") .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()`]. /// Not valid until first call to [`CtxRef::run()`].
/// That's because since we don't know the proper `pixels_per_point` until then. /// That's because since we don't know the proper `pixels_per_point` until then.
pub fn texture(&self) -> Arc<epaint::Texture> { pub fn font_image(&self) -> Arc<epaint::FontImage> {
self.fonts().texture() self.fonts().font_image()
} }
/// Tell `egui` which fonts to use. /// 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. /// 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`]. /// You can transform the returned shapes into triangles with a call to [`Context::tessellate`].
#[must_use] #[must_use]
fn end_frame(&self) -> (Output, Vec<ClippedShape>) { pub fn end_frame(&self) -> (Output, Vec<ClippedShape>) {
if self.input.wants_repaint() { if self.input.wants_repaint() {
self.request_repaint(); self.request_repaint();
} }
@ -664,7 +698,7 @@ impl Context {
let clipped_meshes = tessellator::tessellate_shapes( let clipped_meshes = tessellator::tessellate_shapes(
shapes, shapes,
tessellation_options, tessellation_options,
self.fonts().texture().size(), self.fonts().font_image().size(),
); );
*self.paint_stats.lock() = paint_stats.with_clipped_meshes(&clipped_meshes); *self.paint_stats.lock() = paint_stats.with_clipped_meshes(&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. /// 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 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 { pub fn animate_bool(&self, id: Id, value: bool) -> f32 {
let animation_time = self.style().animation_time; 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 = let animated_value =
self.animation_manager self.animation_manager
.lock() .lock()
@ -808,7 +849,7 @@ impl Context {
.show(ui, |ui| { .show(ui, |ui| {
let mut font_definitions = self.fonts().definitions().clone(); let mut font_definitions = self.fonts().definitions().clone();
font_definitions.ui(ui); font_definitions.ui(ui);
self.fonts().texture().ui(ui); self.fonts().font_image().ui(ui);
self.set_fonts(font_definitions); self.set_fonts(font_definitions);
}); });

View file

@ -1,7 +1,7 @@
//! uis for egui types. //! uis for egui types.
use crate::*; use crate::*;
impl Widget for &epaint::Texture { impl Widget for &epaint::FontImage {
fn ui(self, ui: &mut Ui) -> Response { fn ui(self, ui: &mut Ui) -> Response {
use epaint::Mesh; use epaint::Mesh;

View file

@ -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>. //! 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. //! `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) //! 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). //! which uses [`eframe`](https://docs.rs/eframe).
@ -349,9 +349,9 @@
clippy::verbose_file_reads, clippy::verbose_file_reads,
clippy::zero_sized_map_values, clippy::zero_sized_map_values,
future_incompatible, future_incompatible,
missing_crate_level_docs,
nonstandard_style, nonstandard_style,
rust_2018_idioms rust_2018_idioms,
rustdoc::missing_crate_level_docs
)] )]
#![allow(clippy::float_cmp)] #![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]
@ -386,7 +386,7 @@ pub use emath::{lerp, pos2, remap, remap_clamp, vec2, Align, Align2, NumExt, Pos
pub use epaint::{ pub use epaint::{
color, mutex, color, mutex,
text::{FontData, FontDefinitions, FontFamily, TextStyle}, text::{FontData, FontDefinitions, FontFamily, TextStyle},
ClippedMesh, Color32, Rgba, Shape, Stroke, Texture, TextureId, ClippedMesh, Color32, FontImage, Rgba, Shape, Stroke, TextureId,
}; };
pub mod text { pub mod text {

View file

@ -327,13 +327,16 @@ impl Memory {
self.interaction.focus.id 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) { if self.had_focus_last_frame(id) && self.has_focus(id) {
self.interaction.focus.is_focus_locked = lock_focus; 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) { if self.had_focus_last_frame(id) && self.has_focus(id) {
self.interaction.focus.is_focus_locked self.interaction.focus.is_focus_locked
} else { } else {

View file

@ -42,7 +42,7 @@ impl BarState {
ctx.memory().data.insert_temp(bar_id, self); 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`] /// Should be called from [`Context`] on a [`Response`]
pub fn bar_menu<R>( pub fn bar_menu<R>(
&mut self, &mut self,
@ -87,8 +87,11 @@ pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResp
add_contents(ui) add_contents(ui)
}) })
} }
/// Construct a top level menu in a menu bar. This would be e.g. "File", "Edit" etc. /// 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. /// Returns `None` if the menu is not open.
pub fn menu_button<R>( pub fn menu_button<R>(
ui: &mut Ui, ui: &mut Ui,
@ -97,8 +100,11 @@ pub fn menu_button<R>(
) -> InnerResponse<Option<R>> { ) -> InnerResponse<Option<R>> {
stationary_menu_impl(ui, title, Box::new(add_contents)) stationary_menu_impl(ui, title, Box::new(add_contents))
} }
/// Construct a nested sub menu in another menu. /// Construct a nested sub menu in another menu.
/// ///
/// Opens on hover.
///
/// Returns `None` if the menu is not open. /// Returns `None` if the menu is not open.
pub(crate) fn submenu_button<R>( pub(crate) fn submenu_button<R>(
ui: &mut Ui, ui: &mut Ui,
@ -150,7 +156,9 @@ pub(crate) fn menu_ui<'c, R>(
inner_response 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>( fn stationary_menu_impl<'c, R>(
ui: &mut Ui, ui: &mut Ui,
title: impl Into<WidgetText>, title: impl Into<WidgetText>,
@ -275,7 +283,10 @@ impl MenuRoot {
} }
(MenuResponse::Stay, None) (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( fn stationary_interaction(
response: &Response, response: &Response,
root: &mut MenuRootManager, root: &mut MenuRootManager,
@ -310,7 +321,8 @@ impl MenuRoot {
} }
MenuResponse::Stay MenuResponse::Stay
} }
/// interaction with a context menu
/// Interaction with a context menu (secondary clicks).
fn context_interaction( fn context_interaction(
response: &Response, response: &Response,
root: &mut Option<MenuRoot>, root: &mut Option<MenuRoot>,
@ -328,10 +340,9 @@ impl MenuRoot {
destroy = root.id == response.id; destroy = root.id == response.id;
} }
if !in_old_menu { if !in_old_menu {
let in_target = response.hovered(); if response.hovered() && pointer.secondary_down() {
if in_target && pointer.secondary_down() {
return MenuResponse::Create(pos, id); return MenuResponse::Create(pos, id);
} else if (in_target && pointer.primary_down()) || destroy { } else if (response.hovered() && pointer.primary_down()) || destroy {
return MenuResponse::Close; return MenuResponse::Close;
} }
} }
@ -339,6 +350,7 @@ impl MenuRoot {
} }
MenuResponse::Stay MenuResponse::Stay
} }
fn handle_menu_response(root: &mut MenuRootManager, menu_response: MenuResponse) { fn handle_menu_response(root: &mut MenuRootManager, menu_response: MenuResponse) {
match menu_response { match menu_response {
MenuResponse::Create(pos, id) => { MenuResponse::Create(pos, id) => {
@ -348,11 +360,14 @@ impl MenuRoot {
MenuResponse::Stay => {} MenuResponse::Stay => {}
} }
} }
/// Respond to secondary (right) clicks. /// Respond to secondary (right) clicks.
pub fn context_click_interaction(response: &Response, root: &mut MenuRootManager, id: Id) { pub fn context_click_interaction(response: &Response, root: &mut MenuRootManager, id: Id) {
let menu_response = Self::context_interaction(response, root, id); let menu_response = Self::context_interaction(response, root, id);
Self::handle_menu_response(root, menu_response); Self::handle_menu_response(root, menu_response);
} }
// Responds to primary clicks.
pub fn stationary_click_interaction(response: &Response, root: &mut MenuRootManager, id: Id) { pub fn stationary_click_interaction(response: &Response, root: &mut MenuRootManager, id: Id) {
let menu_response = Self::stationary_interaction(response, root, id); let menu_response = Self::stationary_interaction(response, root, id);
Self::handle_menu_response(root, menu_response); Self::handle_menu_response(root, menu_response);

View file

@ -66,7 +66,7 @@ impl Painter {
self.fade_to_color = fade_to_color; 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) self.fade_to_color != Some(Color32::TRANSPARENT)
} }

View file

@ -437,13 +437,12 @@ impl Response {
/// Move the scroll to this UI with the specified alignment. /// Move the scroll to this UI with the specified alignment.
/// ///
/// ``` /// ```
/// # use egui::Align;
/// # egui::__run_test_ui(|ui| { /// # egui::__run_test_ui(|ui| {
/// egui::ScrollArea::vertical().show(ui, |ui| { /// egui::ScrollArea::vertical().show(ui, |ui| {
/// for i in 0..1000 { /// for i in 0..1000 {
/// let response = ui.button(format!("Button {}", i)); /// let response = ui.button("Scroll to me");
/// if response.clicked() { /// 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 { 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 self
} }
} }

View file

@ -154,10 +154,10 @@ impl Spacing {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(default))]
pub struct Interaction { 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, 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, pub resize_grab_radius_corner: f32,
/// If `false`, tooltips will show up anytime you hover anything, even is mouse is still moving /// If `false`, tooltips will show up anytime you hover anything, even is mouse is still moving

View file

@ -119,6 +119,8 @@ impl Ui {
} }
/// Style options for this `Ui` and its children. /// Style options for this `Ui` and its children.
///
/// Note that this may be a different [`Style`] than that of [`Context::style`].
#[inline] #[inline]
pub fn style(&self) -> &std::sync::Arc<Style> { pub fn style(&self) -> &std::sync::Arc<Style> {
&self.style &self.style
@ -234,7 +236,7 @@ impl Ui {
/// ``` /// ```
pub fn set_enabled(&mut self, enabled: bool) { pub fn set_enabled(&mut self, enabled: bool) {
self.enabled &= enabled; self.enabled &= enabled;
if !self.enabled && self.visible() { if !self.enabled && self.is_visible() {
self.painter self.painter
.set_fade_to_color(Some(self.visuals().window_fill())); .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. /// If `false`, any widgets added to the `Ui` will be invisible and non-interactive.
#[inline] #[inline]
pub fn is_visible(&self) -> bool {
self.painter.is_visible()
}
#[deprecated = "Renamed is_visible"]
pub fn visible(&self) -> bool { pub fn visible(&self) -> bool {
self.painter.visible() self.painter.is_visible()
} }
/// Calling `set_visible(false)` will cause all further widgets to be invisible, /// 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. /// 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 { pub fn is_rect_visible(&self, rect: Rect) -> bool {
self.visible() && rect.intersects(self.clip_rect()) self.is_visible() && rect.intersects(self.clip_rect())
} }
} }
@ -933,9 +940,9 @@ impl Ui {
.inner .inner
} }
/// Add a single[`Widget`] that is possibly disabled, i.e. greyed out and non-interactive. /// 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. /// the widget will always be disabled, even if the `enabled` argument is true.
/// ///
/// See also [`Self::add_enabled_ui`] and [`Self::is_enabled`]. /// 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 { pub fn add_enabled(&mut self, enabled: bool, widget: impl Widget) -> Response {
if enabled || !self.is_enabled() { if self.is_enabled() && !enabled {
self.add(widget)
} else {
let old_painter = self.painter.clone(); let old_painter = self.painter.clone();
self.set_enabled(false); self.set_enabled(false);
let response = self.add(widget); let response = self.add(widget);
self.enabled = true; self.enabled = true;
self.painter = old_painter; self.painter = old_painter;
response response
} else {
self.add(widget)
} }
} }
/// Add a section that is possibly disabled, i.e. greyed out and non-interactive. /// 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. /// the result will always be disabled, even if the `enabled` argument is true.
/// ///
/// See also [`Self::add_enabled`] and [`Self::is_enabled`]. /// 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. /// Add extra space before the next widget.
/// ///
/// The direction is dependent on the layout. /// The direction is dependent on the layout.
@ -1761,7 +1825,9 @@ impl Ui {
result 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) { pub fn close_menu(&mut self) {
if let Some(menu_state) = &mut self.menu_state { if let Some(menu_state) = &mut self.menu_state {
menu_state.write().close(); menu_state.write().close();
@ -1778,7 +1844,9 @@ impl Ui {
} }
#[inline] #[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| { /// # 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>( pub fn menu_button<R>(
&mut self, &mut self,
title: impl Into<WidgetText>, title: impl Into<WidgetText>,

View file

@ -80,18 +80,6 @@ impl Button {
self 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. /// Override background fill color. Note that this will override any on-hover effects.
/// Calling this will also turn on the frame. /// Calling this will also turn on the frame.
pub fn fill(mut self, fill: impl Into<Color32>) -> Self { pub fn fill(mut self, fill: impl Into<Color32>) -> Self {
@ -241,18 +229,6 @@ impl<'a> Checkbox<'a> {
text: text.into(), 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> { impl<'a> Widget for Checkbox<'a> {
@ -347,18 +323,6 @@ impl RadioButton {
text: text.into(), 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 { impl Widget for RadioButton {

View file

@ -44,6 +44,7 @@ fn background_checkers(painter: &Painter, rect: Rect) {
painter.add(Shape::mesh(mesh)); 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 { pub fn show_color(ui: &mut Ui, color: impl Into<Hsva>, desired_size: Vec2) -> Response {
show_hsva(ui, color.into(), desired_size) 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. /// Returns `true` on change.
pub fn color_picker_color32(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) -> bool { pub fn color_picker_color32(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) -> bool {
let mut hsva = color_cache_get(ui.ctx(), *srgba); 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); *srgba = Color32::from(hsva);
color_cache_set(ui.ctx(), *srgba, 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 { pub fn color_edit_button_hsva(ui: &mut Ui, hsva: &mut Hsva, alpha: Alpha) -> Response {

View file

@ -6,8 +6,13 @@ use crate::*;
/// ///
/// ``` /// ```
/// # egui::__run_test_ui(|ui| { /// # egui::__run_test_ui(|ui| {
/// // These are equivalent:
/// ui.hyperlink("https://github.com/emilk/egui"); /// 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);`"] #[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
@ -33,25 +38,6 @@ impl Hyperlink {
text: text.into(), 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 { impl Widget for Hyperlink {

View file

@ -43,90 +43,6 @@ impl Label {
self 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. /// Make the label respond to clicks and/or drags.
/// ///
/// By default, a label is inert and does not respond to click or drags. /// By default, a label is inert and does not respond to click or drags.

View file

@ -17,6 +17,7 @@ mod progress_bar;
mod selected_label; mod selected_label;
mod separator; mod separator;
mod slider; mod slider;
mod spinner;
pub mod text_edit; pub mod text_edit;
pub use button::*; pub use button::*;
@ -28,6 +29,7 @@ pub use progress_bar::ProgressBar;
pub use selected_label::SelectableLabel; pub use selected_label::SelectableLabel;
pub use separator::Separator; pub use separator::Separator;
pub use slider::*; pub use slider::*;
pub use spinner::*;
pub use text_edit::{TextBuffer, TextEdit}; pub use text_edit::{TextBuffer, TextEdit};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View file

@ -7,7 +7,7 @@ use epaint::Mesh;
use crate::*; use crate::*;
use super::{PlotBounds, ScreenTransform}; use super::{CustomLabelFuncRef, PlotBounds, ScreenTransform};
use rect_elem::*; use rect_elem::*;
use values::*; 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() { let points = match self.geometry() {
PlotGeometry::Points(points) => points, PlotGeometry::Points(points) => points,
PlotGeometry::None => { PlotGeometry::None => {
@ -83,7 +89,7 @@ pub(super) trait PlotItem {
let pointer = plot.transform.position_from_value(&value); let pointer = plot.transform.position_from_value(&value);
shapes.push(Shape::circle_filled(pointer, 3.0, line_color)); 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) 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]; let bar = &self.bars[elem.index];
bar.add_shapes(plot.transform, true, shapes); bar.add_shapes(plot.transform, true, shapes);
@ -1501,7 +1513,13 @@ impl PlotItem for BoxPlot {
find_closest_rect(&self.boxes, point, transform) 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]; let box_plot = &self.boxes[elem.index];
box_plot.add_shapes(plot.transform, true, shapes); box_plot.add_shapes(plot.transform, true, shapes);
@ -1619,6 +1637,7 @@ pub(super) fn rulers_at_value(
name: &str, name: &str,
plot: &PlotConfig<'_>, plot: &PlotConfig<'_>,
shapes: &mut Vec<Shape>, shapes: &mut Vec<Shape>,
custom_label_func: &CustomLabelFuncRef,
) { ) {
let line_color = rulers_color(plot.ui); let line_color = rulers_color(plot.ui);
if plot.show_x { if plot.show_x {
@ -1638,7 +1657,9 @@ pub(super) fn rulers_at_value(
let scale = plot.transform.dvalue_dpos(); let scale = plot.transform.dvalue_dpos();
let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).at_most(6); 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); 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!( format!(
"{}x = {:.*}\ny = {:.*}", "{}x = {:.*}\ny = {:.*}",
prefix, x_decimals, value.x, y_decimals, value.y prefix, x_decimals, value.x, y_decimals, value.y

View file

@ -18,6 +18,9 @@ mod items;
mod legend; mod legend;
mod transform; mod transform;
type CustomLabelFunc = dyn Fn(&str, &Value) -> String;
type CustomLabelFuncRef = Option<Box<CustomLabelFunc>>;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
/// Information about the plot that has to persist between frames. /// Information about the plot that has to persist between frames.
@ -76,6 +79,7 @@ pub struct Plot {
show_x: bool, show_x: bool,
show_y: bool, show_y: bool,
custom_label_func: CustomLabelFuncRef,
legend_config: Option<Legend>, legend_config: Option<Legend>,
show_background: bool, show_background: bool,
show_axes: [bool; 2], show_axes: [bool; 2],
@ -102,6 +106,7 @@ impl Plot {
show_x: true, show_x: true,
show_y: true, show_y: true,
custom_label_func: None,
legend_config: None, legend_config: None,
show_background: true, show_background: true,
show_axes: [true; 2], show_axes: [true; 2],
@ -182,6 +187,35 @@ impl Plot {
self 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. /// Expand bounds to include the given x value.
/// For instance, to always show the y axis, call `plot.include_x(0.0)`. /// 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 { pub fn include_x(mut self, x: impl Into<f64>) -> Self {
@ -235,6 +269,7 @@ impl Plot {
view_aspect, view_aspect,
mut show_x, mut show_x,
mut show_y, mut show_y,
custom_label_func,
legend_config, legend_config,
show_background, show_background,
show_axes, show_axes,
@ -406,6 +441,7 @@ impl Plot {
items, items,
show_x, show_x,
show_y, show_y,
custom_label_func,
show_axes, show_axes,
transform: transform.clone(), transform: transform.clone(),
}; };
@ -613,6 +649,7 @@ struct PreparedPlot {
items: Vec<Box<dyn PlotItem>>, items: Vec<Box<dyn PlotItem>>,
show_x: bool, show_x: bool,
show_y: bool, show_y: bool,
custom_label_func: CustomLabelFuncRef,
show_axes: [bool; 2], show_axes: [bool; 2],
transform: ScreenTransform, transform: ScreenTransform,
} }
@ -731,6 +768,7 @@ impl PreparedPlot {
transform, transform,
show_x, show_x,
show_y, show_y,
custom_label_func,
items, items,
.. ..
} = self; } = self;
@ -760,10 +798,10 @@ impl PreparedPlot {
}; };
if let Some((item, elem)) = closest { if let Some((item, elem)) = closest {
item.on_hover(elem, shapes, &plot); item.on_hover(elem, shapes, &plot, custom_label_func);
} else { } else {
let value = transform.value_from_position(pointer); 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);
} }
} }
} }

View file

@ -34,12 +34,6 @@ impl SelectableLabel {
text: text.into(), 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 { impl Widget for SelectableLabel {

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

View file

@ -1,10 +1,10 @@
[package] [package]
name = "egui_datepicker" name = "egui_datepicker"
version = "0.15.0" version = "0.16.0"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
chrono = "0.4" chrono = "0.4"
egui = { version = "0.15.0", path = "../egui", default-features = false } egui = { version = "0.16.0", path = "../egui", default-features = false }
egui_dynamic_grid = { version = "0.15.0", path = "../egui_dynamic_grid" } egui_dynamic_grid = { version = "0.16.0", path = "../egui_dynamic_grid" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }

View file

@ -1,19 +1,22 @@
[package] [package]
name = "egui_demo_app" name = "egui_demo_app"
version = "0.15.0" version = "0.16.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"] authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2021"
rust-version = "1.56"
publish = false publish = false
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
eframe = { version = "0.15.0", path = "../eframe" } eframe = { version = "0.16.0", path = "../eframe" }
# eframe = { version = "0.15.0", path = "../eframe", default-features = false, features = ["default_fonts", "egui_glow"] }
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] [features]
default = ["persistence"] default = ["persistence"]

View file

@ -1,3 +1,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
// Forbid warnings in release builds: // Forbid warnings in release builds:
#![cfg_attr(not(debug_assertions), deny(warnings))] #![cfg_attr(not(debug_assertions), deny(warnings))]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
@ -8,7 +10,7 @@ fn main() {
let app = egui_demo_lib::WrapApp::default(); let app = egui_demo_lib::WrapApp::default();
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
// Let's show off that we support transparent windows // Let's show off that we support transparent windows
transparent: true, // transparent: true,
drag_and_drop_support: true, drag_and_drop_support: true,
..Default::default() ..Default::default()
}; };

View file

@ -1,9 +1,10 @@
[package] [package]
name = "egui_demo_lib" name = "egui_demo_lib"
version = "0.15.0" version = "0.16.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"] authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Example library for egui" description = "Example library for egui"
edition = "2018" edition = "2021"
rust-version = "1.56"
homepage = "https://github.com/emilk/egui/tree/master/egui_demo_lib" homepage = "https://github.com/emilk/egui/tree/master/egui_demo_lib"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
readme = "README.md" readme = "README.md"
@ -18,10 +19,10 @@ all-features = true
[lib] [lib]
[dependencies] [dependencies]
egui = { version = "0.15.0", path = "../egui", default-features = false } egui = { version = "0.16.0", path = "../egui", default-features = false }
epi = { version = "0.15.0", path = "../epi" } epi = { version = "0.16.0", path = "../epi" }
egui_dynamic_grid = { version = "0.15.0", path = "../egui_dynamic_grid" } egui_dynamic_grid = { version = "0.16.0", path = "../egui_dynamic_grid" }
egui_datepicker = { version = "0.15.0", path = "../egui_datepicker", optional = true } egui_datepicker = { version = "0.16.0", path = "../egui_datepicker", optional = true }
chrono = { version = "0.4", features = ["js-sys", "wasmbind"], optional = true } chrono = { version = "0.4", features = ["js-sys", "wasmbind"], optional = true }
enum-map = { version = "1", features = ["serde"] } enum-map = { version = "1", features = ["serde"] }

View file

@ -8,7 +8,8 @@
This crate contains example code for [`egui`](https://github.com/emilk/egui). 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 ensure it only uses the public `egui` api.
* To remove the amount of code in `egui` proper. * To remove the amount of code in `egui` proper.
* To make it easy for other integrations to use the egui demos a test.

View file

@ -97,7 +97,11 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let text_shape = TextShape::new(egui::Pos2::ZERO, galley); let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
c.bench_function("tessellate_text", |b| { c.bench_function("tessellate_text", |b| {
b.iter(|| { 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(); mesh.clear();
}) })
}); });

View file

@ -34,7 +34,7 @@ impl epi::App for ColorTest {
"🎨 Color test" "🎨 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| { egui::CentralPanel::default().show(ctx, |ui| {
if frame.is_web() { if frame.is_web() {
ui.label( ui.label(
@ -43,18 +43,14 @@ impl epi::App for ColorTest {
ui.separator(); ui.separator();
} }
ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| { ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| {
self.ui(ui, &mut Some(frame.tex_allocator())); self.ui(ui, Some(frame));
}); });
}); });
} }
} }
impl ColorTest { impl ColorTest {
pub fn ui( pub fn ui(&mut self, ui: &mut Ui, tex_allocator: Option<&dyn epi::TextureAllocator>) {
&mut self,
ui: &mut Ui,
mut tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>,
) {
ui.set_max_width(680.0); ui.set_max_width(680.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
@ -105,10 +101,10 @@ impl ColorTest {
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g); self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
self.tex_gradient(ui, tex_allocator, "Ground truth (texture)", 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| { ui.horizontal(|ui| {
let g = Gradient::one_color(Color32::from(tex_color)); 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 texel_offset = 0.5 / (g.0.len() as f32);
let uv = let uv =
Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0)); 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( fn show_gradients(
&mut self, &mut self,
ui: &mut Ui, ui: &mut Ui,
tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>, tex_allocator: Option<&dyn epi::TextureAllocator>,
bg_fill: Color32, bg_fill: Color32,
(left, right): (Color32, Color32), (left, right): (Color32, Color32),
) { ) {
@ -261,7 +257,7 @@ impl ColorTest {
fn tex_gradient( fn tex_gradient(
&mut self, &mut self,
ui: &mut Ui, ui: &mut Ui,
tex_allocator: &mut Option<&mut dyn epi::TextureAllocator>, tex_allocator: Option<&dyn epi::TextureAllocator>,
label: &str, label: &str,
bg_fill: Color32, bg_fill: Color32,
gradient: &Gradient, gradient: &Gradient,
@ -271,7 +267,7 @@ impl ColorTest {
} }
if let Some(tex_allocator) = tex_allocator { if let Some(tex_allocator) = tex_allocator {
ui.horizontal(|ui| { 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 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)); 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)) ui.add(Image::new(tex, GRADIENT_SIZE).bg_fill(bg_fill).uv(uv))
@ -391,16 +387,15 @@ impl Gradient {
struct TextureManager(HashMap<Gradient, TextureId>); struct TextureManager(HashMap<Gradient, TextureId>);
impl TextureManager { impl TextureManager {
fn get( fn get(&mut self, tex_allocator: &dyn epi::TextureAllocator, gradient: &Gradient) -> TextureId {
&mut self,
tex_allocator: &mut dyn epi::TextureAllocator,
gradient: &Gradient,
) -> TextureId {
*self.0.entry(gradient.clone()).or_insert_with(|| { *self.0.entry(gradient.clone()).or_insert_with(|| {
let pixels = gradient.to_pixel_row(); let pixels = gradient.to_pixel_row();
let width = pixels.len(); let width = pixels.len();
let height = 1; let height = 1;
tex_allocator.alloc_srgba_premultiplied((width, height), &pixels) tex_allocator.alloc(epi::Image {
size: [width, height],
pixels,
})
}) })
} }
} }

View file

@ -17,7 +17,7 @@ impl epi::App for DemoApp {
fn setup( fn setup(
&mut self, &mut self,
_ctx: &egui::CtxRef, _ctx: &egui::CtxRef,
_frame: &mut epi::Frame<'_>, _frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>, _storage: Option<&dyn epi::Storage>,
) { ) {
#[cfg(feature = "persistence")] #[cfg(feature = "persistence")]
@ -31,7 +31,7 @@ impl epi::App for DemoApp {
epi::set_value(storage, epi::APP_KEY, self); 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); self.demo_windows.ui(ctx);
} }
} }

View file

@ -155,7 +155,7 @@ impl DemoWindows {
egui::SidePanel::right("egui_demo_panel") egui::SidePanel::right("egui_demo_panel")
.min_width(150.0) .min_width(150.0)
.default_width(190.0) .default_width(180.0)
.show(ctx, |ui| { .show(ctx, |ui| {
egui::trace!(ui); egui::trace!(ui);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {

View file

@ -347,8 +347,8 @@ impl Widget for &mut ItemsDemo {
TextureId::Egui, TextureId::Egui,
Value::new(0.0, 10.0), Value::new(0.0, 10.0),
[ [
ui.fonts().texture().width as f32 / 100.0, ui.fonts().font_image().width as f32 / 100.0,
ui.fonts().texture().height as f32 / 100.0, ui.fonts().font_image().height as f32 / 100.0,
], ],
); );

View file

@ -239,6 +239,10 @@ impl WidgetGallery {
This toggle switch is just 15 lines of code.", This toggle switch is just 15 lines of code.",
); );
ui.end_row(); ui.end_row();
ui.add(doc_link_label("Spinner", "spinner"));
ui.add(egui::Spinner::new());
ui.end_row();
} }
} }

View file

@ -37,7 +37,7 @@ impl epi::App for FractalClock {
"🕑 Fractal Clock" "🕑 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() egui::CentralPanel::default()
.frame(Frame::dark_canvas(&ctx.style())) .frame(Frame::dark_canvas(&ctx.style()))
.show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight())); .show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight()));

View file

@ -7,7 +7,7 @@ struct Resource {
text: Option<String>, text: Option<String>,
/// If set, the response was an image. /// 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"). /// If set, the response was text with some supported syntax highlighting (e.g. ".rs" or ".md").
colored_text: Option<ColoredText>, colored_text: Option<ColoredText>,
@ -17,7 +17,7 @@ impl Resource {
fn from_response(ctx: &egui::Context, response: ehttp::Response) -> Self { fn from_response(ctx: &egui::Context, response: ehttp::Response) -> Self {
let content_type = response.content_type().unwrap_or_default(); let content_type = response.content_type().unwrap_or_default();
let image = if content_type.starts_with("image/") { let image = if content_type.starts_with("image/") {
Image::decode(&response.bytes) decode_image(&response.bytes)
} else { } else {
None None
}; };
@ -67,7 +67,7 @@ impl epi::App for HttpApp {
"⬇ HTTP" "⬇ 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 { if let Some(receiver) = &mut self.in_progress {
// Are we there yet? // Are we there yet?
if let Ok(result) = receiver.try_recv() { if let Ok(result) = receiver.try_recv() {
@ -95,13 +95,13 @@ impl epi::App for HttpApp {
if trigger_fetch { if trigger_fetch {
let request = ehttp::Request::get(&self.url); let request = ehttp::Request::get(&self.url);
let repaint_signal = frame.repaint_signal(); let frame = frame.clone();
let (sender, receiver) = std::sync::mpsc::channel(); let (sender, receiver) = std::sync::mpsc::channel();
self.in_progress = Some(receiver); self.in_progress = Some(receiver);
ehttp::fetch(request, move |response| { ehttp::fetch(request, move |response| {
sender.send(response).ok(); 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; let mut trigger_fetch = false;
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -160,12 +160,7 @@ fn ui_url(ui: &mut egui::Ui, frame: &mut epi::Frame<'_>, url: &mut String) -> bo
trigger_fetch trigger_fetch
} }
fn ui_resource( fn ui_resource(ui: &mut egui::Ui, frame: &epi::Frame, tex_mngr: &mut TexMngr, resource: &Resource) {
ui: &mut egui::Ui,
frame: &mut epi::Frame<'_>,
tex_mngr: &mut TexMngr,
resource: &Resource,
) {
let Resource { let Resource {
response, response,
text, text,
@ -218,7 +213,7 @@ fn ui_resource(
if let Some(image) = image { if let Some(image) = image {
if let Some(texture_id) = tex_mngr.texture(frame, &response.url, 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); size *= (ui.available_width() / size.x).min(1.0);
ui.image(texture_id, size); ui.image(texture_id, size);
} }
@ -304,44 +299,27 @@ struct TexMngr {
impl TexMngr { impl TexMngr {
fn texture( fn texture(
&mut self, &mut self,
frame: &mut epi::Frame<'_>, frame: &epi::Frame,
url: &str, url: &str,
image: &Image, image: &epi::Image,
) -> Option<egui::TextureId> { ) -> Option<egui::TextureId> {
if self.loaded_url != url { if self.loaded_url != url {
if let Some(texture_id) = self.texture_id.take() { if let Some(texture_id) = self.texture_id.take() {
frame.tex_allocator().free(texture_id); frame.free_texture(texture_id);
} }
self.texture_id = Some( self.texture_id = Some(frame.alloc_texture(image.clone()));
frame
.tex_allocator()
.alloc_srgba_premultiplied(image.size, &image.pixels),
);
self.loaded_url = url.to_owned(); self.loaded_url = url.to_owned();
} }
self.texture_id self.texture_id
} }
} }
struct Image { fn decode_image(bytes: &[u8]) -> Option<epi::Image> {
size: (usize, usize), use image::GenericImageView;
pixels: Vec<egui::Color32>, let image = image::load_from_memory(bytes).ok()?;
} let image_buffer = image.to_rgba8();
let size = [image.width() as usize, image.height() as usize];
impl Image { let pixels = image_buffer.into_vec();
fn decode(bytes: &[u8]) -> Option<Image> { Some(epi::Image::from_rgba_unmultiplied(size, &pixels))
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 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 })
}
} }

View file

@ -78,7 +78,7 @@ impl Default for BackendPanel {
} }
impl 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 self.frame_history
.on_new_frame(ctx.input().time, frame.info().cpu_usage); .on_new_frame(ctx.input().time, frame.info().cpu_usage);
@ -92,7 +92,7 @@ impl BackendPanel {
self.egui_windows.windows(ctx); 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); egui::trace!(ui);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.heading("💻 Backend"); 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() { if frame.is_web() {
ui.label("egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."); ui.label("egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
ui.label( ui.label(
"Everything you see is rendered as textured triangles. There is no DOM and no HTML elements. \ "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."); 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.hyperlink("https://github.com/emilk/egui");
ui.separator(); 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 // For instance: `egui_web` sets `pixels_per_point` every frame to force
// egui to use the same scale as the web zoom factor. // 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(); let integration_controls_pixels_per_point = ui.input().raw.pixels_per_point.is_some();
if !integration_controls_pixels_per_point { 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); ui.ctx().set_pixels_per_point(new_pixels_per_point);
} }
} }

View file

@ -34,7 +34,7 @@ impl epi::App for EasyMarkEditor {
"🖹 EasyMark editor" "🖹 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| { egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| { ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {

View file

@ -77,9 +77,9 @@
clippy::verbose_file_reads, clippy::verbose_file_reads,
clippy::zero_sized_map_values, clippy::zero_sized_map_values,
future_incompatible, future_incompatible,
missing_crate_level_docs,
nonstandard_style, nonstandard_style,
rust_2018_idioms rust_2018_idioms,
rustdoc::missing_crate_level_docs
)] )]
#![allow(clippy::float_cmp)] #![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]

View file

@ -45,7 +45,7 @@ impl epi::App for WrapApp {
fn setup( fn setup(
&mut self, &mut self,
_ctx: &egui::CtxRef, _ctx: &egui::CtxRef,
_frame: &mut epi::Frame<'_>, _frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>, _storage: Option<&dyn epi::Storage>,
) { ) {
#[cfg(feature = "persistence")] #[cfg(feature = "persistence")]
@ -72,7 +72,7 @@ impl epi::App for WrapApp {
cfg!(not(debug_assertions)) 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(web_info) = frame.info().web_info.as_ref() {
if let Some(anchor) = web_info.web_location_hash.strip_prefix('#') { if let Some(anchor) = web_info.web_location_hash.strip_prefix('#') {
self.selected_anchor = anchor.to_owned(); self.selected_anchor = anchor.to_owned();
@ -126,7 +126,7 @@ impl epi::App for WrapApp {
} }
impl 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. // A menu-bar is a horizontal layout with some special styles applied.
// egui::menu::bar(ui, |ui| { // egui::menu::bar(ui, |ui| {
ui.horizontal_wrapped(|ui| { ui.horizontal_wrapped(|ui| {

View file

@ -1,6 +1,6 @@
[package] [package]
name = "egui_dynamic_grid" name = "egui_dynamic_grid"
version = "0.15.0" version = "0.16.0"
edition = "2018" edition = "2018"
description = "Dynamic grid and table for egui" description = "Dynamic grid and table for egui"
authors = [ authors = [
@ -16,4 +16,4 @@ keywords = ["glium", "egui", "gui", "gamedev"]
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
[dependencies] [dependencies]
egui = { version = "0.15.0", path = "../egui", default-features = false } egui = { version = "0.16.0", path = "../egui", default-features = false }

View file

@ -3,8 +3,13 @@ All notable changes to the `egui_glium` integration will be noted in this file.
## Unreleased ## 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 ## 0.15.0 - 2021-10-24

View file

@ -1,9 +1,10 @@
[package] [package]
name = "egui_glium" name = "egui_glium"
version = "0.15.0" version = "0.16.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"] authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for using egui natively using the glium library" 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" homepage = "https://github.com/emilk/egui/tree/master/egui_glium"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
readme = "README.md" readme = "README.md"
@ -22,11 +23,11 @@ include = [
all-features = true all-features = true
[dependencies] [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"] }
egui-winit = { version = "0.15.0", path = "../egui-winit", default-features = false, features = ["epi"] } egui-winit = { version = "0.16.0", path = "../egui-winit", default-features = false, features = ["epi"] }
epi = { version = "0.15.0", path = "../epi", optional = true } epi = { version = "0.16.0", path = "../epi", optional = true }
glium = "0.30" glium = "0.31"
[dev-dependencies] [dev-dependencies]
image = { version = "0.23", default-features = false, features = ["png"] } image = { version = "0.23", default-features = false, features = ["png"] }

View file

@ -1,4 +1,7 @@
//! Example how to use [epi::NativeTexture] with glium. //! Example how to use [epi::NativeTexture] with glium.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use epi::NativeTexture; use epi::NativeTexture;
use glium::glutin; use glium::glutin;

View file

@ -1,4 +1,7 @@
//! Example how to use pure `egui_glium` without [`epi`]. //! 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; use glium::glutin;
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {

View file

@ -1,22 +1,6 @@
use crate::*;
use egui::Color32;
use glium::glutin; use glium::glutin;
impl epi::TextureAllocator for Painter { use crate::*;
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);
}
}
struct RequestRepaintEvent; struct RequestRepaintEvent;
@ -24,7 +8,7 @@ struct GliumRepaintSignal(
std::sync::Mutex<glutin::event_loop::EventLoopProxy<RequestRepaintEvent>>, std::sync::Mutex<glutin::event_loop::EventLoopProxy<RequestRepaintEvent>>,
); );
impl epi::RepaintSignal for GliumRepaintSignal { impl epi::backend::RepaintSignal for GliumRepaintSignal {
fn request_repaint(&self) { fn request_repaint(&self) {
self.0.lock().unwrap().send_event(RequestRepaintEvent).ok(); 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( let mut integration = egui_winit::epi::EpiIntegration::new(
"egui_glium", "egui_glium",
display.gl_window().window(), display.gl_window().window(),
&mut painter,
repaint_signal, repaint_signal,
persistence, persistence,
app, 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)); std::thread::sleep(std::time::Duration::from_millis(10));
} }
let (needs_repaint, shapes) = let (needs_repaint, mut tex_allocation_data, shapes) =
integration.update(display.gl_window().window(), &mut painter); integration.update(display.gl_window().window());
let clipped_meshes = integration.egui_ctx.tessellate(shapes); 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 _; use glium::Surface as _;
let mut target = display.draw(); let mut target = display.draw();
@ -98,12 +86,16 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
&mut target, &mut target,
integration.egui_ctx.pixels_per_point(), integration.egui_ctx.pixels_per_point(),
clipped_meshes, clipped_meshes,
&integration.egui_ctx.texture(), &integration.egui_ctx.font_image(),
); );
target.finish().unwrap(); target.finish().unwrap();
} }
for id in tex_allocation_data.destructions.drain(..) {
painter.free_texture(id);
}
{ {
*control_flow = if integration.should_quit() { *control_flow = if integration.should_quit() {
glutin::event_loop::ControlFlow::Exit glutin::event_loop::ControlFlow::Exit

View file

@ -80,9 +80,9 @@
clippy::verbose_file_reads, clippy::verbose_file_reads,
clippy::zero_sized_map_values, clippy::zero_sized_map_values,
future_incompatible, future_incompatible,
missing_crate_level_docs,
nonstandard_style, nonstandard_style,
rust_2018_idioms rust_2018_idioms,
rustdoc::missing_crate_level_docs
)] )]
#![allow(clippy::float_cmp)] #![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]
@ -153,7 +153,7 @@ impl EguiGlium {
target, target,
self.egui_ctx.pixels_per_point(), self.egui_ctx.pixels_per_point(),
clipped_meshes, clipped_meshes,
&self.egui_ctx.texture(), &self.egui_ctx.font_image(),
); );
} }
} }

View file

@ -14,7 +14,7 @@ use {
uniform, uniform,
uniforms::{MagnifySamplerFilter, SamplerWrapFunction}, uniforms::{MagnifySamplerFilter, SamplerWrapFunction},
}, },
std::rc::Rc, std::{collections::HashMap, rc::Rc},
}; };
pub struct Painter { pub struct Painter {
@ -22,19 +22,11 @@ pub struct Painter {
egui_texture: Option<SrgbTexture2d>, egui_texture: Option<SrgbTexture2d>,
egui_texture_version: Option<u64>, egui_texture_version: Option<u64>,
/// `None` means unallocated (freed) slot. /// Index is the same as in [`egui::TextureId::User`].
user_textures: Vec<Option<UserTexture>>, user_textures: HashMap<u64, Rc<SrgbTexture2d>>,
}
#[derive(Default)] #[cfg(feature = "epi")]
struct UserTexture { next_native_tex_id: u64, // TODO: 128-bit texture space?
/// 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>>,
} }
impl Painter { impl Painter {
@ -65,21 +57,23 @@ impl Painter {
egui_texture: None, egui_texture: None,
egui_texture_version: None, egui_texture_version: None,
user_textures: Default::default(), user_textures: Default::default(),
#[cfg(feature = "epi")]
next_native_tex_id: 1 << 32,
} }
} }
pub fn upload_egui_texture( pub fn upload_egui_texture(
&mut self, &mut self,
facade: &dyn glium::backend::Facade, 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 return; // No change
} }
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = texture let pixels: Vec<Vec<(u8, u8, u8, u8)>> = font_image
.pixels .pixels
.chunks(texture.width as usize) .chunks(font_image.width as usize)
.map(|row| { .map(|row| {
row.iter() row.iter()
.map(|&a| Color32::from_white_alpha(a).to_tuple()) .map(|&a| Color32::from_white_alpha(a).to_tuple())
@ -91,7 +85,7 @@ impl Painter {
let mipmaps = texture::MipmapsOption::NoMipmap; let mipmaps = texture::MipmapsOption::NoMipmap;
self.egui_texture = self.egui_texture =
Some(SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap()); 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. /// Main entry-point for painting a frame.
@ -103,10 +97,9 @@ impl Painter {
target: &mut T, target: &mut T,
pixels_per_point: f32, pixels_per_point: f32,
cipped_meshes: Vec<egui::ClippedMesh>, cipped_meshes: Vec<egui::ClippedMesh>,
egui_texture: &egui::Texture, font_image: &egui::FontImage,
) { ) {
self.upload_egui_texture(display, egui_texture); self.upload_egui_texture(display, font_image);
self.upload_pending_user_textures(display);
for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes { for egui::ClippedMesh(clip_rect, mesh) in cipped_meshes {
self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh); self.paint_mesh(target, display, pixels_per_point, clip_rect, &mesh);
@ -114,7 +107,7 @@ impl Painter {
} }
#[inline(never)] // Easier profiling #[inline(never)] // Easier profiling
pub fn paint_mesh<T: glium::Surface>( fn paint_mesh<T: glium::Surface>(
&mut self, &mut self,
target: &mut T, target: &mut T,
display: &glium::Display, 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 { #[cfg(feature = "epi")]
for (i, tex) in self.user_textures.iter_mut().enumerate() { pub fn set_texture(
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(
&mut self, &mut self,
id: egui::TextureId, facade: &dyn glium::backend::Facade,
size: (usize, usize), tex_id: u64,
pixels: &[Color32], image: &epi::Image,
) { ) {
assert_eq!( assert_eq!(
size.0 * size.1, image.size[0] * image.size[1],
pixels.len(), image.pixels.len(),
"Mismatch between texture size and texel count" "Mismatch between texture size and texel count"
); );
if let egui::TextureId::User(id) = id { let pixels: Vec<Vec<(u8, u8, u8, u8)>> = image
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) { .pixels
let pixels: Vec<Vec<(u8, u8, u8, u8)>> = pixels .chunks(image.size[0] as usize)
.chunks(size.0 as usize) .map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect())
.map(|row| row.iter().map(|srgba| srgba.to_tuple()).collect()) .collect();
.collect();
*user_texture = UserTexture { let format = texture::SrgbFormat::U8U8U8U8;
pixels, let mipmaps = texture::MipmapsOption::NoMipmap;
gl_texture: None, let gl_texture = SrgbTexture2d::with_format(facade, pixels, format, mipmaps).unwrap();
};
} self.user_textures.insert(tex_id, gl_texture.into());
}
} }
pub fn free_user_texture(&mut self, id: egui::TextureId) { pub fn free_texture(&mut self, tex_id: u64) {
if let egui::TextureId::User(id) = id { self.user_textures.remove(&tex_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> { fn get_texture(&self, texture_id: egui::TextureId) -> Option<&SrgbTexture2d> {
match texture_id { match texture_id {
egui::TextureId::Egui => self.egui_texture.as_ref(), egui::TextureId::Egui => self.egui_texture.as_ref(),
egui::TextureId::User(id) => self egui::TextureId::User(id) => self.user_textures.get(&id).map(|rc| rc.as_ref()),
.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(),
);
}
} }
} }
} }
@ -314,26 +266,15 @@ impl epi::NativeTexture for Painter {
type Texture = Rc<SrgbTexture2d>; type Texture = Rc<SrgbTexture2d>;
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId { fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
let id = self.alloc_user_texture(); let id = self.next_native_tex_id;
if let egui::TextureId::User(id) = id { self.next_native_tex_id += 1;
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) { self.user_textures.insert(id, native);
*user_texture = UserTexture { egui::TextureId::User(id as u64)
pixels: vec![],
gl_texture: Some(native),
}
}
}
id
} }
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) { fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
if let egui::TextureId::User(id) = id { if let egui::TextureId::User(id) = id {
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) { self.user_textures.insert(id, replacing);
*user_texture = UserTexture {
pixels: vec![],
gl_texture: Some(replacing),
};
}
} }
} }
} }

View file

@ -3,9 +3,14 @@ All notable changes to the `egui_glow` integration will be noted in this file.
## Unreleased ## 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 ## 0.15.0 - 2021-10-24

View file

@ -1,9 +1,10 @@
[package] [package]
name = "egui_glow" name = "egui_glow"
version = "0.15.0" version = "0.16.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"] authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for using egui natively using the glow library" 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" homepage = "https://github.com/emilk/egui/tree/master/egui_glow"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
readme = "README.md" readme = "README.md"
@ -22,15 +23,15 @@ include = [
all-features = true all-features = true
[dependencies] [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" glow = "0.11"
memoffset = "0.6" memoffset = "0.6"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
egui-winit = { version = "0.15.0", path = "../egui-winit", default-features = false, features = ["epi"], optional = true } egui-winit = { version = "0.16.0", path = "../egui-winit", default-features = false, features = ["epi"], optional = true }
glutin = { version = "0.27.0", optional = true } glutin = { version = "0.28.0", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features=["console"] } web-sys = { version = "0.3", features=["console"] }

View file

@ -1,5 +1,7 @@
//! Example how to use pure `egui_glow` without [`epi`]. //! Example how to use pure `egui_glow` without [`epi`].
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
fn create_display( fn create_display(
event_loop: &glutin::event_loop::EventLoop<()>, event_loop: &glutin::event_loop::EventLoop<()>,
) -> ( ) -> (

View file

@ -4,7 +4,7 @@ struct RequestRepaintEvent;
struct GlowRepaintSignal(std::sync::Mutex<glutin::event_loop::EventLoopProxy<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) { fn request_repaint(&self) {
self.0.lock().unwrap().send_event(RequestRepaintEvent).ok(); 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( let mut integration = egui_winit::epi::EpiIntegration::new(
"egui_glow", "egui_glow",
gl_window.window(), gl_window.window(),
&mut painter,
repaint_signal, repaint_signal,
persistence, persistence,
app, 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)); 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); 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(); let color = integration.app.clear_color();
unsafe { 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_color(color[0], color[1], color[2], color[3]);
gl.clear(glow::COLOR_BUFFER_BIT); 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( painter.paint_meshes(
gl_window.window().inner_size().into(),
&gl, &gl,
gl_window.window().inner_size().into(),
integration.egui_ctx.pixels_per_point(), integration.egui_ctx.pixels_per_point(),
clipped_meshes, clipped_meshes,
); );
@ -105,6 +110,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
gl_window.swap_buffers().unwrap(); gl_window.swap_buffers().unwrap();
} }
for id in tex_allocation_data.destructions.drain(..) {
painter.free_texture(id);
}
{ {
*control_flow = if integration.should_quit() { *control_flow = if integration.should_quit() {
glutin::event_loop::ControlFlow::Exit glutin::event_loop::ControlFlow::Exit

View file

@ -80,9 +80,9 @@
clippy::verbose_file_reads, clippy::verbose_file_reads,
clippy::zero_sized_map_values, clippy::zero_sized_map_values,
future_incompatible, future_incompatible,
missing_crate_level_docs,
nonstandard_style, nonstandard_style,
rust_2018_idioms rust_2018_idioms,
rustdoc::missing_crate_level_docs
)] )]
#![allow(clippy::float_cmp)] #![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]
@ -164,10 +164,10 @@ impl EguiGlow {
let clipped_meshes = self.egui_ctx.tessellate(shapes); let clipped_meshes = self.egui_ctx.tessellate(shapes);
let dimensions: [u32; 2] = gl_window.window().inner_size().into(); let dimensions: [u32; 2] = gl_window.window().inner_size().into();
self.painter self.painter
.upload_egui_texture(gl, &self.egui_ctx.texture()); .upload_egui_texture(gl, &self.egui_ctx.font_image());
self.painter.paint_meshes( self.painter.paint_meshes(
dimensions,
gl, gl,
dimensions,
self.egui_ctx.pixels_per_point(), self.egui_ctx.pixels_per_point(),
clipped_meshes, clipped_meshes,
); );

View file

@ -1,8 +1,6 @@
#![allow(unsafe_code)] #![allow(unsafe_code)]
use glow::HasContext; use glow::HasContext;
use std::option::Option::Some; use std::option::Option::Some;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsValue;
pub(crate) fn srgbtexture2d( pub(crate) fn srgbtexture2d(
gl: &glow::Context, gl: &glow::Context,

View file

@ -1,14 +1,13 @@
#![allow(unsafe_code)] #![allow(unsafe_code)]
use std::collections::HashMap;
use egui::{ use egui::{
emath::Rect, emath::Rect,
epaint::{Color32, Mesh, Vertex}, epaint::{Color32, Mesh, Vertex},
}; };
pub use glow::Context;
use memoffset::offset_of;
use glow::HasContext; use glow::HasContext;
use memoffset::offset_of;
use crate::misc_util::{ use crate::misc_util::{
as_u8_slice, compile_shader, glow_debug_print, link_program, srgbtexture2d, 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::shader_version::ShaderVersion;
use crate::vao_emulate; use crate::vao_emulate;
pub use glow::Context;
const VERT_SRC: &str = include_str!("shader/vertex.glsl"); const VERT_SRC: &str = include_str!("shader/vertex.glsl");
const FRAG_SRC: &str = include_str!("shader/fragment.glsl"); const FRAG_SRC: &str = include_str!("shader/fragment.glsl");
@ -34,29 +35,23 @@ pub struct Painter {
is_embedded: bool, is_embedded: bool,
vertex_array: crate::misc_util::VAO, vertex_array: crate::misc_util::VAO,
srgb_support: bool, srgb_support: bool,
/// `None` means unallocated (freed) slot.
pub(crate) user_textures: Vec<Option<UserTexture>>,
post_process: Option<PostProcess>, post_process: Option<PostProcess>,
vertex_buffer: glow::Buffer, vertex_buffer: glow::Buffer,
element_array_buffer: glow::Buffer, element_array_buffer: glow::Buffer,
// Stores outdated OpenGL textures that are yet to be deleted /// Index is the same as in [`egui::TextureId::User`].
old_textures: Vec<glow::Texture>, user_textures: HashMap<u64, glow::Texture>,
// Only used in debug builds, to make sure we are destroyed correctly.
#[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, 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 { impl Painter {
/// Create painter. /// Create painter.
/// ///
@ -195,20 +190,22 @@ impl Painter {
is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300), is_embedded: matches!(shader_version, ShaderVersion::Es100 | ShaderVersion::Es300),
vertex_array, vertex_array,
srgb_support, srgb_support,
user_textures: Default::default(),
post_process, post_process,
vertex_buffer, vertex_buffer,
element_array_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, 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(); self.assert_not_destroyed();
if self.egui_texture_version == Some(texture.version) { if self.egui_texture_version == Some(font_image.version) {
return; // No change return; // No change
} }
let gamma = if self.is_embedded && self.post_process.is_none() { let gamma = if self.is_embedded && self.post_process.is_none() {
@ -216,7 +213,7 @@ impl Painter {
} else { } else {
1.0 1.0
}; };
let pixels: Vec<u8> = texture let pixels: Vec<u8> = font_image
.srgba_pixels(gamma) .srgba_pixels(gamma)
.flat_map(|a| Vec::from(a.to_array())) .flat_map(|a| Vec::from(a.to_array()))
.collect(); .collect();
@ -228,15 +225,15 @@ impl Painter {
self.is_webgl_1, self.is_webgl_1,
self.srgb_support, self.srgb_support,
&pixels, &pixels,
texture.width, font_image.width,
texture.height, font_image.height,
)), )),
) { ) {
unsafe { unsafe {
gl.delete_texture(old_tex); gl.delete_texture(old_tex);
} }
} }
self.egui_texture_version = Some(texture.version); self.egui_texture_version = Some(font_image.version);
} }
unsafe fn prepare_painting( 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. /// of the effects your program might have on this code. Look at the source if in doubt.
pub fn paint_meshes( pub fn paint_meshes(
&mut self, &mut self,
inner_size: [u32; 2],
gl: &glow::Context, gl: &glow::Context,
inner_size: [u32; 2],
pixels_per_point: f32, pixels_per_point: f32,
clipped_meshes: Vec<egui::ClippedMesh>, clipped_meshes: Vec<egui::ClippedMesh>,
) { ) {
//chimera of egui_glow and egui_web
self.assert_not_destroyed(); self.assert_not_destroyed();
self.upload_pending_user_textures(gl);
if let Some(ref mut post_process) = self.post_process { if let Some(ref mut post_process) = self.post_process {
unsafe { unsafe {
post_process.begin(gl, inner_size[0] as i32, inner_size[1] as i32); 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(); 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!( assert_eq!(
size.0 * size.1, image.size[0] * image.size[1],
pixels.len(), image.pixels.len(),
"Mismatch between size and texel count" "Mismatch between texture size and texel count"
); );
if let egui::TextureId::User(id) = id { // TODO: optimize
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) { let pixels: Vec<u8> = image
let data: Vec<u8> = pixels .pixels
.iter() .iter()
.flat_map(|srgba| Vec::from(srgba.to_array())) .flat_map(|srgba| Vec::from(srgba.to_array()))
.collect(); .collect();
if let UserTexture { let gl_texture = srgbtexture2d(
gl_texture: Some(old_tex), gl,
.. self.is_webgl_1,
} = std::mem::replace( self.srgb_support,
user_texture, &pixels,
UserTexture { image.size[0],
data, image.size[1],
size, );
gl_texture: None,
}, if let Some(old_tex) = self.user_textures.insert(tex_id, gl_texture) {
) { self.textures_to_destroy.push(old_tex);
self.old_textures.push(old_tex);
}
}
} }
} }
pub fn free_user_texture(&mut self, id: egui::TextureId) { pub fn free_texture(&mut self, tex_id: u64) {
self.assert_not_destroyed(); self.user_textures.remove(&tex_id);
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<glow::Texture> { fn get_texture(&self, texture_id: egui::TextureId) -> Option<glow::Texture> {
self.assert_not_destroyed(); self.assert_not_destroyed();
match texture_id { match texture_id {
egui::TextureId::Egui => self.egui_texture, egui::TextureId::Egui => self.egui_texture,
egui::TextureId::User(id) => self.user_textures.get(id as usize)?.as_ref()?.gl_texture, egui::TextureId::User(id) => self.user_textures.get(&id).copied(),
}
}
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);
}
} }
} }
@ -523,14 +438,12 @@ impl Painter {
if let Some(tex) = self.egui_texture { if let Some(tex) = self.egui_texture {
gl.delete_texture(tex); gl.delete_texture(tex);
} }
for tex in self.user_textures.iter().flatten() { for tex in self.user_textures.values() {
if let Some(t) = tex.gl_texture { gl.delete_texture(*tex);
gl.delete_texture(t);
}
} }
gl.delete_buffer(self.vertex_buffer); gl.delete_buffer(self.vertex_buffer);
gl.delete_buffer(self.element_array_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); gl.delete_texture(*t);
} }
} }
@ -539,18 +452,19 @@ impl Painter {
/// that should be deleted. /// that should be deleted.
pub fn destroy(&mut self, gl: &glow::Context) { pub fn destroy(&mut self, gl: &glow::Context) {
debug_assert!(!self.destroyed, "Only destroy once!"); if !self.destroyed {
unsafe { unsafe {
self.destroy_gl(gl); self.destroy_gl(gl);
if let Some(ref post_process) = self.post_process { if let Some(ref post_process) = self.post_process {
post_process.destroy(gl); post_process.destroy(gl);
}
} }
self.destroyed = true;
} }
self.destroyed = true;
} }
fn assert_not_destroyed(&self) { 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 { impl Drop for Painter {
fn drop(&mut self) { fn drop(&mut self) {
debug_assert!( if !self.destroyed {
self.destroyed, eprintln!(
"Make sure to call destroy() before dropping to avoid leaking OpenGL objects!" "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; type Texture = glow::Texture;
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId { 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) { fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
if let egui::TextureId::User(id) = id { if let egui::TextureId::User(id) = id {
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) { if let Some(old_tex) = self.user_textures.insert(id, replacing) {
*user_texture = UserTexture { self.textures_to_destroy.push(old_tex);
data: vec![],
gl_texture: Some(replacing),
size: (0, 0),
};
} }
} }
} }

View file

@ -221,6 +221,7 @@ impl PostProcess {
gl.bind_texture(glow::TEXTURE_2D, None); gl.bind_texture(glow::TEXTURE_2D, None);
gl.use_program(None); gl.use_program(None);
} }
pub(crate) unsafe fn destroy(&self, gl: &glow::Context) { pub(crate) unsafe fn destroy(&self, gl: &glow::Context) {
gl.delete_buffer(self.pos_buffer); gl.delete_buffer(self.pos_buffer);
gl.delete_buffer(self.index_buffer); gl.delete_buffer(self.index_buffer);

View file

@ -4,8 +4,14 @@ All notable changes to the `egui_web` integration will be noted in this file.
## Unreleased ## Unreleased
* Fix [dark rendering in WebKitGTK](https://github.com/emilk/egui/issues/794) ([#888](https://github.com/emilk/egui/pull/888/)). * The default painter is now glow instead of WebGL ([#1020](https://github.com/emilk/egui/pull/1020)).
* Add feature `glow` to switch to a [`glow`](https://github.com/grovesNL/glow) based painter ([#868](https://github.com/emilk/egui/pull/868)). * 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 ## 0.15.0 - 2021-10-24
### Added ### Added

View file

@ -1,10 +1,11 @@
[package] [package]
name = "egui_web" name = "egui_web"
version = "0.15.0" version = "0.16.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"] authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Bindings for compiling egui code to WASM for a web page" description = "Bindings for compiling egui code to WASM for a web page"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2018" edition = "2021"
rust-version = "1.56"
homepage = "https://github.com/emilk/egui/tree/master/egui_web" homepage = "https://github.com/emilk/egui/tree/master/egui_web"
readme = "README.md" readme = "README.md"
repository = "https://github.com/emilk/egui/tree/master/egui_web" repository = "https://github.com/emilk/egui/tree/master/egui_web"
@ -25,26 +26,31 @@ all-features = true
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[dependencies] [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", "single_threaded",
] } ] }
egui_glow = { version = "0.15.0",path = "../egui_glow", default-features = false, optional = true } egui_glow = { version = "0.16.0",path = "../egui_glow", default-features = false, optional = true }
epi = { version = "0.15.0", path = "../epi" } epi = { version = "0.16.0", path = "../epi" }
js-sys = "0.3" js-sys = "0.3"
ron = { version = "0.7", optional = true } ron = { version = "0.7", optional = true }
serde = { version = "1", 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 = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
[features] [features]
default = ["default_fonts"] default = ["default_fonts", "glow"]
# If set, egui will use `include_bytes!` to bundle some fonts. # If set, egui will use `include_bytes!` to bundle some fonts.
# If you plan on specifying your own fonts you may disable this feature. # If you plan on specifying your own fonts you may disable this feature.
default_fonts = ["egui/default_fonts"] default_fonts = ["egui/default_fonts"]
# Use glow as the renderer
# Use glow as the renderer.
glow = ["egui_glow", "egui_glow/epi"] glow = ["egui_glow", "egui_glow/epi"]
# Alternative to the glow renderer.
webgl = []
persistence = ["egui/persistence", "ron", "serde"] persistence = ["egui/persistence", "ron", "serde"]
screen_reader = ["tts"] # experimental screen_reader = ["tts"] # experimental

View file

@ -5,11 +5,13 @@ pub use egui::{pos2, Color32};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
fn create_painter(canvas_id: &str) -> Result<Box<dyn Painter>, JsValue> { 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( return Ok(Box::new(crate::glow_wrapping::WrappedGlowPainter::new(
canvas_id, canvas_id,
))); )));
#[cfg(not(feature = "glow"))]
#[cfg(all(feature = "webgl", not(feature = "glow")))]
if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) { if let Ok(webgl2_painter) = webgl2::WebGl2Painter::new(canvas_id) {
console_log("Using WebGL2 backend"); console_log("Using WebGL2 backend");
Ok(Box::new(webgl2_painter)) 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)?; let webgl1_painter = webgl1::WebGlPainter::new(canvas_id)?;
Ok(Box::new(webgl1_painter)) 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) { fn request_repaint(&self) {
self.0.store(true, SeqCst); self.0.store(true, SeqCst);
} }
@ -76,28 +81,44 @@ impl epi::RepaintSignal for NeedRepaint {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
pub struct AppRunner { pub struct AppRunner {
frame: epi::Frame,
egui_ctx: egui::CtxRef, egui_ctx: egui::CtxRef,
painter: Box<dyn Painter>, painter: Box<dyn Painter>,
previous_frame_time: Option<f32>,
pub(crate) input: WebInput, pub(crate) input: WebInput,
app: Box<dyn epi::App>, app: Box<dyn epi::App>,
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>, pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
storage: LocalStorage, storage: LocalStorage,
prefer_dark_mode: Option<bool>,
last_save_time: f64, last_save_time: f64,
screen_reader: crate::screen_reader::ScreenReader, screen_reader: crate::screen_reader::ScreenReader,
pub(crate) text_cursor_pos: Option<egui::Pos2>, pub(crate) text_cursor_pos: Option<egui::Pos2>,
pub(crate) mutable_text_under_cursor: bool, pub(crate) mutable_text_under_cursor: bool,
pending_texture_destructions: Vec<u64>,
} }
impl AppRunner { impl AppRunner {
pub fn new(canvas_id: &str, app: Box<dyn epi::App>) -> Result<Self, JsValue> { pub fn new(canvas_id: &str, app: Box<dyn epi::App>) -> Result<Self, JsValue> {
let egui_ctx = egui::CtxRef::default(); let painter = create_painter(canvas_id)?;
load_memory(&egui_ctx);
let prefer_dark_mode = crate::prefer_dark_mode(); let prefer_dark_mode = crate::prefer_dark_mode();
let needs_repaint: std::sync::Arc<NeedRepaint> = Default::default();
let frame = epi::Frame::new(epi::backend::FrameData {
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) { if prefer_dark_mode == Some(true) {
egui_ctx.set_visuals(egui::Visuals::dark()); egui_ctx.set_visuals(egui::Visuals::dark());
} else { } else {
@ -107,32 +128,24 @@ impl AppRunner {
let storage = LocalStorage::default(); let storage = LocalStorage::default();
let mut runner = Self { let mut runner = Self {
frame,
egui_ctx, egui_ctx,
painter: create_painter(canvas_id)?, painter,
previous_frame_time: None,
input: Default::default(), input: Default::default(),
app, app,
needs_repaint: Default::default(), needs_repaint,
storage, storage,
prefer_dark_mode,
last_save_time: now_sec(), last_save_time: now_sec(),
screen_reader: Default::default(), screen_reader: Default::default(),
text_cursor_pos: None, text_cursor_pos: None,
mutable_text_under_cursor: false, 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 runner
.app .app
.setup(&runner.egui_ctx, &mut frame, Some(&runner.storage)); .setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage));
} }
Ok(runner) Ok(runner)
@ -170,18 +183,6 @@ impl AppRunner {
Ok(()) 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> { pub fn logic(&mut self) -> Result<(egui::Output, Vec<egui::ClippedMesh>), JsValue> {
let frame_start = now_sec(); let frame_start = now_sec();
@ -189,42 +190,44 @@ impl AppRunner {
let canvas_size = canvas_size_in_points(self.canvas_id()); let canvas_size = canvas_size_in_points(self.canvas_id());
let raw_input = self.input.new_frame(canvas_size); 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| { 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); let clipped_meshes = self.egui_ctx.tessellate(shapes);
self.handle_egui_output(&egui_output); self.handle_egui_output(&egui_output);
{ {
let app_output = self.frame.take_app_output();
let epi::backend::AppOutput { let epi::backend::AppOutput {
quit: _, // Can't quit a web page quit: _, // Can't quit a web page
window_size: _, // Can't resize a web page window_size: _, // Can't resize a web page
window_title: _, // TODO: change title of window window_title: _, // TODO: change title of window
decorated: _, // Can't toggle decorations decorated: _, // Can't toggle decorations
drag_window: _, // Can't be dragged drag_window: _, // Can't be dragged
tex_allocation_data,
} = app_output; } = 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)) Ok((egui_output, clipped_meshes))
} }
pub fn paint(&mut self, clipped_meshes: Vec<egui::ClippedMesh>) -> Result<(), JsValue> { 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.clear(self.app.clear_color());
self.painter 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) { fn handle_egui_output(&mut self, output: &egui::Output) {

View file

@ -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 crate::{canvas_element_or_die, console_error};
use egui::{ClippedMesh, Rgba, Texture}; use egui::{ClippedMesh, FontImage, Rgba};
use egui_glow::glow; use egui_glow::glow;
use epi::TextureAllocator;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
use web_sys::HtmlCanvasElement; use web_sys::HtmlCanvasElement;
#[cfg(not(target_arch = "wasm32"))]
use web_sys::WebGl2RenderingContext;
use web_sys::WebGlRenderingContext;
pub(crate) struct WrappedGlowPainter { pub(crate) struct WrappedGlowPainter {
pub(crate) gl_ctx: glow::Context, pub(crate) gl_ctx: glow::Context,
@ -63,12 +62,16 @@ fn requires_brightening(canvas: &web_sys::HtmlCanvasElement) -> bool {
.dyn_into::<WebGlRenderingContext>() .dyn_into::<WebGlRenderingContext>()
.unwrap(); .unwrap();
let user_agent = web_sys::window().unwrap().navigator().user_agent().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 { impl crate::Painter for WrappedGlowPainter {
fn as_tex_allocator(&mut self) -> &mut dyn TextureAllocator { fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
&mut self.painter 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 { fn debug_info(&self) -> String {
@ -83,8 +86,8 @@ impl crate::Painter for WrappedGlowPainter {
&self.canvas_id &self.canvas_id
} }
fn upload_egui_texture(&mut self, texture: &Texture) { fn upload_egui_texture(&mut self, font_image: &FontImage) {
self.painter.upload_egui_texture(&self.gl_ctx, texture) self.painter.upload_egui_texture(&self.gl_ctx, font_image)
} }
fn clear(&mut self, clear_color: Rgba) { fn clear(&mut self, clear_color: Rgba) {
@ -99,8 +102,8 @@ impl crate::Painter for WrappedGlowPainter {
) -> Result<(), JsValue> { ) -> Result<(), JsValue> {
let canvas_dimension = [self.canvas.width(), self.canvas.height()]; let canvas_dimension = [self.canvas.width(), self.canvas.height()];
self.painter.paint_meshes( self.painter.paint_meshes(
canvas_dimension,
&self.gl_ctx, &self.gl_ctx,
canvas_dimension,
pixels_per_point, pixels_per_point,
clipped_meshes, clipped_meshes,
); );
@ -113,32 +116,28 @@ impl crate::Painter for WrappedGlowPainter {
} }
pub fn init_glow_context_from_canvas(canvas: &HtmlCanvasElement) -> glow::Context { pub fn init_glow_context_from_canvas(canvas: &HtmlCanvasElement) -> glow::Context {
let ctx = canvas.get_context("webgl2"); let gl2_ctx = canvas
if let Ok(ctx) = ctx { .get_context("webgl2")
crate::console_log("webgl found"); .expect("Failed to query about WebGL2 context");
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 gl_ctx = ctx.dyn_into::<web_sys::WebGlRenderingContext>().unwrap(); if let Some(gl2_ctx) = gl2_ctx {
crate::console_log("success"); crate::console_log("WebGL2 found");
glow::Context::from_webgl1_context(gl_ctx) let gl2_ctx = gl2_ctx
} else { .dyn_into::<web_sys::WebGl2RenderingContext>()
panic!("tried webgl1 but can't get context"); .unwrap();
} glow::Context::from_webgl2_context(gl2_ctx)
} else {
panic!("tried webgl1 but can't get context");
}
}
} else { } else {
panic!("tried webgl2 but something went wrong"); 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!("Failed to get WebGL context.");
}
} }
} }

View file

@ -12,14 +12,17 @@
// Forbid warnings in release builds: // Forbid warnings in release builds:
#![cfg_attr(not(debug_assertions), deny(warnings))] #![cfg_attr(not(debug_assertions), deny(warnings))]
#![forbid(unsafe_code)] #![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; pub mod backend;
#[cfg(feature = "glow")] #[cfg(feature = "glow")]
mod glow_wrapping; mod glow_wrapping;
mod painter; mod painter;
pub mod screen_reader; pub mod screen_reader;
#[cfg(feature = "webgl")]
pub mod webgl1; pub mod webgl1;
#[cfg(feature = "webgl")]
pub mod webgl2; pub mod webgl2;
pub use backend::*; 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() 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
}

View file

@ -1,14 +1,16 @@
use wasm_bindgen::prelude::JsValue; use wasm_bindgen::prelude::JsValue;
pub trait Painter { 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; fn debug_info(&self) -> String;
/// id of the canvas html element containing the rendering /// id of the canvas html element containing the rendering
fn canvas_id(&self) -> &str; 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); fn clear(&mut self, clear_color: egui::Rgba);

View file

@ -1,15 +1,17 @@
use std::collections::HashMap;
use { use {
js_sys::WebAssembly, js_sys::WebAssembly,
wasm_bindgen::{prelude::*, JsCast}, wasm_bindgen::{prelude::*, JsCast},
web_sys::{ web_sys::{
ExtSRgb, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlRenderingContext, WebGlShader, ExtSRgb, WebGlBuffer, WebGlFramebuffer, WebGlProgram, WebGlRenderingContext, WebGlShader,
WebGlTexture, WebglDebugRendererInfo, WebGlTexture,
}, },
}; };
use egui::{ use egui::{
emath::vec2, emath::vec2,
epaint::{Color32, Texture}, epaint::{Color32, FontImage},
}; };
type Gl = WebGlRenderingContext; type Gl = WebGlRenderingContext;
@ -29,19 +31,10 @@ pub struct WebGlPainter {
egui_texture: WebGlTexture, egui_texture: WebGlTexture,
egui_texture_version: Option<u64>, egui_texture_version: Option<u64>,
/// `None` means unallocated (freed) slot. /// Index is the same as in [`egui::TextureId::User`].
user_textures: Vec<Option<UserTexture>>, user_textures: HashMap<u64, WebGlTexture>,
}
#[derive(Default)] next_native_tex_id: u64, // TODO: 128-bit texture space?
struct UserTexture {
size: (usize, usize),
/// Pending upload (will be emptied later).
pixels: Vec<u8>,
/// Lazily uploaded
gl_texture: Option<WebGlTexture>,
} }
impl WebGlPainter { impl WebGlPainter {
@ -111,109 +104,14 @@ impl WebGlPainter {
egui_texture, egui_texture,
egui_texture_version: None, egui_texture_version: None,
user_textures: Default::default(), user_textures: Default::default(),
next_native_tex_id: 1 << 32,
}) })
} }
fn alloc_user_texture_index(&mut self) -> usize { fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
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> {
match texture_id { match texture_id {
egui::TextureId::Egui => Some(&self.egui_texture), egui::TextureId::Egui => Some(&self.egui_texture),
egui::TextureId::User(id) => self egui::TextureId::User(id) => self.user_textures.get(&id),
.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);
}
} }
} }
@ -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 { impl epi::NativeTexture for WebGlPainter {
type Texture = WebGlTexture; type Texture = WebGlTexture;
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId { fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
let id = self.alloc_user_texture_index(); let id = self.next_native_tex_id;
if let Some(Some(user_texture)) = self.user_textures.get_mut(id) { self.next_native_tex_id += 1;
*user_texture = UserTexture { self.user_textures.insert(id, native);
size: (0, 0),
pixels: vec![],
gl_texture: Some(native),
}
}
egui::TextureId::User(id as u64) egui::TextureId::User(id as u64)
} }
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) { fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
if let egui::TextureId::User(id) = id { if let egui::TextureId::User(id) = id {
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) { if let Some(user_texture) = self.user_textures.get_mut(&id) {
*user_texture = UserTexture { *user_texture = replacing;
size: (0, 0),
pixels: vec![],
gl_texture: Some(replacing),
}
} }
} }
} }
} }
impl crate::Painter for WebGlPainter { impl crate::Painter for WebGlPainter {
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator { fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
self 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 { fn debug_info(&self) -> String {
@ -401,8 +323,8 @@ impl crate::Painter for WebGlPainter {
&self.canvas_id &self.canvas_id
} }
fn upload_egui_texture(&mut self, texture: &Texture) { fn upload_egui_texture(&mut self, font_image: &FontImage) {
if self.egui_texture_version == Some(texture.version) { if self.egui_texture_version == Some(font_image.version) {
return; // No change return; // No change
} }
@ -411,8 +333,8 @@ impl crate::Painter for WebGlPainter {
} else { } else {
1.0 // post process enables linear blending 1.0 // post process enables linear blending
}; };
let mut pixels: Vec<u8> = Vec::with_capacity(texture.pixels.len() * 4); let mut pixels: Vec<u8> = Vec::with_capacity(font_image.pixels.len() * 4);
for srgba in texture.srgba_pixels(gamma) { for srgba in font_image.srgba_pixels(gamma) {
pixels.push(srgba.r()); pixels.push(srgba.r());
pixels.push(srgba.g()); pixels.push(srgba.g());
pixels.push(srgba.b()); pixels.push(srgba.b());
@ -431,8 +353,8 @@ impl crate::Painter for WebGlPainter {
Gl::TEXTURE_2D, Gl::TEXTURE_2D,
level, level,
internal_format as i32, internal_format as i32,
texture.width as i32, font_image.width as i32,
texture.height as i32, font_image.height as i32,
border, border,
src_format, src_format,
src_type, src_type,
@ -440,7 +362,7 @@ impl crate::Painter for WebGlPainter {
) )
.unwrap(); .unwrap();
self.egui_texture_version = Some(texture.version); self.egui_texture_version = Some(font_image.version);
} }
fn clear(&mut self, clear_color: egui::Rgba) { fn clear(&mut self, clear_color: egui::Rgba) {
@ -467,8 +389,6 @@ impl crate::Painter for WebGlPainter {
clipped_meshes: Vec<egui::ClippedMesh>, clipped_meshes: Vec<egui::ClippedMesh>,
pixels_per_point: f32, pixels_per_point: f32,
) -> Result<(), JsValue> { ) -> Result<(), JsValue> {
self.upload_user_textures();
let gl = &self.gl; let gl = &self.gl;
if let Some(ref mut post_process) = self.post_process { 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 // See https://github.com/emilk/egui/issues/794
let user_agent = web_sys::window().unwrap().navigator().user_agent().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 PostProcess { impl PostProcess {
@ -768,26 +688,3 @@ fn link_program<'a, T: IntoIterator<Item = &'a WebGlShader>>(
.unwrap_or_else(|| "Unknown error creating program object".into())) .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
}

View file

@ -1,4 +1,5 @@
//! Mostly a carbon-copy of `webgl1.rs`. //! Mostly a carbon-copy of `webgl1.rs`.
use std::collections::HashMap;
use { use {
js_sys::WebAssembly, js_sys::WebAssembly,
@ -11,7 +12,7 @@ use {
use egui::{ use egui::{
emath::vec2, emath::vec2,
epaint::{Color32, Texture}, epaint::{Color32, FontImage},
}; };
type Gl = WebGl2RenderingContext; type Gl = WebGl2RenderingContext;
@ -30,19 +31,10 @@ pub struct WebGl2Painter {
egui_texture: WebGlTexture, egui_texture: WebGlTexture,
egui_texture_version: Option<u64>, egui_texture_version: Option<u64>,
/// `None` means unallocated (freed) slot. /// Index is the same as in [`egui::TextureId::User`].
user_textures: Vec<Option<UserTexture>>, user_textures: HashMap<u64, WebGlTexture>,
}
#[derive(Default)] next_native_tex_id: u64, // TODO: 128-bit texture space?
struct UserTexture {
size: (usize, usize),
/// Pending upload (will be emptied later).
pixels: Vec<u8>,
/// Lazily uploaded
gl_texture: Option<WebGlTexture>,
} }
impl WebGl2Painter { impl WebGl2Painter {
@ -96,109 +88,14 @@ impl WebGl2Painter {
egui_texture, egui_texture,
egui_texture_version: None, egui_texture_version: None,
user_textures: Default::default(), user_textures: Default::default(),
next_native_tex_id: 1 << 32,
}) })
} }
fn alloc_user_texture_index(&mut self) -> usize { fn get_texture(&self, texture_id: egui::TextureId) -> Option<&WebGlTexture> {
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> {
match texture_id { match texture_id {
egui::TextureId::Egui => Some(&self.egui_texture), egui::TextureId::Egui => Some(&self.egui_texture),
egui::TextureId::User(id) => self egui::TextureId::User(id) => self.user_textures.get(&id),
.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);
}
} }
} }
@ -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 { impl epi::NativeTexture for WebGl2Painter {
type Texture = WebGlTexture; type Texture = WebGlTexture;
fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId { fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId {
let id = self.alloc_user_texture_index(); let id = self.next_native_tex_id;
if let Some(Some(user_texture)) = self.user_textures.get_mut(id) { self.next_native_tex_id += 1;
*user_texture = UserTexture { self.user_textures.insert(id, native);
size: (0, 0),
pixels: vec![],
gl_texture: Some(native),
}
}
egui::TextureId::User(id as u64) egui::TextureId::User(id as u64)
} }
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) { fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture) {
if let egui::TextureId::User(id) = id { if let egui::TextureId::User(id) = id {
if let Some(Some(user_texture)) = self.user_textures.get_mut(id as usize) { if let Some(user_texture) = self.user_textures.get_mut(&id) {
*user_texture = UserTexture { *user_texture = replacing;
size: (0, 0),
pixels: vec![],
gl_texture: Some(replacing),
}
} }
} }
} }
} }
impl crate::Painter for WebGl2Painter { impl crate::Painter for WebGl2Painter {
fn as_tex_allocator(&mut self) -> &mut dyn epi::TextureAllocator { fn set_texture(&mut self, tex_id: u64, image: epi::Image) {
self 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 { fn debug_info(&self) -> String {
@ -386,13 +307,13 @@ impl crate::Painter for WebGl2Painter {
&self.canvas_id &self.canvas_id
} }
fn upload_egui_texture(&mut self, texture: &Texture) { fn upload_egui_texture(&mut self, font_image: &FontImage) {
if self.egui_texture_version == Some(texture.version) { if self.egui_texture_version == Some(font_image.version) {
return; // No change return; // No change
} }
let mut pixels: Vec<u8> = Vec::with_capacity(texture.pixels.len() * 4); let mut pixels: Vec<u8> = Vec::with_capacity(font_image.pixels.len() * 4);
for srgba in texture.srgba_pixels(1.0) { for srgba in font_image.srgba_pixels(1.0) {
pixels.push(srgba.r()); pixels.push(srgba.r());
pixels.push(srgba.g()); pixels.push(srgba.g());
pixels.push(srgba.b()); pixels.push(srgba.b());
@ -412,8 +333,8 @@ impl crate::Painter for WebGl2Painter {
Gl::TEXTURE_2D, Gl::TEXTURE_2D,
level, level,
internal_format as i32, internal_format as i32,
texture.width as i32, font_image.width as i32,
texture.height as i32, font_image.height as i32,
border, border,
src_format, src_format,
src_type, src_type,
@ -421,7 +342,7 @@ impl crate::Painter for WebGl2Painter {
) )
.unwrap(); .unwrap();
self.egui_texture_version = Some(texture.version); self.egui_texture_version = Some(font_image.version);
} }
fn clear(&mut self, clear_color: egui::Rgba) { fn clear(&mut self, clear_color: egui::Rgba) {
@ -448,8 +369,6 @@ impl crate::Painter for WebGl2Painter {
clipped_meshes: Vec<egui::ClippedMesh>, clipped_meshes: Vec<egui::ClippedMesh>,
pixels_per_point: f32, pixels_per_point: f32,
) -> Result<(), JsValue> { ) -> Result<(), JsValue> {
self.upload_user_textures();
let gl = &self.gl; let gl = &self.gl;
self.post_process self.post_process

View file

@ -1,9 +1,10 @@
[package] [package]
name = "emath" name = "emath"
version = "0.15.0" version = "0.16.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"] authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Minimal 2D math library for GUI work" 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" homepage = "https://github.com/emilk/egui/tree/master/emath"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
readme = "README.md" readme = "README.md"

View file

@ -84,9 +84,9 @@
clippy::verbose_file_reads, clippy::verbose_file_reads,
clippy::zero_sized_map_values, clippy::zero_sized_map_values,
future_incompatible, future_incompatible,
missing_crate_level_docs,
nonstandard_style, nonstandard_style,
rust_2018_idioms rust_2018_idioms,
rustdoc::missing_crate_level_docs
)] )]
#![allow(clippy::float_cmp)] #![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]

View file

@ -4,8 +4,12 @@ All notable changes to the epaint crate will be documented in this file.
## Unreleased ## 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)). * 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 ## 0.15.0 - 2021-10-24

View file

@ -1,9 +1,10 @@
[package] [package]
name = "epaint" name = "epaint"
version = "0.15.0" version = "0.16.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"] authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Minimal 2D graphics library for GUI work" 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" homepage = "https://github.com/emilk/egui/tree/master/epaint"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
readme = "README.md" readme = "README.md"
@ -25,7 +26,7 @@ all-features = true
[lib] [lib]
[dependencies] [dependencies]
emath = { version = "0.15.0", path = "../emath" } emath = { version = "0.16.0", path = "../emath" }
ab_glyph = "0.2.11" ab_glyph = "0.2.11"
ahash = { version = "0.7", features = ["std"], default-features = false } ahash = { version = "0.7", features = ["std"], default-features = false }

View file

@ -80,9 +80,9 @@
clippy::verbose_file_reads, clippy::verbose_file_reads,
clippy::zero_sized_map_values, clippy::zero_sized_map_values,
future_incompatible, future_incompatible,
missing_crate_level_docs,
nonstandard_style, nonstandard_style,
rust_2018_idioms rust_2018_idioms,
rustdoc::missing_crate_level_docs
)] )]
#![allow(clippy::float_cmp)] #![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]
@ -109,7 +109,7 @@ pub use {
stroke::Stroke, stroke::Stroke,
tessellator::{tessellate_shapes, TessellationOptions, Tessellator}, tessellator::{tessellate_shapes, TessellationOptions, Tessellator},
text::{Fonts, Galley, TextStyle}, text::{Fonts, Galley, TextStyle},
texture_atlas::{Texture, TextureAtlas}, texture_atlas::{FontImage, TextureAtlas},
}; };
pub use emath::{pos2, vec2, Pos2, Rect, Vec2}; pub use emath::{pos2, vec2, Pos2, Rect, Vec2};

View file

@ -64,7 +64,6 @@ impl Mesh {
/// Are all indices within the bounds of the contained vertices? /// Are all indices within the bounds of the contained vertices?
pub fn is_valid(&self) -> bool { pub fn is_valid(&self) -> bool {
use std::convert::TryFrom;
if let Ok(n) = u32::try_from(self.vertices.len()) { if let Ok(n) = u32::try_from(self.vertices.len()) {
self.indices.iter().all(|&i| i < n) self.indices.iter().all(|&i| i < n)
} else { } else {
@ -223,7 +222,6 @@ impl Mesh {
MAX_SIZE MAX_SIZE
); );
use std::convert::TryFrom;
let mesh = Mesh16 { let mesh = Mesh16 {
indices: self.indices[span_start..index_cursor] indices: self.indices[span_start..index_cursor]
.iter() .iter()
@ -267,7 +265,6 @@ pub struct Mesh16 {
impl Mesh16 { impl Mesh16 {
/// Are all indices within the bounds of the contained vertices? /// Are all indices within the bounds of the contained vertices?
pub fn is_valid(&self) -> bool { pub fn is_valid(&self) -> bool {
use std::convert::TryFrom;
if let Ok(n) = u16::try_from(self.vertices.len()) { if let Ok(n) = u16::try_from(self.vertices.len()) {
self.indices.iter().all(|&i| i < n) self.indices.iter().all(|&i| i < n)
} else { } else {

View file

@ -377,7 +377,7 @@ fn allocate_glyph(
} else { } else {
let glyph_pos = atlas.allocate((glyph_width, glyph_height)); let glyph_pos = atlas.allocate((glyph_width, glyph_height));
let texture = atlas.texture_mut(); let texture = atlas.image_mut();
glyph.draw(|x, y, v| { glyph.draw(|x, y, v| {
if v > 0.0 { if v > 0.0 {
let px = glyph_pos.0 + x as usize; let px = glyph_pos.0 + x as usize;

View file

@ -6,7 +6,7 @@ use crate::{
font::{Font, FontImpl}, font::{Font, FontImpl},
Galley, LayoutJob, Galley, LayoutJob,
}, },
Texture, TextureAtlas, FontImage, TextureAtlas,
}; };
// TODO: rename // TODO: rename
@ -56,7 +56,7 @@ pub enum FontFamily {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct FontData { 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]>, pub font: std::borrow::Cow<'static, [u8]>,
/// Which font face in the file to use. /// Which font face in the file to use.
/// When in doubt, use `0`. /// When in doubt, use `0`.
@ -233,9 +233,10 @@ pub struct Fonts {
definitions: FontDefinitions, definitions: FontDefinitions,
fonts: BTreeMap<TextStyle, Font>, fonts: BTreeMap<TextStyle, Font>,
atlas: Arc<Mutex<TextureAtlas>>, 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). /// 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>, galley_cache: Mutex<GalleyCache>,
} }
@ -258,7 +259,7 @@ impl Fonts {
// Make the top left pixel fully white: // Make the top left pixel fully white:
let pos = atlas.allocate((1, 1)); let pos = atlas.allocate((1, 1));
assert_eq!(pos, (0, 0)); assert_eq!(pos, (0, 0));
atlas.texture_mut()[pos] = 255; atlas.image_mut()[pos] = 255;
} }
let atlas = Arc::new(Mutex::new(atlas)); let atlas = Arc::new(Mutex::new(atlas));
@ -284,7 +285,7 @@ impl Fonts {
{ {
let mut atlas = atlas.lock(); 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: // Make sure we seed the texture version with something unique based on the default characters:
texture.version = crate::util::hash(&texture.pixels); texture.version = crate::util::hash(&texture.pixels);
} }
@ -294,7 +295,7 @@ impl Fonts {
definitions, definitions,
fonts, fonts,
atlas, atlas,
buffered_texture: Default::default(), //atlas.lock().texture().clone(); buffered_font_image: Default::default(), //atlas.lock().texture().clone();
galley_cache: Default::default(), galley_cache: Default::default(),
} }
} }
@ -319,11 +320,11 @@ impl Fonts {
} }
/// Call each frame to get the latest available font texture data. /// 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 atlas = self.atlas.lock();
let mut buffered_texture = self.buffered_texture.lock(); let mut buffered_texture = self.buffered_font_image.lock();
if buffered_texture.version != atlas.texture().version { if buffered_texture.version != atlas.image().version {
*buffered_texture = Arc::new(atlas.texture().clone()); *buffered_texture = Arc::new(atlas.image().clone());
} }
buffered_texture.clone() buffered_texture.clone()

View file

@ -1,7 +1,6 @@
// TODO: `TextureData` or similar?
/// An 8-bit texture containing font data. /// An 8-bit texture containing font data.
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct Texture { pub struct FontImage {
/// e.g. a hash of the data. Use this to detect changes! /// e.g. a hash of the data. Use this to detect changes!
/// If the texture changes, this too will change. /// If the texture changes, this too will change.
pub version: u64, pub version: u64,
@ -11,7 +10,7 @@ pub struct Texture {
pub pixels: Vec<u8>, pub pixels: Vec<u8>,
} }
impl Texture { impl FontImage {
pub fn size(&self) -> [usize; 2] { pub fn size(&self) -> [usize; 2] {
[self.width, self.height] [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; type Output = u8;
#[inline] #[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] #[inline]
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut u8 { fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut u8 {
assert!(x < self.width); 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. /// More characters can be added, possibly expanding the texture.
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct TextureAtlas { pub struct TextureAtlas {
texture: Texture, image: FontImage,
/// Used for when allocating new rectangles. /// Used for when allocating new rectangles.
cursor: (usize, usize), cursor: (usize, usize),
@ -71,7 +70,7 @@ pub struct TextureAtlas {
impl TextureAtlas { impl TextureAtlas {
pub fn new(width: usize, height: usize) -> Self { pub fn new(width: usize, height: usize) -> Self {
Self { Self {
texture: Texture { image: FontImage {
version: 0, version: 0,
width, width,
height, height,
@ -81,13 +80,13 @@ impl TextureAtlas {
} }
} }
pub fn texture(&self) -> &Texture { pub fn image(&self) -> &FontImage {
&self.texture &self.image
} }
pub fn texture_mut(&mut self) -> &mut Texture { pub fn image_mut(&mut self) -> &mut FontImage {
self.texture.version += 1; self.image.version += 1;
&mut self.texture &mut self.image
} }
/// Returns the coordinates of where the rect ended up. /// Returns the coordinates of where the rect ended up.
@ -98,12 +97,12 @@ impl TextureAtlas {
const PADDING: usize = 1; const PADDING: usize = 1;
assert!( assert!(
w <= self.texture.width, w <= self.image.width,
"Tried to allocate a {} wide glyph in a {} wide texture atlas", "Tried to allocate a {} wide glyph in a {} wide texture atlas",
w, 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: // New row:
self.cursor.0 = 0; self.cursor.0 = 0;
self.cursor.1 += self.row_height + PADDING; self.cursor.1 += self.row_height + PADDING;
@ -111,19 +110,19 @@ impl TextureAtlas {
} }
self.row_height = self.row_height.max(h); self.row_height = self.row_height.max(h);
while self.cursor.1 + self.row_height >= self.texture.height { while self.cursor.1 + self.row_height >= self.image.height {
self.texture.height *= 2; self.image.height *= 2;
} }
if self.texture.width * self.texture.height > self.texture.pixels.len() { if self.image.width * self.image.height > self.image.pixels.len() {
self.texture self.image
.pixels .pixels
.resize(self.texture.width * self.texture.height, 0); .resize(self.image.width * self.image.height, 0);
} }
let pos = self.cursor; let pos = self.cursor;
self.cursor.0 += w + PADDING; self.cursor.0 += w + PADDING;
self.texture.version += 1; self.image.version += 1;
(pos.0 as usize, pos.1 as usize) (pos.0 as usize, pos.1 as usize)
} }
} }

View file

@ -1,9 +1,10 @@
[package] [package]
name = "epi" name = "epi"
version = "0.15.0" version = "0.16.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"] authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
description = "Backend-agnostic interface for writing apps using egui" 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" homepage = "https://github.com/emilk/egui/tree/master/epi"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
readme = "README.md" readme = "README.md"
@ -23,7 +24,7 @@ all-features = true
[lib] [lib]
[dependencies] [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 } directories-next = { version = "2", optional = true }
ron = { version = "0.7", optional = true } ron = { version = "0.7", optional = true }

View file

@ -81,9 +81,9 @@
clippy::verbose_file_reads, clippy::verbose_file_reads,
clippy::zero_sized_map_values, clippy::zero_sized_map_values,
future_incompatible, future_incompatible,
missing_crate_level_docs,
nonstandard_style, nonstandard_style,
rust_2018_idioms rust_2018_idioms,
rustdoc::missing_crate_level_docs
)] )]
#![allow(clippy::float_cmp)] #![allow(clippy::float_cmp)]
#![allow(clippy::manual_range_contains)] #![allow(clippy::manual_range_contains)]
@ -95,6 +95,8 @@ pub mod file_storage;
pub use egui; // Re-export for user convenience 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, /// 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`]. /// 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`]. /// The given [`egui::CtxRef`] is only valid for the duration of this call.
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut Frame<'_>); /// 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. /// Called once before the first frame.
/// ///
@ -113,13 +118,7 @@ pub trait App {
/// [`egui::Context::set_visuals`] etc. /// [`egui::Context::set_visuals`] etc.
/// ///
/// Also allows you to restore state, if there is a storage (required the "persistence" feature). /// Also allows you to restore state, if there is a storage (required the "persistence" feature).
fn setup( fn setup(&mut self, _ctx: &egui::CtxRef, _frame: &Frame, _storage: Option<&dyn Storage>) {}
&mut self,
_ctx: &egui::CtxRef,
_frame: &mut Frame<'_>,
_storage: Option<&dyn Storage>,
) {
}
/// If `true` a warm-up call to [`Self::update`] will be issued where /// If `true` a warm-up call to [`Self::update`] will be issued where
/// `ctx.memory().everything_is_visible()` will be set to `true`. /// `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)] #[derive(Clone)]
pub struct NativeOptions { pub struct NativeOptions {
/// Sets whether or not the window will always be on top of other windows. /// 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. /// Image data for the icon.
#[derive(Clone)] #[derive(Clone)]
pub struct IconData { pub struct IconData {
/// RGBA pixels. /// RGBA pixels, unmultiplied.
pub rgba: Vec<u8>, pub rgba: Vec<u8>,
/// Image width. This should be a multiple of 4. /// 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?), /// It provides methods to inspect the surroundings (are we on the web?),
/// allocate textures, and change settings (e.g. window size). /// 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. /// True if you are in a web environment.
pub fn is_web(&self) -> bool { pub fn is_web(&self) -> bool {
self.info().web_info.is_some() self.lock().info.web_info.is_some()
} }
/// Information about the integration. /// Information about the integration.
pub fn info(&self) -> &IntegrationInfo { pub fn info(&self) -> IntegrationInfo {
&self.0.info self.lock().info.clone()
}
/// A way to allocate textures.
pub fn tex_allocator(&mut self) -> &mut dyn TextureAllocator {
self.0.tex_allocator
} }
/// Signal the app to stop/exit/quit the app (only works for native apps, not web apps). /// 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. /// The framework will not quit immediately, but at the end of the this frame.
pub fn quit(&mut self) { pub fn quit(&self) {
self.0.output.quit = true; self.lock().output.quit = true;
} }
/// Set the desired inner size of the window (in egui points). /// Set the desired inner size of the window (in egui points).
pub fn set_window_size(&mut self, size: egui::Vec2) { pub fn set_window_size(&self, size: egui::Vec2) {
self.0.output.window_size = Some(size); self.lock().output.window_size = Some(size);
} }
/// Set the desired title of the window. /// Set the desired title of the window.
pub fn set_window_title(&mut self, title: &str) { pub fn set_window_title(&self, title: &str) {
self.0.output.window_title = Some(title.to_owned()); self.lock().output.window_title = Some(title.to_owned());
} }
/// Set whether to show window decorations (i.e. a frame around you app). /// 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. /// If false it will be difficult to move and resize the app.
pub fn set_decorations(&mut self, decorated: bool) { pub fn set_decorations(&self, decorated: bool) {
self.0.output.decorated = Some(decorated); self.lock().output.decorated = Some(decorated);
} }
/// When called, the native window will follow the /// When called, the native window will follow the
/// movement of the cursor while the primary mouse button is down. /// movement of the cursor while the primary mouse button is down.
/// ///
/// Does not work on the web, and works badly on Mac. /// Does not work on the web.
pub fn drag_window(&mut self) { pub fn drag_window(&self) {
self.0.output.drag_window = true; 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. /// This signals the [`egui`] integration that a repaint is required.
pub fn repaint_signal(&self) -> std::sync::Arc<dyn RepaintSignal> { ///
self.0.repaint_signal.clone() /// 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. /// There is no way to change a texture.
/// Instead allocate a new texture and free the previous one with [`Self::free`]. /// Instead allocate a new texture and free the previous one with [`Self::free`].
fn alloc_srgba_premultiplied( fn alloc(&self, image: Image) -> egui::TextureId;
&mut self,
size: (usize, usize),
srgba_pixels: &[egui::Color32],
) -> egui::TextureId;
/// Free the given texture. /// 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 /// Abstraction for platform dependent texture reference
@ -359,21 +421,13 @@ pub trait NativeTexture {
/// The native texture type. /// The native texture type.
type Texture; 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; fn register_native_texture(&mut self, native: Self::Texture) -> egui::TextureId;
/// Change id's actual pointing texture /// Change what texture the given id refers to.
/// only for user texture
fn replace_native_texture(&mut self, id: egui::TextureId, replacing: Self::Texture); 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. /// 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`. /// You only need to look here if you are writing a backend for `epi`.
pub mod backend { pub mod backend {
use std::collections::HashMap;
use super::*; use super::*;
/// How to signal the [`egui`] integration that a repaint is required.
pub trait RepaintSignal: Send + Sync {
/// This signals the [`egui`] integration that a repaint is required.
///
/// Call this e.g. when a background process finishes in an async context and/or background thread.
fn request_repaint(&self);
}
/// The data required by [`Frame`] each frame. /// The data required by [`Frame`] each frame.
pub struct FrameBuilder<'a> { pub struct FrameData {
/// Information about the integration. /// Information about the integration.
pub info: IntegrationInfo, 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. /// 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. /// 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>, pub repaint_signal: std::sync::Arc<dyn RepaintSignal>,
} }
impl<'a> FrameBuilder<'a> { /// The data needed in order to allocate and free textures/images.
/// Wrap us in a [`Frame`] to send to [`App::update`]. #[derive(Default)]
pub fn build(self) -> Frame<'a> { #[must_use]
Frame(self) 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. /// Action that can be taken by the user app.
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Default)]
#[must_use]
pub struct AppOutput { pub struct AppOutput {
/// Set to `true` to stop the app. /// Set to `true` to stop the app.
/// This does nothing for web apps. /// 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. /// Set to some string to rename the outer window (e.g. glium window) to this title.
pub window_title: Option<String>, 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>, 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, pub drag_window: bool,
/// A way to allocate textures (on integrations that support it).
pub tex_allocation_data: TexAllocationData,
} }
} }

View file

@ -5,6 +5,6 @@
# to the user in the error, instead of "error: invalid channel name '[toolchain]'". # to the user in the error, instead of "error: invalid channel name '[toolchain]'".
[toolchain] [toolchain]
channel = "1.54.0" channel = "1.56.0"
components = [ "rustfmt", "clippy" ] components = [ "rustfmt", "clippy" ]
targets = [ "wasm32-unknown-unknown" ] targets = [ "wasm32-unknown-unknown" ]

View file

@ -50,9 +50,12 @@ cargo build \
--no-default-features \ --no-default-features \
--features ${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…" echo "Generating JS bindings for wasm…"
TARGET_NAME="${CRATE_NAME}.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 --out-dir docs --no-modules --no-typescript
# to get wasm-strip: apt/brew/dnf install wabt # to get wasm-strip: apt/brew/dnf install wabt

View file

@ -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 egui && cargo check --no-default-features --features "multi_threaded,serialize")
(cd eframe && cargo check --no-default-features --features "egui_glow") (cd eframe && cargo check --no-default-features --features "egui_glow")
(cd epi && cargo check --no-default-features) (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-winit && cargo check --no-default-features) # we don't pick singlethreaded or multithreaded
(cd egui_glium && cargo check --no-default-features) (cd egui_glium && cargo check --no-default-features)
(cd egui_glow && cargo check --no-default-features) (cd egui_glow && cargo check --no-default-features)

View file

@ -5,5 +5,5 @@ cd "$script_path/.."
# Pre-requisites: # Pre-requisites:
rustup target add wasm32-unknown-unknown rustup target add wasm32-unknown-unknown
cargo install -f wasm-bindgen-cli cargo install wasm-bindgen-cli
cargo update -p wasm-bindgen cargo update -p wasm-bindgen