Compare commits
10 commits
master
...
feat-windo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a8b7eebc19 | ||
![]() |
9c98962357 | ||
![]() |
1cb9d954d1 | ||
![]() |
e81f7c8a55 | ||
![]() |
2b819ee826 | ||
![]() |
df19fa87d8 | ||
![]() |
da056696e4 | ||
![]() |
d644b6bc8c | ||
![]() |
817236f640 | ||
![]() |
634d506012 |
102 changed files with 1810 additions and 1929 deletions
|
@ -1,6 +0,0 @@
|
||||||
# clipboard api is still unstable, so web-sys requires the below flag to be passed for copy (ctrl + c) to work
|
|
||||||
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
|
|
||||||
# check status at https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility
|
|
||||||
# we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93
|
|
||||||
[target.wasm32-unknown-unknown]
|
|
||||||
rustflags = ["--cfg=web_sys_unstable_apis"]
|
|
53
.github/workflows/rust.yml
vendored
53
.github/workflows/rust.yml
vendored
|
@ -16,93 +16,66 @@ jobs:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: default
|
profile: default
|
||||||
toolchain: 1.65.0
|
toolchain: 1.65.0
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- name: Install packages (Linux)
|
- name: Install packages (Linux)
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
#uses: awalsh128/cache-apt-pkgs-action@v1.2.2
|
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
|
||||||
#TODO(emilk) use upstream when https://github.com/awalsh128/cache-apt-pkgs-action/pull/90 is merged
|
|
||||||
uses: rerun-io/cache-apt-pkgs-action@59534850182063abf1b2c11bb3686722a12a8397
|
|
||||||
with:
|
|
||||||
packages: libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev libgtk-3-dev # libgtk-3-dev is used by rfd
|
|
||||||
version: 1.0
|
|
||||||
execute_install_scripts: true
|
|
||||||
|
|
||||||
- name: Set up cargo cache
|
- name: Set up cargo cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Rustfmt
|
- name: Rustfmt
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: fmt
|
command: fmt
|
||||||
args: --all -- --check
|
args: --all -- --check
|
||||||
|
|
||||||
- name: Install cargo-cranky
|
- name: Install cargo-cranky
|
||||||
uses: baptiste0928/cargo-install@v1
|
uses: baptiste0928/cargo-install@v1
|
||||||
with:
|
with:
|
||||||
crate: cargo-cranky
|
crate: cargo-cranky
|
||||||
|
- name: Check all features
|
||||||
- name: check --all-features
|
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: check
|
command: check
|
||||||
args: --locked --all-features --all-targets
|
args: --locked --all-features
|
||||||
|
- name: Check default features
|
||||||
- name: check default features
|
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: check
|
command: check
|
||||||
args: --locked --all-targets
|
args: --locked
|
||||||
|
- name: Check no default features
|
||||||
- name: check --no-default-features
|
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: check
|
command: check
|
||||||
args: --locked --no-default-features --lib --all-targets
|
args: --locked --no-default-features --lib
|
||||||
|
|
||||||
- name: check eframe --no-default-features
|
|
||||||
uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: check
|
|
||||||
args: --locked --no-default-features --lib --all-targets -p eframe
|
|
||||||
|
|
||||||
- name: Test doc-tests
|
- name: Test doc-tests
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --doc --all-features
|
args: --doc --all-features
|
||||||
|
|
||||||
- name: cargo doc --lib
|
- name: cargo doc --lib
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: doc
|
command: doc
|
||||||
args: --lib --no-deps --all-features
|
args: --lib --no-deps --all-features
|
||||||
|
|
||||||
- name: cargo doc --document-private-items
|
- name: cargo doc --document-private-items
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: doc
|
command: doc
|
||||||
args: --document-private-items --no-deps --all-features
|
args: --document-private-items --no-deps --all-features
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --all-features
|
args: --all-features
|
||||||
|
|
||||||
- name: Cranky
|
- name: Cranky
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: cranky
|
command: cranky
|
||||||
args: --all-targets --all-features -- -D warnings
|
args: --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
check_wasm:
|
check_wasm:
|
||||||
name: Check wasm32 + wasm-bindgen
|
name: Check wasm32 + wasm-bindgen
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
@ -146,7 +119,7 @@ jobs:
|
||||||
- name: wasm-bindgen
|
- name: wasm-bindgen
|
||||||
uses: jetli/wasm-bindgen-action@v0.1.0
|
uses: jetli/wasm-bindgen-action@v0.1.0
|
||||||
with:
|
with:
|
||||||
version: "0.2.84"
|
version: "0.2.83"
|
||||||
|
|
||||||
- run: ./sh/wasm_bindgen_check.sh --skip-setup
|
- run: ./sh/wasm_bindgen_check.sh --skip-setup
|
||||||
|
|
||||||
|
@ -156,8 +129,6 @@ jobs:
|
||||||
command: cranky
|
command: cranky
|
||||||
args: --target wasm32-unknown-unknown --all-features -p egui_demo_app --lib -- -D warnings
|
args: --target wasm32-unknown-unknown --all-features -p egui_demo_app --lib -- -D warnings
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
cargo-deny:
|
cargo-deny:
|
||||||
name: cargo deny
|
name: cargo deny
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
@ -167,29 +138,22 @@ jobs:
|
||||||
with:
|
with:
|
||||||
rust-version: "1.65.0"
|
rust-version: "1.65.0"
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
android:
|
android:
|
||||||
name: android
|
name: android
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.65.0
|
toolchain: 1.65.0
|
||||||
target: aarch64-linux-android
|
target: aarch64-linux-android
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- name: Set up cargo cache
|
- name: Set up cargo cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- run: cargo check --features wgpu --target aarch64-linux-android
|
- run: cargo check --features wgpu --target aarch64-linux-android
|
||||||
working-directory: crates/eframe
|
working-directory: crates/eframe
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
name: Check Windows
|
name: Check Windows
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
@ -209,3 +173,4 @@ jobs:
|
||||||
with:
|
with:
|
||||||
command: check
|
command: check
|
||||||
args: --all-targets --all-features
|
args: --all-targets --all-features
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,6 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
|
||||||
## 0.21.0 - 2023-02-08 - Deadlock fix and style customizability
|
|
||||||
* ⚠️ BREAKING: `egui::Context` now use closures for locking ([#2625](https://github.com/emilk/egui/pull/2625)):
|
* ⚠️ BREAKING: `egui::Context` now use closures for locking ([#2625](https://github.com/emilk/egui/pull/2625)):
|
||||||
* `ctx.input().key_pressed(Key::A)` -> `ctx.input(|i| i.key_pressed(Key::A))`
|
* `ctx.input().key_pressed(Key::A)` -> `ctx.input(|i| i.key_pressed(Key::A))`
|
||||||
* `ui.memory().toggle_popup(popup_id)` -> `ui.memory_mut(|mem| mem.toggle_popup(popup_id))`
|
* `ui.memory().toggle_popup(popup_id)` -> `ui.memory_mut(|mem| mem.toggle_popup(popup_id))`
|
||||||
|
@ -29,7 +26,6 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
||||||
* You can turn off the vertical line left of indented regions with `Visuals::indent_has_left_vline` ([#2636](https://github.com/emilk/egui/pull/2636)).
|
* You can turn off the vertical line left of indented regions with `Visuals::indent_has_left_vline` ([#2636](https://github.com/emilk/egui/pull/2636)).
|
||||||
* Add `Response.highlight` to highlight a widget ([#2632](https://github.com/emilk/egui/pull/2632)).
|
* Add `Response.highlight` to highlight a widget ([#2632](https://github.com/emilk/egui/pull/2632)).
|
||||||
* Add `Separator::grow` and `Separator::shrink` ([#2665](https://github.com/emilk/egui/pull/2665)).
|
* Add `Separator::grow` and `Separator::shrink` ([#2665](https://github.com/emilk/egui/pull/2665)).
|
||||||
* Add `Slider::trailing_fill` for trailing color behind the circle like a `ProgressBar` ([#2660](https://github.com/emilk/egui/pull/2660)).
|
|
||||||
|
|
||||||
### Changed 🔧
|
### Changed 🔧
|
||||||
* Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)).
|
* Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)).
|
||||||
|
@ -37,16 +33,14 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
||||||
* Default `ComboBox` is now controlled with `Spacing::combo_width` ([#2621](https://github.com/emilk/egui/pull/2621)).
|
* Default `ComboBox` is now controlled with `Spacing::combo_width` ([#2621](https://github.com/emilk/egui/pull/2621)).
|
||||||
* `DragValue` and `Slider` now use the proportional font ([#2638](https://github.com/emilk/egui/pull/2638)).
|
* `DragValue` and `Slider` now use the proportional font ([#2638](https://github.com/emilk/egui/pull/2638)).
|
||||||
* `ScrollArea` is less aggressive about clipping its contents ([#2665](https://github.com/emilk/egui/pull/2665)).
|
* `ScrollArea` is less aggressive about clipping its contents ([#2665](https://github.com/emilk/egui/pull/2665)).
|
||||||
* Updated to be compatible with a major breaking change in AccessKit that drastically reduces memory usage when accessibility is enabled ([#2678](https://github.com/emilk/egui/pull/2678)).
|
|
||||||
* Improve `DragValue` behavior ([#2649](https://github.com/emilk/egui/pull/2649), [#2650](https://github.com/emilk/egui/pull/2650), [#2688](https://github.com/emilk/egui/pull/2688), [#2638](https://github.com/emilk/egui/pull/2638)).
|
|
||||||
|
|
||||||
### Fixed 🐛
|
### Fixed 🐛
|
||||||
* Trigger `PointerEvent::Released` for drags ([#2507](https://github.com/emilk/egui/pull/2507)).
|
* Trigger `PointerEvent::Released` for drags ([#2507](https://github.com/emilk/egui/pull/2507)).
|
||||||
* Expose `TextEdit`'s multiline flag to AccessKit ([#2448](https://github.com/emilk/egui/pull/2448)).
|
* Expose `TextEdit`'s multiline flag to AccessKit ([#2448](https://github.com/emilk/egui/pull/2448)).
|
||||||
* Don't render `\r` (Carriage Return) ([#2452](https://github.com/emilk/egui/pull/2452)).
|
* Don't render `\r` (Carriage Return) ([#2452](https://github.com/emilk/egui/pull/2452)).
|
||||||
* The `button_padding` style option works closer as expected with image+text buttons now ([#2510](https://github.com/emilk/egui/pull/2510)).
|
* The `button_padding` style option works closer as expected with image+text buttons now ([#2510](https://github.com/emilk/egui/pull/2510)).
|
||||||
|
* Fixed rendering of `…` (ellipsis).
|
||||||
* Menus are now moved to fit on the screen.
|
* Menus are now moved to fit on the screen.
|
||||||
* Fix `Window::pivot` causing windows to move around ([#2694](https://github.com/emilk/egui/pull/2694)).
|
|
||||||
|
|
||||||
|
|
||||||
## 0.20.1 - 2022-12-11 - Fix key-repeat
|
## 0.20.1 - 2022-12-11 - Fix key-repeat
|
||||||
|
|
524
Cargo.lock
generated
524
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -66,11 +66,11 @@ To test the demo app locally, run `cargo run --release -p egui_demo_app`.
|
||||||
|
|
||||||
The native backend is [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) (using [`glow`](https://crates.io/crates/glow)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run:
|
The native backend is [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) (using [`glow`](https://crates.io/crates/glow)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run:
|
||||||
|
|
||||||
`sudo apt-get install -y libclang-dev libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev`
|
`sudo apt-get install -y libclang-dev libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev`
|
||||||
|
|
||||||
On Fedora Rawhide you need to run:
|
On Fedora Rawhide you need to run:
|
||||||
|
|
||||||
`dnf install clang clang-devel clang-tools-extra libxkbcommon-devel pkg-config openssl-devel libxcb-devel`
|
`dnf install clang clang-devel clang-tools-extra speech-dispatcher-devel libxkbcommon-devel pkg-config openssl-devel libxcb-devel`
|
||||||
|
|
||||||
**NOTE**: This is just for the demo app - egui itself is completely platform agnostic!
|
**NOTE**: This is just for the demo app - egui itself is completely platform agnostic!
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,6 @@ All notable changes to the `ecolor` crate will be noted in this file.
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
|
||||||
## 0.21.0 - 2023-02-08
|
|
||||||
* Add `Color32::gamma_multiply` ([#2437](https://github.com/emilk/egui/pull/2437)).
|
* Add `Color32::gamma_multiply` ([#2437](https://github.com/emilk/egui/pull/2437)).
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ecolor"
|
name = "ecolor"
|
||||||
version = "0.21.0"
|
version = "0.20.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
|
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
|
||||||
"Andreas Reich <reichandreas@gmx.de>",
|
"Andreas Reich <reichandreas@gmx.de>",
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
# ecolor - egui color library
|
# ecolor - egui color library
|
||||||
|
|
||||||
[](https://crates.io/crates/ecolor)
|
|
||||||
[](https://docs.rs/ecolor)
|
|
||||||
[](https://github.com/rust-secure-code/safety-dance/)
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
A simple color storage and conversion library.
|
A simple color storage and conversion library.
|
||||||
|
|
||||||
Made for [`egui`](https://github.com/emilk/egui/).
|
Made for [`egui`](https://github.com/emilk/egui/).
|
||||||
|
|
|
@ -5,38 +5,20 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
|
||||||
## 0.21.3 - 2023-02-15
|
|
||||||
* Fix typing the letter 'P' on web ([#2740](https://github.com/emilk/egui/pull/2740)).
|
|
||||||
|
|
||||||
|
|
||||||
## 0.21.2 - 2023-02-12
|
|
||||||
* Allow compiling `eframe` with `--no-default-features` ([#2728](https://github.com/emilk/egui/pull/2728)).
|
|
||||||
|
|
||||||
|
|
||||||
## 0.21.1 - 2023-02-12
|
|
||||||
* Fixed crash when native window position is in an invalid state, which could happen e.g. due to changes in monitor size or DPI ([#2722](https://github.com/emilk/egui/issues/2722)).
|
|
||||||
|
|
||||||
|
|
||||||
## 0.21.0 - 2023-02-08 - Update to `winit` 0.28
|
|
||||||
* ⚠️ BREAKING: `App::clear_color` now expects you to return a raw float array ([#2666](https://github.com/emilk/egui/pull/2666)).
|
* ⚠️ BREAKING: `App::clear_color` now expects you to return a raw float array ([#2666](https://github.com/emilk/egui/pull/2666)).
|
||||||
* The `screen_reader` feature has now been renamed `web_screen_reader` and only work on web. On other platforms, use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)).
|
* The `screen_reader` feature has now been renamed `web_screen_reader` and only work on web. On other platforms, use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)).
|
||||||
|
|
||||||
#### Desktop/Native:
|
#### Desktop/Native:
|
||||||
* `eframe::run_native` now returns a `Result` ([#2433](https://github.com/emilk/egui/pull/2433)).
|
* `eframe::run_native` now returns a `Result` ([#2433](https://github.com/emilk/egui/pull/2433)).
|
||||||
* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)).
|
* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)).
|
||||||
* Fix bug where the cursor could get stuck using the wrong icon.
|
* Add `Frame::set_minimized` and `set_maximized` ([#2292](https://github.com/emilk/egui/pull/2292)).
|
||||||
* `NativeOptions::transparent` now works with the wgpu backend ([#2684](https://github.com/emilk/egui/pull/2684)).
|
|
||||||
* Add `Frame::set_minimized` and `set_maximized` ([#2292](https://github.com/emilk/egui/pull/2292), [#2672](https://github.com/emilk/egui/pull/2672)).
|
|
||||||
* Fixed persistence of native window position on Windows OS ([#2583](https://github.com/emilk/egui/issues/2583)).
|
|
||||||
|
|
||||||
#### Web:
|
#### Web:
|
||||||
* Prevent ctrl-P/cmd-P from opening the print dialog ([#2598](https://github.com/emilk/egui/pull/2598)).
|
* Prevent ctrl-P/cmd-P from opening the print dialog ([#2598](https://github.com/emilk/egui/pull/2598)).
|
||||||
|
|
||||||
|
|
||||||
## 0.20.1 - 2022-12-11
|
## 0.20.1 - 2022-12-11
|
||||||
* Fix [docs.rs](https://docs.rs/eframe) build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||||
|
|
||||||
|
|
||||||
## 0.20.0 - 2022-12-08 - AccessKit integration and `wgpu` web support
|
## 0.20.0 - 2022-12-08 - AccessKit integration and `wgpu` web support
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "eframe"
|
name = "eframe"
|
||||||
version = "0.21.3"
|
version = "0.20.1"
|
||||||
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 = "2021"
|
edition = "2021"
|
||||||
|
@ -36,7 +36,7 @@ dark-light = ["dep:dark-light"]
|
||||||
default_fonts = ["egui/default_fonts"]
|
default_fonts = ["egui/default_fonts"]
|
||||||
|
|
||||||
## Use [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow).
|
## Use [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow).
|
||||||
glow = ["dep:glow", "dep:egui_glow", "dep:glutin", "dep:glutin-winit"]
|
glow = ["dep:glow", "dep:egui_glow", "dep:glutin"]
|
||||||
|
|
||||||
## Enable saving app state to disk.
|
## Enable saving app state to disk.
|
||||||
persistence = [
|
persistence = [
|
||||||
|
@ -68,7 +68,7 @@ wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"tracing",
|
"tracing",
|
||||||
] }
|
] }
|
||||||
|
@ -79,33 +79,39 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
## Enable this when generating docs.
|
## Enable this when generating docs.
|
||||||
document-features = { version = "0.2", optional = true }
|
document-features = { version = "0.2", optional = true }
|
||||||
|
|
||||||
egui_glow = { version = "0.21.0", path = "../egui_glow", optional = true, default-features = false }
|
egui_glow = { version = "0.20.0", path = "../egui_glow", optional = true, default-features = false }
|
||||||
glow = { version = "0.12", optional = true }
|
glow = { version = "0.11", optional = true }
|
||||||
ron = { version = "0.8", optional = true, features = ["integer128"] }
|
ron = { version = "0.8", optional = true, features = ["integer128"] }
|
||||||
serde = { version = "1", optional = true, features = ["derive"] }
|
serde = { version = "1", optional = true, features = ["derive"] }
|
||||||
|
|
||||||
# -------------------------------------------
|
# -------------------------------------------
|
||||||
# native:
|
# native:
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
egui-winit = { version = "0.21.1", path = "../egui-winit", default-features = false, features = [
|
egui-winit = { version = "0.20.0", path = "../egui-winit", default-features = false, features = [
|
||||||
"clipboard",
|
"clipboard",
|
||||||
"links",
|
"links",
|
||||||
] }
|
] }
|
||||||
raw-window-handle = { version = "0.5.0" }
|
raw-window-handle = { version = "0.5.0" }
|
||||||
winit = "0.28.1"
|
winit = "0.28"
|
||||||
|
|
||||||
# optional native:
|
# optional native:
|
||||||
dark-light = { version = "1.0", optional = true }
|
dark-light = { version = "1.0", optional = true }
|
||||||
directories-next = { version = "2", optional = true }
|
directories-next = { version = "2", optional = true }
|
||||||
egui-wgpu = { version = "0.21.0", path = "../egui-wgpu", optional = true, features = [
|
egui-wgpu = { version = "0.20.0", path = "../egui-wgpu", optional = true, features = [
|
||||||
"winit",
|
"winit",
|
||||||
] } # if wgpu is used, use it with winit
|
] } # if wgpu is used, use it with winit
|
||||||
pollster = { version = "0.3", optional = true } # needed for wgpu
|
pollster = { version = "0.2", optional = true } # needed for wgpu
|
||||||
|
|
||||||
# we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps.
|
# we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps.
|
||||||
# this can be done at the same time we expose x11/wayland features of winit crate.
|
# this can be done at the same time we expose x11/wayland features of winit crate.
|
||||||
glutin = { version = "0.30", optional = true }
|
glutin = { version = "0.30.0", optional = true, es = [
|
||||||
glutin-winit = { version = "0.3.0", optional = true }
|
"egl",
|
||||||
|
"glx",
|
||||||
|
"x11",
|
||||||
|
"wayland",
|
||||||
|
"wgl",
|
||||||
|
] }
|
||||||
|
|
||||||
image = { version = "0.24", optional = true, default-features = false, features = [
|
image = { version = "0.24", optional = true, default-features = false, features = [
|
||||||
"png",
|
"png",
|
||||||
] }
|
] }
|
||||||
|
@ -118,7 +124,7 @@ wgpu = { version = "0.15.0", optional = true }
|
||||||
bytemuck = "1.7"
|
bytemuck = "1.7"
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
wasm-bindgen = "=0.2.84"
|
wasm-bindgen = "=0.2.83"
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
web-sys = { version = "0.3.58", features = [
|
web-sys = { version = "0.3.58", features = [
|
||||||
"BinaryType",
|
"BinaryType",
|
||||||
|
@ -164,6 +170,6 @@ web-sys = { version = "0.3.58", features = [
|
||||||
] }
|
] }
|
||||||
|
|
||||||
# optional web:
|
# optional web:
|
||||||
egui-wgpu = { version = "0.21.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit
|
egui-wgpu = { version = "0.20.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit
|
||||||
tts = { version = "0.25", optional = true, default-features = false }
|
tts = { version = "0.25", optional = true, default-features = false }
|
||||||
wgpu = { version = "0.15.0", optional = true, features = ["webgl"] }
|
wgpu = { version = "0.15.0", optional = true, features = ["webgl"] }
|
||||||
|
|
|
@ -22,7 +22,7 @@ For how to use `egui`, see [the egui docs](https://docs.rs/egui).
|
||||||
To use on Linux, first run:
|
To use on Linux, first run:
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
|
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
You need to either use `edition = "2021"`, or set `resolver = "2"` in the `[workspace]` section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info.
|
You need to either use `edition = "2021"`, or set `resolver = "2"` in the `[workspace]` section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info.
|
||||||
|
|
|
@ -10,11 +10,9 @@
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
pub use crate::native::run::UserEvent;
|
pub use crate::native::run::UserEvent;
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
pub use winit::event_loop::EventLoopBuilder;
|
pub use winit::event_loop::EventLoopBuilder;
|
||||||
|
|
||||||
/// Hook into the building of an event loop before it is run
|
/// Hook into the building of an event loop before it is run
|
||||||
|
@ -22,7 +20,6 @@ pub use winit::event_loop::EventLoopBuilder;
|
||||||
/// You can configure any platform specific details required on top of the default configuration
|
/// You can configure any platform specific details required on top of the default configuration
|
||||||
/// done by `EFrame`.
|
/// done by `EFrame`.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
pub type EventLoopBuilderHook = Box<dyn FnOnce(&mut EventLoopBuilder<UserEvent>)>;
|
pub type EventLoopBuilderHook = Box<dyn FnOnce(&mut EventLoopBuilder<UserEvent>)>;
|
||||||
|
|
||||||
/// This is how your app is created.
|
/// This is how your app is created.
|
||||||
|
@ -320,7 +317,6 @@ pub struct NativeOptions {
|
||||||
pub hardware_acceleration: HardwareAcceleration,
|
pub hardware_acceleration: HardwareAcceleration,
|
||||||
|
|
||||||
/// What rendering backend to use.
|
/// What rendering backend to use.
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
pub renderer: Renderer,
|
pub renderer: Renderer,
|
||||||
|
|
||||||
/// Only used if the `dark-light` feature is enabled:
|
/// Only used if the `dark-light` feature is enabled:
|
||||||
|
@ -359,7 +355,6 @@ pub struct NativeOptions {
|
||||||
/// event loop before it is run.
|
/// event loop before it is run.
|
||||||
///
|
///
|
||||||
/// Note: A [`NativeOptions`] clone will not include any `event_loop_builder` hook.
|
/// Note: A [`NativeOptions`] clone will not include any `event_loop_builder` hook.
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
pub event_loop_builder: Option<EventLoopBuilderHook>,
|
pub event_loop_builder: Option<EventLoopBuilderHook>,
|
||||||
|
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
|
@ -386,13 +381,9 @@ impl Clone for NativeOptions {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
icon_data: self.icon_data.clone(),
|
icon_data: self.icon_data.clone(),
|
||||||
|
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
event_loop_builder: None, // Skip any builder callbacks if cloning
|
event_loop_builder: None, // Skip any builder callbacks if cloning
|
||||||
|
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
wgpu_options: self.wgpu_options.clone(),
|
wgpu_options: self.wgpu_options.clone(),
|
||||||
|
|
||||||
..*self
|
..*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -406,10 +397,8 @@ impl Default for NativeOptions {
|
||||||
maximized: false,
|
maximized: false,
|
||||||
decorated: true,
|
decorated: true,
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fullsize_content: false,
|
fullsize_content: false,
|
||||||
|
|
||||||
drag_and_drop_support: true,
|
drag_and_drop_support: true,
|
||||||
icon_data: None,
|
icon_data: None,
|
||||||
initial_window_pos: None,
|
initial_window_pos: None,
|
||||||
|
@ -424,22 +413,14 @@ impl Default for NativeOptions {
|
||||||
depth_buffer: 0,
|
depth_buffer: 0,
|
||||||
stencil_buffer: 0,
|
stencil_buffer: 0,
|
||||||
hardware_acceleration: HardwareAcceleration::Preferred,
|
hardware_acceleration: HardwareAcceleration::Preferred,
|
||||||
|
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
renderer: Renderer::default(),
|
renderer: Renderer::default(),
|
||||||
|
|
||||||
follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
|
follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
|
||||||
default_theme: Theme::Dark,
|
default_theme: Theme::Dark,
|
||||||
run_and_return: true,
|
run_and_return: true,
|
||||||
|
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
event_loop_builder: None,
|
event_loop_builder: None,
|
||||||
|
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
shader_version: None,
|
shader_version: None,
|
||||||
|
|
||||||
centered: false,
|
centered: false,
|
||||||
|
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
|
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
|
||||||
}
|
}
|
||||||
|
@ -578,7 +559,6 @@ pub enum WebGlContextOption {
|
||||||
/// What rendering backend to use.
|
/// What rendering backend to use.
|
||||||
///
|
///
|
||||||
/// You need to enable the "glow" and "wgpu" features to have a choice.
|
/// You need to enable the "glow" and "wgpu" features to have a choice.
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
||||||
|
@ -592,7 +572,6 @@ pub enum Renderer {
|
||||||
Wgpu,
|
Wgpu,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
impl Default for Renderer {
|
impl Default for Renderer {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
|
@ -608,7 +587,6 @@ impl Default for Renderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
impl std::fmt::Display for Renderer {
|
impl std::fmt::Display for Renderer {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
@ -621,7 +599,6 @@ impl std::fmt::Display for Renderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
impl std::str::FromStr for Renderer {
|
impl std::str::FromStr for Renderer {
|
||||||
type Err = String;
|
type Err = String;
|
||||||
|
|
||||||
|
@ -834,7 +811,6 @@ impl Frame {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// for integrations only: call once per frame
|
/// for integrations only: call once per frame
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
pub(crate) fn take_app_output(&mut self) -> backend::AppOutput {
|
pub(crate) fn take_app_output(&mut self) -> backend::AppOutput {
|
||||||
std::mem::take(&mut self.output)
|
std::mem::take(&mut self.output)
|
||||||
}
|
}
|
||||||
|
@ -865,12 +841,6 @@ pub struct WindowInfo {
|
||||||
/// Are we in fullscreen mode?
|
/// Are we in fullscreen mode?
|
||||||
pub fullscreen: bool,
|
pub fullscreen: bool,
|
||||||
|
|
||||||
/// Are we minimized?
|
|
||||||
pub minimized: bool,
|
|
||||||
|
|
||||||
/// Are we maximized?
|
|
||||||
pub maximized: bool,
|
|
||||||
|
|
||||||
/// Window inner size in egui points (logical pixels).
|
/// Window inner size in egui points (logical pixels).
|
||||||
pub size: egui::Vec2,
|
pub size: egui::Vec2,
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,6 @@ pub async fn start_web(
|
||||||
// When compiling natively
|
// When compiling natively
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
mod native;
|
mod native;
|
||||||
|
|
||||||
/// This is how you start a native (desktop) app.
|
/// This is how you start a native (desktop) app.
|
||||||
|
@ -180,7 +179,6 @@ mod native;
|
||||||
/// This function can fail if we fail to set up a graphics context.
|
/// This function can fail if we fail to set up a graphics context.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
|
||||||
pub fn run_native(
|
pub fn run_native(
|
||||||
app_name: &str,
|
app_name: &str,
|
||||||
native_options: NativeOptions,
|
native_options: NativeOptions,
|
||||||
|
@ -223,8 +221,8 @@ pub enum Error {
|
||||||
Glutin(#[from] glutin::error::Error),
|
Glutin(#[from] glutin::error::Error),
|
||||||
|
|
||||||
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
|
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
|
||||||
#[error("Found no glutin configs matching the template: {0:?}. error: {1:?}")]
|
#[error("Found no glutin configs matching the template: {0:?}")]
|
||||||
NoGlutinConfigs(glutin::config::ConfigTemplate, Box<dyn std::error::Error>),
|
NoGlutinConfigs(glutin::config::ConfigTemplate),
|
||||||
|
|
||||||
#[cfg(feature = "wgpu")]
|
#[cfg(feature = "wgpu")]
|
||||||
#[error("WGPU error: {0}")]
|
#[error("WGPU error: {0}")]
|
||||||
|
@ -235,28 +233,24 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Profiling macro for feature "puffin"
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
macro_rules! profile_function {
|
||||||
mod profiling_scopes {
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_function {
|
|
||||||
($($arg: tt)*) => {
|
($($arg: tt)*) => {
|
||||||
#[cfg(feature = "puffin")]
|
#[cfg(feature = "puffin")]
|
||||||
puffin::profile_function!($($arg)*);
|
puffin::profile_function!($($arg)*);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub(crate) use profile_function;
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(crate) use profile_function;
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
/// Profiling macro for feature "puffin"
|
||||||
macro_rules! profile_scope {
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
macro_rules! profile_scope {
|
||||||
($($arg: tt)*) => {
|
($($arg: tt)*) => {
|
||||||
#[cfg(feature = "puffin")]
|
#[cfg(feature = "puffin")]
|
||||||
puffin::profile_scope!($($arg)*);
|
puffin::profile_scope!($($arg)*);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub(crate) use profile_scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
pub(crate) use profile_scope;
|
||||||
pub(crate) use profiling_scopes::*;
|
|
||||||
|
|
|
@ -12,14 +12,6 @@ use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings};
|
||||||
|
|
||||||
use crate::{epi, Theme, WindowInfo};
|
use crate::{epi, Theme, WindowInfo};
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct WindowState {
|
|
||||||
// We cannot simply call `winit::Window::is_minimized/is_maximized`
|
|
||||||
// because that deadlocks on mac.
|
|
||||||
pub minimized: bool,
|
|
||||||
pub maximized: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
|
pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
|
||||||
winit::dpi::LogicalSize {
|
winit::dpi::LogicalSize {
|
||||||
width: points.x as f64,
|
width: points.x as f64,
|
||||||
|
@ -27,11 +19,7 @@ pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_window_info(
|
pub fn read_window_info(window: &winit::window::Window, pixels_per_point: f32) -> WindowInfo {
|
||||||
window: &winit::window::Window,
|
|
||||||
pixels_per_point: f32,
|
|
||||||
window_state: &WindowState,
|
|
||||||
) -> WindowInfo {
|
|
||||||
let position = window
|
let position = window
|
||||||
.outer_position()
|
.outer_position()
|
||||||
.ok()
|
.ok()
|
||||||
|
@ -50,13 +38,9 @@ pub fn read_window_info(
|
||||||
.inner_size()
|
.inner_size()
|
||||||
.to_logical::<f32>(pixels_per_point.into());
|
.to_logical::<f32>(pixels_per_point.into());
|
||||||
|
|
||||||
// NOTE: calling window.is_minimized() or window.is_maximized() deadlocks on Mac.
|
|
||||||
|
|
||||||
WindowInfo {
|
WindowInfo {
|
||||||
position,
|
position,
|
||||||
fullscreen: window.fullscreen().is_some(),
|
fullscreen: window.fullscreen().is_some(),
|
||||||
minimized: window_state.minimized,
|
|
||||||
maximized: window_state.maximized,
|
|
||||||
size: egui::Vec2 {
|
size: egui::Vec2 {
|
||||||
x: size.width,
|
x: size.width,
|
||||||
y: size.height,
|
y: size.height,
|
||||||
|
@ -65,13 +49,14 @@ pub fn read_window_info(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn window_builder<E>(
|
pub fn build_window<E>(
|
||||||
event_loop: &EventLoopWindowTarget<E>,
|
event_loop: &EventLoopWindowTarget<E>,
|
||||||
title: &str,
|
title: &str,
|
||||||
native_options: &epi::NativeOptions,
|
native_options: &epi::NativeOptions,
|
||||||
window_settings: Option<WindowSettings>,
|
window_settings: Option<WindowSettings>,
|
||||||
) -> winit::window::WindowBuilder {
|
) -> Result<winit::window::Window, winit::error::OsError> {
|
||||||
let epi::NativeOptions {
|
let epi::NativeOptions {
|
||||||
|
always_on_top,
|
||||||
maximized,
|
maximized,
|
||||||
decorated,
|
decorated,
|
||||||
fullscreen,
|
fullscreen,
|
||||||
|
@ -123,8 +108,6 @@ pub fn window_builder<E>(
|
||||||
let inner_size_points = if let Some(mut window_settings) = window_settings {
|
let inner_size_points = if let Some(mut window_settings) = window_settings {
|
||||||
// Restore pos/size from previous session
|
// Restore pos/size from previous session
|
||||||
window_settings.clamp_to_sane_values(largest_monitor_point_size(event_loop));
|
window_settings.clamp_to_sane_values(largest_monitor_point_size(event_loop));
|
||||||
#[cfg(windows)]
|
|
||||||
window_settings.clamp_window_to_sane_position(&event_loop);
|
|
||||||
window_builder = window_settings.initialize_window(window_builder);
|
window_builder = window_settings.initialize_window(window_builder);
|
||||||
window_settings.inner_size_points()
|
window_settings.inner_size_points()
|
||||||
} else {
|
} else {
|
||||||
|
@ -158,19 +141,17 @@ pub fn window_builder<E>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
window_builder
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_native_options_to_window(
|
let window = window_builder.build(event_loop)?;
|
||||||
window: &winit::window::Window,
|
|
||||||
native_options: &crate::NativeOptions,
|
|
||||||
) {
|
|
||||||
use winit::window::WindowLevel;
|
use winit::window::WindowLevel;
|
||||||
window.set_window_level(if native_options.always_on_top {
|
window.set_window_level(if *always_on_top {
|
||||||
WindowLevel::AlwaysOnTop
|
WindowLevel::AlwaysOnTop
|
||||||
} else {
|
} else {
|
||||||
WindowLevel::Normal
|
WindowLevel::Normal
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Ok(window)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui::Vec2 {
|
fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui::Vec2 {
|
||||||
|
@ -215,7 +196,6 @@ 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,
|
||||||
window_state: &mut WindowState,
|
|
||||||
) {
|
) {
|
||||||
let epi::backend::AppOutput {
|
let epi::backend::AppOutput {
|
||||||
close: _,
|
close: _,
|
||||||
|
@ -275,12 +255,10 @@ pub fn handle_app_output(
|
||||||
|
|
||||||
if let Some(minimized) = minimized {
|
if let Some(minimized) = minimized {
|
||||||
window.set_minimized(minimized);
|
window.set_minimized(minimized);
|
||||||
window_state.minimized = minimized;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(maximized) = maximized {
|
if let Some(maximized) = maximized {
|
||||||
window.set_maximized(maximized);
|
window.set_maximized(maximized);
|
||||||
window_state.maximized = maximized;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +285,6 @@ pub struct EpiIntegration {
|
||||||
/// When set, it is time to close the native window.
|
/// When set, it is time to close the native window.
|
||||||
close: bool,
|
close: bool,
|
||||||
can_drag_window: bool,
|
can_drag_window: bool,
|
||||||
window_state: WindowState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EpiIntegration {
|
impl EpiIntegration {
|
||||||
|
@ -327,17 +304,12 @@ impl EpiIntegration {
|
||||||
|
|
||||||
let native_pixels_per_point = window.scale_factor() as f32;
|
let native_pixels_per_point = window.scale_factor() as f32;
|
||||||
|
|
||||||
let window_state = WindowState {
|
|
||||||
minimized: window.is_minimized().unwrap_or(false),
|
|
||||||
maximized: window.is_maximized(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let frame = epi::Frame {
|
let frame = epi::Frame {
|
||||||
info: epi::IntegrationInfo {
|
info: epi::IntegrationInfo {
|
||||||
system_theme,
|
system_theme,
|
||||||
cpu_usage: None,
|
cpu_usage: None,
|
||||||
native_pixels_per_point: Some(native_pixels_per_point),
|
native_pixels_per_point: Some(native_pixels_per_point),
|
||||||
window_info: read_window_info(window, egui_ctx.pixels_per_point(), &window_state),
|
window_info: read_window_info(window, egui_ctx.pixels_per_point()),
|
||||||
},
|
},
|
||||||
output: epi::backend::AppOutput {
|
output: epi::backend::AppOutput {
|
||||||
visible: Some(true),
|
visible: Some(true),
|
||||||
|
@ -362,7 +334,6 @@ impl EpiIntegration {
|
||||||
pending_full_output: Default::default(),
|
pending_full_output: Default::default(),
|
||||||
close: false,
|
close: false,
|
||||||
can_drag_window: false,
|
can_drag_window: false,
|
||||||
window_state,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,7 +352,7 @@ impl EpiIntegration {
|
||||||
egui_ctx.enable_accesskit();
|
egui_ctx.enable_accesskit();
|
||||||
// Enqueue a repaint so we'll receive a full tree update soon.
|
// Enqueue a repaint so we'll receive a full tree update soon.
|
||||||
egui_ctx.request_repaint();
|
egui_ctx.request_repaint();
|
||||||
egui_ctx.accesskit_placeholder_tree_update()
|
egui::accesskit_placeholder_tree_update()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,16 +415,12 @@ impl EpiIntegration {
|
||||||
) -> egui::FullOutput {
|
) -> egui::FullOutput {
|
||||||
let frame_start = std::time::Instant::now();
|
let frame_start = std::time::Instant::now();
|
||||||
|
|
||||||
self.frame.info.window_info =
|
self.frame.info.window_info = read_window_info(window, self.egui_ctx.pixels_per_point());
|
||||||
read_window_info(window, self.egui_ctx.pixels_per_point(), &self.window_state);
|
|
||||||
let raw_input = self.egui_winit.take_egui_input(window);
|
let raw_input = self.egui_winit.take_egui_input(window);
|
||||||
|
|
||||||
// Run user code:
|
|
||||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||||
crate::profile_scope!("App::update");
|
crate::profile_scope!("App::update");
|
||||||
app.update(egui_ctx, &mut self.frame);
|
app.update(egui_ctx, &mut self.frame);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.pending_full_output.append(full_output);
|
self.pending_full_output.append(full_output);
|
||||||
let full_output = std::mem::take(&mut self.pending_full_output);
|
let full_output = std::mem::take(&mut self.pending_full_output);
|
||||||
|
|
||||||
|
@ -466,12 +433,7 @@ impl EpiIntegration {
|
||||||
tracing::debug!("App::on_close_event returned {}", self.close);
|
tracing::debug!("App::on_close_event returned {}", self.close);
|
||||||
}
|
}
|
||||||
self.frame.output.visible = app_output.visible; // this is handled by post_present
|
self.frame.output.visible = app_output.visible; // this is handled by post_present
|
||||||
handle_app_output(
|
handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||||
window,
|
|
||||||
self.egui_ctx.pixels_per_point(),
|
|
||||||
app_output,
|
|
||||||
&mut self.window_state,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame_time = frame_start.elapsed().as_secs_f64() as f32;
|
let frame_time = frame_start.elapsed().as_secs_f64() as f32;
|
||||||
|
|
|
@ -38,7 +38,6 @@ pub use epi::NativeOptions;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum EventResult {
|
enum EventResult {
|
||||||
Wait,
|
Wait,
|
||||||
|
|
||||||
/// Causes a synchronous repaint inside the event handler. This should only
|
/// Causes a synchronous repaint inside the event handler. This should only
|
||||||
/// be used in special situations if the window must be repainted while
|
/// be used in special situations if the window must be repainted while
|
||||||
/// handling a specific event. This occurs on Windows when handling resizes.
|
/// handling a specific event. This occurs on Windows when handling resizes.
|
||||||
|
@ -46,28 +45,20 @@ enum EventResult {
|
||||||
/// `RepaintNow` creates a new frame synchronously, and should therefore
|
/// `RepaintNow` creates a new frame synchronously, and should therefore
|
||||||
/// only be used for extremely urgent repaints.
|
/// only be used for extremely urgent repaints.
|
||||||
RepaintNow,
|
RepaintNow,
|
||||||
|
|
||||||
/// Queues a repaint for once the event loop handles its next redraw. Exists
|
/// Queues a repaint for once the event loop handles its next redraw. Exists
|
||||||
/// so that multiple input events can be handled in one frame. Does not
|
/// so that multiple input events can be handled in one frame. Does not
|
||||||
/// cause any delay like `RepaintNow`.
|
/// cause any delay like `RepaintNow`.
|
||||||
RepaintNext,
|
RepaintNext,
|
||||||
|
|
||||||
RepaintAt(Instant),
|
RepaintAt(Instant),
|
||||||
|
|
||||||
Exit,
|
Exit,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait WinitApp {
|
trait WinitApp {
|
||||||
fn is_focused(&self) -> bool;
|
fn is_focused(&self) -> bool;
|
||||||
|
|
||||||
fn integration(&self) -> Option<&EpiIntegration>;
|
fn integration(&self) -> Option<&EpiIntegration>;
|
||||||
|
|
||||||
fn window(&self) -> Option<&winit::window::Window>;
|
fn window(&self) -> Option<&winit::window::Window>;
|
||||||
|
|
||||||
fn save_and_destroy(&mut self);
|
fn save_and_destroy(&mut self);
|
||||||
|
|
||||||
fn paint(&mut self) -> EventResult;
|
fn paint(&mut self) -> EventResult;
|
||||||
|
|
||||||
fn on_event(
|
fn on_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||||
|
@ -161,8 +152,8 @@ fn run_and_return(
|
||||||
event => match winit_app.on_event(event_loop, event) {
|
event => match winit_app.on_event(event_loop, event) {
|
||||||
Ok(event_result) => event_result,
|
Ok(event_result) => event_result,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
tracing::error!("Exiting because of error: {err:?} on event {event:?}");
|
|
||||||
returned_result = Err(err);
|
returned_result = Err(err);
|
||||||
|
tracing::debug!("Exiting because of an error");
|
||||||
EventResult::Exit
|
EventResult::Exit
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -306,12 +297,6 @@ mod glow_integration {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use egui::NumExt as _;
|
use egui::NumExt as _;
|
||||||
use glutin::{
|
|
||||||
display::GetGlDisplay,
|
|
||||||
prelude::{GlDisplay, NotCurrentGlContextSurfaceAccessor, PossiblyCurrentGlContext},
|
|
||||||
surface::GlSurface,
|
|
||||||
};
|
|
||||||
use raw_window_handle::HasRawWindowHandle;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -336,260 +321,132 @@ mod glow_integration {
|
||||||
painter: egui_glow::Painter,
|
painter: egui_glow::Painter,
|
||||||
integration: epi_integration::EpiIntegration,
|
integration: epi_integration::EpiIntegration,
|
||||||
app: Box<dyn epi::App>,
|
app: Box<dyn epi::App>,
|
||||||
|
|
||||||
// Conceptually this will be split out eventually so that the rest of the state
|
// Conceptually this will be split out eventually so that the rest of the state
|
||||||
// can be persistent.
|
// can be persistent.
|
||||||
gl_window: GlutinWindowContext,
|
gl_window: GlutinWindowContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This struct will contain both persistent and temporary glutin state.
|
|
||||||
///
|
|
||||||
/// Platform Quirks:
|
|
||||||
/// * Microsoft Windows: requires that we create a window before opengl context.
|
|
||||||
/// * Android: window and surface should be destroyed when we receive a suspend event. recreate on resume event.
|
|
||||||
///
|
|
||||||
/// winit guarantees that we will get a Resumed event on startup on all platforms.
|
|
||||||
/// * Before Resumed event: `gl_config`, `gl_context` can be created at any time. on windows, a window must be created to get `gl_context`.
|
|
||||||
/// * Resumed: `gl_surface` will be created here. `window` will be re-created here for android.
|
|
||||||
/// * Suspended: on android, we drop window + surface. on other platforms, we don't get Suspended event.
|
|
||||||
///
|
|
||||||
/// The setup is divided between the `new` fn and `on_resume` fn. we can just assume that `on_resume` is a continuation of
|
|
||||||
/// `new` fn on all platforms. only on android, do we get multiple resumed events because app can be suspended.
|
|
||||||
struct GlutinWindowContext {
|
struct GlutinWindowContext {
|
||||||
builder: winit::window::WindowBuilder,
|
window: winit::window::Window,
|
||||||
swap_interval: glutin::surface::SwapInterval,
|
gl_context: glutin::context::PossiblyCurrentContext,
|
||||||
gl_config: glutin::config::Config,
|
gl_display: glutin::display::Display,
|
||||||
current_gl_context: Option<glutin::context::PossiblyCurrentContext>,
|
gl_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
|
||||||
gl_surface: Option<glutin::surface::Surface<glutin::surface::WindowSurface>>,
|
|
||||||
not_current_gl_context: Option<glutin::context::NotCurrentContext>,
|
|
||||||
window: Option<winit::window::Window>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GlutinWindowContext {
|
impl GlutinWindowContext {
|
||||||
/// There is a lot of complexity with opengl creation, so prefer extensivve logging to get all the help we can to debug issues.
|
// refactor this function to use `glutin-winit` crate eventually.
|
||||||
///
|
// preferably add android support at the same time.
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
unsafe fn new(
|
unsafe fn new(
|
||||||
winit_window_builder: winit::window::WindowBuilder,
|
winit_window: winit::window::Window,
|
||||||
native_options: &epi::NativeOptions,
|
native_options: &epi::NativeOptions,
|
||||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
use glutin::prelude::*;
|
use glutin::prelude::*;
|
||||||
// convert native options to glutin options
|
use raw_window_handle::*;
|
||||||
let hardware_acceleration = match native_options.hardware_acceleration {
|
let hardware_acceleration = match native_options.hardware_acceleration {
|
||||||
crate::HardwareAcceleration::Required => Some(true),
|
crate::HardwareAcceleration::Required => Some(true),
|
||||||
crate::HardwareAcceleration::Preferred => None,
|
crate::HardwareAcceleration::Preferred => None,
|
||||||
crate::HardwareAcceleration::Off => Some(false),
|
crate::HardwareAcceleration::Off => Some(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let raw_display_handle = winit_window.raw_display_handle();
|
||||||
|
let raw_window_handle = winit_window.raw_window_handle();
|
||||||
|
|
||||||
|
// EGL is crossplatform and the official khronos way
|
||||||
|
// but sometimes platforms/drivers may not have it, so we use back up options where possible.
|
||||||
|
// TODO: check whether we can expose these options as "features", so that users can select the relevant backend they want.
|
||||||
|
|
||||||
|
// try egl and fallback to windows wgl. Windows is the only platform that *requires* window handle to create display.
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let preference =
|
||||||
|
glutin::display::DisplayApiPreference::EglThenWgl(Some(raw_window_handle));
|
||||||
|
// try egl and fallback to x11 glx
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let preference = glutin::display::DisplayApiPreference::EglThenGlx(Box::new(
|
||||||
|
winit::platform::x11::register_xlib_error_hook,
|
||||||
|
));
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let preference = glutin::display::DisplayApiPreference::Cgl;
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
let preference = glutin::display::DisplayApiPreference::Egl;
|
||||||
|
|
||||||
|
let gl_display = glutin::display::Display::new(raw_display_handle, preference)?;
|
||||||
let swap_interval = if native_options.vsync {
|
let swap_interval = if native_options.vsync {
|
||||||
glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap())
|
glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap())
|
||||||
} else {
|
} else {
|
||||||
glutin::surface::SwapInterval::DontWait
|
glutin::surface::SwapInterval::DontWait
|
||||||
};
|
};
|
||||||
/* opengl setup flow goes like this:
|
|
||||||
1. we create a configuration for opengl "Display" / "Config" creation
|
let config_template = glutin::config::ConfigTemplateBuilder::new()
|
||||||
2. choose between special extensions like glx or egl or wgl and use them to create config/display
|
|
||||||
3. opengl context configuration
|
|
||||||
4. opengl context creation
|
|
||||||
*/
|
|
||||||
// start building config for gl display
|
|
||||||
let config_template_builder = glutin::config::ConfigTemplateBuilder::new()
|
|
||||||
.prefer_hardware_accelerated(hardware_acceleration)
|
.prefer_hardware_accelerated(hardware_acceleration)
|
||||||
.with_depth_size(native_options.depth_buffer)
|
.with_depth_size(native_options.depth_buffer);
|
||||||
.with_stencil_size(native_options.stencil_buffer)
|
|
||||||
.with_transparency(native_options.transparent);
|
|
||||||
// we don't know if multi sampling option is set. so, check if its more than 0.
|
// we don't know if multi sampling option is set. so, check if its more than 0.
|
||||||
let config_template_builder = if native_options.multisampling > 0 {
|
let config_template = if native_options.multisampling > 0 {
|
||||||
config_template_builder.with_multisampling(
|
config_template.with_multisampling(
|
||||||
native_options
|
native_options
|
||||||
.multisampling
|
.multisampling
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("failed to fit multisamples option of native_options into u8"),
|
.expect("failed to fit multisamples into u8"),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
config_template_builder
|
config_template
|
||||||
};
|
};
|
||||||
|
let config_template = config_template
|
||||||
|
.with_stencil_size(native_options.stencil_buffer)
|
||||||
|
.with_transparency(native_options.transparent)
|
||||||
|
.compatible_with_native_window(raw_window_handle)
|
||||||
|
.build();
|
||||||
|
// finds all valid configurations supported by this display that match the config_template
|
||||||
|
// this is where we will try to get a "fallback" config if we are okay with ignoring some native
|
||||||
|
// options required by user like multi sampling, srgb, transparency etc..
|
||||||
|
// TODO: need to figure out a good fallback config template
|
||||||
|
let config = gl_display
|
||||||
|
.find_configs(config_template.clone())?
|
||||||
|
.next()
|
||||||
|
.ok_or(crate::Error::NoGlutinConfigs(config_template))?;
|
||||||
|
|
||||||
tracing::debug!(
|
|
||||||
"trying to create glutin Display with config: {:?}",
|
|
||||||
&config_template_builder
|
|
||||||
);
|
|
||||||
// create gl display. this may probably create a window too on most platforms. definitely on `MS windows`. never on android.
|
|
||||||
let (window, gl_config) = glutin_winit::DisplayBuilder::new()
|
|
||||||
// we might want to expose this option to users in the future. maybe using an env var or using native_options.
|
|
||||||
.with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
|
|
||||||
.with_window_builder(Some(winit_window_builder.clone()))
|
|
||||||
.build(
|
|
||||||
event_loop,
|
|
||||||
config_template_builder.clone(),
|
|
||||||
|mut config_iterator| {
|
|
||||||
let config = config_iterator.next().expect(
|
|
||||||
"failed to find a matching configuration for creating glutin config",
|
|
||||||
);
|
|
||||||
tracing::debug!(
|
|
||||||
"using the first config from config picker closure. config: {:?}",
|
|
||||||
&config
|
|
||||||
);
|
|
||||||
config
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(|e| crate::Error::NoGlutinConfigs(config_template_builder.build(), e))?;
|
|
||||||
|
|
||||||
let gl_display = gl_config.display();
|
|
||||||
tracing::debug!(
|
|
||||||
"successfully created GL Display with version: {} and supported features: {:?}",
|
|
||||||
gl_display.version_string(),
|
|
||||||
gl_display.supported_features()
|
|
||||||
);
|
|
||||||
let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle());
|
|
||||||
tracing::debug!(
|
|
||||||
"creating gl context using raw window handle: {:?}",
|
|
||||||
raw_window_handle
|
|
||||||
);
|
|
||||||
|
|
||||||
// create gl context. if core context cannot be created, try gl es context as fallback.
|
|
||||||
let context_attributes =
|
let context_attributes =
|
||||||
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
|
glutin::context::ContextAttributesBuilder::new().build(Some(raw_window_handle));
|
||||||
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
|
// for surface creation.
|
||||||
.with_context_api(glutin::context::ContextApi::Gles(None))
|
let (width, height): (u32, u32) = winit_window.inner_size().into();
|
||||||
.build(raw_window_handle);
|
|
||||||
let gl_context = match gl_config
|
|
||||||
.display()
|
|
||||||
.create_context(&gl_config, &context_attributes)
|
|
||||||
{
|
|
||||||
Ok(it) => it,
|
|
||||||
Err(err) => {
|
|
||||||
tracing::warn!("failed to create context using default context attributes {context_attributes:?} due to error: {err}");
|
|
||||||
tracing::debug!("retrying with fallback context attributes: {fallback_context_attributes:?}");
|
|
||||||
gl_config
|
|
||||||
.display()
|
|
||||||
.create_context(&gl_config, &fallback_context_attributes)?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let not_current_gl_context = Some(gl_context);
|
|
||||||
|
|
||||||
// the fun part with opengl gl is that we never know whether there is an error. the context creation might have failed, but
|
|
||||||
// it could keep working until we try to make surface current or swap buffers or something else. future glutin improvements might
|
|
||||||
// help us start from scratch again if we fail context creation and go back to preferEgl or try with different config etc..
|
|
||||||
// https://github.com/emilk/egui/pull/2541#issuecomment-1370767582
|
|
||||||
Ok(GlutinWindowContext {
|
|
||||||
builder: winit_window_builder,
|
|
||||||
swap_interval,
|
|
||||||
gl_config,
|
|
||||||
current_gl_context: None,
|
|
||||||
window,
|
|
||||||
gl_surface: None,
|
|
||||||
not_current_gl_context,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This will be run after `new`. on android, it might be called multiple times over the course of the app's lifetime.
|
|
||||||
/// roughly,
|
|
||||||
/// 1. check if window already exists. otherwise, create one now.
|
|
||||||
/// 2. create attributes for surface creation.
|
|
||||||
/// 3. create surface.
|
|
||||||
/// 4. make surface and context current.
|
|
||||||
///
|
|
||||||
/// we presently assume that we will
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
fn on_resume(&mut self, event_loop: &EventLoopWindowTarget<UserEvent>) -> Result<()> {
|
|
||||||
if self.gl_surface.is_some() {
|
|
||||||
tracing::warn!(
|
|
||||||
"on_resume called even thought we already have a surface. early return"
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
tracing::debug!("running on_resume fn.");
|
|
||||||
// make sure we have a window or create one.
|
|
||||||
let window = self.window.take().unwrap_or_else(|| {
|
|
||||||
tracing::debug!("window doesn't exist yet. creating one now with finalize_window");
|
|
||||||
glutin_winit::finalize_window(event_loop, self.builder.clone(), &self.gl_config)
|
|
||||||
.expect("failed to finalize glutin window")
|
|
||||||
});
|
|
||||||
// surface attributes
|
|
||||||
let (width, height): (u32, u32) = window.inner_size().into();
|
|
||||||
let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap();
|
let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap();
|
||||||
let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap();
|
let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap();
|
||||||
let surface_attributes =
|
let surface_attributes =
|
||||||
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
|
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
|
||||||
.build(window.raw_window_handle(), width, height);
|
.build(raw_window_handle, width, height);
|
||||||
tracing::debug!(
|
// start creating the gl objects
|
||||||
"creating surface with attributes: {:?}",
|
let gl_context = gl_display.create_context(&config, &context_attributes)?;
|
||||||
&surface_attributes
|
|
||||||
);
|
|
||||||
// create surface
|
|
||||||
let gl_surface = unsafe {
|
|
||||||
self.gl_config
|
|
||||||
.display()
|
|
||||||
.create_window_surface(&self.gl_config, &surface_attributes)?
|
|
||||||
};
|
|
||||||
tracing::debug!("surface created successfully: {gl_surface:?}.making context current");
|
|
||||||
// make surface and context current.
|
|
||||||
let not_current_gl_context = self
|
|
||||||
.not_current_gl_context
|
|
||||||
.take()
|
|
||||||
.expect("failed to get not current context after resume event. impossible!");
|
|
||||||
let current_gl_context = not_current_gl_context.make_current(&gl_surface)?;
|
|
||||||
// try setting swap interval. but its not absolutely necessary, so don't panic on failure.
|
|
||||||
tracing::debug!("made context current. setting swap interval for surface");
|
|
||||||
if let Err(e) = gl_surface.set_swap_interval(¤t_gl_context, self.swap_interval) {
|
|
||||||
tracing::error!("failed to set swap interval due to error: {e:?}");
|
|
||||||
}
|
|
||||||
// we will reach this point only once in most platforms except android.
|
|
||||||
// create window/surface/make context current once and just use them forever.
|
|
||||||
self.gl_surface = Some(gl_surface);
|
|
||||||
self.current_gl_context = Some(current_gl_context);
|
|
||||||
self.window = Some(window);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// only applies for android. but we basically drop surface + window and make context not current
|
let gl_surface = gl_display.create_window_surface(&config, &surface_attributes)?;
|
||||||
fn on_suspend(&mut self) -> Result<()> {
|
let gl_context = gl_context.make_current(&gl_surface)?;
|
||||||
tracing::debug!("received suspend event. dropping window and surface");
|
gl_surface.set_swap_interval(&gl_context, swap_interval)?;
|
||||||
self.gl_surface.take();
|
Ok(GlutinWindowContext {
|
||||||
self.window.take();
|
window: winit_window,
|
||||||
if let Some(current) = self.current_gl_context.take() {
|
gl_context,
|
||||||
tracing::debug!("context is current, so making it non-current");
|
gl_display,
|
||||||
self.not_current_gl_context = Some(current.make_not_current()?);
|
gl_surface,
|
||||||
} else {
|
})
|
||||||
tracing::debug!(
|
|
||||||
"context is already not current??? could be duplicate suspend event"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window(&self) -> &winit::window::Window {
|
fn window(&self) -> &winit::window::Window {
|
||||||
self.window.as_ref().expect("winit window doesn't exist")
|
&self.window
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(&self, physical_size: winit::dpi::PhysicalSize<u32>) {
|
fn resize(&self, physical_size: winit::dpi::PhysicalSize<u32>) {
|
||||||
|
use glutin::surface::GlSurface;
|
||||||
let width = std::num::NonZeroU32::new(physical_size.width.at_least(1)).unwrap();
|
let width = std::num::NonZeroU32::new(physical_size.width.at_least(1)).unwrap();
|
||||||
let height = std::num::NonZeroU32::new(physical_size.height.at_least(1)).unwrap();
|
let height = std::num::NonZeroU32::new(physical_size.height.at_least(1)).unwrap();
|
||||||
self.gl_surface
|
self.gl_surface.resize(&self.gl_context, width, height);
|
||||||
.as_ref()
|
|
||||||
.expect("failed to get surface to resize")
|
|
||||||
.resize(
|
|
||||||
self.current_gl_context
|
|
||||||
.as_ref()
|
|
||||||
.expect("failed to get current context to resize surface"),
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swap_buffers(&self) -> glutin::error::Result<()> {
|
fn swap_buffers(&self) -> glutin::error::Result<()> {
|
||||||
self.gl_surface
|
use glutin::surface::GlSurface;
|
||||||
.as_ref()
|
self.gl_surface.swap_buffers(&self.gl_context)
|
||||||
.expect("failed to get surface to swap buffers")
|
|
||||||
.swap_buffers(
|
|
||||||
self.current_gl_context
|
|
||||||
.as_ref()
|
|
||||||
.expect("failed to get current context to swap buffers"),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void {
|
fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void {
|
||||||
self.gl_config.display().get_proc_address(addr)
|
use glutin::display::GlDisplay;
|
||||||
|
self.gl_display.get_proc_address(addr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -637,17 +494,11 @@ mod glow_integration {
|
||||||
|
|
||||||
let window_settings = epi_integration::load_window_settings(storage);
|
let window_settings = epi_integration::load_window_settings(storage);
|
||||||
|
|
||||||
let winit_window_builder =
|
let winit_window =
|
||||||
epi_integration::window_builder(event_loop, title, native_options, window_settings);
|
epi_integration::build_window(event_loop, title, native_options, window_settings)?;
|
||||||
let mut glutin_window_context = unsafe {
|
// a lot of the code below has been lifted from glutin example in their repo.
|
||||||
GlutinWindowContext::new(winit_window_builder, native_options, event_loop)?
|
let glutin_window_context =
|
||||||
};
|
unsafe { GlutinWindowContext::new(winit_window, native_options)? };
|
||||||
glutin_window_context.on_resume(event_loop)?;
|
|
||||||
|
|
||||||
if let Some(window) = &glutin_window_context.window {
|
|
||||||
epi_integration::apply_native_options_to_window(window, native_options);
|
|
||||||
}
|
|
||||||
|
|
||||||
let gl = unsafe {
|
let gl = unsafe {
|
||||||
glow::Context::from_loader_function(|s| {
|
glow::Context::from_loader_function(|s| {
|
||||||
let s = std::ffi::CString::new(s)
|
let s = std::ffi::CString::new(s)
|
||||||
|
@ -876,24 +727,26 @@ mod glow_integration {
|
||||||
) -> Result<EventResult> {
|
) -> Result<EventResult> {
|
||||||
Ok(match event {
|
Ok(match event {
|
||||||
winit::event::Event::Resumed => {
|
winit::event::Event::Resumed => {
|
||||||
// first resume event.
|
|
||||||
// we can actually move this outside of event loop.
|
|
||||||
// and just run the on_resume fn of gl_window
|
|
||||||
if self.running.is_none() {
|
if self.running.is_none() {
|
||||||
self.init_run_state(event_loop)?;
|
self.init_run_state(event_loop)?;
|
||||||
} else {
|
|
||||||
// not the first resume event. create whatever you need.
|
|
||||||
self.running
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.gl_window
|
|
||||||
.on_resume(event_loop)?;
|
|
||||||
}
|
}
|
||||||
EventResult::RepaintNow
|
EventResult::RepaintNow
|
||||||
}
|
}
|
||||||
winit::event::Event::Suspended => {
|
winit::event::Event::Suspended => {
|
||||||
self.running.as_mut().unwrap().gl_window.on_suspend()?;
|
#[cfg(target_os = "android")]
|
||||||
|
{
|
||||||
|
tracing::error!("Suspended app can't destroy Window surface state with current Egui Glow backend (undefined behaviour)");
|
||||||
|
// Instead of destroying everything which we _know_ we can't re-create
|
||||||
|
// we instead currently just try our luck with not destroying anything.
|
||||||
|
//
|
||||||
|
// When the application resumes then it will get a new `SurfaceView` but
|
||||||
|
// we have no practical way currently of creating a new EGL surface
|
||||||
|
// via the Glutin API while keeping the GL context and the rest of
|
||||||
|
// our app state. This will likely result in a black screen or
|
||||||
|
// frozen screen.
|
||||||
|
//
|
||||||
|
//self.running = None;
|
||||||
|
}
|
||||||
EventResult::Wait
|
EventResult::Wait
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1064,11 +917,7 @@ mod wgpu_integration {
|
||||||
native_options: &NativeOptions,
|
native_options: &NativeOptions,
|
||||||
) -> std::result::Result<winit::window::Window, winit::error::OsError> {
|
) -> std::result::Result<winit::window::Window, winit::error::OsError> {
|
||||||
let window_settings = epi_integration::load_window_settings(storage);
|
let window_settings = epi_integration::load_window_settings(storage);
|
||||||
let window_builder =
|
epi_integration::build_window(event_loop, title, native_options, window_settings)
|
||||||
epi_integration::window_builder(event_loop, title, native_options, window_settings);
|
|
||||||
let window = window_builder.build(event_loop)?;
|
|
||||||
epi_integration::apply_native_options_to_window(&window, native_options);
|
|
||||||
Ok(window)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
|
@ -1109,7 +958,6 @@ mod wgpu_integration {
|
||||||
self.native_options.wgpu_options.clone(),
|
self.native_options.wgpu_options.clone(),
|
||||||
self.native_options.multisampling.max(1) as _,
|
self.native_options.multisampling.max(1) as _,
|
||||||
self.native_options.depth_buffer,
|
self.native_options.depth_buffer,
|
||||||
self.native_options.transparent,
|
|
||||||
);
|
);
|
||||||
pollster::block_on(painter.set_window(Some(&window)))?;
|
pollster::block_on(painter.set_window(Some(&window)))?;
|
||||||
painter
|
painter
|
||||||
|
|
|
@ -450,6 +450,8 @@ pub enum EventToUnsubscribe {
|
||||||
|
|
||||||
impl EventToUnsubscribe {
|
impl EventToUnsubscribe {
|
||||||
pub fn unsubscribe(self) -> Result<(), JsValue> {
|
pub fn unsubscribe(self) -> Result<(), JsValue> {
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
EventToUnsubscribe::TargetEvent(handle) => {
|
EventToUnsubscribe::TargetEvent(handle) => {
|
||||||
handle.target.remove_event_listener_with_callback(
|
handle.target.remove_event_listener_with_callback(
|
||||||
|
@ -466,7 +468,6 @@ impl EventToUnsubscribe {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppRunnerContainer {
|
pub struct AppRunnerContainer {
|
||||||
pub runner: AppRunnerRef,
|
pub runner: AppRunnerRef,
|
||||||
|
|
||||||
|
@ -485,6 +486,8 @@ impl AppRunnerContainer {
|
||||||
event_name: &'static str,
|
event_name: &'static str,
|
||||||
mut closure: impl FnMut(E, MutexGuard<'_, AppRunner>) + 'static,
|
mut closure: impl FnMut(E, MutexGuard<'_, AppRunner>) + 'static,
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
// Create a JS closure based on the FnMut provided
|
// Create a JS closure based on the FnMut provided
|
||||||
let closure = Closure::wrap({
|
let closure = Closure::wrap({
|
||||||
// Clone atomics
|
// Clone atomics
|
||||||
|
|
|
@ -31,6 +31,7 @@ pub fn paint_and_schedule(
|
||||||
runner_ref: AppRunnerRef,
|
runner_ref: AppRunnerRef,
|
||||||
panicked: Arc<AtomicBool>,
|
panicked: Arc<AtomicBool>,
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
let window = web_sys::window().unwrap();
|
let window = web_sys::window().unwrap();
|
||||||
let closure = Closure::once(move || paint_and_schedule(&runner_ref, panicked));
|
let closure = Closure::once(move || paint_and_schedule(&runner_ref, panicked));
|
||||||
window.request_animation_frame(closure.as_ref().unchecked_ref())?;
|
window.request_animation_frame(closure.as_ref().unchecked_ref())?;
|
||||||
|
@ -94,12 +95,7 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
|
||||||
// egui wants to use tab to move to the next text field.
|
// egui wants to use tab to move to the next text field.
|
||||||
true
|
true
|
||||||
} else if egui_key == Some(Key::P) {
|
} else if egui_key == Some(Key::P) {
|
||||||
#[allow(clippy::needless_bool)]
|
|
||||||
if modifiers.ctrl || modifiers.command || modifiers.mac_cmd {
|
|
||||||
true // Prevent ctrl-P opening the print dialog. Users may want to use it for a command palette.
|
true // Prevent ctrl-P opening the print dialog. Users may want to use it for a command palette.
|
||||||
} else {
|
|
||||||
false // let normal P:s through
|
|
||||||
}
|
|
||||||
} else if egui_wants_keyboard {
|
} else if egui_wants_keyboard {
|
||||||
matches!(
|
matches!(
|
||||||
event.key().as_str(),
|
event.key().as_str(),
|
||||||
|
|
|
@ -83,6 +83,7 @@ pub fn system_theme() -> Option<Theme> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
|
pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
let document = web_sys::window()?.document()?;
|
let document = web_sys::window()?.document()?;
|
||||||
let canvas = document.get_element_by_id(canvas_id)?;
|
let canvas = document.get_element_by_id(canvas_id)?;
|
||||||
canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
|
canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
|
||||||
|
|
|
@ -10,6 +10,7 @@ use wasm_bindgen::prelude::*;
|
||||||
static AGENT_ID: &str = "egui_text_agent";
|
static AGENT_ID: &str = "egui_text_agent";
|
||||||
|
|
||||||
pub fn text_agent() -> web_sys::HtmlInputElement {
|
pub fn text_agent() -> web_sys::HtmlInputElement {
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
web_sys::window()
|
web_sys::window()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.document()
|
.document()
|
||||||
|
@ -22,6 +23,7 @@ pub fn text_agent() -> web_sys::HtmlInputElement {
|
||||||
|
|
||||||
/// Text event handler,
|
/// Text event handler,
|
||||||
pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
|
pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
let window = web_sys::window().unwrap();
|
let window = web_sys::window().unwrap();
|
||||||
let document = window.document().unwrap();
|
let document = window.document().unwrap();
|
||||||
let body = document.body().expect("document should have a body");
|
let body = document.body().expect("document should have a body");
|
||||||
|
@ -127,6 +129,7 @@ pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(
|
||||||
|
|
||||||
/// Focus or blur text agent to toggle mobile keyboard.
|
/// Focus or blur text agent to toggle mobile keyboard.
|
||||||
pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> {
|
pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> {
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
use web_sys::HtmlInputElement;
|
use web_sys::HtmlInputElement;
|
||||||
let window = web_sys::window()?;
|
let window = web_sys::window()?;
|
||||||
let document = window.document()?;
|
let document = window.document()?;
|
||||||
|
|
|
@ -3,14 +3,10 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
* update to wgpu 0.15 ([#2629](https://github.com/emilk/egui/pull/2629))
|
||||||
|
|
||||||
## 0.21.0 - 2023-02-08
|
|
||||||
* Update to `wgpu` 0.15 ([#2629](https://github.com/emilk/egui/pull/2629))
|
|
||||||
* Return `Err` instead of panic if we can't find a device ([#2428](https://github.com/emilk/egui/pull/2428)).
|
* Return `Err` instead of panic if we can't find a device ([#2428](https://github.com/emilk/egui/pull/2428)).
|
||||||
* `winit::Painter::set_window` is now `async` ([#2434](https://github.com/emilk/egui/pull/2434)).
|
* `winit::Painter::set_window` is now `async` ([#2434](https://github.com/emilk/egui/pull/2434)).
|
||||||
* `egui-wgpu` now only depends on `epaint` instead of the entire `egui` ([#2438](https://github.com/emilk/egui/pull/2438)).
|
* `egui-wgpu` now only depends on `epaint` instead of the entire `egui` ([#2438](https://github.com/emilk/egui/pull/2438)).
|
||||||
* `winit::Painter` now supports transparent backbuffer ([#2684](https://github.com/emilk/egui/pull/2684)).
|
|
||||||
|
|
||||||
|
|
||||||
## 0.20.0 - 2022-12-08 - web support
|
## 0.20.0 - 2022-12-08 - web support
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "egui-wgpu"
|
name = "egui-wgpu"
|
||||||
version = "0.21.0"
|
version = "0.20.0"
|
||||||
description = "Bindings for using egui natively using the wgpu library"
|
description = "Bindings for using egui natively using the wgpu library"
|
||||||
authors = [
|
authors = [
|
||||||
"Nils Hasenbanck <nils@hasenbanck.de>",
|
"Nils Hasenbanck <nils@hasenbanck.de>",
|
||||||
|
@ -36,7 +36,7 @@ winit = ["dep:winit"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
epaint = { version = "0.21.0", path = "../epaint", default-features = false, features = [
|
epaint = { version = "0.20.0", path = "../epaint", default-features = false, features = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|
|
@ -105,13 +105,11 @@ pub enum WgpuError {
|
||||||
DeviceError(wgpu::RequestDeviceError),
|
DeviceError(wgpu::RequestDeviceError),
|
||||||
SurfaceError(wgpu::CreateSurfaceError),
|
SurfaceError(wgpu::CreateSurfaceError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for WgpuError {
|
impl std::fmt::Display for WgpuError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
std::fmt::Debug::fmt(self, f)
|
std::fmt::Debug::fmt(self, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for WgpuError {
|
impl std::error::Error for WgpuError {
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -120,13 +118,11 @@ impl std::error::Error for WgpuError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<wgpu::RequestDeviceError> for WgpuError {
|
impl From<wgpu::RequestDeviceError> for WgpuError {
|
||||||
fn from(e: wgpu::RequestDeviceError) -> Self {
|
fn from(e: wgpu::RequestDeviceError) -> Self {
|
||||||
Self::DeviceError(e)
|
Self::DeviceError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<wgpu::CreateSurfaceError> for WgpuError {
|
impl From<wgpu::CreateSurfaceError> for WgpuError {
|
||||||
fn from(e: wgpu::CreateSurfaceError) -> Self {
|
fn from(e: wgpu::CreateSurfaceError) -> Self {
|
||||||
Self::SurfaceError(e)
|
Self::SurfaceError(e)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use epaint::mutex::RwLock;
|
|
||||||
|
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
use wgpu::{Adapter, Instance, Surface};
|
||||||
|
|
||||||
|
use epaint::mutex::RwLock;
|
||||||
|
|
||||||
use crate::{renderer, RenderState, Renderer, SurfaceErrorAction, WgpuConfiguration};
|
use crate::{renderer, RenderState, Renderer, SurfaceErrorAction, WgpuConfiguration};
|
||||||
|
|
||||||
struct SurfaceState {
|
struct SurfaceState {
|
||||||
surface: wgpu::Surface,
|
surface: Surface,
|
||||||
alpha_mode: wgpu::CompositeAlphaMode,
|
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,11 @@ struct SurfaceState {
|
||||||
pub struct Painter {
|
pub struct Painter {
|
||||||
configuration: WgpuConfiguration,
|
configuration: WgpuConfiguration,
|
||||||
msaa_samples: u32,
|
msaa_samples: u32,
|
||||||
support_transparent_backbuffer: bool,
|
|
||||||
depth_format: Option<wgpu::TextureFormat>,
|
depth_format: Option<wgpu::TextureFormat>,
|
||||||
depth_texture_view: Option<wgpu::TextureView>,
|
depth_texture_view: Option<wgpu::TextureView>,
|
||||||
|
|
||||||
instance: wgpu::Instance,
|
instance: Instance,
|
||||||
adapter: Option<wgpu::Adapter>,
|
adapter: Option<Adapter>,
|
||||||
render_state: Option<RenderState>,
|
render_state: Option<RenderState>,
|
||||||
surface_state: Option<SurfaceState>,
|
surface_state: Option<SurfaceState>,
|
||||||
}
|
}
|
||||||
|
@ -42,12 +41,7 @@ impl Painter {
|
||||||
/// [`set_window()`](Self::set_window) once you have
|
/// [`set_window()`](Self::set_window) once you have
|
||||||
/// a [`winit::window::Window`] with a valid `.raw_window_handle()`
|
/// a [`winit::window::Window`] with a valid `.raw_window_handle()`
|
||||||
/// associated.
|
/// associated.
|
||||||
pub fn new(
|
pub fn new(configuration: WgpuConfiguration, msaa_samples: u32, depth_bits: u8) -> Self {
|
||||||
configuration: WgpuConfiguration,
|
|
||||||
msaa_samples: u32,
|
|
||||||
depth_bits: u8,
|
|
||||||
support_transparent_backbuffer: bool,
|
|
||||||
) -> Self {
|
|
||||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||||
backends: configuration.backends,
|
backends: configuration.backends,
|
||||||
dx12_shader_compiler: Default::default(), //
|
dx12_shader_compiler: Default::default(), //
|
||||||
|
@ -56,7 +50,6 @@ impl Painter {
|
||||||
Self {
|
Self {
|
||||||
configuration,
|
configuration,
|
||||||
msaa_samples,
|
msaa_samples,
|
||||||
support_transparent_backbuffer,
|
|
||||||
depth_format: (depth_bits > 0).then_some(wgpu::TextureFormat::Depth32Float),
|
depth_format: (depth_bits > 0).then_some(wgpu::TextureFormat::Depth32Float),
|
||||||
depth_texture_view: None,
|
depth_texture_view: None,
|
||||||
|
|
||||||
|
@ -76,7 +69,7 @@ impl Painter {
|
||||||
|
|
||||||
async fn init_render_state(
|
async fn init_render_state(
|
||||||
&self,
|
&self,
|
||||||
adapter: &wgpu::Adapter,
|
adapter: &Adapter,
|
||||||
target_format: wgpu::TextureFormat,
|
target_format: wgpu::TextureFormat,
|
||||||
) -> Result<RenderState, wgpu::RequestDeviceError> {
|
) -> Result<RenderState, wgpu::RequestDeviceError> {
|
||||||
adapter
|
adapter
|
||||||
|
@ -101,7 +94,7 @@ impl Painter {
|
||||||
// will have the same format and so this render state will remain valid.
|
// will have the same format and so this render state will remain valid.
|
||||||
async fn ensure_render_state_for_surface(
|
async fn ensure_render_state_for_surface(
|
||||||
&mut self,
|
&mut self,
|
||||||
surface: &wgpu::Surface,
|
surface: &Surface,
|
||||||
) -> Result<(), wgpu::RequestDeviceError> {
|
) -> Result<(), wgpu::RequestDeviceError> {
|
||||||
if self.adapter.is_none() {
|
if self.adapter.is_none() {
|
||||||
self.adapter = self
|
self.adapter = self
|
||||||
|
@ -128,23 +121,34 @@ impl Painter {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configure_surface(
|
fn configure_surface(&mut self, width_in_pixels: u32, height_in_pixels: u32) {
|
||||||
surface_state: &SurfaceState,
|
crate::profile_function!();
|
||||||
render_state: &RenderState,
|
|
||||||
present_mode: wgpu::PresentMode,
|
let render_state = self
|
||||||
) {
|
.render_state
|
||||||
surface_state.surface.configure(
|
.as_ref()
|
||||||
&render_state.device,
|
.expect("Render state should exist before surface configuration");
|
||||||
&wgpu::SurfaceConfiguration {
|
let format = render_state.target_format;
|
||||||
|
|
||||||
|
let config = wgpu::SurfaceConfiguration {
|
||||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
format: render_state.target_format,
|
format,
|
||||||
width: surface_state.width,
|
width: width_in_pixels,
|
||||||
height: surface_state.height,
|
height: height_in_pixels,
|
||||||
present_mode,
|
present_mode: self.configuration.present_mode,
|
||||||
alpha_mode: surface_state.alpha_mode,
|
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||||
view_formats: vec![render_state.target_format],
|
view_formats: vec![format],
|
||||||
},
|
};
|
||||||
);
|
|
||||||
|
let surface_state = self
|
||||||
|
.surface_state
|
||||||
|
.as_mut()
|
||||||
|
.expect("Surface state should exist before surface configuration");
|
||||||
|
surface_state
|
||||||
|
.surface
|
||||||
|
.configure(&render_state.device, &config);
|
||||||
|
surface_state.width = width_in_pixels;
|
||||||
|
surface_state.height = height_in_pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`]
|
/// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`]
|
||||||
|
@ -184,34 +188,15 @@ impl Painter {
|
||||||
|
|
||||||
self.ensure_render_state_for_surface(&surface).await?;
|
self.ensure_render_state_for_surface(&surface).await?;
|
||||||
|
|
||||||
let alpha_mode = if self.support_transparent_backbuffer {
|
|
||||||
let supported_alpha_modes = surface
|
|
||||||
.get_capabilities(self.adapter.as_ref().unwrap())
|
|
||||||
.alpha_modes;
|
|
||||||
|
|
||||||
// Prefer pre multiplied over post multiplied!
|
|
||||||
if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
|
|
||||||
wgpu::CompositeAlphaMode::PreMultiplied
|
|
||||||
} else if supported_alpha_modes
|
|
||||||
.contains(&wgpu::CompositeAlphaMode::PostMultiplied)
|
|
||||||
{
|
|
||||||
wgpu::CompositeAlphaMode::PostMultiplied
|
|
||||||
} else {
|
|
||||||
tracing::warn!("Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency.");
|
|
||||||
wgpu::CompositeAlphaMode::Auto
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wgpu::CompositeAlphaMode::Auto
|
|
||||||
};
|
|
||||||
|
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
|
let width = size.width;
|
||||||
|
let height = size.height;
|
||||||
self.surface_state = Some(SurfaceState {
|
self.surface_state = Some(SurfaceState {
|
||||||
surface,
|
surface,
|
||||||
width: size.width,
|
width,
|
||||||
height: size.height,
|
height,
|
||||||
alpha_mode,
|
|
||||||
});
|
});
|
||||||
self.resize_and_generate_depth_texture_view(size.width, size.height);
|
self.resize_and_generate_depth_texture_view(width, height);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.surface_state = None;
|
self.surface_state = None;
|
||||||
|
@ -236,17 +221,10 @@ impl Painter {
|
||||||
width_in_pixels: u32,
|
width_in_pixels: u32,
|
||||||
height_in_pixels: u32,
|
height_in_pixels: u32,
|
||||||
) {
|
) {
|
||||||
let render_state = self.render_state.as_ref().unwrap();
|
self.configure_surface(width_in_pixels, height_in_pixels);
|
||||||
let surface_state = self.surface_state.as_mut().unwrap();
|
let device = &self.render_state.as_ref().unwrap().device;
|
||||||
|
|
||||||
surface_state.width = width_in_pixels;
|
|
||||||
surface_state.height = height_in_pixels;
|
|
||||||
|
|
||||||
Self::configure_surface(surface_state, render_state, self.configuration.present_mode);
|
|
||||||
|
|
||||||
self.depth_texture_view = self.depth_format.map(|depth_format| {
|
self.depth_texture_view = self.depth_format.map(|depth_format| {
|
||||||
render_state
|
device
|
||||||
.device
|
|
||||||
.create_texture(&wgpu::TextureDescriptor {
|
.create_texture(&wgpu::TextureDescriptor {
|
||||||
label: Some("egui_depth_texture"),
|
label: Some("egui_depth_texture"),
|
||||||
size: wgpu::Extent3d {
|
size: wgpu::Extent3d {
|
||||||
|
@ -291,6 +269,7 @@ impl Painter {
|
||||||
Some(rs) => rs,
|
Some(rs) => rs,
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
let (width, height) = (surface_state.width, surface_state.height);
|
||||||
|
|
||||||
let output_frame = {
|
let output_frame = {
|
||||||
crate::profile_scope!("get_current_texture");
|
crate::profile_scope!("get_current_texture");
|
||||||
|
@ -303,11 +282,7 @@ impl Painter {
|
||||||
#[allow(clippy::single_match_else)]
|
#[allow(clippy::single_match_else)]
|
||||||
Err(e) => match (*self.configuration.on_surface_error)(e) {
|
Err(e) => match (*self.configuration.on_surface_error)(e) {
|
||||||
SurfaceErrorAction::RecreateSurface => {
|
SurfaceErrorAction::RecreateSurface => {
|
||||||
Self::configure_surface(
|
self.configure_surface(width, height);
|
||||||
surface_state,
|
|
||||||
render_state,
|
|
||||||
self.configuration.present_mode,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SurfaceErrorAction::SkipFrame => {
|
SurfaceErrorAction::SkipFrame => {
|
||||||
|
@ -325,7 +300,7 @@ impl Painter {
|
||||||
|
|
||||||
// Upload all resources for the GPU.
|
// Upload all resources for the GPU.
|
||||||
let screen_descriptor = renderer::ScreenDescriptor {
|
let screen_descriptor = renderer::ScreenDescriptor {
|
||||||
size_in_pixels: [surface_state.width, surface_state.height],
|
size_in_pixels: [width, height],
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,21 +3,12 @@ All notable changes to the `egui-winit` integration will be noted in this file.
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
|
||||||
## 0.21.1 - 2023-02-12
|
|
||||||
* Fixed crash when window position is in an invalid state, which could happen e.g. due to changes in monitor size or DPI ([#2722](https://github.com/emilk/egui/issues/2722)).
|
|
||||||
|
|
||||||
|
|
||||||
## 0.21.0 - 2023-02-08
|
|
||||||
* Fixed persistence of native window position on Windows OS ([#2583](https://github.com/emilk/egui/issues/2583)).
|
|
||||||
* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)).
|
* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)).
|
||||||
* Remove the `screen_reader` feature. Use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)).
|
* Remove the `screen_reader` feature. Use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)).
|
||||||
* Fix bug where the cursor could get stuck using the wrong icon.
|
|
||||||
|
|
||||||
|
|
||||||
## 0.20.1 - 2022-12-11
|
## 0.20.1 - 2022-12-11
|
||||||
* Fix [docs.rs](https://docs.rs/egui-winit) build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||||
|
|
||||||
|
|
||||||
## 0.20.0 - 2022-12-08
|
## 0.20.0 - 2022-12-08
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "egui-winit"
|
name = "egui-winit"
|
||||||
version = "0.21.1"
|
version = "0.20.1"
|
||||||
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 = "2021"
|
edition = "2021"
|
||||||
|
@ -43,7 +43,7 @@ serde = ["egui/serde", "dep:serde"]
|
||||||
wayland = ["winit/wayland"]
|
wayland = ["winit/wayland"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
|
||||||
"tracing",
|
"tracing",
|
||||||
] }
|
] }
|
||||||
instant = { version = "0.1", features = [
|
instant = { version = "0.1", features = [
|
||||||
|
@ -55,7 +55,7 @@ winit = { version = "0.28", default-features = false }
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
|
|
||||||
# feature accesskit
|
# feature accesskit
|
||||||
accesskit_winit = { version = "0.10.0", optional = true }
|
accesskit_winit = { version = "0.9.0", optional = true }
|
||||||
|
|
||||||
## Enable this when generating docs.
|
## Enable this when generating docs.
|
||||||
document-features = { version = "0.2", optional = true }
|
document-features = { version = "0.2", optional = true }
|
||||||
|
|
|
@ -59,8 +59,7 @@ pub struct State {
|
||||||
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,
|
||||||
current_cursor_icon: Option<egui::CursorIcon>,
|
current_cursor_icon: egui::CursorIcon,
|
||||||
|
|
||||||
/// What egui uses.
|
/// What egui uses.
|
||||||
current_pixels_per_point: f32,
|
current_pixels_per_point: f32,
|
||||||
|
|
||||||
|
@ -100,7 +99,7 @@ impl State {
|
||||||
egui_input,
|
egui_input,
|
||||||
pointer_pos_in_points: None,
|
pointer_pos_in_points: None,
|
||||||
any_pointer_button_down: false,
|
any_pointer_button_down: false,
|
||||||
current_cursor_icon: None,
|
current_cursor_icon: egui::CursorIcon::Default,
|
||||||
current_pixels_per_point: 1.0,
|
current_pixels_per_point: 1.0,
|
||||||
|
|
||||||
clipboard: clipboard::Clipboard::new(wayland_display),
|
clipboard: clipboard::Clipboard::new(wayland_display),
|
||||||
|
@ -655,25 +654,22 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) {
|
fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) {
|
||||||
if self.current_cursor_icon == Some(cursor_icon) {
|
|
||||||
// Prevent flickering near frame boundary when Windows OS tries to control cursor icon for window resizing.
|
// Prevent flickering near frame boundary when Windows OS tries to control cursor icon for window resizing.
|
||||||
// On other platforms: just early-out to save CPU.
|
// On other platforms: just early-out to save CPU.
|
||||||
|
if self.current_cursor_icon == cursor_icon {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
self.current_cursor_icon = cursor_icon;
|
||||||
|
|
||||||
|
if let Some(cursor_icon) = translate_cursor(cursor_icon) {
|
||||||
|
window.set_cursor_visible(true);
|
||||||
|
|
||||||
let is_pointer_in_window = self.pointer_pos_in_points.is_some();
|
let is_pointer_in_window = self.pointer_pos_in_points.is_some();
|
||||||
if is_pointer_in_window {
|
if is_pointer_in_window {
|
||||||
self.current_cursor_icon = Some(cursor_icon);
|
window.set_cursor_icon(cursor_icon);
|
||||||
|
|
||||||
if let Some(winit_cursor_icon) = translate_cursor(cursor_icon) {
|
|
||||||
window.set_cursor_visible(true);
|
|
||||||
window.set_cursor_icon(winit_cursor_icon);
|
|
||||||
} else {
|
|
||||||
window.set_cursor_visible(false);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Remember to set the cursor again once the cursor returns to the screen:
|
window.set_cursor_visible(false);
|
||||||
self.current_cursor_icon = None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,14 +53,17 @@ impl WindowSettings {
|
||||||
// If the app last ran on two monitors and only one is now connected, then
|
// If the app last ran on two monitors and only one is now connected, then
|
||||||
// the given position is invalid.
|
// the given position is invalid.
|
||||||
// If this happens on Mac, the window is clamped into valid area.
|
// If this happens on Mac, the window is clamped into valid area.
|
||||||
// If this happens on Windows, the clamping behavior is managed by the function
|
// If this happens on Windows, the window is hidden and very difficult to find.
|
||||||
// clamp_window_to_sane_position.
|
// So we don't restore window positions on Windows.
|
||||||
|
let try_restore_position = !cfg!(target_os = "windows");
|
||||||
|
if try_restore_position {
|
||||||
if let Some(pos) = self.position {
|
if let Some(pos) = self.position {
|
||||||
window = window.with_position(winit::dpi::PhysicalPosition {
|
window = window.with_position(winit::dpi::PhysicalPosition {
|
||||||
x: pos.x as f64,
|
x: pos.x as f64,
|
||||||
y: pos.y as f64,
|
y: pos.y as f64,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(inner_size_points) = self.inner_size_points {
|
if let Some(inner_size_points) = self.inner_size_points {
|
||||||
window
|
window
|
||||||
|
@ -87,58 +90,4 @@ impl WindowSettings {
|
||||||
*size = size.at_most(max_size);
|
*size = size.at_most(max_size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clamp_window_to_sane_position<E>(
|
|
||||||
&mut self,
|
|
||||||
event_loop: &winit::event_loop::EventLoopWindowTarget<E>,
|
|
||||||
) {
|
|
||||||
if let (Some(position), Some(inner_size_points)) =
|
|
||||||
(&mut self.position, &self.inner_size_points)
|
|
||||||
{
|
|
||||||
let monitors = event_loop.available_monitors();
|
|
||||||
// default to primary monitor, in case the correct monitor was disconnected.
|
|
||||||
let mut active_monitor = if let Some(active_monitor) = event_loop
|
|
||||||
.primary_monitor()
|
|
||||||
.or_else(|| event_loop.available_monitors().next())
|
|
||||||
{
|
|
||||||
active_monitor
|
|
||||||
} else {
|
|
||||||
return; // no monitors 🤷
|
|
||||||
};
|
|
||||||
for monitor in monitors {
|
|
||||||
let monitor_x_range = (monitor.position().x - inner_size_points.x as i32)
|
|
||||||
..(monitor.position().x + monitor.size().width as i32);
|
|
||||||
let monitor_y_range = (monitor.position().y - inner_size_points.y as i32)
|
|
||||||
..(monitor.position().y + monitor.size().height as i32);
|
|
||||||
|
|
||||||
if monitor_x_range.contains(&(position.x as i32))
|
|
||||||
&& monitor_y_range.contains(&(position.y as i32))
|
|
||||||
{
|
|
||||||
active_monitor = monitor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut inner_size_pixels = *inner_size_points * (active_monitor.scale_factor() as f32);
|
|
||||||
// Add size of title bar. This is 32 px by default in Win 10/11.
|
|
||||||
if cfg!(target_os = "windows") {
|
|
||||||
inner_size_pixels +=
|
|
||||||
egui::Vec2::new(0.0, 32.0 * active_monitor.scale_factor() as f32);
|
|
||||||
}
|
|
||||||
let monitor_position = egui::Pos2::new(
|
|
||||||
active_monitor.position().x as f32,
|
|
||||||
active_monitor.position().y as f32,
|
|
||||||
);
|
|
||||||
let monitor_size = egui::Vec2::new(
|
|
||||||
active_monitor.size().width as f32,
|
|
||||||
active_monitor.size().height as f32,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Window size cannot be negative or the subsequent `clamp` will panic.
|
|
||||||
let window_size = (monitor_size - inner_size_pixels).max(egui::Vec2::ZERO);
|
|
||||||
// To get the maximum position, we get the rightmost corner of the display, then
|
|
||||||
// subtract the size of the window to get the bottom right most value window.position
|
|
||||||
// can have.
|
|
||||||
*position = position.clamp(monitor_position, monitor_position + window_size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "egui"
|
name = "egui"
|
||||||
version = "0.21.0"
|
version = "0.20.1"
|
||||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||||
description = "An easy-to-use immediate mode GUI that runs on both web and native"
|
description = "An easy-to-use immediate mode GUI that runs on both web and native"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
@ -54,12 +54,8 @@ persistence = ["serde", "epaint/serde", "ron"]
|
||||||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||||
serde = ["dep:serde", "epaint/serde", "accesskit?/serde"]
|
serde = ["dep:serde", "epaint/serde", "accesskit?/serde"]
|
||||||
|
|
||||||
## Change Vertex layout to be compatible with unity
|
|
||||||
unity = ["epaint/unity"]
|
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
epaint = { version = "0.21.0", path = "../epaint", default-features = false }
|
epaint = { version = "0.20.0", path = "../epaint", default-features = false }
|
||||||
|
|
||||||
ahash = { version = "0.8.1", default-features = false, features = [
|
ahash = { version = "0.8.1", default-features = false, features = [
|
||||||
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
|
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
|
||||||
|
@ -70,7 +66,7 @@ nohash-hasher = "0.2"
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
## Exposes detailed accessibility implementation required by platform
|
## Exposes detailed accessibility implementation required by platform
|
||||||
## accessibility APIs. Also requires support in the egui integration.
|
## accessibility APIs. Also requires support in the egui integration.
|
||||||
accesskit = { version = "0.9.0", optional = true }
|
accesskit = { version = "0.8.1", optional = true }
|
||||||
|
|
||||||
## Enable this when generating docs.
|
## Enable this when generating docs.
|
||||||
document-features = { version = "0.2", optional = true }
|
document-features = { version = "0.2", optional = true }
|
||||||
|
|
|
@ -9,7 +9,6 @@ pub(crate) struct AnimationManager {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct BoolAnim {
|
struct BoolAnim {
|
||||||
value: bool,
|
value: bool,
|
||||||
|
|
||||||
/// when did `value` last toggle?
|
/// when did `value` last toggle?
|
||||||
toggle_time: f64,
|
toggle_time: f64,
|
||||||
}
|
}
|
||||||
|
@ -17,9 +16,7 @@ struct BoolAnim {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct ValueAnim {
|
struct ValueAnim {
|
||||||
from_value: f32,
|
from_value: f32,
|
||||||
|
|
||||||
to_value: f32,
|
to_value: f32,
|
||||||
|
|
||||||
/// when did `value` last toggle?
|
/// when did `value` last toggle?
|
||||||
toggle_time: f64,
|
toggle_time: f64,
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,8 @@ use crate::*;
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub(crate) struct State {
|
pub(crate) struct State {
|
||||||
/// Last known pos of the pivot
|
/// Last known pos
|
||||||
pub pivot_pos: Pos2,
|
pub pos: Pos2,
|
||||||
|
|
||||||
pub pivot: Align2,
|
|
||||||
|
|
||||||
/// Last know size. Used for catching clicks.
|
/// Last know size. Used for catching clicks.
|
||||||
pub size: Vec2,
|
pub size: Vec2,
|
||||||
|
@ -23,22 +21,8 @@ pub(crate) struct State {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn left_top_pos(&self) -> Pos2 {
|
|
||||||
pos2(
|
|
||||||
self.pivot_pos.x - self.pivot.x().to_factor() * self.size.x,
|
|
||||||
self.pivot_pos.y - self.pivot.y().to_factor() * self.size.y,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_left_top_pos(&mut self, pos: Pos2) {
|
|
||||||
self.pivot_pos = pos2(
|
|
||||||
pos.x + self.pivot.x().to_factor() * self.size.x,
|
|
||||||
pos.y + self.pivot.y().to_factor() * self.size.y,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rect(&self) -> Rect {
|
pub fn rect(&self) -> Rect {
|
||||||
Rect::from_min_size(self.left_top_pos(), self.size)
|
Rect::from_min_size(self.pos, self.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,19 +237,21 @@ impl Area {
|
||||||
ctx.request_repaint(); // if we don't know the previous size we are likely drawing the area in the wrong place
|
ctx.request_repaint(); // if we don't know the previous size we are likely drawing the area in the wrong place
|
||||||
}
|
}
|
||||||
let mut state = state.unwrap_or_else(|| State {
|
let mut state = state.unwrap_or_else(|| State {
|
||||||
pivot_pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)),
|
pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)),
|
||||||
pivot,
|
|
||||||
size: Vec2::ZERO,
|
size: Vec2::ZERO,
|
||||||
interactable,
|
interactable,
|
||||||
});
|
});
|
||||||
state.pivot_pos = new_pos.unwrap_or(state.pivot_pos);
|
state.pos = new_pos.unwrap_or(state.pos);
|
||||||
state.interactable = interactable;
|
state.interactable = interactable;
|
||||||
|
|
||||||
|
if pivot != Align2::LEFT_TOP {
|
||||||
|
state.pos.x -= pivot.x().to_factor() * state.size.x;
|
||||||
|
state.pos.y -= pivot.y().to_factor() * state.size.y;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((anchor, offset)) = anchor {
|
if let Some((anchor, offset)) = anchor {
|
||||||
let screen = ctx.available_rect();
|
let screen = ctx.available_rect();
|
||||||
state.set_left_top_pos(
|
state.pos = anchor.align_size_within_rect(state.size, screen).min + offset;
|
||||||
anchor.align_size_within_rect(state.size, screen).left_top() + offset,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// interact right away to prevent frame-delay
|
// interact right away to prevent frame-delay
|
||||||
|
@ -292,13 +278,12 @@ impl Area {
|
||||||
// Important check - don't try to move e.g. a combobox popup!
|
// Important check - don't try to move e.g. a combobox popup!
|
||||||
if movable {
|
if movable {
|
||||||
if move_response.dragged() {
|
if move_response.dragged() {
|
||||||
state.pivot_pos += ctx.input(|i| i.pointer.delta());
|
state.pos += ctx.input(|i| i.pointer.delta());
|
||||||
}
|
}
|
||||||
|
|
||||||
state.set_left_top_pos(
|
state.pos = ctx
|
||||||
ctx.constrain_window_rect_to_area(state.rect(), drag_bounds)
|
.constrain_window_rect_to_area(state.rect(), drag_bounds)
|
||||||
.min,
|
.min;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (move_response.dragged() || move_response.clicked())
|
if (move_response.dragged() || move_response.clicked())
|
||||||
|
@ -312,13 +297,12 @@ impl Area {
|
||||||
move_response
|
move_response
|
||||||
};
|
};
|
||||||
|
|
||||||
state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos()));
|
state.pos = ctx.round_pos_to_pixels(state.pos);
|
||||||
|
|
||||||
if constrain {
|
if constrain {
|
||||||
state.set_left_top_pos(
|
state.pos = ctx
|
||||||
ctx.constrain_window_rect_to_area(state.rect(), drag_bounds)
|
.constrain_window_rect_to_area(state.rect(), drag_bounds)
|
||||||
.left_top(),
|
.min;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Prepared {
|
Prepared {
|
||||||
|
@ -390,16 +374,14 @@ impl Prepared {
|
||||||
};
|
};
|
||||||
|
|
||||||
let max_rect = Rect::from_min_max(
|
let max_rect = Rect::from_min_max(
|
||||||
self.state.left_top_pos(),
|
self.state.pos,
|
||||||
bounds
|
bounds.max.at_least(self.state.pos + Vec2::splat(32.0)),
|
||||||
.max
|
|
||||||
.at_least(self.state.left_top_pos() + Vec2::splat(32.0)),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky
|
let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky
|
||||||
let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius);
|
let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius);
|
||||||
|
|
||||||
let clip_rect = Rect::from_min_max(self.state.left_top_pos(), bounds.max)
|
let clip_rect = Rect::from_min_max(self.state.pos, bounds.max)
|
||||||
.expand(clip_rect_margin)
|
.expand(clip_rect_margin)
|
||||||
.intersect(bounds);
|
.intersect(bounds);
|
||||||
|
|
||||||
|
|
|
@ -141,10 +141,9 @@ impl CollapsingState {
|
||||||
add_header: impl FnOnce(&mut Ui) -> HeaderRet,
|
add_header: impl FnOnce(&mut Ui) -> HeaderRet,
|
||||||
) -> HeaderResponse<'_, HeaderRet> {
|
) -> HeaderResponse<'_, HeaderRet> {
|
||||||
let header_response = ui.horizontal(|ui| {
|
let header_response = ui.horizontal(|ui| {
|
||||||
let prev_item_spacing = ui.spacing_mut().item_spacing;
|
|
||||||
ui.spacing_mut().item_spacing.x = 0.0; // the toggler button uses the full indent width
|
ui.spacing_mut().item_spacing.x = 0.0; // the toggler button uses the full indent width
|
||||||
let collapser = self.show_default_button_indented(ui);
|
let collapser = self.show_default_button_indented(ui);
|
||||||
ui.spacing_mut().item_spacing = prev_item_spacing;
|
ui.spacing_mut().item_spacing.x = ui.spacing_mut().icon_spacing; // Restore spacing
|
||||||
(collapser, add_header(ui))
|
(collapser, add_header(ui))
|
||||||
});
|
});
|
||||||
HeaderResponse {
|
HeaderResponse {
|
||||||
|
|
|
@ -19,16 +19,11 @@ use epaint::*;
|
||||||
pub struct Frame {
|
pub struct Frame {
|
||||||
/// Margin within the painted frame.
|
/// Margin within the painted frame.
|
||||||
pub inner_margin: Margin,
|
pub inner_margin: Margin,
|
||||||
|
|
||||||
/// Margin outside the painted frame.
|
/// Margin outside the painted frame.
|
||||||
pub outer_margin: Margin,
|
pub outer_margin: Margin,
|
||||||
|
|
||||||
pub rounding: Rounding,
|
pub rounding: Rounding,
|
||||||
|
|
||||||
pub shadow: Shadow,
|
pub shadow: Shadow,
|
||||||
|
|
||||||
pub fill: Color32,
|
pub fill: Color32,
|
||||||
|
|
||||||
pub stroke: Stroke,
|
pub stroke: Stroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -260,9 +260,8 @@ fn show_tooltip_area_dyn<'c, R>(
|
||||||
Area::new(area_id)
|
Area::new(area_id)
|
||||||
.order(Order::Tooltip)
|
.order(Order::Tooltip)
|
||||||
.fixed_pos(window_pos)
|
.fixed_pos(window_pos)
|
||||||
.constrain(true)
|
|
||||||
.interactable(false)
|
.interactable(false)
|
||||||
.drag_bounds(ctx.screen_rect())
|
.drag_bounds(Rect::EVERYTHING) // disable clip rect
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
Frame::popup(&ctx.style())
|
Frame::popup(&ctx.style())
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
|
|
|
@ -438,12 +438,9 @@ impl<'open> Window<'open> {
|
||||||
content_inner
|
content_inner
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
area.state_mut().pos = ctx
|
||||||
let pos = ctx
|
|
||||||
.constrain_window_rect_to_area(area.state().rect(), area.drag_bounds())
|
.constrain_window_rect_to_area(area.state().rect(), area.drag_bounds())
|
||||||
.left_top();
|
.min;
|
||||||
area.state_mut().set_left_top_pos(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
let full_response = area.end(ctx, area_content_ui);
|
let full_response = area.end(ctx, area_content_ui);
|
||||||
|
|
||||||
|
@ -553,7 +550,7 @@ fn interact(
|
||||||
let new_rect = ctx.constrain_window_rect_to_area(new_rect, area.drag_bounds());
|
let new_rect = ctx.constrain_window_rect_to_area(new_rect, area.drag_bounds());
|
||||||
|
|
||||||
// TODO(emilk): add this to a Window state instead as a command "move here next frame"
|
// TODO(emilk): add this to a Window state instead as a command "move here next frame"
|
||||||
area.state_mut().set_left_top_pos(new_rect.left_top());
|
area.state_mut().pos = new_rect.min;
|
||||||
|
|
||||||
if window_interaction.is_resize() {
|
if window_interaction.is_resize() {
|
||||||
if let Some(mut state) = resize::State::load(ctx, resize_id) {
|
if let Some(mut state) = resize::State::load(ctx, resize_id) {
|
||||||
|
|
|
@ -65,14 +65,11 @@ struct ContextImpl {
|
||||||
|
|
||||||
/// Written to during the frame.
|
/// Written to during the frame.
|
||||||
layer_rects_this_frame: ahash::HashMap<LayerId, Vec<(Id, Rect)>>,
|
layer_rects_this_frame: ahash::HashMap<LayerId, Vec<(Id, Rect)>>,
|
||||||
|
|
||||||
/// Read
|
/// Read
|
||||||
layer_rects_prev_frame: ahash::HashMap<LayerId, Vec<(Id, Rect)>>,
|
layer_rects_prev_frame: ahash::HashMap<LayerId, Vec<(Id, Rect)>>,
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
is_accesskit_enabled: bool,
|
is_accesskit_enabled: bool,
|
||||||
#[cfg(feature = "accesskit")]
|
|
||||||
accesskit_node_classes: accesskit::NodeClassSet,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextImpl {
|
impl ContextImpl {
|
||||||
|
@ -106,8 +103,7 @@ impl ContextImpl {
|
||||||
self.memory.areas.set_state(
|
self.memory.areas.set_state(
|
||||||
LayerId::background(),
|
LayerId::background(),
|
||||||
containers::area::State {
|
containers::area::State {
|
||||||
pivot_pos: screen_rect.left_top(),
|
pos: screen_rect.min,
|
||||||
pivot: Align2::LEFT_TOP,
|
|
||||||
size: screen_rect.size(),
|
size: screen_rect.size(),
|
||||||
interactable: true,
|
interactable: true,
|
||||||
},
|
},
|
||||||
|
@ -117,14 +113,17 @@ impl ContextImpl {
|
||||||
if self.is_accesskit_enabled {
|
if self.is_accesskit_enabled {
|
||||||
use crate::frame_state::AccessKitFrameState;
|
use crate::frame_state::AccessKitFrameState;
|
||||||
let id = crate::accesskit_root_id();
|
let id = crate::accesskit_root_id();
|
||||||
let mut builder = accesskit::NodeBuilder::new(accesskit::Role::Window);
|
let node = Box::new(accesskit::Node {
|
||||||
builder.set_transform(accesskit::Affine::scale(
|
role: accesskit::Role::Window,
|
||||||
self.input.pixels_per_point().into(),
|
transform: Some(
|
||||||
));
|
accesskit::kurbo::Affine::scale(self.input.pixels_per_point().into()).into(),
|
||||||
let mut node_builders = IdMap::default();
|
),
|
||||||
node_builders.insert(id, builder);
|
..Default::default()
|
||||||
|
});
|
||||||
|
let mut nodes = IdMap::default();
|
||||||
|
nodes.insert(id, node);
|
||||||
self.frame_state.accesskit_state = Some(AccessKitFrameState {
|
self.frame_state.accesskit_state = Some(AccessKitFrameState {
|
||||||
node_builders,
|
nodes,
|
||||||
parent_stack: vec![id],
|
parent_stack: vec![id],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -157,16 +156,16 @@ impl ContextImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::NodeBuilder {
|
fn accesskit_node(&mut self, id: Id) -> &mut accesskit::Node {
|
||||||
let state = self.frame_state.accesskit_state.as_mut().unwrap();
|
let state = self.frame_state.accesskit_state.as_mut().unwrap();
|
||||||
let builders = &mut state.node_builders;
|
let nodes = &mut state.nodes;
|
||||||
if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) {
|
if let std::collections::hash_map::Entry::Vacant(entry) = nodes.entry(id) {
|
||||||
entry.insert(Default::default());
|
entry.insert(Default::default());
|
||||||
let parent_id = state.parent_stack.last().unwrap();
|
let parent_id = state.parent_stack.last().unwrap();
|
||||||
let parent_builder = builders.get_mut(parent_id).unwrap();
|
let parent = nodes.get_mut(parent_id).unwrap();
|
||||||
parent_builder.push_child(id.accesskit_id());
|
parent.children.push(id.accesskit_id());
|
||||||
}
|
}
|
||||||
builders.get_mut(&id).unwrap()
|
nodes.get_mut(&id).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -656,7 +655,7 @@ impl Context {
|
||||||
// Make sure anything that can receive focus has an AccessKit node.
|
// Make sure anything that can receive focus has an AccessKit node.
|
||||||
// TODO(mwcampbell): For nodes that are filled from widget info,
|
// TODO(mwcampbell): For nodes that are filled from widget info,
|
||||||
// some information is written to the node twice.
|
// some information is written to the node twice.
|
||||||
self.accesskit_node_builder(id, |builder| response.fill_accesskit_node_common(builder));
|
self.accesskit_node(id, |node| response.fill_accesskit_node_common(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
let clicked_elsewhere = response.clicked_elsewhere();
|
let clicked_elsewhere = response.clicked_elsewhere();
|
||||||
|
@ -1129,20 +1128,12 @@ impl Context {
|
||||||
if let Some(state) = state {
|
if let Some(state) = state {
|
||||||
let has_focus = self.input(|i| i.raw.has_focus);
|
let has_focus = self.input(|i| i.raw.has_focus);
|
||||||
let root_id = crate::accesskit_root_id().accesskit_id();
|
let root_id = crate::accesskit_root_id().accesskit_id();
|
||||||
let nodes = self.write(|ctx| {
|
|
||||||
state
|
|
||||||
.node_builders
|
|
||||||
.into_iter()
|
|
||||||
.map(|(id, builder)| {
|
|
||||||
(
|
|
||||||
id.accesskit_id(),
|
|
||||||
builder.build(&mut ctx.accesskit_node_classes),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
platform_output.accesskit_update = Some(accesskit::TreeUpdate {
|
platform_output.accesskit_update = Some(accesskit::TreeUpdate {
|
||||||
nodes,
|
nodes: state
|
||||||
|
.nodes
|
||||||
|
.into_iter()
|
||||||
|
.map(|(id, node)| (id.accesskit_id(), Arc::from(node)))
|
||||||
|
.collect(),
|
||||||
tree: Some(accesskit::Tree::new(root_id)),
|
tree: Some(accesskit::Tree::new(root_id)),
|
||||||
focus: has_focus.then(|| {
|
focus: has_focus.then(|| {
|
||||||
let focus_id = self.memory(|mem| mem.interaction.focus.id);
|
let focus_id = self.memory(|mem| mem.interaction.focus.id);
|
||||||
|
@ -1729,8 +1720,8 @@ impl Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If AccessKit support is active for the current frame, get or create
|
/// If AccessKit support is active for the current frame, get or create
|
||||||
/// a node builder with the specified ID and return a mutable reference to it.
|
/// a node with the specified ID and return a mutable reference to it.
|
||||||
/// For newly created nodes, the parent is the node with the ID at the top
|
/// For newly crated nodes, the parent is the node with the ID at the top
|
||||||
/// of the stack managed by [`Context::with_accessibility_parent`].
|
/// of the stack managed by [`Context::with_accessibility_parent`].
|
||||||
///
|
///
|
||||||
/// The `Context` lock is held while the given closure is called!
|
/// The `Context` lock is held while the given closure is called!
|
||||||
|
@ -1738,16 +1729,16 @@ impl Context {
|
||||||
/// Returns `None` if acesskit is off.
|
/// Returns `None` if acesskit is off.
|
||||||
// TODO: consider making both RO and RW versions
|
// TODO: consider making both RO and RW versions
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
pub fn accesskit_node_builder<R>(
|
pub fn accesskit_node<R>(
|
||||||
&self,
|
&self,
|
||||||
id: Id,
|
id: Id,
|
||||||
writer: impl FnOnce(&mut accesskit::NodeBuilder) -> R,
|
writer: impl FnOnce(&mut accesskit::Node) -> R,
|
||||||
) -> Option<R> {
|
) -> Option<R> {
|
||||||
self.write(|ctx| {
|
self.write(|ctx| {
|
||||||
ctx.frame_state
|
ctx.frame_state
|
||||||
.accesskit_state
|
.accesskit_state
|
||||||
.is_some()
|
.is_some()
|
||||||
.then(|| ctx.accesskit_node_builder(id))
|
.then(|| ctx.accesskit_node(id))
|
||||||
.map(writer)
|
.map(writer)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1759,30 +1750,12 @@ impl Context {
|
||||||
/// being called by the AccessKit adapter to provide the initial tree update,
|
/// being called by the AccessKit adapter to provide the initial tree update,
|
||||||
/// then it should do so, to provide a complete AccessKit tree to the adapter
|
/// then it should do so, to provide a complete AccessKit tree to the adapter
|
||||||
/// immediately. Otherwise, it should enqueue a repaint and use the
|
/// immediately. Otherwise, it should enqueue a repaint and use the
|
||||||
/// placeholder tree update from [`Context::accesskit_placeholder_tree_update`]
|
/// placeholder tree update from [`crate::accesskit_placeholder_tree_update`]
|
||||||
/// in the meantime.
|
/// in the meantime.
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
pub fn enable_accesskit(&self) {
|
pub fn enable_accesskit(&self) {
|
||||||
self.write(|ctx| ctx.is_accesskit_enabled = true);
|
self.write(|ctx| ctx.is_accesskit_enabled = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a tree update that the egui integration should provide to the
|
|
||||||
/// AccessKit adapter if it cannot immediately run the egui application
|
|
||||||
/// to get a full tree update after running [`Context::enable_accesskit`].
|
|
||||||
#[cfg(feature = "accesskit")]
|
|
||||||
pub fn accesskit_placeholder_tree_update(&self) -> accesskit::TreeUpdate {
|
|
||||||
use accesskit::{NodeBuilder, Role, Tree, TreeUpdate};
|
|
||||||
|
|
||||||
let root_id = crate::accesskit_root_id().accesskit_id();
|
|
||||||
self.write(|ctx| TreeUpdate {
|
|
||||||
nodes: vec![(
|
|
||||||
root_id,
|
|
||||||
NodeBuilder::new(Role::Window).build(&mut ctx.accesskit_node_classes),
|
|
||||||
)],
|
|
||||||
tree: Some(Tree::new(root_id)),
|
|
||||||
focus: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub(crate) struct TooltipFrameState {
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct AccessKitFrameState {
|
pub(crate) struct AccessKitFrameState {
|
||||||
pub(crate) node_builders: IdMap<accesskit::NodeBuilder>,
|
pub(crate) nodes: IdMap<Box<accesskit::Node>>,
|
||||||
pub(crate) parent_stack: Vec<Id>,
|
pub(crate) parent_stack: Vec<Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -447,10 +447,8 @@ impl InputState {
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub(crate) struct Click {
|
pub(crate) struct Click {
|
||||||
pub pos: Pos2,
|
pub pos: Pos2,
|
||||||
|
|
||||||
/// 1 or 2 (double-click) or 3 (triple-click)
|
/// 1 or 2 (double-click) or 3 (triple-click)
|
||||||
pub count: u32,
|
pub count: u32,
|
||||||
|
|
||||||
/// Allows you to check for e.g. shift-click
|
/// Allows you to check for e.g. shift-click
|
||||||
pub modifiers: Modifiers,
|
pub modifiers: Modifiers,
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,14 +96,10 @@ struct GestureState {
|
||||||
struct DynGestureState {
|
struct DynGestureState {
|
||||||
/// used for proportional zooming
|
/// used for proportional zooming
|
||||||
avg_distance: f32,
|
avg_distance: f32,
|
||||||
|
|
||||||
/// used for non-proportional zooming
|
/// used for non-proportional zooming
|
||||||
avg_abs_distance2: Vec2,
|
avg_abs_distance2: Vec2,
|
||||||
|
|
||||||
avg_pos: Pos2,
|
avg_pos: Pos2,
|
||||||
|
|
||||||
avg_force: f32,
|
avg_force: f32,
|
||||||
|
|
||||||
heading: f32,
|
heading: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,6 @@ impl Order {
|
||||||
Self::Tooltip,
|
Self::Tooltip,
|
||||||
Self::Debug,
|
Self::Debug,
|
||||||
];
|
];
|
||||||
pub const TOP: Self = Self::Debug;
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_interaction(&self) -> bool {
|
pub fn allow_interaction(&self) -> bool {
|
||||||
|
|
|
@ -513,30 +513,18 @@ pub mod special_emojis {
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum WidgetType {
|
pub enum WidgetType {
|
||||||
Label, // TODO(emilk): emit Label events
|
Label, // TODO(emilk): emit Label events
|
||||||
|
|
||||||
/// e.g. a hyperlink
|
/// e.g. a hyperlink
|
||||||
Link,
|
Link,
|
||||||
|
|
||||||
TextEdit,
|
TextEdit,
|
||||||
|
|
||||||
Button,
|
Button,
|
||||||
|
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
|
||||||
RadioButton,
|
RadioButton,
|
||||||
|
|
||||||
SelectableLabel,
|
SelectableLabel,
|
||||||
|
|
||||||
ComboBox,
|
ComboBox,
|
||||||
|
|
||||||
Slider,
|
Slider,
|
||||||
|
|
||||||
DragValue,
|
DragValue,
|
||||||
|
|
||||||
ColorButton,
|
ColorButton,
|
||||||
|
|
||||||
ImageButton,
|
ImageButton,
|
||||||
|
|
||||||
CollapsingHeader,
|
CollapsingHeader,
|
||||||
|
|
||||||
/// If you cannot fit any of the above slots.
|
/// If you cannot fit any of the above slots.
|
||||||
|
@ -571,3 +559,25 @@ pub fn __run_test_ui(mut add_contents: impl FnMut(&mut Ui)) {
|
||||||
pub fn accesskit_root_id() -> Id {
|
pub fn accesskit_root_id() -> Id {
|
||||||
Id::new("accesskit_root")
|
Id::new("accesskit_root")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a tree update that the egui integration should provide to the
|
||||||
|
/// AccessKit adapter if it cannot immediately run the egui application
|
||||||
|
/// to get a full tree update after running [`Context::enable_accesskit`].
|
||||||
|
#[cfg(feature = "accesskit")]
|
||||||
|
pub fn accesskit_placeholder_tree_update() -> accesskit::TreeUpdate {
|
||||||
|
use accesskit::{Node, Role, Tree, TreeUpdate};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
let root_id = accesskit_root_id().accesskit_id();
|
||||||
|
TreeUpdate {
|
||||||
|
nodes: vec![(
|
||||||
|
root_id,
|
||||||
|
Arc::new(Node {
|
||||||
|
role: Role::Window,
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
)],
|
||||||
|
tree: Some(Tree::new(root_id)),
|
||||||
|
focus: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -573,47 +573,47 @@ impl Response {
|
||||||
self.output_event(event);
|
self.output_event(event);
|
||||||
} else {
|
} else {
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
self.ctx.accesskit_node_builder(self.id, |builder| {
|
self.ctx.accesskit_node(self.id, |node| {
|
||||||
self.fill_accesskit_node_from_widget_info(builder, make_info());
|
self.fill_accesskit_node_from_widget_info(node, make_info());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn output_event(&self, event: crate::output::OutputEvent) {
|
pub fn output_event(&self, event: crate::output::OutputEvent) {
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
self.ctx.accesskit_node_builder(self.id, |builder| {
|
self.ctx.accesskit_node(self.id, |node| {
|
||||||
self.fill_accesskit_node_from_widget_info(builder, event.widget_info().clone());
|
self.fill_accesskit_node_from_widget_info(node, event.widget_info().clone());
|
||||||
});
|
});
|
||||||
self.ctx.output_mut(|o| o.events.push(event));
|
self.ctx.output_mut(|o| o.events.push(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
pub(crate) fn fill_accesskit_node_common(&self, builder: &mut accesskit::NodeBuilder) {
|
pub(crate) fn fill_accesskit_node_common(&self, node: &mut accesskit::Node) {
|
||||||
builder.set_bounds(accesskit::Rect {
|
node.bounds = Some(accesskit::kurbo::Rect {
|
||||||
x0: self.rect.min.x.into(),
|
x0: self.rect.min.x.into(),
|
||||||
y0: self.rect.min.y.into(),
|
y0: self.rect.min.y.into(),
|
||||||
x1: self.rect.max.x.into(),
|
x1: self.rect.max.x.into(),
|
||||||
y1: self.rect.max.y.into(),
|
y1: self.rect.max.y.into(),
|
||||||
});
|
});
|
||||||
if self.sense.focusable {
|
if self.sense.focusable {
|
||||||
builder.add_action(accesskit::Action::Focus);
|
node.focusable = true;
|
||||||
}
|
}
|
||||||
if self.sense.click && builder.default_action_verb().is_none() {
|
if self.sense.click && node.default_action_verb.is_none() {
|
||||||
builder.set_default_action_verb(accesskit::DefaultActionVerb::Click);
|
node.default_action_verb = Some(accesskit::DefaultActionVerb::Click);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
fn fill_accesskit_node_from_widget_info(
|
fn fill_accesskit_node_from_widget_info(
|
||||||
&self,
|
&self,
|
||||||
builder: &mut accesskit::NodeBuilder,
|
node: &mut accesskit::Node,
|
||||||
info: crate::WidgetInfo,
|
info: crate::WidgetInfo,
|
||||||
) {
|
) {
|
||||||
use crate::WidgetType;
|
use crate::WidgetType;
|
||||||
use accesskit::{CheckedState, Role};
|
use accesskit::{CheckedState, Role};
|
||||||
|
|
||||||
self.fill_accesskit_node_common(builder);
|
self.fill_accesskit_node_common(node);
|
||||||
builder.set_role(match info.typ {
|
node.role = match info.typ {
|
||||||
WidgetType::Label => Role::StaticText,
|
WidgetType::Label => Role::StaticText,
|
||||||
WidgetType::Link => Role::Link,
|
WidgetType::Link => Role::Link,
|
||||||
WidgetType::TextEdit => Role::TextField,
|
WidgetType::TextEdit => Role::TextField,
|
||||||
|
@ -628,18 +628,18 @@ impl Response {
|
||||||
WidgetType::DragValue => Role::SpinButton,
|
WidgetType::DragValue => Role::SpinButton,
|
||||||
WidgetType::ColorButton => Role::ColorWell,
|
WidgetType::ColorButton => Role::ColorWell,
|
||||||
WidgetType::Other => Role::Unknown,
|
WidgetType::Other => Role::Unknown,
|
||||||
});
|
};
|
||||||
if let Some(label) = info.label {
|
if let Some(label) = info.label {
|
||||||
builder.set_name(label);
|
node.name = Some(label.into());
|
||||||
}
|
}
|
||||||
if let Some(value) = info.current_text_value {
|
if let Some(value) = info.current_text_value {
|
||||||
builder.set_value(value);
|
node.value = Some(value.into());
|
||||||
}
|
}
|
||||||
if let Some(value) = info.value {
|
if let Some(value) = info.value {
|
||||||
builder.set_numeric_value(value);
|
node.numeric_value = Some(value);
|
||||||
}
|
}
|
||||||
if let Some(selected) = info.selected {
|
if let Some(selected) = info.selected {
|
||||||
builder.set_checked_state(if selected {
|
node.checked_state = Some(if selected {
|
||||||
CheckedState::True
|
CheckedState::True
|
||||||
} else {
|
} else {
|
||||||
CheckedState::False
|
CheckedState::False
|
||||||
|
@ -662,9 +662,8 @@ impl Response {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn labelled_by(self, id: Id) -> Self {
|
pub fn labelled_by(self, id: Id) -> Self {
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
self.ctx.accesskit_node_builder(self.id, |builder| {
|
self.ctx
|
||||||
builder.push_labelled_by(id.accesskit_id());
|
.accesskit_node(self.id, |node| node.labelled_by.push(id.accesskit_id()));
|
||||||
});
|
|
||||||
#[cfg(not(feature = "accesskit"))]
|
#[cfg(not(feature = "accesskit"))]
|
||||||
{
|
{
|
||||||
let _ = id;
|
let _ = id;
|
||||||
|
|
|
@ -305,7 +305,6 @@ pub struct Spacing {
|
||||||
|
|
||||||
/// Margin between contents and scroll bar.
|
/// Margin between contents and scroll bar.
|
||||||
pub scroll_bar_inner_margin: f32,
|
pub scroll_bar_inner_margin: f32,
|
||||||
|
|
||||||
/// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar).
|
/// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar).
|
||||||
pub scroll_bar_outer_margin: f32,
|
pub scroll_bar_outer_margin: f32,
|
||||||
}
|
}
|
||||||
|
@ -492,7 +491,6 @@ pub struct Visuals {
|
||||||
pub resize_corner_size: f32,
|
pub resize_corner_size: f32,
|
||||||
|
|
||||||
pub text_cursor_width: f32,
|
pub text_cursor_width: f32,
|
||||||
|
|
||||||
/// show where the text cursor would be if you clicked
|
/// show where the text cursor would be if you clicked
|
||||||
pub text_cursor_preview: bool,
|
pub text_cursor_preview: bool,
|
||||||
|
|
||||||
|
@ -511,11 +509,6 @@ pub struct Visuals {
|
||||||
/// Wether or not Grids and Tables should be striped by default
|
/// Wether or not Grids and Tables should be striped by default
|
||||||
/// (have alternating rows differently colored).
|
/// (have alternating rows differently colored).
|
||||||
pub striped: bool,
|
pub striped: bool,
|
||||||
|
|
||||||
/// Show trailing color behind the circle of a [`Slider`]. Default is OFF.
|
|
||||||
///
|
|
||||||
/// Enabling this will affect ALL sliders, and can be enabled/disabled per slider with [`Slider::trailing_fill`].
|
|
||||||
pub slider_trailing_fill: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Visuals {
|
impl Visuals {
|
||||||
|
@ -775,8 +768,6 @@ impl Visuals {
|
||||||
indent_has_left_vline: true,
|
indent_has_left_vline: true,
|
||||||
|
|
||||||
striped: false,
|
striped: false,
|
||||||
|
|
||||||
slider_trailing_fill: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1342,8 +1333,6 @@ impl Visuals {
|
||||||
indent_has_left_vline,
|
indent_has_left_vline,
|
||||||
|
|
||||||
striped,
|
striped,
|
||||||
|
|
||||||
slider_trailing_fill,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
ui.collapsing("Background Colors", |ui| {
|
ui.collapsing("Background Colors", |ui| {
|
||||||
|
@ -1406,8 +1395,6 @@ impl Visuals {
|
||||||
|
|
||||||
ui.checkbox(striped, "By default, add stripes to grids and tables?");
|
ui.checkbox(striped, "By default, add stripes to grids and tables?");
|
||||||
|
|
||||||
ui.checkbox(slider_trailing_fill, "Add trailing color to sliders");
|
|
||||||
|
|
||||||
ui.vertical_centered(|ui| reset_button(ui, self));
|
ui.vertical_centered(|ui| reset_button(ui, self));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2174,6 +2174,7 @@ impl Ui {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
/// Create a menu button with an image that when clicked will show the given menu.
|
/// Create a menu button with an image that when clicked will show the given menu.
|
||||||
///
|
///
|
||||||
/// If called from within a menu this will instead create a button for a sub-menu.
|
/// If called from within a menu this will instead create a button for a sub-menu.
|
||||||
|
@ -2197,7 +2198,6 @@ impl Ui {
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// See also: [`Self::close_menu`] and [`Response::context_menu`].
|
/// See also: [`Self::close_menu`] and [`Response::context_menu`].
|
||||||
#[inline]
|
|
||||||
pub fn menu_image_button<R>(
|
pub fn menu_image_button<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
texture_id: TextureId,
|
texture_id: TextureId,
|
||||||
|
|
|
@ -269,10 +269,6 @@ impl<'a> Checkbox<'a> {
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn without_text(checked: &'a mut bool) -> Self {
|
|
||||||
Self::new(checked, WidgetText::default())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Widget for Checkbox<'a> {
|
impl<'a> Widget for Checkbox<'a> {
|
||||||
|
|
|
@ -461,23 +461,16 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
// some clones below are redundant if AccessKit is disabled
|
// some clones below are redundant if AccessKit is disabled
|
||||||
#[allow(clippy::redundant_clone)]
|
#[allow(clippy::redundant_clone)]
|
||||||
let mut response = if is_kb_editing {
|
let mut response = if is_kb_editing {
|
||||||
|
let button_width = ui.spacing().interact_size.x;
|
||||||
let mut value_text = ui
|
let mut value_text = ui
|
||||||
.memory_mut(|mem| mem.drag_value.edit_string.take())
|
.memory_mut(|mem| mem.drag_value.edit_string.take())
|
||||||
.unwrap_or_else(|| value_text.clone());
|
.unwrap_or_else(|| value_text.clone());
|
||||||
let response = ui.add(
|
let response = ui.add(
|
||||||
TextEdit::singleline(&mut value_text)
|
TextEdit::singleline(&mut value_text)
|
||||||
.clip_text(false)
|
|
||||||
.horizontal_align(ui.layout().horizontal_align())
|
|
||||||
.vertical_align(ui.layout().vertical_align())
|
|
||||||
.margin(ui.spacing().button_padding)
|
|
||||||
.min_size(ui.spacing().interact_size)
|
|
||||||
.id(id)
|
.id(id)
|
||||||
.desired_width(ui.spacing().interact_size.x)
|
.desired_width(button_width)
|
||||||
.font(text_style),
|
.font(text_style),
|
||||||
);
|
);
|
||||||
// Only update the value when the user presses enter, or clicks elsewhere. NOT every frame.
|
|
||||||
// See https://github.com/emilk/egui/issues/2687
|
|
||||||
if response.lost_focus() {
|
|
||||||
let parsed_value = match custom_parser {
|
let parsed_value = match custom_parser {
|
||||||
Some(parser) => parser(&value_text),
|
Some(parser) => parser(&value_text),
|
||||||
None => value_text.parse().ok(),
|
None => value_text.parse().ok(),
|
||||||
|
@ -486,7 +479,6 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
let parsed_value = clamp_to_range(parsed_value, clamp_range.clone());
|
let parsed_value = clamp_to_range(parsed_value, clamp_range.clone());
|
||||||
set(&mut get_set_value, parsed_value);
|
set(&mut get_set_value, parsed_value);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
ui.memory_mut(|mem| mem.drag_value.edit_string = Some(value_text));
|
ui.memory_mut(|mem| mem.drag_value.edit_string = Some(value_text));
|
||||||
response
|
response
|
||||||
} else {
|
} else {
|
||||||
|
@ -565,28 +557,28 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
response.widget_info(|| WidgetInfo::drag_value(value));
|
response.widget_info(|| WidgetInfo::drag_value(value));
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
ui.ctx().accesskit_node_builder(response.id, |builder| {
|
ui.ctx().accesskit_node(response.id, |node| {
|
||||||
use accesskit::Action;
|
use accesskit::Action;
|
||||||
// If either end of the range is unbounded, it's better
|
// If either end of the range is unbounded, it's better
|
||||||
// to leave the corresponding AccessKit field set to None,
|
// to leave the corresponding AccessKit field set to None,
|
||||||
// to allow for platform-specific default behavior.
|
// to allow for platform-specific default behavior.
|
||||||
if clamp_range.start().is_finite() {
|
if clamp_range.start().is_finite() {
|
||||||
builder.set_min_numeric_value(*clamp_range.start());
|
node.min_numeric_value = Some(*clamp_range.start());
|
||||||
}
|
}
|
||||||
if clamp_range.end().is_finite() {
|
if clamp_range.end().is_finite() {
|
||||||
builder.set_max_numeric_value(*clamp_range.end());
|
node.max_numeric_value = Some(*clamp_range.end());
|
||||||
}
|
}
|
||||||
builder.set_numeric_value_step(speed);
|
node.numeric_value_step = Some(speed);
|
||||||
builder.add_action(Action::SetValue);
|
node.actions |= Action::SetValue;
|
||||||
if value < *clamp_range.end() {
|
if value < *clamp_range.end() {
|
||||||
builder.add_action(Action::Increment);
|
node.actions |= Action::Increment;
|
||||||
}
|
}
|
||||||
if value > *clamp_range.start() {
|
if value > *clamp_range.start() {
|
||||||
builder.add_action(Action::Decrement);
|
node.actions |= Action::Decrement;
|
||||||
}
|
}
|
||||||
// The name field is set to the current value by the button,
|
// The name field is set to the current value by the button,
|
||||||
// but we don't want it set that way on this widget type.
|
// but we don't want it set that way on this widget type.
|
||||||
builder.clear_name();
|
node.name = None;
|
||||||
// Always expose the value as a string. This makes the widget
|
// Always expose the value as a string. This makes the widget
|
||||||
// more stable to accessibility users as it switches
|
// more stable to accessibility users as it switches
|
||||||
// between edit and button modes. This is particularly important
|
// between edit and button modes. This is particularly important
|
||||||
|
@ -607,7 +599,7 @@ impl<'a> Widget for DragValue<'a> {
|
||||||
// when in edit mode.
|
// when in edit mode.
|
||||||
if !is_kb_editing {
|
if !is_kb_editing {
|
||||||
let value_text = format!("{}{}{}", prefix, value_text, suffix);
|
let value_text = format!("{}{}{}", prefix, value_text, suffix);
|
||||||
builder.set_value(value_text);
|
node.value = Some(value_text.into());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -742,7 +742,7 @@ impl Plot {
|
||||||
});
|
});
|
||||||
|
|
||||||
let PlotMemory {
|
let PlotMemory {
|
||||||
bounds_modified,
|
mut bounds_modified,
|
||||||
mut hovered_entry,
|
mut hovered_entry,
|
||||||
mut hidden_items,
|
mut hidden_items,
|
||||||
last_screen_transform,
|
last_screen_transform,
|
||||||
|
@ -754,7 +754,6 @@ impl Plot {
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
next_auto_color_idx: 0,
|
next_auto_color_idx: 0,
|
||||||
last_screen_transform,
|
last_screen_transform,
|
||||||
bounds_modified,
|
|
||||||
response,
|
response,
|
||||||
ctx: ui.ctx().clone(),
|
ctx: ui.ctx().clone(),
|
||||||
};
|
};
|
||||||
|
@ -763,7 +762,6 @@ impl Plot {
|
||||||
mut items,
|
mut items,
|
||||||
mut response,
|
mut response,
|
||||||
last_screen_transform,
|
last_screen_transform,
|
||||||
mut bounds_modified,
|
|
||||||
..
|
..
|
||||||
} = plot_ui;
|
} = plot_ui;
|
||||||
|
|
||||||
|
@ -1044,7 +1042,6 @@ pub struct PlotUi {
|
||||||
items: Vec<Box<dyn PlotItem>>,
|
items: Vec<Box<dyn PlotItem>>,
|
||||||
next_auto_color_idx: usize,
|
next_auto_color_idx: usize,
|
||||||
last_screen_transform: ScreenTransform,
|
last_screen_transform: ScreenTransform,
|
||||||
bounds_modified: AxisBools,
|
|
||||||
response: Response,
|
response: Response,
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
}
|
}
|
||||||
|
@ -1072,13 +1069,11 @@ impl PlotUi {
|
||||||
/// Set the plot bounds. Can be useful for implementing alternative plot navigation methods.
|
/// Set the plot bounds. Can be useful for implementing alternative plot navigation methods.
|
||||||
pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) {
|
pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) {
|
||||||
self.last_screen_transform.set_bounds(plot_bounds);
|
self.last_screen_transform.set_bounds(plot_bounds);
|
||||||
self.bounds_modified = true.into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the plot bounds. Can be useful for implementing alternative plot navigation methods.
|
/// Move the plot bounds. Can be useful for implementing alternative plot navigation methods.
|
||||||
pub fn translate_bounds(&mut self, delta_pos: Vec2) {
|
pub fn translate_bounds(&mut self, delta_pos: Vec2) {
|
||||||
self.last_screen_transform.translate_bounds(delta_pos);
|
self.last_screen_transform.translate_bounds(delta_pos);
|
||||||
self.bounds_modified = true.into();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the plot area is currently hovered.
|
/// Returns `true` if the plot area is currently hovered.
|
||||||
|
|
|
@ -84,7 +84,6 @@ pub struct Slider<'a> {
|
||||||
max_decimals: Option<usize>,
|
max_decimals: Option<usize>,
|
||||||
custom_formatter: Option<NumFormatter<'a>>,
|
custom_formatter: Option<NumFormatter<'a>>,
|
||||||
custom_parser: Option<NumParser<'a>>,
|
custom_parser: Option<NumParser<'a>>,
|
||||||
trailing_fill: Option<bool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Slider<'a> {
|
impl<'a> Slider<'a> {
|
||||||
|
@ -130,7 +129,6 @@ impl<'a> Slider<'a> {
|
||||||
max_decimals: None,
|
max_decimals: None,
|
||||||
custom_formatter: None,
|
custom_formatter: None,
|
||||||
custom_parser: None,
|
custom_parser: None,
|
||||||
trailing_fill: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,17 +269,6 @@ impl<'a> Slider<'a> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display trailing color behind the slider's circle. Default is OFF.
|
|
||||||
///
|
|
||||||
/// This setting can be enabled globally for all sliders with [`Visuals::slider_trailing_fill`].
|
|
||||||
/// Toggling it here will override the above setting ONLY for this individual slider.
|
|
||||||
///
|
|
||||||
/// The fill color will be taken from `selection.bg_fill` in your [`Visuals`], the same as a [`ProgressBar`].
|
|
||||||
pub fn trailing_fill(mut self, trailing_fill: bool) -> Self {
|
|
||||||
self.trailing_fill = Some(trailing_fill);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set custom formatter defining how numbers are converted into text.
|
/// Set custom formatter defining how numbers are converted into text.
|
||||||
///
|
///
|
||||||
/// A custom formatter takes a `f64` for the numeric value and a `RangeInclusive<usize>` representing
|
/// A custom formatter takes a `f64` for the numeric value and a `RangeInclusive<usize>` representing
|
||||||
|
@ -629,40 +616,18 @@ impl<'a> Slider<'a> {
|
||||||
let rail_radius = ui.painter().round_to_pixel(self.rail_radius_limit(rect));
|
let rail_radius = ui.painter().round_to_pixel(self.rail_radius_limit(rect));
|
||||||
let rail_rect = self.rail_rect(rect, rail_radius);
|
let rail_rect = self.rail_rect(rect, rail_radius);
|
||||||
|
|
||||||
let visuals = ui.style().interact(response);
|
|
||||||
let widget_visuals = &ui.visuals().widgets;
|
|
||||||
|
|
||||||
ui.painter().rect_filled(
|
|
||||||
rail_rect,
|
|
||||||
widget_visuals.inactive.rounding,
|
|
||||||
widget_visuals.inactive.bg_fill,
|
|
||||||
);
|
|
||||||
|
|
||||||
let position_1d = self.position_from_value(value, position_range);
|
let position_1d = self.position_from_value(value, position_range);
|
||||||
|
|
||||||
|
let visuals = ui.style().interact(response);
|
||||||
|
ui.painter().add(epaint::RectShape {
|
||||||
|
rect: rail_rect,
|
||||||
|
rounding: ui.visuals().widgets.inactive.rounding,
|
||||||
|
fill: ui.visuals().widgets.inactive.bg_fill,
|
||||||
|
stroke: Default::default(),
|
||||||
|
});
|
||||||
|
|
||||||
let center = self.marker_center(position_1d, &rail_rect);
|
let center = self.marker_center(position_1d, &rail_rect);
|
||||||
|
|
||||||
// Decide if we should add trailing fill.
|
|
||||||
let trailing_fill = self
|
|
||||||
.trailing_fill
|
|
||||||
.unwrap_or_else(|| ui.visuals().slider_trailing_fill);
|
|
||||||
|
|
||||||
// Paint trailing fill.
|
|
||||||
if trailing_fill {
|
|
||||||
let mut trailing_rail_rect = rail_rect;
|
|
||||||
|
|
||||||
// The trailing rect has to be drawn differently depending on the orientation.
|
|
||||||
match self.orientation {
|
|
||||||
SliderOrientation::Vertical => trailing_rail_rect.min.y = center.y,
|
|
||||||
SliderOrientation::Horizontal => trailing_rail_rect.max.x = center.x,
|
|
||||||
};
|
|
||||||
|
|
||||||
ui.painter().rect_filled(
|
|
||||||
trailing_rail_rect,
|
|
||||||
widget_visuals.inactive.rounding,
|
|
||||||
ui.visuals().selection.bg_fill,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.painter().add(epaint::CircleShape {
|
ui.painter().add(epaint::CircleShape {
|
||||||
center,
|
center,
|
||||||
radius: self.handle_radius(rect) + visuals.expansion,
|
radius: self.handle_radius(rect) + visuals.expansion,
|
||||||
|
@ -792,20 +757,18 @@ impl<'a> Slider<'a> {
|
||||||
response.widget_info(|| WidgetInfo::slider(value, self.text.text()));
|
response.widget_info(|| WidgetInfo::slider(value, self.text.text()));
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
ui.ctx().accesskit_node_builder(response.id, |builder| {
|
ui.ctx().accesskit_node(response.id, |node| {
|
||||||
use accesskit::Action;
|
use accesskit::Action;
|
||||||
builder.set_min_numeric_value(*self.range.start());
|
node.min_numeric_value = Some(*self.range.start());
|
||||||
builder.set_max_numeric_value(*self.range.end());
|
node.max_numeric_value = Some(*self.range.end());
|
||||||
if let Some(step) = self.step {
|
node.numeric_value_step = self.step;
|
||||||
builder.set_numeric_value_step(step);
|
node.actions |= Action::SetValue;
|
||||||
}
|
|
||||||
builder.add_action(Action::SetValue);
|
|
||||||
let clamp_range = self.clamp_range();
|
let clamp_range = self.clamp_range();
|
||||||
if value < *clamp_range.end() {
|
if value < *clamp_range.end() {
|
||||||
builder.add_action(Action::Increment);
|
node.actions |= Action::Increment;
|
||||||
}
|
}
|
||||||
if value > *clamp_range.start() {
|
if value > *clamp_range.start() {
|
||||||
builder.add_action(Action::Decrement);
|
node.actions |= Action::Decrement;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -67,9 +67,6 @@ pub struct TextEdit<'t> {
|
||||||
desired_height_rows: usize,
|
desired_height_rows: usize,
|
||||||
lock_focus: bool,
|
lock_focus: bool,
|
||||||
cursor_at_end: bool,
|
cursor_at_end: bool,
|
||||||
min_size: Vec2,
|
|
||||||
align: Align2,
|
|
||||||
clip_text: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> WidgetWithState for TextEdit<'t> {
|
impl<'t> WidgetWithState for TextEdit<'t> {
|
||||||
|
@ -92,7 +89,6 @@ impl<'t> TextEdit<'t> {
|
||||||
Self {
|
Self {
|
||||||
desired_height_rows: 1,
|
desired_height_rows: 1,
|
||||||
multiline: false,
|
multiline: false,
|
||||||
clip_text: true,
|
|
||||||
..Self::multiline(text)
|
..Self::multiline(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,9 +112,6 @@ impl<'t> TextEdit<'t> {
|
||||||
desired_height_rows: 4,
|
desired_height_rows: 4,
|
||||||
lock_focus: false,
|
lock_focus: false,
|
||||||
cursor_at_end: true,
|
cursor_at_end: true,
|
||||||
min_size: Vec2::ZERO,
|
|
||||||
align: Align2::LEFT_TOP,
|
|
||||||
clip_text: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,37 +269,6 @@ impl<'t> TextEdit<'t> {
|
||||||
self.cursor_at_end = b;
|
self.cursor_at_end = b;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When `true` (default), overflowing text will be clipped.
|
|
||||||
///
|
|
||||||
/// When `false`, widget width will expand to make all text visible.
|
|
||||||
///
|
|
||||||
/// This only works for singleline [`TextEdit`].
|
|
||||||
pub fn clip_text(mut self, b: bool) -> Self {
|
|
||||||
// always show everything in multiline
|
|
||||||
if !self.multiline {
|
|
||||||
self.clip_text = b;
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the horizontal align of the inner text.
|
|
||||||
pub fn horizontal_align(mut self, align: Align) -> Self {
|
|
||||||
self.align.0[0] = align;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the vertical align of the inner text.
|
|
||||||
pub fn vertical_align(mut self, align: Align) -> Self {
|
|
||||||
self.align.0[1] = align;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the minimum size of the [`TextEdit`].
|
|
||||||
pub fn min_size(mut self, min_size: Vec2) -> Self {
|
|
||||||
self.min_size = min_size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -402,16 +364,13 @@ impl<'t> TextEdit<'t> {
|
||||||
layouter,
|
layouter,
|
||||||
password,
|
password,
|
||||||
frame: _,
|
frame: _,
|
||||||
margin,
|
margin: _,
|
||||||
multiline,
|
multiline,
|
||||||
interactive,
|
interactive,
|
||||||
desired_width,
|
desired_width,
|
||||||
desired_height_rows,
|
desired_height_rows,
|
||||||
lock_focus,
|
lock_focus,
|
||||||
cursor_at_end,
|
cursor_at_end,
|
||||||
min_size,
|
|
||||||
align,
|
|
||||||
clip_text,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let text_color = text_color
|
let text_color = text_color
|
||||||
|
@ -430,7 +389,7 @@ impl<'t> TextEdit<'t> {
|
||||||
available_width
|
available_width
|
||||||
} else {
|
} else {
|
||||||
desired_width.min(available_width)
|
desired_width.min(available_width)
|
||||||
} - margin.x * 2.0;
|
};
|
||||||
|
|
||||||
let font_id_clone = font_id.clone();
|
let font_id_clone = font_id.clone();
|
||||||
let mut default_layouter = move |ui: &Ui, text: &str, wrap_width: f32| {
|
let mut default_layouter = move |ui: &Ui, text: &str, wrap_width: f32| {
|
||||||
|
@ -447,14 +406,13 @@ impl<'t> TextEdit<'t> {
|
||||||
|
|
||||||
let mut galley = layouter(ui, text.as_str(), wrap_width);
|
let mut galley = layouter(ui, text.as_str(), wrap_width);
|
||||||
|
|
||||||
let desired_width = if clip_text {
|
let desired_width = if multiline {
|
||||||
wrap_width // visual clipping with scroll in singleline input.
|
galley.size().x.max(wrap_width) // always show everything in multiline
|
||||||
} else {
|
} else {
|
||||||
galley.size().x.max(wrap_width)
|
wrap_width // visual clipping with scroll in singleline input. TODO(emilk): opt-in/out?
|
||||||
};
|
};
|
||||||
let desired_height = (desired_height_rows.at_least(1) as f32) * row_height;
|
let desired_height = (desired_height_rows.at_least(1) as f32) * row_height;
|
||||||
let desired_size = vec2(desired_width, galley.size().y.max(desired_height))
|
let desired_size = vec2(desired_width, galley.size().y.max(desired_height));
|
||||||
.at_least(min_size - margin * 2.0);
|
|
||||||
|
|
||||||
let (auto_id, rect) = ui.allocate_space(desired_size);
|
let (auto_id, rect) = ui.allocate_space(desired_size);
|
||||||
|
|
||||||
|
@ -589,14 +547,10 @@ impl<'t> TextEdit<'t> {
|
||||||
cursor_range = Some(new_cursor_range);
|
cursor_range = Some(new_cursor_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut text_draw_pos = align
|
let mut text_draw_pos = response.rect.min;
|
||||||
.align_size_within_rect(galley.size(), response.rect)
|
|
||||||
.intersect(response.rect) // limit pos to the response rect area
|
|
||||||
.min;
|
|
||||||
let align_offset = response.rect.left() - text_draw_pos.x;
|
|
||||||
|
|
||||||
// Visual clipping for singleline text editor with text larger than width
|
// Visual clipping for singleline text editor with text larger than width
|
||||||
if clip_text && align_offset == 0.0 {
|
if !multiline {
|
||||||
let cursor_pos = match (cursor_range, ui.memory(|mem| mem.has_focus(id))) {
|
let cursor_pos = match (cursor_range, ui.memory(|mem| mem.has_focus(id))) {
|
||||||
(Some(cursor_range), true) => galley.pos_from_cursor(&cursor_range.primary).min.x,
|
(Some(cursor_range), true) => galley.pos_from_cursor(&cursor_range.primary).min.x,
|
||||||
_ => 0.0,
|
_ => 0.0,
|
||||||
|
@ -619,8 +573,6 @@ impl<'t> TextEdit<'t> {
|
||||||
|
|
||||||
state.singleline_offset = offset_x;
|
state.singleline_offset = offset_x;
|
||||||
text_draw_pos -= vec2(offset_x, 0.0);
|
text_draw_pos -= vec2(offset_x, 0.0);
|
||||||
} else {
|
|
||||||
state.singleline_offset = align_offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let selection_changed = if let (Some(cursor_range), Some(prev_cursor_range)) =
|
let selection_changed = if let (Some(cursor_range), Some(prev_cursor_range)) =
|
||||||
|
@ -714,7 +666,7 @@ impl<'t> TextEdit<'t> {
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
{
|
{
|
||||||
let parent_id = ui.ctx().accesskit_node_builder(response.id, |builder| {
|
let parent_id = ui.ctx().accesskit_node(response.id, |node| {
|
||||||
use accesskit::{TextPosition, TextSelection};
|
use accesskit::{TextPosition, TextSelection};
|
||||||
|
|
||||||
let parent_id = response.id;
|
let parent_id = response.id;
|
||||||
|
@ -722,7 +674,7 @@ impl<'t> TextEdit<'t> {
|
||||||
if let Some(cursor_range) = &cursor_range {
|
if let Some(cursor_range) = &cursor_range {
|
||||||
let anchor = &cursor_range.secondary.rcursor;
|
let anchor = &cursor_range.secondary.rcursor;
|
||||||
let focus = &cursor_range.primary.rcursor;
|
let focus = &cursor_range.primary.rcursor;
|
||||||
builder.set_text_selection(TextSelection {
|
node.text_selection = Some(TextSelection {
|
||||||
anchor: TextPosition {
|
anchor: TextPosition {
|
||||||
node: parent_id.with(anchor.row).accesskit_id(),
|
node: parent_id.with(anchor.row).accesskit_id(),
|
||||||
character_index: anchor.column,
|
character_index: anchor.column,
|
||||||
|
@ -734,10 +686,8 @@ impl<'t> TextEdit<'t> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.set_default_action_verb(accesskit::DefaultActionVerb::Focus);
|
node.default_action_verb = Some(accesskit::DefaultActionVerb::Focus);
|
||||||
if self.multiline {
|
node.multiline = self.multiline;
|
||||||
builder.set_multiline();
|
|
||||||
}
|
|
||||||
|
|
||||||
parent_id
|
parent_id
|
||||||
});
|
});
|
||||||
|
@ -749,16 +699,16 @@ impl<'t> TextEdit<'t> {
|
||||||
ui.ctx().with_accessibility_parent(parent_id, || {
|
ui.ctx().with_accessibility_parent(parent_id, || {
|
||||||
for (i, row) in galley.rows.iter().enumerate() {
|
for (i, row) in galley.rows.iter().enumerate() {
|
||||||
let id = parent_id.with(i);
|
let id = parent_id.with(i);
|
||||||
ui.ctx().accesskit_node_builder(id, |builder| {
|
ui.ctx().accesskit_node(id, |node| {
|
||||||
builder.set_role(Role::InlineTextBox);
|
node.role = Role::InlineTextBox;
|
||||||
let rect = row.rect.translate(text_draw_pos.to_vec2());
|
let rect = row.rect.translate(text_draw_pos.to_vec2());
|
||||||
builder.set_bounds(accesskit::Rect {
|
node.bounds = Some(accesskit::kurbo::Rect {
|
||||||
x0: rect.min.x.into(),
|
x0: rect.min.x.into(),
|
||||||
y0: rect.min.y.into(),
|
y0: rect.min.y.into(),
|
||||||
x1: rect.max.x.into(),
|
x1: rect.max.x.into(),
|
||||||
y1: rect.max.y.into(),
|
y1: rect.max.y.into(),
|
||||||
});
|
});
|
||||||
builder.set_text_direction(TextDirection::LeftToRight);
|
node.text_direction = Some(TextDirection::LeftToRight);
|
||||||
// TODO(mwcampbell): Set more node fields for the row
|
// TODO(mwcampbell): Set more node fields for the row
|
||||||
// once AccessKit adapters expose text formatting info.
|
// once AccessKit adapters expose text formatting info.
|
||||||
|
|
||||||
|
@ -798,11 +748,11 @@ impl<'t> TextEdit<'t> {
|
||||||
}
|
}
|
||||||
word_lengths.push((character_lengths.len() - last_word_start) as _);
|
word_lengths.push((character_lengths.len() - last_word_start) as _);
|
||||||
|
|
||||||
builder.set_value(value);
|
node.value = Some(value.into());
|
||||||
builder.set_character_lengths(character_lengths);
|
node.character_lengths = character_lengths.into();
|
||||||
builder.set_character_positions(character_positions);
|
node.character_positions = Some(character_positions.into());
|
||||||
builder.set_character_widths(character_widths);
|
node.character_widths = Some(character_widths.into());
|
||||||
builder.set_word_lengths(word_lengths);
|
node.word_lengths = word_lengths.into();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "egui_demo_app"
|
name = "egui_demo_app"
|
||||||
version = "0.21.0"
|
version = "0.20.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 = "2021"
|
edition = "2021"
|
||||||
|
@ -30,11 +30,11 @@ wgpu = ["eframe/wgpu", "bytemuck"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4", features = ["js-sys", "wasmbind"] }
|
chrono = { version = "0.4", features = ["js-sys", "wasmbind"] }
|
||||||
eframe = { version = "0.21.0", path = "../eframe", default-features = false }
|
eframe = { version = "0.20.0", path = "../eframe", default-features = false }
|
||||||
egui = { version = "0.21.0", path = "../egui", features = [
|
egui = { version = "0.20.0", path = "../egui", features = [
|
||||||
"extra_debug_asserts",
|
"extra_debug_asserts",
|
||||||
] }
|
] }
|
||||||
egui_demo_lib = { version = "0.21.0", path = "../egui_demo_lib", features = [
|
egui_demo_lib = { version = "0.20.0", path = "../egui_demo_lib", features = [
|
||||||
"chrono",
|
"chrono",
|
||||||
] }
|
] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
@ -42,7 +42,7 @@ tracing = "0.1"
|
||||||
# Optional dependencies:
|
# Optional dependencies:
|
||||||
|
|
||||||
bytemuck = { version = "1.7.1", optional = true }
|
bytemuck = { version = "1.7.1", optional = true }
|
||||||
egui_extras = { version = "0.21.0", optional = true, path = "../egui_extras" }
|
egui_extras = { version = "0.20.0", optional = true, path = "../egui_extras" }
|
||||||
|
|
||||||
# feature "http":
|
# feature "http":
|
||||||
ehttp = { version = "0.2.0", optional = true }
|
ehttp = { version = "0.2.0", optional = true }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "egui_demo_lib"
|
name = "egui_demo_lib"
|
||||||
version = "0.21.0"
|
version = "0.20.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 = "2021"
|
edition = "2021"
|
||||||
|
@ -30,8 +30,8 @@ syntax_highlighting = ["syntect"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.21.0", path = "../egui", default-features = false }
|
egui = { version = "0.20.0", path = "../egui", default-features = false }
|
||||||
egui_extras = { version = "0.21.0", path = "../egui_extras" }
|
egui_extras = { version = "0.20.0", path = "../egui_extras" }
|
||||||
enum-map = { version = "2", features = ["serde"] }
|
enum-map = { version = "2", features = ["serde"] }
|
||||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
unicode_names2 = { version = "0.6.0", default-features = false }
|
unicode_names2 = { version = "0.6.0", default-features = false }
|
||||||
|
|
|
@ -16,7 +16,6 @@ pub struct Sliders {
|
||||||
pub integer: bool,
|
pub integer: bool,
|
||||||
pub vertical: bool,
|
pub vertical: bool,
|
||||||
pub value: f64,
|
pub value: f64,
|
||||||
pub trailing_fill: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Sliders {
|
impl Default for Sliders {
|
||||||
|
@ -32,7 +31,6 @@ impl Default for Sliders {
|
||||||
integer: false,
|
integer: false,
|
||||||
vertical: false,
|
vertical: false,
|
||||||
value: 10.0,
|
value: 10.0,
|
||||||
trailing_fill: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +64,6 @@ impl super::View for Sliders {
|
||||||
integer,
|
integer,
|
||||||
vertical,
|
vertical,
|
||||||
value,
|
value,
|
||||||
trailing_fill,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
ui.label("You can click a slider value to edit it with the keyboard.");
|
ui.label("You can click a slider value to edit it with the keyboard.");
|
||||||
|
@ -98,8 +95,7 @@ impl super::View for Sliders {
|
||||||
.smart_aim(*smart_aim)
|
.smart_aim(*smart_aim)
|
||||||
.orientation(orientation)
|
.orientation(orientation)
|
||||||
.text("i32 demo slider")
|
.text("i32 demo slider")
|
||||||
.step_by(istep)
|
.step_by(istep),
|
||||||
.trailing_fill(*trailing_fill),
|
|
||||||
);
|
);
|
||||||
*value = value_i32 as f64;
|
*value = value_i32 as f64;
|
||||||
} else {
|
} else {
|
||||||
|
@ -110,8 +106,7 @@ impl super::View for Sliders {
|
||||||
.smart_aim(*smart_aim)
|
.smart_aim(*smart_aim)
|
||||||
.orientation(orientation)
|
.orientation(orientation)
|
||||||
.text("f64 demo slider")
|
.text("f64 demo slider")
|
||||||
.step_by(istep)
|
.step_by(istep),
|
||||||
.trailing_fill(*trailing_fill),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
ui.label(
|
ui.label(
|
||||||
|
@ -131,24 +126,17 @@ impl super::View for Sliders {
|
||||||
Slider::new(min, type_min..=type_max)
|
Slider::new(min, type_min..=type_max)
|
||||||
.logarithmic(true)
|
.logarithmic(true)
|
||||||
.smart_aim(*smart_aim)
|
.smart_aim(*smart_aim)
|
||||||
.text("left")
|
.text("left"),
|
||||||
.trailing_fill(*trailing_fill),
|
|
||||||
);
|
);
|
||||||
ui.add(
|
ui.add(
|
||||||
Slider::new(max, type_min..=type_max)
|
Slider::new(max, type_min..=type_max)
|
||||||
.logarithmic(true)
|
.logarithmic(true)
|
||||||
.smart_aim(*smart_aim)
|
.smart_aim(*smart_aim)
|
||||||
.text("right")
|
.text("right"),
|
||||||
.trailing_fill(*trailing_fill),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.checkbox(trailing_fill, "Toggle trailing color");
|
|
||||||
ui.label("When enabled, trailing color will be painted up until the circle.");
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
|
|
||||||
ui.checkbox(use_steps, "Use steps");
|
ui.checkbox(use_steps, "Use steps");
|
||||||
ui.label("When enabled, the minimal value change would be restricted to a given step.");
|
ui.label("When enabled, the minimal value change would be restricted to a given step.");
|
||||||
if *use_steps {
|
if *use_steps {
|
||||||
|
|
|
@ -5,10 +5,6 @@ All notable changes to the `egui_extras` integration will be noted in this file.
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
|
||||||
## 0.21.0 - 2023-02-08
|
|
||||||
* Update to egui 0.21
|
|
||||||
|
|
||||||
|
|
||||||
## 0.20.0 - 2022-12-08
|
## 0.20.0 - 2022-12-08
|
||||||
* Added `RetainedImage::from_svg_bytes_with_size` to be able to specify a size for SVGs to be rasterized at.
|
* Added `RetainedImage::from_svg_bytes_with_size` to be able to specify a size for SVGs to be rasterized at.
|
||||||
* Lots of `Table` improvements ([#2369](https://github.com/emilk/egui/pull/2369)):
|
* Lots of `Table` improvements ([#2369](https://github.com/emilk/egui/pull/2369)):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "egui_extras"
|
name = "egui_extras"
|
||||||
version = "0.21.0"
|
version = "0.20.0"
|
||||||
authors = [
|
authors = [
|
||||||
"Dominik Rössler <dominik@freshx.de>",
|
"Dominik Rössler <dominik@freshx.de>",
|
||||||
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
|
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
|
||||||
|
@ -37,7 +37,7 @@ tracing = ["dep:tracing", "egui/tracing"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.21.0", path = "../egui", default-features = false }
|
egui = { version = "0.20.0", path = "../egui", default-features = false }
|
||||||
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
[](https://crates.io/crates/egui_extras)
|
[](https://crates.io/crates/egui_extras)
|
||||||
[](https://docs.rs/egui_extras)
|
[](https://docs.rs/egui_extras)
|
||||||
[](https://github.com/rust-secure-code/safety-dance/)
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ All notable changes to the `egui_glium` integration will be noted in this file.
|
||||||
|
|
||||||
|
|
||||||
## 0.20.1 - 2022-12-11
|
## 0.20.1 - 2022-12-11
|
||||||
* Fix [docs.rs](https://docs.rs/egui_glium) build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||||
|
|
||||||
|
|
||||||
## 0.20.0 - 2022-12-08
|
## 0.20.0 - 2022-12-08
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "egui_glium"
|
name = "egui_glium"
|
||||||
version = "0.21.0"
|
version = "0.20.1"
|
||||||
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 = "2021"
|
edition = "2021"
|
||||||
|
@ -36,10 +36,10 @@ links = ["egui-winit/links"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
] }
|
] }
|
||||||
egui-winit = { version = "0.21.1", path = "../egui-winit", default-features = false }
|
egui-winit = { version = "0.20.0", path = "../egui-winit", default-features = false }
|
||||||
|
|
||||||
ahash = { version = "0.8.1", default-features = false, features = [
|
ahash = { version = "0.8.1", default-features = false, features = [
|
||||||
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
|
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
|
||||||
|
@ -54,5 +54,5 @@ document-features = { version = "0.2", optional = true }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
egui_demo_lib = { version = "0.21.0", path = "../egui_demo_lib", default-features = false }
|
egui_demo_lib = { version = "0.20.0", path = "../egui_demo_lib", default-features = false }
|
||||||
image = { version = "0.24", default-features = false, features = ["png"] }
|
image = { version = "0.24", default-features = false, features = ["png"] }
|
||||||
|
|
|
@ -11,7 +11,7 @@ This crates provides bindings between [`egui`](https://github.com/emilk/egui) an
|
||||||
To use on Linux, first run:
|
To use on Linux, first run:
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
|
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
This crate depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit).
|
This crate depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit).
|
||||||
|
|
|
@ -3,15 +3,11 @@ All notable changes to the `egui_glow` integration will be noted in this file.
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
|
||||||
## 0.21.0 - 2023-02-08
|
|
||||||
* Update to `glow` 0.12 ([#2695](https://github.com/emilk/egui/pull/2695)).
|
|
||||||
* Remove the `screen_reader` feature ([#2669](https://github.com/emilk/egui/pull/2669)).
|
* Remove the `screen_reader` feature ([#2669](https://github.com/emilk/egui/pull/2669)).
|
||||||
|
|
||||||
|
|
||||||
## 0.20.1 - 2022-12-11
|
## 0.20.1 - 2022-12-11
|
||||||
* Fix [docs.rs](https://docs.rs/egui_glow) build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||||
|
|
||||||
|
|
||||||
## 0.20.0 - 2022-12-08
|
## 0.20.0 - 2022-12-08
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "egui_glow"
|
name = "egui_glow"
|
||||||
version = "0.21.0"
|
version = "0.20.1"
|
||||||
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 = "2021"
|
edition = "2021"
|
||||||
|
@ -44,12 +44,12 @@ winit = ["egui-winit"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
|
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
bytemuck = "1.7"
|
bytemuck = "1.7"
|
||||||
glow = "0.12"
|
glow = "0.11"
|
||||||
memoffset = "0.6"
|
memoffset = "0.6"
|
||||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ document-features = { version = "0.2", optional = true }
|
||||||
|
|
||||||
# Native:
|
# Native:
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
egui-winit = { version = "0.21.1", path = "../egui-winit", optional = true, default-features = false }
|
egui-winit = { version = "0.20.0", path = "../egui-winit", optional = true, default-features = false }
|
||||||
puffin = { version = "0.14", optional = true }
|
puffin = { version = "0.14", optional = true }
|
||||||
|
|
||||||
# Web:
|
# Web:
|
||||||
|
@ -69,9 +69,8 @@ wasm-bindgen = { version = "0.2" }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
glutin = "0.30" # examples/pure_glow
|
glutin = "0.30.2" # examples/pure_glow
|
||||||
raw-window-handle = "0.5.0"
|
raw-window-handle = "0.5.0"
|
||||||
glutin-winit = "0.3.0"
|
|
||||||
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
|
|
|
@ -14,7 +14,7 @@ To write web apps using `glow` you can use [`eframe`](https://github.com/emilk/e
|
||||||
To use on Linux, first run:
|
To use on Linux, first run:
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
|
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
This crate optionally depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit).
|
This crate optionally depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit).
|
||||||
|
|
|
@ -17,91 +17,66 @@ impl GlutinWindowContext {
|
||||||
// refactor this function to use `glutin-winit` crate eventually.
|
// refactor this function to use `glutin-winit` crate eventually.
|
||||||
// preferably add android support at the same time.
|
// preferably add android support at the same time.
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
unsafe fn new(event_loop: &winit::event_loop::EventLoopWindowTarget<()>) -> Self {
|
unsafe fn new(winit_window: winit::window::Window) -> Self {
|
||||||
use egui::NumExt;
|
use glutin::prelude::*;
|
||||||
use glutin::context::NotCurrentGlContextSurfaceAccessor;
|
use raw_window_handle::*;
|
||||||
use glutin::display::GetGlDisplay;
|
|
||||||
use glutin::display::GlDisplay;
|
|
||||||
use glutin::prelude::GlSurface;
|
|
||||||
use raw_window_handle::HasRawWindowHandle;
|
|
||||||
let winit_window_builder = winit::window::WindowBuilder::new()
|
|
||||||
.with_resizable(true)
|
|
||||||
.with_inner_size(winit::dpi::LogicalSize {
|
|
||||||
width: 800.0,
|
|
||||||
height: 600.0,
|
|
||||||
})
|
|
||||||
.with_title("egui_glow example") // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
|
||||||
.with_visible(false);
|
|
||||||
|
|
||||||
let config_template_builder = glutin::config::ConfigTemplateBuilder::new()
|
let raw_display_handle = winit_window.raw_display_handle();
|
||||||
|
let raw_window_handle = winit_window.raw_window_handle();
|
||||||
|
|
||||||
|
// EGL is crossplatform and the official khronos way
|
||||||
|
// but sometimes platforms/drivers may not have it, so we use back up options where possible.
|
||||||
|
|
||||||
|
// try egl and fallback to windows wgl. Windows is the only platform that *requires* window handle to create display.
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let preference = glutin::display::DisplayApiPreference::EglThenWgl(Some(raw_window_handle));
|
||||||
|
// try egl and fallback to x11 glx
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let preference = glutin::display::DisplayApiPreference::EglThenGlx(Box::new(
|
||||||
|
winit::platform::x11::register_xlib_error_hook,
|
||||||
|
));
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let preference = glutin::display::DisplayApiPreference::Cgl;
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
let preference = glutin::display::DisplayApiPreference::Egl;
|
||||||
|
|
||||||
|
let gl_display = glutin::display::Display::new(raw_display_handle, preference).unwrap();
|
||||||
|
|
||||||
|
let config_template = glutin::config::ConfigTemplateBuilder::new()
|
||||||
.prefer_hardware_accelerated(None)
|
.prefer_hardware_accelerated(None)
|
||||||
.with_depth_size(0)
|
.with_depth_size(0)
|
||||||
.with_stencil_size(0)
|
.with_stencil_size(0)
|
||||||
.with_transparency(false);
|
.with_transparency(false)
|
||||||
|
.compatible_with_native_window(raw_window_handle)
|
||||||
|
.build();
|
||||||
|
|
||||||
tracing::debug!("trying to get gl_config");
|
let config = gl_display
|
||||||
let (mut window, gl_config) =
|
.find_configs(config_template)
|
||||||
glutin_winit::DisplayBuilder::new() // let glutin-winit helper crate handle the complex parts of opengl context creation
|
.unwrap()
|
||||||
.with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
|
.next()
|
||||||
.with_window_builder(Some(winit_window_builder.clone()))
|
.unwrap();
|
||||||
.build(
|
|
||||||
event_loop,
|
|
||||||
config_template_builder,
|
|
||||||
|mut config_iterator| {
|
|
||||||
config_iterator.next().expect(
|
|
||||||
"failed to find a matching configuration for creating glutin config",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.expect("failed to create gl_config");
|
|
||||||
let gl_display = gl_config.display();
|
|
||||||
tracing::debug!("found gl_config: {:?}", &gl_config);
|
|
||||||
|
|
||||||
let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle());
|
|
||||||
tracing::debug!("raw window handle: {:?}", raw_window_handle);
|
|
||||||
let context_attributes =
|
let context_attributes =
|
||||||
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
|
glutin::context::ContextAttributesBuilder::new().build(Some(raw_window_handle));
|
||||||
// by default, glutin will try to create a core opengl context. but, if it is not available, try to create a gl-es context using this fallback attributes
|
// for surface creation.
|
||||||
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
|
let (width, height): (u32, u32) = winit_window.inner_size().into();
|
||||||
.with_context_api(glutin::context::ContextApi::Gles(None))
|
|
||||||
.build(raw_window_handle);
|
|
||||||
let not_current_gl_context = unsafe {
|
|
||||||
gl_display
|
|
||||||
.create_context(&gl_config, &context_attributes)
|
|
||||||
.unwrap_or_else(|_| {
|
|
||||||
tracing::debug!("failed to create gl_context with attributes: {:?}. retrying with fallback context attributes: {:?}",
|
|
||||||
&context_attributes,
|
|
||||||
&fallback_context_attributes);
|
|
||||||
gl_config
|
|
||||||
.display()
|
|
||||||
.create_context(&gl_config, &fallback_context_attributes)
|
|
||||||
.expect("failed to create context even with fallback attributes")
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// this is where the window is created, if it has not been created while searching for suitable gl_config
|
|
||||||
let window = window.take().unwrap_or_else(|| {
|
|
||||||
tracing::debug!("window doesn't exist yet. creating one now with finalize_window");
|
|
||||||
glutin_winit::finalize_window(event_loop, winit_window_builder.clone(), &gl_config)
|
|
||||||
.expect("failed to finalize glutin window")
|
|
||||||
});
|
|
||||||
let (width, height): (u32, u32) = window.inner_size().into();
|
|
||||||
let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap();
|
|
||||||
let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap();
|
|
||||||
let surface_attributes =
|
let surface_attributes =
|
||||||
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
|
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
|
||||||
.build(window.raw_window_handle(), width, height);
|
.build(
|
||||||
tracing::debug!(
|
raw_window_handle,
|
||||||
"creating surface with attributes: {:?}",
|
std::num::NonZeroU32::new(width).unwrap(),
|
||||||
&surface_attributes
|
std::num::NonZeroU32::new(height).unwrap(),
|
||||||
);
|
);
|
||||||
let gl_surface = unsafe {
|
// start creating the gl objects
|
||||||
gl_display
|
let gl_context = gl_display
|
||||||
.create_window_surface(&gl_config, &surface_attributes)
|
.create_context(&config, &context_attributes)
|
||||||
.unwrap()
|
.unwrap();
|
||||||
};
|
|
||||||
tracing::debug!("surface created successfully: {gl_surface:?}.making context current");
|
let gl_surface = gl_display
|
||||||
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
|
.create_window_surface(&config, &surface_attributes)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let gl_context = gl_context.make_current(&gl_surface).unwrap();
|
||||||
|
|
||||||
gl_surface
|
gl_surface
|
||||||
.set_swap_interval(
|
.set_swap_interval(
|
||||||
|
@ -111,7 +86,7 @@ impl GlutinWindowContext {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
GlutinWindowContext {
|
GlutinWindowContext {
|
||||||
window,
|
window: winit_window,
|
||||||
gl_context,
|
gl_context,
|
||||||
gl_display,
|
gl_display,
|
||||||
gl_surface,
|
gl_surface,
|
||||||
|
@ -241,7 +216,19 @@ fn main() {
|
||||||
fn create_display(
|
fn create_display(
|
||||||
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
|
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
|
||||||
) -> (GlutinWindowContext, glow::Context) {
|
) -> (GlutinWindowContext, glow::Context) {
|
||||||
let glutin_window_context = unsafe { GlutinWindowContext::new(event_loop) };
|
let winit_window = winit::window::WindowBuilder::new()
|
||||||
|
.with_resizable(true)
|
||||||
|
.with_inner_size(winit::dpi::LogicalSize {
|
||||||
|
width: 800.0,
|
||||||
|
height: 600.0,
|
||||||
|
})
|
||||||
|
.with_title("egui_glow example")
|
||||||
|
.with_visible(false) // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||||
|
.build(event_loop)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// a lot of the code below has been lifted from glutin example in their repo.
|
||||||
|
let glutin_window_context = unsafe { GlutinWindowContext::new(winit_window) };
|
||||||
let gl = unsafe {
|
let gl = unsafe {
|
||||||
glow::Context::from_loader_function(|s| {
|
glow::Context::from_loader_function(|s| {
|
||||||
let s = std::ffi::CString::new(s)
|
let s = std::ffi::CString::new(s)
|
||||||
|
|
|
@ -106,23 +106,6 @@ impl Painter {
|
||||||
crate::profile_function!();
|
crate::profile_function!();
|
||||||
crate::check_for_gl_error_even_in_release!(&gl, "before Painter::new");
|
crate::check_for_gl_error_even_in_release!(&gl, "before Painter::new");
|
||||||
|
|
||||||
// some useful debug info. all three of them are present in gl 1.1.
|
|
||||||
unsafe {
|
|
||||||
let version = gl.get_parameter_string(glow::VERSION);
|
|
||||||
let renderer = gl.get_parameter_string(glow::RENDERER);
|
|
||||||
let vendor = gl.get_parameter_string(glow::VENDOR);
|
|
||||||
tracing::debug!(
|
|
||||||
"\nopengl version: {version}\nopengl renderer: {renderer}\nopengl vendor: {vendor}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
if gl.version().major < 2 {
|
|
||||||
// this checks on desktop that we are not using opengl 1.1 microsoft sw rendering context.
|
|
||||||
// ShaderVersion::get fn will segfault due to SHADING_LANGUAGE_VERSION (added in gl2.0)
|
|
||||||
return Err("egui_glow requires opengl 2.0+. ".to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize;
|
let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize;
|
||||||
let shader_version = shader_version.unwrap_or_else(|| ShaderVersion::get(&gl));
|
let shader_version = shader_version.unwrap_or_else(|| ShaderVersion::get(&gl));
|
||||||
let is_webgl_1 = shader_version == ShaderVersion::Es100;
|
let is_webgl_1 = shader_version == ShaderVersion::Es100;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "emath"
|
name = "emath"
|
||||||
version = "0.21.0"
|
version = "0.20.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 = "2021"
|
edition = "2021"
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
# emath - egui math library
|
# emath - egui math library
|
||||||
|
|
||||||
[](https://crates.io/crates/emath)
|
|
||||||
[](https://docs.rs/emath)
|
|
||||||
[](https://github.com/rust-secure-code/safety-dance/)
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
A bare-bones 2D math library with types and functions useful for GUI building.
|
A bare-bones 2D math library with types and functions useful for GUI building.
|
||||||
|
|
||||||
Made for [`egui`](https://github.com/emilk/egui/).
|
Made for [`egui`](https://github.com/emilk/egui/).
|
||||||
|
|
|
@ -15,7 +15,6 @@ use crate::*;
|
||||||
pub struct Pos2 {
|
pub struct Pos2 {
|
||||||
/// How far to the right.
|
/// How far to the right.
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
|
|
||||||
/// How far down.
|
/// How far down.
|
||||||
pub y: f32,
|
pub y: f32,
|
||||||
// implicit w = 1
|
// implicit w = 1
|
||||||
|
|
|
@ -3,9 +3,6 @@ All notable changes to the epaint crate will be documented in this file.
|
||||||
|
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
|
||||||
## 0.21.0 - 2023-02-08
|
|
||||||
* Improve the look of thin white lines ([#2437](https://github.com/emilk/egui/pull/2437)).
|
* Improve the look of thin white lines ([#2437](https://github.com/emilk/egui/pull/2437)).
|
||||||
* Don't render `\r` (Carriage Return) ([#2452](https://github.com/emilk/egui/pull/2452)).
|
* Don't render `\r` (Carriage Return) ([#2452](https://github.com/emilk/egui/pull/2452)).
|
||||||
* Fix bug in `Mesh::split_to_u16` ([#2459](https://github.com/emilk/egui/pull/2459)).
|
* Fix bug in `Mesh::split_to_u16` ([#2459](https://github.com/emilk/egui/pull/2459)).
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "epaint"
|
name = "epaint"
|
||||||
version = "0.21.0"
|
version = "0.20.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 = "2021"
|
edition = "2021"
|
||||||
|
@ -63,12 +63,9 @@ mint = ["emath/mint"]
|
||||||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||||
serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/serde"]
|
serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/serde"]
|
||||||
|
|
||||||
## Change Vertex layout to be compatible with unity
|
|
||||||
unity = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
emath = { version = "0.21.0", path = "../emath" }
|
emath = { version = "0.20.0", path = "../emath" }
|
||||||
ecolor = { version = "0.21.0", path = "../ecolor" }
|
ecolor = { version = "0.20.0", path = "../ecolor" }
|
||||||
|
|
||||||
ab_glyph = "0.2.11"
|
ab_glyph = "0.2.11"
|
||||||
ahash = { version = "0.8.1", default-features = false, features = [
|
ahash = { version = "0.8.1", default-features = false, features = [
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
# epaint - egui paint library
|
# epaint - egui paint library
|
||||||
|
|
||||||
[](https://crates.io/crates/epaint)
|
|
||||||
[](https://docs.rs/epaint)
|
|
||||||
[](https://github.com/rust-secure-code/safety-dance/)
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
A bare-bones 2D graphics library for turning simple 2D shapes and text into textured triangles.
|
A bare-bones 2D graphics library for turning simple 2D shapes and text into textured triangles.
|
||||||
|
|
||||||
Made for [`egui`](https://github.com/emilk/egui/).
|
Made for [`egui`](https://github.com/emilk/egui/).
|
||||||
|
|
|
@ -6,7 +6,6 @@ use emath::*;
|
||||||
/// Should be friendly to send to GPU as is.
|
/// Should be friendly to send to GPU as is.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
#[cfg(not(feature = "unity"))]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||||
pub struct Vertex {
|
pub struct Vertex {
|
||||||
|
@ -23,25 +22,6 @@ pub struct Vertex {
|
||||||
pub color: Color32, // 32 bit
|
pub color: Color32, // 32 bit
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
||||||
#[cfg(feature = "unity")]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
||||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
|
||||||
pub struct Vertex {
|
|
||||||
/// Logical pixel coordinates (points).
|
|
||||||
/// (0,0) is the top left corner of the screen.
|
|
||||||
pub pos: Pos2, // 64 bit
|
|
||||||
|
|
||||||
/// sRGBA with premultiplied alpha
|
|
||||||
pub color: Color32, // 32 bit
|
|
||||||
|
|
||||||
/// Normalized texture coordinates.
|
|
||||||
/// (0, 0) is the top left corner of the texture.
|
|
||||||
/// (1, 1) is the bottom right corner of the texture.
|
|
||||||
pub uv: Pos2, // 64 bit
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Textured triangles in two dimensions.
|
/// Textured triangles in two dimensions.
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
|
|
@ -973,16 +973,12 @@ pub struct Tessellator {
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
options: TessellationOptions,
|
options: TessellationOptions,
|
||||||
font_tex_size: [usize; 2],
|
font_tex_size: [usize; 2],
|
||||||
|
|
||||||
/// See [`TextureAtlas::prepared_discs`].
|
/// See [`TextureAtlas::prepared_discs`].
|
||||||
prepared_discs: Vec<PreparedDisc>,
|
prepared_discs: Vec<PreparedDisc>,
|
||||||
|
|
||||||
/// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled
|
/// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled
|
||||||
feathering: f32,
|
feathering: f32,
|
||||||
|
|
||||||
/// Only used for culling
|
/// Only used for culling
|
||||||
clip_rect: Rect,
|
clip_rect: Rect,
|
||||||
|
|
||||||
scratchpad_points: Vec<Pos2>,
|
scratchpad_points: Vec<Pos2>,
|
||||||
scratchpad_path: Path,
|
scratchpad_path: Path,
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,10 +33,6 @@ impl UvRect {
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct GlyphInfo {
|
pub struct GlyphInfo {
|
||||||
/// Used for pair-kerning.
|
|
||||||
///
|
|
||||||
/// Doesn't need to be unique.
|
|
||||||
/// Use `ab_glyph::GlyphId(0)` if you just want to have an id, and don't care.
|
|
||||||
pub(crate) id: ab_glyph::GlyphId,
|
pub(crate) id: ab_glyph::GlyphId,
|
||||||
|
|
||||||
/// Unit: points.
|
/// Unit: points.
|
||||||
|
|
|
@ -24,9 +24,8 @@ ignore = [
|
||||||
multiple-versions = "deny"
|
multiple-versions = "deny"
|
||||||
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
||||||
deny = [
|
deny = [
|
||||||
{ name = "cmake" }, # Lord no
|
|
||||||
{ name = "openssl-sys" }, # prefer rustls
|
|
||||||
{ name = "openssl" }, # prefer rustls
|
{ name = "openssl" }, # prefer rustls
|
||||||
|
{ name = "openssl-sys" }, # prefer rustls
|
||||||
]
|
]
|
||||||
|
|
||||||
skip = [
|
skip = [
|
||||||
|
@ -47,6 +46,7 @@ skip-tree = [
|
||||||
{ name = "darling" }, # old version via tts
|
{ name = "darling" }, # old version via tts
|
||||||
{ name = "foreign-types" }, # old version from wgpu
|
{ name = "foreign-types" }, # old version from wgpu
|
||||||
{ name = "rfd" }, # example dependency
|
{ name = "rfd" }, # example dependency
|
||||||
|
{ name = "three-d" }, # example dependency
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
@ -4,7 +4,3 @@ All the examples in this folder uses [`eframe`](https://github.com/emilk/egui/tr
|
||||||
There are a lot more examples at <https://www.egui.rs>, and it has links to the source code of each example.
|
There are a lot more examples at <https://www.egui.rs>, and it has links to the source code of each example.
|
||||||
|
|
||||||
Also check out the official docs at <https://docs.rs/egui> and <https://docs.rs/eframe>.
|
Also check out the official docs at <https://docs.rs/egui> and <https://docs.rs/eframe>.
|
||||||
|
|
||||||
Note that all the examples on `master` are for the latest `master` version of `egui`.
|
|
||||||
|
|
||||||
If you want to look for examples for a specific version of egui, go to that tag, e.g. <https://github.com/emilk/egui/tree/latest/examples>.
|
|
||||||
|
|
|
@ -10,5 +10,5 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
|
|
|
@ -10,7 +10,7 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
egui_glow = { path = "../../crates/egui_glow" }
|
egui_glow = { path = "../../crates/egui_glow" }
|
||||||
glow = "0.12"
|
glow = "0.11"
|
||||||
|
|
|
@ -9,7 +9,7 @@ use std::sync::Arc;
|
||||||
fn main() -> Result<(), eframe::Error> {
|
fn main() -> Result<(), eframe::Error> {
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
initial_window_size: Some(egui::vec2(350.0, 380.0)),
|
initial_window_size: Some(egui::vec2(350.0, 380.0)),
|
||||||
multisampling: 4,
|
multisampling: 8,
|
||||||
renderer: eframe::Renderer::Glow,
|
renderer: eframe::Renderer::Glow,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
24
examples/custom_3d_three-d/Cargo.toml
Normal file
24
examples/custom_3d_three-d/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "custom_3d_three-d"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.65"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
|
] }
|
||||||
|
egui_glow = { path = "../../crates/egui_glow" }
|
||||||
|
glow = "0.11"
|
||||||
|
three-d = { version = "0.13", default-features = false }
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies] # Web dependencies
|
||||||
|
console_error_panic_hook = "0.1" # For logging
|
||||||
|
wasm-bindgen = "0.2" # Core bindings
|
||||||
|
wasm-bindgen-futures = "0.4" # Core bindings
|
22
examples/custom_3d_three-d/README.md
Normal file
22
examples/custom_3d_three-d/README.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
This demo shows how to embed 3D rendering using [`three-d`](https://github.com/asny/three-d) in `eframe`.
|
||||||
|
|
||||||
|
Any 3D library built on top of [`glow`](https://github.com/grovesNL/glow) can be used in `eframe`.
|
||||||
|
|
||||||
|
Alternatively you can render 3D stuff to a texture and display it using [`egui::Ui::image`].
|
||||||
|
|
||||||
|
If you are content of having egui sit on top of a 3D background, take a look at:
|
||||||
|
|
||||||
|
* [`bevy_egui`](https://github.com/mvlabat/bevy_egui)
|
||||||
|
* [`three-d`](https://github.com/asny/three-d)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run -p custom_3d_three-d
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
wasm-pack build examples/custom_3d_three-d --target web
|
||||||
|
```
|
||||||
|
|
||||||
|

|
38
examples/custom_3d_three-d/index.html
Normal file
38
examples/custom_3d_three-d/index.html
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="my" style="position: absolute;top:0;bottom: 0;left: 0;right: 0;margin:auto;"></canvas>
|
||||||
|
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
||||||
|
<script type="module">
|
||||||
|
// Use ES module import syntax to import functionality from the module
|
||||||
|
// that we have compiled.
|
||||||
|
//
|
||||||
|
// Note that the `default` import is an initialization function which
|
||||||
|
// will "boot" the module and make it ready to use. Currently browsers
|
||||||
|
// don't support natively imported WebAssembly as an ES module, but
|
||||||
|
// eventually the manual initialization won't be required!
|
||||||
|
import init from './pkg/custom_3d_three_d.js';
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
// First up we need to actually load the wasm file, so we use the
|
||||||
|
// default export to inform it where the wasm file is located on the
|
||||||
|
// server, and then we wait on the returned promise to wait for the
|
||||||
|
// wasm to be loaded.
|
||||||
|
// It may look like this: `await init('./pkg/without_a_bundler_bg.wasm');`,
|
||||||
|
// but there is also a handy default inside `init` function, which uses
|
||||||
|
// `import.meta` to locate the wasm file relatively to js file
|
||||||
|
//
|
||||||
|
// Note that instead of a string here you can also pass in an instance
|
||||||
|
// of `WebAssembly.Module` which allows you to compile your own module.
|
||||||
|
// Also note that the promise, when resolved, yields the wasm module's
|
||||||
|
// exports which is the same as importing the `*_bg` module in other
|
||||||
|
// modes
|
||||||
|
await init('./pkg/custom_3d_three_d_bg.wasm');
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
examples/custom_3d_three-d/screenshot.png
Normal file
BIN
examples/custom_3d_three-d/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 52 KiB |
21
examples/custom_3d_three-d/src/lib.rs
Normal file
21
examples/custom_3d_three-d/src/lib.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#![allow(special_module_name)]
|
||||||
|
|
||||||
|
mod main;
|
||||||
|
|
||||||
|
// Entry point for wasm
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
#[wasm_bindgen(start)]
|
||||||
|
pub async fn start() -> Result<(), JsValue> {
|
||||||
|
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||||
|
|
||||||
|
let web_options = eframe::WebOptions::default();
|
||||||
|
eframe::start_web(
|
||||||
|
"my",
|
||||||
|
web_options,
|
||||||
|
Box::new(|cc| Box::new(main::MyApp::new(cc))),
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
231
examples/custom_3d_three-d/src/main.rs
Normal file
231
examples/custom_3d_three-d/src/main.rs
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
fn main() -> Result<(), eframe::Error> {
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
initial_window_size: Some(egui::vec2(550.0, 610.0)),
|
||||||
|
multisampling: 8,
|
||||||
|
renderer: eframe::Renderer::Glow,
|
||||||
|
depth_buffer: 24,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
eframe::run_native(
|
||||||
|
"Custom 3D painting in eframe!",
|
||||||
|
options,
|
||||||
|
Box::new(|cc| Box::new(MyApp::new(cc))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MyApp {
|
||||||
|
angle: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MyApp {
|
||||||
|
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||||
|
Self { angle: 0.2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for MyApp {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
|
ui.label("The triangle is being painted using ");
|
||||||
|
ui.hyperlink_to("three-d", "https://github.com/asny/three-d");
|
||||||
|
ui.label(".");
|
||||||
|
});
|
||||||
|
|
||||||
|
egui::ScrollArea::both().show(ui, |ui| {
|
||||||
|
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||||
|
let (rect, response) =
|
||||||
|
ui.allocate_exact_size(egui::Vec2::splat(512.0), egui::Sense::drag());
|
||||||
|
|
||||||
|
self.angle += response.drag_delta().x * 0.01;
|
||||||
|
|
||||||
|
// Clone locals so we can move them into the paint callback:
|
||||||
|
let angle = self.angle;
|
||||||
|
|
||||||
|
let callback = egui::PaintCallback {
|
||||||
|
rect,
|
||||||
|
callback: std::sync::Arc::new(egui_glow::CallbackFn::new(
|
||||||
|
move |info, painter| {
|
||||||
|
with_three_d(painter.gl(), |three_d| {
|
||||||
|
three_d.frame(
|
||||||
|
FrameInput::new(&three_d.context, &info, painter),
|
||||||
|
angle,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
ui.painter().add(callback);
|
||||||
|
});
|
||||||
|
ui.label("Drag to rotate!");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We get a [`glow::Context`] from `eframe` and we want to construct a [`ThreeDApp`].
|
||||||
|
///
|
||||||
|
/// Sadly we can't just create a [`ThreeDApp`] in [`MyApp::new`] and pass it
|
||||||
|
/// to the [`egui::PaintCallback`] because [`glow::Context`] isn't `Send+Sync` on web, which
|
||||||
|
/// [`egui::PaintCallback`] needs. If you do not target web, then you can construct the [`ThreeDApp`] in [`MyApp::new`].
|
||||||
|
fn with_three_d<R>(gl: &std::sync::Arc<glow::Context>, f: impl FnOnce(&mut ThreeDApp) -> R) -> R {
|
||||||
|
use std::cell::RefCell;
|
||||||
|
thread_local! {
|
||||||
|
pub static THREE_D: RefCell<Option<ThreeDApp>> = RefCell::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
THREE_D.with(|three_d| {
|
||||||
|
let mut three_d = three_d.borrow_mut();
|
||||||
|
let three_d = three_d.get_or_insert_with(|| ThreeDApp::new(gl.clone()));
|
||||||
|
f(three_d)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Translates from egui input to three-d input
|
||||||
|
///
|
||||||
|
pub struct FrameInput<'a> {
|
||||||
|
screen: three_d::RenderTarget<'a>,
|
||||||
|
viewport: three_d::Viewport,
|
||||||
|
scissor_box: three_d::ScissorBox,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameInput<'_> {
|
||||||
|
pub fn new(
|
||||||
|
context: &three_d::Context,
|
||||||
|
info: &egui::PaintCallbackInfo,
|
||||||
|
painter: &egui_glow::Painter,
|
||||||
|
) -> Self {
|
||||||
|
use three_d::*;
|
||||||
|
|
||||||
|
// Disable sRGB textures for three-d
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
unsafe {
|
||||||
|
use glow::HasContext as _;
|
||||||
|
context.disable(glow::FRAMEBUFFER_SRGB);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructs a screen render target to render the final image to
|
||||||
|
let screen = painter.intermediate_fbo().map_or_else(
|
||||||
|
|| {
|
||||||
|
RenderTarget::screen(
|
||||||
|
context,
|
||||||
|
info.viewport.width() as u32,
|
||||||
|
info.viewport.height() as u32,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|fbo| {
|
||||||
|
RenderTarget::from_framebuffer(
|
||||||
|
context,
|
||||||
|
info.viewport.width() as u32,
|
||||||
|
info.viewport.height() as u32,
|
||||||
|
fbo,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set where to paint
|
||||||
|
let viewport = info.viewport_in_pixels();
|
||||||
|
let viewport = Viewport {
|
||||||
|
x: viewport.left_px.round() as _,
|
||||||
|
y: viewport.from_bottom_px.round() as _,
|
||||||
|
width: viewport.width_px.round() as _,
|
||||||
|
height: viewport.height_px.round() as _,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Respect the egui clip region (e.g. if we are inside an `egui::ScrollArea`).
|
||||||
|
let clip_rect = info.clip_rect_in_pixels();
|
||||||
|
let scissor_box = ScissorBox {
|
||||||
|
x: clip_rect.left_px.round() as _,
|
||||||
|
y: clip_rect.from_bottom_px.round() as _,
|
||||||
|
width: clip_rect.width_px.round() as _,
|
||||||
|
height: clip_rect.height_px.round() as _,
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
screen,
|
||||||
|
scissor_box,
|
||||||
|
viewport,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Based on the `three-d` [Triangle example](https://github.com/asny/three-d/blob/master/examples/triangle/src/main.rs).
|
||||||
|
/// This is where you'll need to customize
|
||||||
|
///
|
||||||
|
use three_d::*;
|
||||||
|
pub struct ThreeDApp {
|
||||||
|
context: Context,
|
||||||
|
camera: Camera,
|
||||||
|
model: Gm<Mesh, ColorMaterial>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreeDApp {
|
||||||
|
pub fn new(gl: std::sync::Arc<glow::Context>) -> Self {
|
||||||
|
let context = Context::from_gl_context(gl).unwrap();
|
||||||
|
// Create a camera
|
||||||
|
let camera = Camera::new_perspective(
|
||||||
|
Viewport::new_at_origo(1, 1),
|
||||||
|
vec3(0.0, 0.0, 2.0),
|
||||||
|
vec3(0.0, 0.0, 0.0),
|
||||||
|
vec3(0.0, 1.0, 0.0),
|
||||||
|
degrees(45.0),
|
||||||
|
0.1,
|
||||||
|
10.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a CPU-side mesh consisting of a single colored triangle
|
||||||
|
let positions = vec![
|
||||||
|
vec3(0.5, -0.5, 0.0), // bottom right
|
||||||
|
vec3(-0.5, -0.5, 0.0), // bottom left
|
||||||
|
vec3(0.0, 0.5, 0.0), // top
|
||||||
|
];
|
||||||
|
let colors = vec![
|
||||||
|
Color::new(255, 0, 0, 255), // bottom right
|
||||||
|
Color::new(0, 255, 0, 255), // bottom left
|
||||||
|
Color::new(0, 0, 255, 255), // top
|
||||||
|
];
|
||||||
|
let cpu_mesh = CpuMesh {
|
||||||
|
positions: Positions::F32(positions),
|
||||||
|
colors: Some(colors),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Construct a model, with a default color material, thereby transferring the mesh data to the GPU
|
||||||
|
let model = Gm::new(Mesh::new(&context, &cpu_mesh), ColorMaterial::default());
|
||||||
|
Self {
|
||||||
|
context,
|
||||||
|
camera,
|
||||||
|
model,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn frame(&mut self, frame_input: FrameInput<'_>, angle: f32) -> Option<glow::Framebuffer> {
|
||||||
|
// Ensure the viewport matches the current window viewport which changes if the window is resized
|
||||||
|
self.camera.set_viewport(frame_input.viewport);
|
||||||
|
|
||||||
|
// Set the current transformation of the triangle
|
||||||
|
self.model
|
||||||
|
.set_transformation(Mat4::from_angle_y(radians(angle)));
|
||||||
|
|
||||||
|
// Get the screen render target to be able to render something on the screen
|
||||||
|
frame_input
|
||||||
|
.screen
|
||||||
|
// Clear the color and depth of the screen render target
|
||||||
|
.clear_partially(frame_input.scissor_box, ClearState::depth(1.0))
|
||||||
|
// Render the triangle with the color material which uses the per vertex colors defined at construction
|
||||||
|
.render_partially(frame_input.scissor_box, &self.camera, &[&self.model], &[]);
|
||||||
|
|
||||||
|
frame_input.screen.into_framebuffer() // Take back the screen fbo, we will continue to use it.
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,5 +10,5 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
|
|
|
@ -10,5 +10,5 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
|
|
|
@ -10,5 +10,5 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.2 KiB |
|
@ -22,7 +22,9 @@ fn main() -> Result<(), eframe::Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct MyApp {}
|
struct MyApp {
|
||||||
|
maximized: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl eframe::App for MyApp {
|
impl eframe::App for MyApp {
|
||||||
fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
|
fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
|
||||||
|
@ -30,8 +32,8 @@ impl eframe::App for MyApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
custom_window_frame(ctx, frame, "egui with custom frame", |ui| {
|
custom_window_frame(self, ctx, frame, "egui with custom frame", |ui| {
|
||||||
ui.label("This is just the contents of the window.");
|
ui.label("This is just the contents of the window");
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("egui theme:");
|
ui.label("egui theme:");
|
||||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||||
|
@ -41,35 +43,104 @@ impl eframe::App for MyApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn custom_window_frame(
|
fn custom_window_frame(
|
||||||
|
app: &mut MyApp,
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
frame: &mut eframe::Frame,
|
frame: &mut eframe::Frame,
|
||||||
title: &str,
|
title: &str,
|
||||||
add_contents: impl FnOnce(&mut egui::Ui),
|
add_contents: impl FnOnce(&mut egui::Ui),
|
||||||
) {
|
) {
|
||||||
use egui::*;
|
use egui::*;
|
||||||
|
let text_color = ctx.style().visuals.text_color();
|
||||||
|
|
||||||
let panel_frame = egui::Frame {
|
// Height of the title bar
|
||||||
fill: ctx.style().visuals.window_fill(),
|
let height = 28.0;
|
||||||
rounding: 10.0.into(),
|
|
||||||
stroke: ctx.style().visuals.widgets.noninteractive.fg_stroke,
|
|
||||||
outer_margin: 0.5.into(), // so the stroke is within the bounds
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
CentralPanel::default().frame(panel_frame).show(ctx, |ui| {
|
CentralPanel::default()
|
||||||
let app_rect = ui.max_rect();
|
.frame(Frame::none())
|
||||||
|
.show(ctx, |ui| {
|
||||||
|
let rect = ui.max_rect();
|
||||||
|
let painter = ui.painter();
|
||||||
|
|
||||||
let title_bar_height = 32.0;
|
// Paint the frame:
|
||||||
|
painter.rect(
|
||||||
|
rect.shrink(1.0),
|
||||||
|
10.0,
|
||||||
|
ctx.style().visuals.window_fill(),
|
||||||
|
Stroke::new(1.0, text_color),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Paint the title:
|
||||||
|
painter.text(
|
||||||
|
rect.center_top() + vec2(0.0, height / 2.0),
|
||||||
|
Align2::CENTER_CENTER,
|
||||||
|
title,
|
||||||
|
FontId::proportional(height * 0.8),
|
||||||
|
text_color,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Paint the line under the title:
|
||||||
|
painter.line_segment(
|
||||||
|
[
|
||||||
|
rect.left_top() + vec2(2.0, height),
|
||||||
|
rect.right_top() + vec2(-2.0, height),
|
||||||
|
],
|
||||||
|
Stroke::new(1.0, text_color),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Interact with the title bar (drag to move window):
|
||||||
let title_bar_rect = {
|
let title_bar_rect = {
|
||||||
let mut rect = app_rect;
|
let mut rect = rect;
|
||||||
rect.max.y = rect.min.y + title_bar_height;
|
rect.max.y = rect.min.y + height;
|
||||||
rect
|
rect
|
||||||
};
|
};
|
||||||
title_bar_ui(ui, frame, title_bar_rect, title);
|
let title_bar_response =
|
||||||
|
ui.interact(title_bar_rect, Id::new("title_bar"), Sense::click());
|
||||||
|
|
||||||
|
if title_bar_response.double_clicked() {
|
||||||
|
app.maximized = !app.maximized;
|
||||||
|
frame.set_maximized(app.maximized);
|
||||||
|
} else if title_bar_response.is_pointer_button_down_on() {
|
||||||
|
frame.drag_window();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the close button:
|
||||||
|
let close_response = ui.put(
|
||||||
|
Rect::from_min_size(rect.left_top(), Vec2::splat(height)),
|
||||||
|
Button::new(RichText::new("❌").size(height - 4.0)).frame(false),
|
||||||
|
);
|
||||||
|
if close_response.clicked() {
|
||||||
|
frame.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
let minimized_response = ui.put(
|
||||||
|
Rect::from_min_size(
|
||||||
|
rect.left_top() + vec2((height - 4.0) * 1.0, 0.0),
|
||||||
|
Vec2::splat(height),
|
||||||
|
),
|
||||||
|
Button::new(RichText::new("🗕").size(height - 4.0)).frame(false),
|
||||||
|
);
|
||||||
|
if minimized_response.clicked() {
|
||||||
|
frame.set_minimized(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let maximized_response = ui.put(
|
||||||
|
Rect::from_min_size(
|
||||||
|
rect.left_top() + vec2((height - 4.0) * 2.0, 0.0),
|
||||||
|
Vec2::splat(height),
|
||||||
|
),
|
||||||
|
Button::new(
|
||||||
|
RichText::new(if app.maximized { "🗗" } else { "🗖" }).size(height - 4.0),
|
||||||
|
)
|
||||||
|
.frame(false),
|
||||||
|
);
|
||||||
|
if maximized_response.clicked() {
|
||||||
|
app.maximized = !app.maximized;
|
||||||
|
frame.set_maximized(app.maximized);
|
||||||
|
}
|
||||||
|
|
||||||
// Add the contents:
|
// Add the contents:
|
||||||
let content_rect = {
|
let content_rect = {
|
||||||
let mut rect = app_rect;
|
let mut rect = rect;
|
||||||
rect.min.y = title_bar_rect.max.y;
|
rect.min.y = title_bar_rect.max.y;
|
||||||
rect
|
rect
|
||||||
}
|
}
|
||||||
|
@ -78,87 +149,3 @@ fn custom_window_frame(
|
||||||
add_contents(&mut content_ui);
|
add_contents(&mut content_ui);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title_bar_ui(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
frame: &mut eframe::Frame,
|
|
||||||
title_bar_rect: eframe::epaint::Rect,
|
|
||||||
title: &str,
|
|
||||||
) {
|
|
||||||
use egui::*;
|
|
||||||
|
|
||||||
let painter = ui.painter();
|
|
||||||
|
|
||||||
let title_bar_response = ui.interact(title_bar_rect, Id::new("title_bar"), Sense::click());
|
|
||||||
|
|
||||||
// Paint the title:
|
|
||||||
painter.text(
|
|
||||||
title_bar_rect.center(),
|
|
||||||
Align2::CENTER_CENTER,
|
|
||||||
title,
|
|
||||||
FontId::proportional(20.0),
|
|
||||||
ui.style().visuals.text_color(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Paint the line under the title:
|
|
||||||
painter.line_segment(
|
|
||||||
[
|
|
||||||
title_bar_rect.left_bottom() + vec2(1.0, 0.0),
|
|
||||||
title_bar_rect.right_bottom() + vec2(-1.0, 0.0),
|
|
||||||
],
|
|
||||||
ui.visuals().widgets.noninteractive.bg_stroke,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Interact with the title bar (drag to move window):
|
|
||||||
if title_bar_response.double_clicked() {
|
|
||||||
frame.set_maximized(!frame.info().window_info.maximized);
|
|
||||||
} else if title_bar_response.is_pointer_button_down_on() {
|
|
||||||
frame.drag_window();
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.allocate_ui_at_rect(title_bar_rect, |ui| {
|
|
||||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
|
||||||
ui.spacing_mut().item_spacing.x = 0.0;
|
|
||||||
ui.visuals_mut().button_frame = false;
|
|
||||||
ui.add_space(8.0);
|
|
||||||
close_maximize_minimize(ui, frame);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Show some close/maximize/minimize buttons for the native window.
|
|
||||||
fn close_maximize_minimize(ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
|
||||||
use egui::{Button, RichText};
|
|
||||||
|
|
||||||
let button_height = 12.0;
|
|
||||||
|
|
||||||
let close_response = ui
|
|
||||||
.add(Button::new(RichText::new("❌").size(button_height)))
|
|
||||||
.on_hover_text("Close the window");
|
|
||||||
if close_response.clicked() {
|
|
||||||
frame.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if frame.info().window_info.maximized {
|
|
||||||
let maximized_response = ui
|
|
||||||
.add(Button::new(RichText::new("🗗").size(button_height)))
|
|
||||||
.on_hover_text("Restore window");
|
|
||||||
if maximized_response.clicked() {
|
|
||||||
frame.set_maximized(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let maximized_response = ui
|
|
||||||
.add(Button::new(RichText::new("🗗").size(button_height)))
|
|
||||||
.on_hover_text("Maximize window");
|
|
||||||
if maximized_response.clicked() {
|
|
||||||
frame.set_maximized(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let minimized_response = ui
|
|
||||||
.add(Button::new(RichText::new("🗕").size(button_height)))
|
|
||||||
.on_hover_text("Minimize the window");
|
|
||||||
if minimized_response.clicked() {
|
|
||||||
frame.set_minimized(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
egui_extras = { path = "../../crates/egui_extras", features = ["image"] }
|
egui_extras = { path = "../../crates/egui_extras", features = ["image"] }
|
||||||
ehttp = "0.2"
|
ehttp = "0.2"
|
||||||
|
|
|
@ -10,6 +10,6 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
rfd = "0.11"
|
rfd = "0.10"
|
||||||
|
|
|
@ -10,6 +10,6 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 7.5 KiB |
|
@ -10,6 +10,6 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
use egui::*;
|
use egui::*;
|
||||||
|
|
||||||
fn main() -> Result<(), eframe::Error> {
|
fn main() -> Result<(), eframe::Error> {
|
||||||
// Log to stdout (if you run with `RUST_LOG=debug`).
|
// Log to stdout (if you run with `RUST_LOG=debug`).
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
|
@ -10,8 +10,7 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"puffin",
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
|
||||||
] }
|
] }
|
||||||
puffin = "0.14"
|
puffin = "0.14"
|
||||||
puffin_http = "0.11"
|
puffin_http = "0.11"
|
||||||
|
|
|
@ -10,7 +10,7 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
egui_extras = { path = "../../crates/egui_extras", features = ["image"] }
|
egui_extras = { path = "../../crates/egui_extras", features = ["image"] }
|
||||||
image = { version = "0.24", default-features = false, features = ["png"] }
|
image = { version = "0.24", default-features = false, features = ["png"] }
|
||||||
|
|
|
@ -10,6 +10,6 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
|
|
|
@ -10,5 +10,5 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
|
|
|
@ -10,6 +10,6 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
eframe = { path = "../../crates/eframe", features = [
|
eframe = { path = "../../crates/eframe", features = [
|
||||||
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
"__screenshot", # __screenshot is so we can dump a ascreenshot using EFRAME_SCREENSHOT_TO
|
||||||
] }
|
] }
|
||||||
egui_extras = { path = "../../crates/egui_extras", features = ["svg"] }
|
egui_extras = { path = "../../crates/egui_extras", features = ["svg"] }
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue