mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 06:40:06 -04:00
Merge branch 'master' into cache_galley_lines
This commit is contained in:
5
.gitattributes
vendored
5
.gitattributes
vendored
@@ -1,5 +1,8 @@
|
|||||||
* text=auto eol=lf
|
* text=auto eol=lf
|
||||||
Cargo.lock linguist-generated=false
|
Cargo.lock linguist-generated=false
|
||||||
*.png filter=lfs diff=lfs merge=lfs -text
|
*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
# The icon.png is needed when including eframe via git, so it may not be in lfs
|
|
||||||
|
# Exclude some small files from LFS:
|
||||||
crates/eframe/data/* !filter !diff !merge text=auto eol=lf
|
crates/eframe/data/* !filter !diff !merge text=auto eol=lf
|
||||||
|
crates/egui_demo_lib/data/* !filter !diff !merge text=auto eol=lf
|
||||||
|
crates/egui/assets/* !filter !diff !merge text=auto eol=lf
|
||||||
|
|||||||
2
.github/workflows/deploy_web_demo.yml
vendored
2
.github/workflows/deploy_web_demo.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
target: wasm32-unknown-unknown
|
target: wasm32-unknown-unknown
|
||||||
toolchain: 1.79.0
|
toolchain: 1.80.0
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|||||||
11
.github/workflows/png_only_on_lfs.yml
vendored
11
.github/workflows/png_only_on_lfs.yml
vendored
@@ -13,11 +13,18 @@ jobs:
|
|||||||
- name: Check that png files are on git LFS
|
- name: Check that png files are on git LFS
|
||||||
run: |
|
run: |
|
||||||
binary_extensions="png"
|
binary_extensions="png"
|
||||||
exclude="crates/eframe/data"
|
exclude_paths=(
|
||||||
|
"crates/eframe/data"
|
||||||
|
"crates/egui_demo_lib/data/"
|
||||||
|
"crates/egui/assets/"
|
||||||
|
)
|
||||||
|
|
||||||
# Find binary files that are not tracked by Git LFS
|
# Find binary files that are not tracked by Git LFS
|
||||||
for ext in $binary_extensions; do
|
for ext in $binary_extensions; do
|
||||||
if comm -23 <(git ls-files | grep -v "^$exclude" | sort) <(git lfs ls-files -n | sort) | grep "\.${ext}$"; then
|
# Create grep pattern to exclude multiple paths
|
||||||
|
exclude_pattern=$(printf "|^%s" "${exclude_paths[@]}" | sed 's/^|//')
|
||||||
|
|
||||||
|
if comm -23 <(git ls-files | grep -Ev "$exclude_pattern" | sort) <(git lfs ls-files -n | sort) | grep "\.${ext}$"; then
|
||||||
echo "Error: Found binary file with extension .$ext not tracked by git LFS. See CONTRIBUTING.md"
|
echo "Error: Found binary file with extension .$ext not tracked by git LFS. See CONTRIBUTING.md"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
6
.github/workflows/preview_build.yml
vendored
6
.github/workflows/preview_build.yml
vendored
@@ -42,9 +42,11 @@ jobs:
|
|||||||
- name: Generate meta.json
|
- name: Generate meta.json
|
||||||
env:
|
env:
|
||||||
PR_NUMBER: ${{ github.event.number }}
|
PR_NUMBER: ${{ github.event.number }}
|
||||||
PR_BRANCH: ${{ github.head_ref }}
|
URL_SLUG: ${{ github.event.number }}-${{ github.head_ref }}
|
||||||
run: |
|
run: |
|
||||||
echo "{\"pr_number\": \"$PR_NUMBER\", \"pr_branch\": \"$PR_BRANCH\"}" > meta.json
|
# Sanitize the URL_SLUG to only contain alphanumeric characters and dashes
|
||||||
|
URL_SLUG=$(echo $URL_SLUG | tr -cd '[:alnum:]-')
|
||||||
|
echo "{\"pr_number\": \"$PR_NUMBER\", \"url_slug\": \"$URL_SLUG\"}" > meta.json
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
9
.github/workflows/preview_cleanup.yml
vendored
9
.github/workflows/preview_cleanup.yml
vendored
@@ -15,9 +15,14 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- run: mkdir -p empty_dir
|
- run: mkdir -p empty_dir
|
||||||
- name: Url slug variable
|
- name: Generate URL_SLUG
|
||||||
|
env:
|
||||||
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||||
|
URL_SLUG: ${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.ref }}
|
||||||
run: |
|
run: |
|
||||||
echo "URL_SLUG=${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.ref }}" >> $GITHUB_ENV
|
# Sanitize the URL_SLUG to only contain alphanumeric characters and dashes
|
||||||
|
URL_SLUG=$(echo $URL_SLUG | tr -cd '[:alnum:]-')
|
||||||
|
echo "URL_SLUG=$URL_SLUG" >> $GITHUB_ENV
|
||||||
- name: Deploy
|
- name: Deploy
|
||||||
uses: JamesIves/github-pages-deploy-action@v4
|
uses: JamesIves/github-pages-deploy-action@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
6
.github/workflows/preview_deploy.yml
vendored
6
.github/workflows/preview_deploy.yml
vendored
@@ -40,11 +40,7 @@ jobs:
|
|||||||
- name: Parse meta.json
|
- name: Parse meta.json
|
||||||
run: |
|
run: |
|
||||||
echo "PR_NUMBER=$(jq -r .pr_number meta.json)" >> $GITHUB_ENV
|
echo "PR_NUMBER=$(jq -r .pr_number meta.json)" >> $GITHUB_ENV
|
||||||
echo "PR_BRANCH=$(jq -r .pr_branch meta.json)" >> $GITHUB_ENV
|
echo "URL_SLUG=$(jq -r .url_slug meta.json)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Url slug variable
|
|
||||||
run: |
|
|
||||||
echo "URL_SLUG=${{ env.PR_NUMBER }}-${{ env.PR_BRANCH }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Deploy
|
- name: Deploy
|
||||||
uses: JamesIves/github-pages-deploy-action@v4
|
uses: JamesIves/github-pages-deploy-action@v4
|
||||||
|
|||||||
14
.github/workflows/rust.yml
vendored
14
.github/workflows/rust.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: 1.79.0
|
toolchain: 1.80.0
|
||||||
|
|
||||||
- name: Install packages (Linux)
|
- name: Install packages (Linux)
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: 1.79.0
|
toolchain: 1.80.0
|
||||||
targets: wasm32-unknown-unknown
|
targets: wasm32-unknown-unknown
|
||||||
|
|
||||||
- run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev
|
- run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev
|
||||||
@@ -155,7 +155,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||||
with:
|
with:
|
||||||
rust-version: "1.79.0"
|
rust-version: "1.80.0"
|
||||||
log-level: error
|
log-level: error
|
||||||
command: check
|
command: check
|
||||||
arguments: --target ${{ matrix.target }}
|
arguments: --target ${{ matrix.target }}
|
||||||
@@ -170,7 +170,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: 1.79.0
|
toolchain: 1.80.0
|
||||||
targets: aarch64-linux-android
|
targets: aarch64-linux-android
|
||||||
|
|
||||||
- name: Set up cargo cache
|
- name: Set up cargo cache
|
||||||
@@ -189,7 +189,7 @@ jobs:
|
|||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: 1.79.0
|
toolchain: 1.80.0
|
||||||
targets: aarch64-apple-ios
|
targets: aarch64-apple-ios
|
||||||
|
|
||||||
- name: Set up cargo cache
|
- name: Set up cargo cache
|
||||||
@@ -208,7 +208,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: 1.79.0
|
toolchain: 1.80.0
|
||||||
|
|
||||||
- name: Set up cargo cache
|
- name: Set up cargo cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
@@ -232,7 +232,7 @@ jobs:
|
|||||||
lfs: true
|
lfs: true
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: 1.79.0
|
toolchain: 1.80.0
|
||||||
|
|
||||||
- name: Set up cargo cache
|
- name: Set up cargo cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
|
|||||||
97
CHANGELOG.md
97
CHANGELOG.md
@@ -1,12 +1,107 @@
|
|||||||
# egui changelog
|
# egui changelog
|
||||||
All notable changes to the `egui` crate will be documented in this file.
|
All notable changes to the `egui` crate will be documented in this file.
|
||||||
|
|
||||||
NOTE: this is just the changelog for the core `egui` crate. [`eframe`](crates/eframe/CHANGELOG.md), [`ecolor`](crates/ecolor/CHANGELOG.md), [`epaint`](crates/epaint/CHANGELOG.md), [`egui-winit`](crates/egui-winit/CHANGELOG.md), [`egui_glow`](crates/egui_glow/CHANGELOG.md) and [`egui-wgpu`](crates/egui-wgpu/CHANGELOG.md) have their own changelogs!
|
This is just the changelog for the core `egui` crate. Every crate in this repository has their own changelog:
|
||||||
|
* [`epaint` changelog](crates/epaint/CHANGELOG.md)
|
||||||
|
* [`egui-winit` changelog](crates/egui-winit/CHANGELOG.md)
|
||||||
|
* [`egui-wgpu` changelog](crates/egui-wgpu/CHANGELOG.md)
|
||||||
|
* [`egui_kittest` changelog](crates/egui_kittest/CHANGELOG.md)
|
||||||
|
* [`egui_glow` changelog](crates/egui_glow/CHANGELOG.md)
|
||||||
|
* [`ecolor` changelog](crates/ecolor/CHANGELOG.md)
|
||||||
|
* [`eframe` changelog](crates/eframe/CHANGELOG.md)
|
||||||
|
|
||||||
This file is updated upon each release.
|
This file is updated upon each release.
|
||||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.30.0 - 2024-12-16 - Modals and better layer support
|
||||||
|
|
||||||
|
### ✨ Highlights
|
||||||
|
* Add `Modal`, a popup that blocks input to the rest of the application ([#5358](https://github.com/emilk/egui/pull/5358) by [@lucasmerlin](https://github.com/lucasmerlin))
|
||||||
|
* Improved support for transform layers ([#5465](https://github.com/emilk/egui/pull/5465), [#5468](https://github.com/emilk/egui/pull/5468), [#5429](https://github.com/emilk/egui/pull/5429))
|
||||||
|
|
||||||
|
#### `egui_kittest`
|
||||||
|
This release welcomes a new crate to the family: [egui_kittest](https://github.com/emilk/egui/tree/master/crates/egui_kittest).
|
||||||
|
`egui_kittest` is a testing framework for egui, allowing you to test both automation (simulated clicks and other events),
|
||||||
|
and also do screenshot testing (useful for regression tests).
|
||||||
|
`egui_kittest` is built using [`kittest`](https://github.com/rerun-io/kittest), which is a general GUI testing framework that aims to work with any Rust GUI (not just egui!).
|
||||||
|
`kittest` uses the accessibility library [`AccessKit`](https://github.com/AccessKit/accesskit/) for automatation and to query the widget tree.
|
||||||
|
|
||||||
|
`kittest` and `egui_kittest` are written by [@lucasmerlin](https://github.com/lucasmerlin).
|
||||||
|
|
||||||
|
Here's a quick example of how to use `egui_kittest` to test a checkbox:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use egui::accesskit::Toggled;
|
||||||
|
use egui_kittest::{Harness, kittest::Queryable};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut checked = false;
|
||||||
|
let app = |ui: &mut egui::Ui| {
|
||||||
|
ui.checkbox(&mut checked, "Check me!");
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut harness = egui_kittest::Harness::new_ui(app);
|
||||||
|
|
||||||
|
let checkbox = harness.get_by_label("Check me!");
|
||||||
|
assert_eq!(checkbox.toggled(), Some(Toggled::False));
|
||||||
|
checkbox.click();
|
||||||
|
|
||||||
|
harness.run();
|
||||||
|
|
||||||
|
let checkbox = harness.get_by_label("Check me!");
|
||||||
|
assert_eq!(checkbox.toggled(), Some(Toggled::True));
|
||||||
|
|
||||||
|
// You can even render the ui and do image snapshot tests
|
||||||
|
#[cfg(all(feature = "wgpu", feature = "snapshot"))]
|
||||||
|
harness.wgpu_snapshot("readme_example");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ⭐ Added
|
||||||
|
* Add `Modal` and `Memory::set_modal_layer` [#5358](https://github.com/emilk/egui/pull/5358) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||||
|
* Add `UiBuilder::layer_id` and remove `layer_id` from `Ui::new` [#5195](https://github.com/emilk/egui/pull/5195) by [@emilk](https://github.com/emilk)
|
||||||
|
* Allow easier setting of background color for `TextEdit` [#5203](https://github.com/emilk/egui/pull/5203) by [@bircni](https://github.com/bircni)
|
||||||
|
* Set `Response::intrinsic_size` for `TextEdit` [#5266](https://github.com/emilk/egui/pull/5266) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||||
|
* Expose center position in `MultiTouchInfo` [#5247](https://github.com/emilk/egui/pull/5247) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||||
|
* `Context::add_font` [#5228](https://github.com/emilk/egui/pull/5228) by [@frederik-uni](https://github.com/frederik-uni)
|
||||||
|
* Impl from `Box<str>` for `WidgetText`, `RichText` [#5309](https://github.com/emilk/egui/pull/5309) by [@dimtpap](https://github.com/dimtpap)
|
||||||
|
* Add `Window::scroll_bar_visibility` [#5231](https://github.com/emilk/egui/pull/5231) by [@Zeenobit](https://github.com/Zeenobit)
|
||||||
|
* Add `ComboBox::close_behavior` [#5305](https://github.com/emilk/egui/pull/5305) by [@avalsch](https://github.com/avalsch)
|
||||||
|
* Add `painter.line()` [#5291](https://github.com/emilk/egui/pull/5291) by [@bircni](https://github.com/bircni)
|
||||||
|
* Allow attaching custom user data to a screenshot command [#5416](https://github.com/emilk/egui/pull/5416) by [@emilk](https://github.com/emilk)
|
||||||
|
* Add `Button::image_tint_follows_text_color` [#5430](https://github.com/emilk/egui/pull/5430) by [@emilk](https://github.com/emilk)
|
||||||
|
* Consume escape keystroke when bailing out from a drag operation [#5433](https://github.com/emilk/egui/pull/5433) by [@abey79](https://github.com/abey79)
|
||||||
|
* Add `Context::layer_transform_to_global` & `layer_transform_from_global` [#5465](https://github.com/emilk/egui/pull/5465) by [@emilk](https://github.com/emilk)
|
||||||
|
|
||||||
|
### 🔧 Changed
|
||||||
|
* Update MSRV to Rust 1.80 [#5421](https://github.com/emilk/egui/pull/5421), [#5457](https://github.com/emilk/egui/pull/5457) by [@emilk](https://github.com/emilk)
|
||||||
|
* Expand max font atlas size from 8k to 16k [#5257](https://github.com/emilk/egui/pull/5257) by [@rustbasic](https://github.com/rustbasic)
|
||||||
|
* Put font data into `Arc` to reduce memory consumption [#5276](https://github.com/emilk/egui/pull/5276) by [@StarStarJ](https://github.com/StarStarJ)
|
||||||
|
* Move `egui::util::cache` to `egui::cache`; add `FramePublisher` [#5426](https://github.com/emilk/egui/pull/5426) by [@emilk](https://github.com/emilk)
|
||||||
|
* Remove `Order::PanelResizeLine` [#5455](https://github.com/emilk/egui/pull/5455) by [@emilk](https://github.com/emilk)
|
||||||
|
* Drag-and-drop: keep cursor set by user, if any [#5467](https://github.com/emilk/egui/pull/5467) by [@abey79](https://github.com/abey79)
|
||||||
|
* Use `profiling` crate to support more profiler backends [#5150](https://github.com/emilk/egui/pull/5150) by [@teddemunnik](https://github.com/teddemunnik)
|
||||||
|
* Improve hit-test of thin widgets, and widgets across layers [#5468](https://github.com/emilk/egui/pull/5468) by [@emilk](https://github.com/emilk)
|
||||||
|
|
||||||
|
### 🐛 Fixed
|
||||||
|
* Update `ScrollArea` drag velocity when drag stopped [#5175](https://github.com/emilk/egui/pull/5175) by [@valadaptive](https://github.com/valadaptive)
|
||||||
|
* Fix bug causing wrong-fire of `ViewportCommand::Visible` [#5244](https://github.com/emilk/egui/pull/5244) by [@rustbasic](https://github.com/rustbasic)
|
||||||
|
* Fix: `Ui::new_child` does not consider the `sizing_pass` field of `UiBuilder` [#5262](https://github.com/emilk/egui/pull/5262) by [@zhatuokun](https://github.com/zhatuokun)
|
||||||
|
* Fix Ctrl+Shift+Z redo shortcut [#5258](https://github.com/emilk/egui/pull/5258) by [@YgorSouza](https://github.com/YgorSouza)
|
||||||
|
* Fix: `Window::default_pos` does not work [#5315](https://github.com/emilk/egui/pull/5315) by [@rustbasic](https://github.com/rustbasic)
|
||||||
|
* Fix: `Sides` did not apply the layout position correctly [#5303](https://github.com/emilk/egui/pull/5303) by [@zhatuokun](https://github.com/zhatuokun)
|
||||||
|
* Respect `Style::override_font_id` in `RichText` [#5310](https://github.com/emilk/egui/pull/5310) by [@MStarha](https://github.com/MStarha)
|
||||||
|
* Fix disabled widgets "eating" focus [#5370](https://github.com/emilk/egui/pull/5370) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||||
|
* Fix cursor clipping in `TextEdit` inside a `ScrollArea` [#3660](https://github.com/emilk/egui/pull/3660) by [@juancampa](https://github.com/juancampa)
|
||||||
|
* Make text cursor always appear on click [#5420](https://github.com/emilk/egui/pull/5420) by [@juancampa](https://github.com/juancampa)
|
||||||
|
* Fix `on_hover_text_at_pointer` for transformed layers [#5429](https://github.com/emilk/egui/pull/5429) by [@emilk](https://github.com/emilk)
|
||||||
|
* Fix: don't interact with `Area` outside its `constrain_rect` [#5459](https://github.com/emilk/egui/pull/5459) by [@MScottMcBee](https://github.com/MScottMcBee)
|
||||||
|
* Fix broken images on egui.rs (move from git lfs to normal git) [#5480](https://github.com/emilk/egui/pull/5480) by [@emilk](https://github.com/emilk)
|
||||||
|
* Fix: `ui.new_child` should now respect `disabled` [#5483](https://github.com/emilk/egui/pull/5483) by [@emilk](https://github.com/emilk)
|
||||||
|
* Fix zero-width strokes still affecting the feathering color of boxes [#5485](https://github.com/emilk/egui/pull/5485) by [@emilk](https://github.com/emilk)
|
||||||
|
|
||||||
|
|
||||||
## 0.29.1 - 2024-10-01 - Bug fixes
|
## 0.29.1 - 2024-10-01 - Bug fixes
|
||||||
* Remove debug-assert triggered by `with_layer_id/dnd_drag_source` [#5191](https://github.com/emilk/egui/pull/5191) by [@emilk](https://github.com/emilk)
|
* Remove debug-assert triggered by `with_layer_id/dnd_drag_source` [#5191](https://github.com/emilk/egui/pull/5191) by [@emilk](https://github.com/emilk)
|
||||||
* Fix id clash in `Ui::response` [#5192](https://github.com/emilk/egui/pull/5192) by [@emilk](https://github.com/emilk)
|
* Fix id clash in `Ui::response` [#5192](https://github.com/emilk/egui/pull/5192) by [@emilk](https://github.com/emilk)
|
||||||
|
|||||||
831
Cargo.lock
831
Cargo.lock
File diff suppressed because it is too large
Load Diff
38
Cargo.toml
38
Cargo.toml
@@ -23,8 +23,8 @@ members = [
|
|||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
rust-version = "1.79"
|
rust-version = "1.80"
|
||||||
version = "0.29.1"
|
version = "0.30.0"
|
||||||
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
@@ -55,18 +55,18 @@ opt-level = 2
|
|||||||
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
emath = { version = "0.29.1", path = "crates/emath", default-features = false }
|
emath = { version = "0.30.0", path = "crates/emath", default-features = false }
|
||||||
ecolor = { version = "0.29.1", path = "crates/ecolor", default-features = false }
|
ecolor = { version = "0.30.0", path = "crates/ecolor", default-features = false }
|
||||||
epaint = { version = "0.29.1", path = "crates/epaint", default-features = false }
|
epaint = { version = "0.30.0", path = "crates/epaint", default-features = false }
|
||||||
epaint_default_fonts = { version = "0.29.1", path = "crates/epaint_default_fonts" }
|
epaint_default_fonts = { version = "0.30.0", path = "crates/epaint_default_fonts" }
|
||||||
egui = { version = "0.29.1", path = "crates/egui", default-features = false }
|
egui = { version = "0.30.0", path = "crates/egui", default-features = false }
|
||||||
egui-winit = { version = "0.29.1", path = "crates/egui-winit", default-features = false }
|
egui-winit = { version = "0.30.0", path = "crates/egui-winit", default-features = false }
|
||||||
egui_extras = { version = "0.29.1", path = "crates/egui_extras", default-features = false }
|
egui_extras = { version = "0.30.0", path = "crates/egui_extras", default-features = false }
|
||||||
egui-wgpu = { version = "0.29.1", path = "crates/egui-wgpu", default-features = false }
|
egui-wgpu = { version = "0.30.0", path = "crates/egui-wgpu", default-features = false }
|
||||||
egui_demo_lib = { version = "0.29.1", path = "crates/egui_demo_lib", default-features = false }
|
egui_demo_lib = { version = "0.30.0", path = "crates/egui_demo_lib", default-features = false }
|
||||||
egui_glow = { version = "0.29.1", path = "crates/egui_glow", default-features = false }
|
egui_glow = { version = "0.30.0", path = "crates/egui_glow", default-features = false }
|
||||||
egui_kittest = { version = "0.29.1", path = "crates/egui_kittest", default-features = false }
|
egui_kittest = { version = "0.30.0", path = "crates/egui_kittest", default-features = false }
|
||||||
eframe = { version = "0.29.1", path = "crates/eframe", default-features = false }
|
eframe = { version = "0.30.0", path = "crates/eframe", default-features = false }
|
||||||
|
|
||||||
ahash = { version = "0.8.11", default-features = false, features = [
|
ahash = { version = "0.8.11", 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
|
||||||
@@ -82,11 +82,12 @@ glutin = { version = "0.32.0", default-features = false }
|
|||||||
glutin-winit = { version = "0.5.0", default-features = false }
|
glutin-winit = { version = "0.5.0", default-features = false }
|
||||||
home = "0.5.9"
|
home = "0.5.9"
|
||||||
image = { version = "0.25", default-features = false }
|
image = { version = "0.25", default-features = false }
|
||||||
kittest = { git = "https://github.com/rerun-io/kittest", version = "0.1", branch = "main" }
|
kittest = { version = "0.1" }
|
||||||
log = { version = "0.4", features = ["std"] }
|
log = { version = "0.4", features = ["std"] }
|
||||||
nohash-hasher = "0.2"
|
nohash-hasher = "0.2"
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
pollster = "0.4"
|
pollster = "0.4"
|
||||||
|
profiling = { version = "1.0.16", default-features = false }
|
||||||
puffin = "0.19"
|
puffin = "0.19"
|
||||||
puffin_http = "0.16"
|
puffin_http = "0.16"
|
||||||
raw-window-handle = "0.6.0"
|
raw-window-handle = "0.6.0"
|
||||||
@@ -106,9 +107,9 @@ winit = { version = "0.30.5", default-features = false }
|
|||||||
unsafe_code = "deny"
|
unsafe_code = "deny"
|
||||||
|
|
||||||
elided_lifetimes_in_paths = "warn"
|
elided_lifetimes_in_paths = "warn"
|
||||||
future_incompatible = "warn"
|
future_incompatible = { level = "warn", priority = -1 }
|
||||||
nonstandard_style = "warn"
|
nonstandard_style = { level = "warn", priority = -1 }
|
||||||
rust_2018_idioms = "warn"
|
rust_2018_idioms = { level = "warn", priority = -1 }
|
||||||
rust_2021_prelude_collisions = "warn"
|
rust_2021_prelude_collisions = "warn"
|
||||||
semicolon_in_expressions_from_macros = "warn"
|
semicolon_in_expressions_from_macros = "warn"
|
||||||
trivial_numeric_casts = "warn"
|
trivial_numeric_casts = "warn"
|
||||||
@@ -233,6 +234,7 @@ ref_patterns = "warn"
|
|||||||
rest_pat_in_fully_bound_structs = "warn"
|
rest_pat_in_fully_bound_structs = "warn"
|
||||||
same_functions_in_if_condition = "warn"
|
same_functions_in_if_condition = "warn"
|
||||||
semicolon_if_nothing_returned = "warn"
|
semicolon_if_nothing_returned = "warn"
|
||||||
|
single_char_pattern = "warn"
|
||||||
single_match_else = "warn"
|
single_match_else = "warn"
|
||||||
str_split_at_newline = "warn"
|
str_split_at_newline = "warn"
|
||||||
str_to_string = "warn"
|
str_to_string = "warn"
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ We don't update the MSRV in a patch release, unless we really, really need to.
|
|||||||
* [ ] run `scripts/generate_example_screenshots.sh` if needed
|
* [ ] run `scripts/generate_example_screenshots.sh` if needed
|
||||||
* [ ] write a short release note that fits in a tweet
|
* [ ] write a short release note that fits in a tweet
|
||||||
* [ ] record gif for `CHANGELOG.md` release note (and later twitter post)
|
* [ ] record gif for `CHANGELOG.md` release note (and later twitter post)
|
||||||
* [ ] update changelogs using `scripts/generate_changelog.py --write`
|
* [ ] update changelogs using `scripts/generate_changelog.py --version 0.x.0 --write`
|
||||||
- For major releases, always diff to the latest MAJOR release, e.g. `--commit-range 0.29.0..HEAD`
|
|
||||||
* [ ] bump version numbers in workspace `Cargo.toml`
|
* [ ] bump version numbers in workspace `Cargo.toml`
|
||||||
|
|
||||||
## Actual release
|
## Actual release
|
||||||
@@ -79,8 +78,9 @@ I usually do this all on the `master` branch, but doing it in a release branch i
|
|||||||
(cd crates/epaint && cargo publish --quiet) && echo "✅ epaint"
|
(cd crates/epaint && cargo publish --quiet) && echo "✅ epaint"
|
||||||
(cd crates/egui && cargo publish --quiet) && echo "✅ egui"
|
(cd crates/egui && cargo publish --quiet) && echo "✅ egui"
|
||||||
(cd crates/egui-winit && cargo publish --quiet) && echo "✅ egui-winit"
|
(cd crates/egui-winit && cargo publish --quiet) && echo "✅ egui-winit"
|
||||||
(cd crates/egui_extras && cargo publish --quiet) && echo "✅ egui_extras"
|
|
||||||
(cd crates/egui-wgpu && cargo publish --quiet) && echo "✅ egui-wgpu"
|
(cd crates/egui-wgpu && cargo publish --quiet) && echo "✅ egui-wgpu"
|
||||||
|
(cd crates/egui_kittest && cargo publish --quiet) && echo "✅ egui_kittest"
|
||||||
|
(cd crates/egui_extras && cargo publish --quiet) && echo "✅ egui_extras"
|
||||||
(cd crates/egui_demo_lib && cargo publish --quiet) && echo "✅ egui_demo_lib"
|
(cd crates/egui_demo_lib && cargo publish --quiet) && echo "✅ egui_demo_lib"
|
||||||
(cd crates/egui_glow && cargo publish --quiet) && echo "✅ egui_glow"
|
(cd crates/egui_glow && cargo publish --quiet) && echo "✅ egui_glow"
|
||||||
(cd crates/eframe && cargo publish --quiet) && echo "✅ eframe"
|
(cd crates/eframe && cargo publish --quiet) && echo "✅ eframe"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Section identical to scripts/clippy_wasm/clippy.toml:
|
# Section identical to scripts/clippy_wasm/clippy.toml:
|
||||||
|
|
||||||
msrv = "1.79"
|
msrv = "1.80"
|
||||||
|
|
||||||
allow-unwrap-in-tests = true
|
allow-unwrap-in-tests = true
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ This file is updated upon each release.
|
|||||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.30.0 - 2024-12-16
|
||||||
|
* Use boxed slice for lookup table to avoid stack overflow [#5212](https://github.com/emilk/egui/pull/5212) by [@YgorSouza](https://github.com/YgorSouza)
|
||||||
|
* Add `Color32::mul` [#5437](https://github.com/emilk/egui/pull/5437) by [@emilk](https://github.com/emilk)
|
||||||
|
|
||||||
|
|
||||||
## 0.29.1 - 2024-10-01
|
## 0.29.1 - 2024-10-01
|
||||||
Nothing new
|
Nothing new
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,25 @@ This file is updated upon each release.
|
|||||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.30.0 - 2024-12-16 - Android support
|
||||||
|
NOTE: you now need to enable the `wayland` or `x11` features to get Linux support, including getting it to work on most CI systems.
|
||||||
|
|
||||||
|
### ⭐ Added
|
||||||
|
* Support `ViewportCommand::Screenshot` on web [#5438](https://github.com/emilk/egui/pull/5438) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||||
|
|
||||||
|
### 🔧 Changed
|
||||||
|
* Android support [#5318](https://github.com/emilk/egui/pull/5318) by [@parasyte](https://github.com/parasyte)
|
||||||
|
* Update MSRV to 1.80 [#5457](https://github.com/emilk/egui/pull/5457) by [@emilk](https://github.com/emilk)
|
||||||
|
* Use `profiling` crate to support more profiler backends [#5150](https://github.com/emilk/egui/pull/5150) by [@teddemunnik](https://github.com/teddemunnik)
|
||||||
|
* Update glow to 0.16 [#5395](https://github.com/emilk/egui/pull/5395) by [@sagudev](https://github.com/sagudev)
|
||||||
|
* Forward `x11` and `wayland` features to `glutin` [#5391](https://github.com/emilk/egui/pull/5391) by [@e00E](https://github.com/e00E)
|
||||||
|
|
||||||
|
### 🐛 Fixed
|
||||||
|
* iOS: Support putting UI next to the dynamic island [#5211](https://github.com/emilk/egui/pull/5211) by [@frederik-uni](https://github.com/frederik-uni)
|
||||||
|
* Prevent panic when copying text outside of a secure context [#5326](https://github.com/emilk/egui/pull/5326) by [@YgorSouza](https://github.com/YgorSouza)
|
||||||
|
* Fix accidental change of `FallbackEgl` to `PreferEgl` [#5408](https://github.com/emilk/egui/pull/5408) by [@e00E](https://github.com/e00E)
|
||||||
|
|
||||||
|
|
||||||
## 0.29.1 - 2024-10-01 - Fix backspace/arrow keys on X11
|
## 0.29.1 - 2024-10-01 - Fix backspace/arrow keys on X11
|
||||||
* Linux: Disable IME to fix backspace/arrow keys [#5188](https://github.com/emilk/egui/pull/5188) by [@emilk](https://github.com/emilk)
|
* Linux: Disable IME to fix backspace/arrow keys [#5188](https://github.com/emilk/egui/pull/5188) by [@emilk](https://github.com/emilk)
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ default = [
|
|||||||
"accesskit",
|
"accesskit",
|
||||||
"default_fonts",
|
"default_fonts",
|
||||||
"glow",
|
"glow",
|
||||||
"wayland",
|
"wayland", # Required for Linux support (including CI!)
|
||||||
"web_screen_reader",
|
"web_screen_reader",
|
||||||
"winit/default",
|
"winit/default",
|
||||||
"x11",
|
"x11",
|
||||||
@@ -71,21 +71,16 @@ persistence = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
|
||||||
##
|
|
||||||
## `eframe` will call `puffin::GlobalProfiler::lock().new_frame()` for you
|
|
||||||
##
|
|
||||||
## Only enabled on native, because of the low resolution (1ms) of clocks in browsers.
|
|
||||||
puffin = [
|
|
||||||
"dep:puffin",
|
|
||||||
"egui/puffin",
|
|
||||||
"egui_glow?/puffin",
|
|
||||||
"egui-wgpu?/puffin",
|
|
||||||
"egui-winit/puffin",
|
|
||||||
]
|
|
||||||
|
|
||||||
## Enables wayland support and fixes clipboard issue.
|
## Enables wayland support and fixes clipboard issue.
|
||||||
wayland = ["egui-winit/wayland", "egui-wgpu?/wayland", "egui_glow?/wayland", "glutin?/wayland", "glutin-winit?/wayland"]
|
##
|
||||||
|
## If you are compiling for Linux (or want to test on a CI system using Linux), you should enable this feature.
|
||||||
|
wayland = [
|
||||||
|
"egui-winit/wayland",
|
||||||
|
"egui-wgpu?/wayland",
|
||||||
|
"egui_glow?/wayland",
|
||||||
|
"glutin?/wayland",
|
||||||
|
"glutin-winit?/wayland",
|
||||||
|
]
|
||||||
|
|
||||||
## Enable screen reader support (requires `ctx.options_mut(|o| o.screen_reader = true);`) on web.
|
## Enable screen reader support (requires `ctx.options_mut(|o| o.screen_reader = true);`) on web.
|
||||||
##
|
##
|
||||||
@@ -111,7 +106,15 @@ web_screen_reader = [
|
|||||||
wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"]
|
wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"]
|
||||||
|
|
||||||
## Enables compiling for x11.
|
## Enables compiling for x11.
|
||||||
x11 = ["egui-winit/x11", "egui-wgpu?/x11", "egui_glow?/x11", "glutin?/x11", "glutin?/glx", "glutin-winit?/x11", "glutin-winit?/glx"]
|
x11 = [
|
||||||
|
"egui-winit/x11",
|
||||||
|
"egui-wgpu?/x11",
|
||||||
|
"egui_glow?/x11",
|
||||||
|
"glutin?/x11",
|
||||||
|
"glutin?/glx",
|
||||||
|
"glutin-winit?/x11",
|
||||||
|
"glutin-winit?/glx",
|
||||||
|
]
|
||||||
|
|
||||||
## If set, eframe will look for the env-var `EFRAME_SCREENSHOT_TO` and write a screenshot to that location, and then quit.
|
## If set, eframe will look for the env-var `EFRAME_SCREENSHOT_TO` and write a screenshot to that location, and then quit.
|
||||||
## This is used to generate images for examples.
|
## This is used to generate images for examples.
|
||||||
@@ -127,6 +130,7 @@ ahash.workspace = true
|
|||||||
document-features.workspace = true
|
document-features.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
|
profiling.workspace = true
|
||||||
raw-window-handle.workspace = true
|
raw-window-handle.workspace = true
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
web-time.workspace = true
|
web-time.workspace = true
|
||||||
@@ -154,10 +158,15 @@ egui-wgpu = { workspace = true, optional = true, features = [
|
|||||||
] } # if wgpu is used, use it with winit
|
] } # if wgpu is used, use it with winit
|
||||||
pollster = { workspace = true, optional = true } # needed for wgpu
|
pollster = { workspace = true, optional = true } # needed for wgpu
|
||||||
|
|
||||||
glutin = { workspace = true, optional = true, default-features = false, features = ["egl", "wgl"] }
|
glutin = { workspace = true, optional = true, default-features = false, features = [
|
||||||
glutin-winit = { workspace = true, optional = true, default-features = false, features = ["egl", "wgl"] }
|
"egl",
|
||||||
|
"wgl",
|
||||||
|
] }
|
||||||
|
glutin-winit = { workspace = true, optional = true, default-features = false, features = [
|
||||||
|
"egl",
|
||||||
|
"wgl",
|
||||||
|
] }
|
||||||
home = { workspace = true, optional = true }
|
home = { workspace = true, optional = true }
|
||||||
puffin = { workspace = true, optional = true }
|
|
||||||
wgpu = { workspace = true, optional = true, features = [
|
wgpu = { workspace = true, optional = true, features = [
|
||||||
# Let's enable some backends so that users can use `eframe` out-of-the-box
|
# Let's enable some backends so that users can use `eframe` out-of-the-box
|
||||||
# without having to explicitly opt-in to backends
|
# without having to explicitly opt-in to backends
|
||||||
|
|||||||
@@ -364,6 +364,16 @@ pub struct NativeOptions {
|
|||||||
///
|
///
|
||||||
/// Defaults to true.
|
/// Defaults to true.
|
||||||
pub dithering: bool,
|
pub dithering: bool,
|
||||||
|
|
||||||
|
/// Android application for `winit`'s event loop.
|
||||||
|
///
|
||||||
|
/// This value is required on Android to correctly create the event loop. See
|
||||||
|
/// [`EventLoopBuilder::build`] and [`with_android_app`] for details.
|
||||||
|
///
|
||||||
|
/// [`EventLoopBuilder::build`]: winit::event_loop::EventLoopBuilder::build
|
||||||
|
/// [`with_android_app`]: winit::platform::android::EventLoopBuilderExtAndroid::with_android_app
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub android_app: Option<winit::platform::android::activity::AndroidApp>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -383,6 +393,9 @@ impl Clone for NativeOptions {
|
|||||||
|
|
||||||
persistence_path: self.persistence_path.clone(),
|
persistence_path: self.persistence_path.clone(),
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
android_app: self.android_app.clone(),
|
||||||
|
|
||||||
..*self
|
..*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -424,6 +437,9 @@ impl Default for NativeOptions {
|
|||||||
persistence_path: None,
|
persistence_path: None,
|
||||||
|
|
||||||
dithering: true,
|
dithering: true,
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
android_app: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -772,8 +788,7 @@ pub struct IntegrationInfo {
|
|||||||
///
|
///
|
||||||
/// This includes [`App::update`] as well as rendering (except for vsync waiting).
|
/// This includes [`App::update`] as well as rendering (except for vsync waiting).
|
||||||
///
|
///
|
||||||
/// For a more detailed view of cpu usage, use the [`puffin`](https://crates.io/crates/puffin)
|
/// For a more detailed view of cpu usage, connect your preferred profiler by enabling it's feature in [`profiling`](https://crates.io/crates/profiling).
|
||||||
/// profiler together with the `puffin` feature of `eframe`.
|
|
||||||
///
|
///
|
||||||
/// `None` if this is the first frame.
|
/// `None` if this is the first frame.
|
||||||
pub cpu_usage: Option<f32>,
|
pub cpu_usage: Option<f32>,
|
||||||
@@ -815,7 +830,7 @@ impl Storage for DummyStorage {
|
|||||||
/// Get and deserialize the [RON](https://github.com/ron-rs/ron) stored at the given key.
|
/// Get and deserialize the [RON](https://github.com/ron-rs/ron) stored at the given key.
|
||||||
#[cfg(feature = "ron")]
|
#[cfg(feature = "ron")]
|
||||||
pub fn get_value<T: serde::de::DeserializeOwned>(storage: &dyn Storage, key: &str) -> Option<T> {
|
pub fn get_value<T: serde::de::DeserializeOwned>(storage: &dyn Storage, key: &str) -> Option<T> {
|
||||||
crate::profile_function!(key);
|
profiling::function_scope!(key);
|
||||||
storage
|
storage
|
||||||
.get_string(key)
|
.get_string(key)
|
||||||
.and_then(|value| match ron::from_str(&value) {
|
.and_then(|value| match ron::from_str(&value) {
|
||||||
@@ -831,7 +846,7 @@ pub fn get_value<T: serde::de::DeserializeOwned>(storage: &dyn Storage, key: &st
|
|||||||
/// Serialize the given value as [RON](https://github.com/ron-rs/ron) and store with the given key.
|
/// Serialize the given value as [RON](https://github.com/ron-rs/ron) and store with the given key.
|
||||||
#[cfg(feature = "ron")]
|
#[cfg(feature = "ron")]
|
||||||
pub fn set_value<T: serde::Serialize>(storage: &mut dyn Storage, key: &str, value: &T) {
|
pub fn set_value<T: serde::Serialize>(storage: &mut dyn Storage, key: &str, value: &T) {
|
||||||
crate::profile_function!(key);
|
profiling::function_scope!(key);
|
||||||
match ron::ser::to_string(value) {
|
match ron::ser::to_string(value) {
|
||||||
Ok(string) => storage.set_string(key, string),
|
Ok(string) => storage.set_string(key, string),
|
||||||
Err(err) => log::error!("eframe failed to encode data using ron: {}", err),
|
Err(err) => log::error!("eframe failed to encode data using ron: {}", err),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub trait IconDataExt {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
/// If this is not a valid png.
|
/// If this is not a valid png.
|
||||||
pub fn from_png_bytes(png_bytes: &[u8]) -> Result<IconData, image::ImageError> {
|
pub fn from_png_bytes(png_bytes: &[u8]) -> Result<IconData, image::ImageError> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
let image = image::load_from_memory(png_bytes)?;
|
let image = image::load_from_memory(png_bytes)?;
|
||||||
Ok(from_image(image))
|
Ok(from_image(image))
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ fn from_image(image: image::DynamicImage) -> IconData {
|
|||||||
|
|
||||||
impl IconDataExt for IconData {
|
impl IconDataExt for IconData {
|
||||||
fn to_image(&self) -> Result<image::RgbaImage, String> {
|
fn to_image(&self) -> Result<image::RgbaImage, String> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
let Self {
|
let Self {
|
||||||
rgba,
|
rgba,
|
||||||
width,
|
width,
|
||||||
@@ -48,7 +48,7 @@ impl IconDataExt for IconData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn to_png_bytes(&self) -> Result<Vec<u8>, String> {
|
fn to_png_bytes(&self) -> Result<Vec<u8>, String> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
let image = self.to_image()?;
|
let image = self.to_image()?;
|
||||||
let mut png_bytes: Vec<u8> = Vec::new();
|
let mut png_bytes: Vec<u8> = Vec::new();
|
||||||
image
|
image
|
||||||
|
|||||||
@@ -129,6 +129,17 @@
|
|||||||
//! ## Feature flags
|
//! ## Feature flags
|
||||||
#![doc = document_features::document_features!()]
|
#![doc = document_features::document_features!()]
|
||||||
//!
|
//!
|
||||||
|
//! ## Instrumentation
|
||||||
|
//! This crate supports using the [profiling](https://crates.io/crates/profiling) crate for instrumentation.
|
||||||
|
//! You can enable features on the profiling crates in your application to add instrumentation for all
|
||||||
|
//! crates that support it, including egui. See the profiling crate docs for more information.
|
||||||
|
//! ```toml
|
||||||
|
//! [dependencies]
|
||||||
|
//! profiling = "1.0"
|
||||||
|
//! [features]
|
||||||
|
//! profile-with-puffin = ["profiling/profile-with-puffin"]
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
|
||||||
#![warn(missing_docs)] // let's keep eframe well-documented
|
#![warn(missing_docs)] // let's keep eframe well-documented
|
||||||
#![allow(clippy::needless_doctest_main)]
|
#![allow(clippy::needless_doctest_main)]
|
||||||
@@ -445,33 +456,3 @@ impl std::fmt::Display for Error {
|
|||||||
|
|
||||||
/// Short for `Result<T, eframe::Error>`.
|
/// Short for `Result<T, eframe::Error>`.
|
||||||
pub type Result<T = (), E = Error> = std::result::Result<T, E>;
|
pub type Result<T = (), E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
mod profiling_scopes {
|
|
||||||
#![allow(unused_macros)]
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_function {
|
|
||||||
($($arg: tt)*) => {
|
|
||||||
#[cfg(feature = "puffin")]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
|
||||||
puffin::profile_function!($($arg)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use profile_function;
|
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_scope {
|
|
||||||
($($arg: tt)*) => {
|
|
||||||
#[cfg(feature = "puffin")]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
|
||||||
puffin::profile_scope!($($arg)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use profile_scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub(crate) use profiling_scopes::{profile_function, profile_scope};
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ enum AppIconStatus {
|
|||||||
/// Since window creation can be lazy, call this every frame until it's either successfully or gave up.
|
/// Since window creation can be lazy, call this every frame until it's either successfully or gave up.
|
||||||
/// (See [`AppIconStatus`])
|
/// (See [`AppIconStatus`])
|
||||||
fn set_title_and_icon(_title: &str, _icon_data: Option<&IconData>) -> AppIconStatus {
|
fn set_title_and_icon(_title: &str, _icon_data: Option<&IconData>) -> AppIconStatus {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
@@ -201,7 +201,7 @@ fn set_app_icon_windows(icon_data: &IconData) -> AppIconStatus {
|
|||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconStatus {
|
fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconStatus {
|
||||||
use crate::icon_data::IconDataExt as _;
|
use crate::icon_data::IconDataExt as _;
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
use objc2::ClassType;
|
use objc2::ClassType;
|
||||||
use objc2_app_kit::{NSApplication, NSImage};
|
use objc2_app_kit::{NSApplication, NSImage};
|
||||||
@@ -237,7 +237,7 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS
|
|||||||
log::trace!("NSImage::initWithData…");
|
log::trace!("NSImage::initWithData…");
|
||||||
let app_icon = NSImage::initWithData(NSImage::alloc(), &data);
|
let app_icon = NSImage::initWithData(NSImage::alloc(), &data);
|
||||||
|
|
||||||
crate::profile_scope!("setApplicationIconImage_");
|
profiling::scope!("setApplicationIconImage_");
|
||||||
log::trace!("setApplicationIconImage…");
|
log::trace!("setApplicationIconImage…");
|
||||||
app.setApplicationIconImage(app_icon.as_deref());
|
app.setApplicationIconImage(app_icon.as_deref());
|
||||||
}
|
}
|
||||||
@@ -246,7 +246,7 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS
|
|||||||
if let Some(main_menu) = app.mainMenu() {
|
if let Some(main_menu) = app.mainMenu() {
|
||||||
if let Some(item) = main_menu.itemAtIndex(0) {
|
if let Some(item) = main_menu.itemAtIndex(0) {
|
||||||
if let Some(app_menu) = item.submenu() {
|
if let Some(app_menu) = item.submenu() {
|
||||||
crate::profile_scope!("setTitle_");
|
profiling::scope!("setTitle_");
|
||||||
app_menu.setTitle(&NSString::from_str(title));
|
app_menu.setTitle(&NSString::from_str(title));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub fn viewport_builder(
|
|||||||
native_options: &mut epi::NativeOptions,
|
native_options: &mut epi::NativeOptions,
|
||||||
window_settings: Option<WindowSettings>,
|
window_settings: Option<WindowSettings>,
|
||||||
) -> ViewportBuilder {
|
) -> ViewportBuilder {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let mut viewport_builder = native_options.viewport.clone();
|
let mut viewport_builder = native_options.viewport.clone();
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ pub fn viewport_builder(
|
|||||||
|
|
||||||
#[cfg(not(target_os = "ios"))]
|
#[cfg(not(target_os = "ios"))]
|
||||||
if native_options.centered {
|
if native_options.centered {
|
||||||
crate::profile_scope!("center");
|
profiling::scope!("center");
|
||||||
if let Some(monitor) = event_loop
|
if let Some(monitor) = event_loop
|
||||||
.primary_monitor()
|
.primary_monitor()
|
||||||
.or_else(|| event_loop.available_monitors().next())
|
.or_else(|| event_loop.available_monitors().next())
|
||||||
@@ -94,8 +94,7 @@ pub fn apply_window_settings(
|
|||||||
window: &winit::window::Window,
|
window: &winit::window::Window,
|
||||||
window_settings: Option<WindowSettings>,
|
window_settings: Option<WindowSettings>,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if let Some(window_settings) = window_settings {
|
if let Some(window_settings) = window_settings {
|
||||||
window_settings.initialize_window(window);
|
window_settings.initialize_window(window);
|
||||||
}
|
}
|
||||||
@@ -103,12 +102,11 @@ pub fn apply_window_settings(
|
|||||||
|
|
||||||
#[cfg(not(target_os = "ios"))]
|
#[cfg(not(target_os = "ios"))]
|
||||||
fn largest_monitor_point_size(egui_zoom_factor: f32, event_loop: &ActiveEventLoop) -> egui::Vec2 {
|
fn largest_monitor_point_size(egui_zoom_factor: f32, event_loop: &ActiveEventLoop) -> egui::Vec2 {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let mut max_size = egui::Vec2::ZERO;
|
let mut max_size = egui::Vec2::ZERO;
|
||||||
|
|
||||||
let available_monitors = {
|
let available_monitors = {
|
||||||
crate::profile_scope!("available_monitors");
|
profiling::scope!("available_monitors");
|
||||||
event_loop.available_monitors()
|
event_loop.available_monitors()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -238,7 +236,7 @@ impl EpiIntegration {
|
|||||||
egui_winit: &mut egui_winit::State,
|
egui_winit: &mut egui_winit::State,
|
||||||
event: &winit::event::WindowEvent,
|
event: &winit::event::WindowEvent,
|
||||||
) -> EventResponse {
|
) -> EventResponse {
|
||||||
crate::profile_function!(egui_winit::short_window_event_description(event));
|
profiling::function_scope!(egui_winit::short_window_event_description(event));
|
||||||
|
|
||||||
use winit::event::{ElementState, MouseButton, WindowEvent};
|
use winit::event::{ElementState, MouseButton, WindowEvent};
|
||||||
|
|
||||||
@@ -276,10 +274,10 @@ impl EpiIntegration {
|
|||||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||||
if let Some(viewport_ui_cb) = viewport_ui_cb {
|
if let Some(viewport_ui_cb) = viewport_ui_cb {
|
||||||
// Child viewport
|
// Child viewport
|
||||||
crate::profile_scope!("viewport_callback");
|
profiling::scope!("viewport_callback");
|
||||||
viewport_ui_cb(egui_ctx);
|
viewport_ui_cb(egui_ctx);
|
||||||
} else {
|
} else {
|
||||||
crate::profile_scope!("App::update");
|
profiling::scope!("App::update");
|
||||||
app.update(egui_ctx, &mut self.frame);
|
app.update(egui_ctx, &mut self.frame);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -306,7 +304,7 @@ impl EpiIntegration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn post_rendering(&mut self, window: &winit::window::Window) {
|
pub fn post_rendering(&mut self, window: &winit::window::Window) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
if std::mem::take(&mut self.is_first_frame) {
|
if std::mem::take(&mut self.is_first_frame) {
|
||||||
// We keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
// We keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||||
window.set_visible(true);
|
window.set_visible(true);
|
||||||
@@ -332,11 +330,11 @@ impl EpiIntegration {
|
|||||||
pub fn save(&mut self, _app: &mut dyn epi::App, _window: Option<&winit::window::Window>) {
|
pub fn save(&mut self, _app: &mut dyn epi::App, _window: Option<&winit::window::Window>) {
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
if let Some(storage) = self.frame.storage_mut() {
|
if let Some(storage) = self.frame.storage_mut() {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if let Some(window) = _window {
|
if let Some(window) = _window {
|
||||||
if self.persist_window {
|
if self.persist_window {
|
||||||
crate::profile_scope!("native_window");
|
profiling::scope!("native_window");
|
||||||
epi::set_value(
|
epi::set_value(
|
||||||
storage,
|
storage,
|
||||||
STORAGE_WINDOW_KEY,
|
STORAGE_WINDOW_KEY,
|
||||||
@@ -345,23 +343,23 @@ impl EpiIntegration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _app.persist_egui_memory() {
|
if _app.persist_egui_memory() {
|
||||||
crate::profile_scope!("egui_memory");
|
profiling::scope!("egui_memory");
|
||||||
self.egui_ctx
|
self.egui_ctx
|
||||||
.memory(|mem| epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, mem));
|
.memory(|mem| epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, mem));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
crate::profile_scope!("App::save");
|
profiling::scope!("App::save");
|
||||||
_app.save(storage);
|
_app.save(storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::profile_scope!("Storage::flush");
|
profiling::scope!("Storage::flush");
|
||||||
storage.flush();
|
storage.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_default_egui_icon() -> egui::IconData {
|
fn load_default_egui_icon() -> egui::IconData {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
crate::icon_data::from_png_bytes(&include_bytes!("../../data/icon.png")[..]).unwrap()
|
crate::icon_data::from_png_bytes(&include_bytes!("../../data/icon.png")[..]).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,7 +370,7 @@ const STORAGE_EGUI_MEMORY_KEY: &str = "egui";
|
|||||||
const STORAGE_WINDOW_KEY: &str = "window";
|
const STORAGE_WINDOW_KEY: &str = "window";
|
||||||
|
|
||||||
pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option<WindowSettings> {
|
pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option<WindowSettings> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
{
|
{
|
||||||
epi::get_value(_storage?, STORAGE_WINDOW_KEY)
|
epi::get_value(_storage?, STORAGE_WINDOW_KEY)
|
||||||
@@ -382,7 +380,7 @@ pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option<Windo
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option<egui::Memory> {
|
pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option<egui::Memory> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
{
|
{
|
||||||
epi::get_value(_storage?, STORAGE_EGUI_MEMORY_KEY)
|
epi::get_value(_storage?, STORAGE_EGUI_MEMORY_KEY)
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ pub struct FileStorage {
|
|||||||
impl Drop for FileStorage {
|
impl Drop for FileStorage {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Some(join_handle) = self.last_save_join_handle.take() {
|
if let Some(join_handle) = self.last_save_join_handle.take() {
|
||||||
crate::profile_scope!("wait_for_save");
|
profiling::scope!("wait_for_save");
|
||||||
join_handle.join().ok();
|
join_handle.join().ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ impl Drop for FileStorage {
|
|||||||
impl FileStorage {
|
impl FileStorage {
|
||||||
/// Store the state in this .ron file.
|
/// Store the state in this .ron file.
|
||||||
pub(crate) fn from_ron_filepath(ron_filepath: impl Into<PathBuf>) -> Self {
|
pub(crate) fn from_ron_filepath(ron_filepath: impl Into<PathBuf>) -> Self {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
let ron_filepath: PathBuf = ron_filepath.into();
|
let ron_filepath: PathBuf = ron_filepath.into();
|
||||||
log::debug!("Loading app state from {:?}…", ron_filepath);
|
log::debug!("Loading app state from {:?}…", ron_filepath);
|
||||||
Self {
|
Self {
|
||||||
@@ -122,7 +122,7 @@ impl FileStorage {
|
|||||||
|
|
||||||
/// Find a good place to put the files that the OS likes.
|
/// Find a good place to put the files that the OS likes.
|
||||||
pub fn from_app_id(app_id: &str) -> Option<Self> {
|
pub fn from_app_id(app_id: &str) -> Option<Self> {
|
||||||
crate::profile_function!(app_id);
|
profiling::function_scope!();
|
||||||
if let Some(data_dir) = storage_dir(app_id) {
|
if let Some(data_dir) = storage_dir(app_id) {
|
||||||
if let Err(err) = std::fs::create_dir_all(&data_dir) {
|
if let Err(err) = std::fs::create_dir_all(&data_dir) {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
@@ -155,7 +155,7 @@ impl crate::Storage for FileStorage {
|
|||||||
|
|
||||||
fn flush(&mut self) {
|
fn flush(&mut self) {
|
||||||
if self.dirty {
|
if self.dirty {
|
||||||
crate::profile_function!();
|
profiling::scope!("FileStorage::flush");
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
|
|
||||||
let file_path = self.ron_filepath.clone();
|
let file_path = self.ron_filepath.clone();
|
||||||
@@ -184,7 +184,7 @@ impl crate::Storage for FileStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn save_to_disk(file_path: &PathBuf, kv: &HashMap<String, String>) {
|
fn save_to_disk(file_path: &PathBuf, kv: &HashMap<String, String>) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if let Some(parent_dir) = file_path.parent() {
|
if let Some(parent_dir) = file_path.parent() {
|
||||||
if !parent_dir.exists() {
|
if !parent_dir.exists() {
|
||||||
@@ -199,7 +199,7 @@ fn save_to_disk(file_path: &PathBuf, kv: &HashMap<String, String>) {
|
|||||||
let mut writer = std::io::BufWriter::new(file);
|
let mut writer = std::io::BufWriter::new(file);
|
||||||
let config = Default::default();
|
let config = Default::default();
|
||||||
|
|
||||||
crate::profile_scope!("ron::serialize");
|
profiling::scope!("ron::serialize");
|
||||||
if let Err(err) = ron::ser::to_writer_pretty(&mut writer, &kv, config)
|
if let Err(err) = ron::ser::to_writer_pretty(&mut writer, &kv, config)
|
||||||
.and_then(|_| writer.flush().map_err(|err| err.into()))
|
.and_then(|_| writer.flush().map_err(|err| err.into()))
|
||||||
{
|
{
|
||||||
@@ -220,7 +220,7 @@ fn read_ron<T>(ron_path: impl AsRef<Path>) -> Option<T>
|
|||||||
where
|
where
|
||||||
T: serde::de::DeserializeOwned,
|
T: serde::de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
match std::fs::File::open(ron_path) {
|
match std::fs::File::open(ron_path) {
|
||||||
Ok(file) => {
|
Ok(file) => {
|
||||||
let reader = std::io::BufReader::new(file);
|
let reader = std::io::BufReader::new(file);
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ impl<'app> GlowWinitApp<'app> {
|
|||||||
native_options: NativeOptions,
|
native_options: NativeOptions,
|
||||||
app_creator: AppCreator<'app>,
|
app_creator: AppCreator<'app>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
Self {
|
Self {
|
||||||
repaint_proxy: Arc::new(egui::mutex::Mutex::new(event_loop.create_proxy())),
|
repaint_proxy: Arc::new(egui::mutex::Mutex::new(event_loop.create_proxy())),
|
||||||
app_name: app_name.to_owned(),
|
app_name: app_name.to_owned(),
|
||||||
@@ -146,8 +146,7 @@ impl<'app> GlowWinitApp<'app> {
|
|||||||
storage: Option<&dyn Storage>,
|
storage: Option<&dyn Storage>,
|
||||||
native_options: &mut NativeOptions,
|
native_options: &mut NativeOptions,
|
||||||
) -> Result<(GlutinWindowContext, egui_glow::Painter)> {
|
) -> Result<(GlutinWindowContext, egui_glow::Painter)> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let window_settings = epi_integration::load_window_settings(storage);
|
let window_settings = epi_integration::load_window_settings(storage);
|
||||||
|
|
||||||
let winit_window_builder = epi_integration::viewport_builder(
|
let winit_window_builder = epi_integration::viewport_builder(
|
||||||
@@ -172,7 +171,7 @@ impl<'app> GlowWinitApp<'app> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let gl = unsafe {
|
let gl = unsafe {
|
||||||
crate::profile_scope!("glow::Context::from_loader_function");
|
profiling::scope!("glow::Context::from_loader_function");
|
||||||
Arc::new(glow::Context::from_loader_function(|s| {
|
Arc::new(glow::Context::from_loader_function(|s| {
|
||||||
let s = std::ffi::CString::new(s)
|
let s = std::ffi::CString::new(s)
|
||||||
.expect("failed to construct C string from string for gl proc address");
|
.expect("failed to construct C string from string for gl proc address");
|
||||||
@@ -195,7 +194,7 @@ impl<'app> GlowWinitApp<'app> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
event_loop: &ActiveEventLoop,
|
event_loop: &ActiveEventLoop,
|
||||||
) -> Result<&mut GlowWinitRunning<'app>> {
|
) -> Result<&mut GlowWinitRunning<'app>> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let storage = if let Some(file) = &self.native_options.persistence_path {
|
let storage = if let Some(file) = &self.native_options.persistence_path {
|
||||||
epi_integration::create_storage_with_file(file)
|
epi_integration::create_storage_with_file(file)
|
||||||
@@ -308,7 +307,7 @@ impl<'app> GlowWinitApp<'app> {
|
|||||||
raw_display_handle: window.display_handle().map(|h| h.as_raw()),
|
raw_display_handle: window.display_handle().map(|h| h.as_raw()),
|
||||||
raw_window_handle: window.window_handle().map(|h| h.as_raw()),
|
raw_window_handle: window.window_handle().map(|h| h.as_raw()),
|
||||||
};
|
};
|
||||||
crate::profile_scope!("app_creator");
|
profiling::scope!("app_creator");
|
||||||
app_creator(&cc).map_err(crate::Error::AppCreation)?
|
app_creator(&cc).map_err(crate::Error::AppCreation)?
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -369,7 +368,7 @@ impl<'app> WinitApp for GlowWinitApp<'app> {
|
|||||||
|
|
||||||
fn save_and_destroy(&mut self) {
|
fn save_and_destroy(&mut self) {
|
||||||
if let Some(mut running) = self.running.take() {
|
if let Some(mut running) = self.running.take() {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
running.integration.save(
|
running.integration.save(
|
||||||
running.app.as_mut(),
|
running.app.as_mut(),
|
||||||
@@ -486,7 +485,7 @@ impl<'app> GlowWinitRunning<'app> {
|
|||||||
event_loop: &ActiveEventLoop,
|
event_loop: &ActiveEventLoop,
|
||||||
window_id: WindowId,
|
window_id: WindowId,
|
||||||
) -> Result<EventResult> {
|
) -> Result<EventResult> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let Some(viewport_id) = self
|
let Some(viewport_id) = self
|
||||||
.glutin
|
.glutin
|
||||||
@@ -498,8 +497,7 @@ impl<'app> GlowWinitRunning<'app> {
|
|||||||
return Ok(EventResult::Wait);
|
return Ok(EventResult::Wait);
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "puffin")]
|
profiling::finish_frame!();
|
||||||
puffin::GlobalProfiler::lock().new_frame();
|
|
||||||
|
|
||||||
let mut frame_timer = crate::stopwatch::Stopwatch::new();
|
let mut frame_timer = crate::stopwatch::Stopwatch::new();
|
||||||
frame_timer.start();
|
frame_timer.start();
|
||||||
@@ -698,7 +696,7 @@ impl<'app> GlowWinitRunning<'app> {
|
|||||||
{
|
{
|
||||||
// vsync - don't count as frame-time:
|
// vsync - don't count as frame-time:
|
||||||
frame_timer.pause();
|
frame_timer.pause();
|
||||||
crate::profile_scope!("swap_buffers");
|
profiling::scope!("swap_buffers");
|
||||||
let context = current_gl_context
|
let context = current_gl_context
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(egui_glow::PainterError::from(
|
.ok_or(egui_glow::PainterError::from(
|
||||||
@@ -726,7 +724,7 @@ impl<'app> GlowWinitRunning<'app> {
|
|||||||
if window.is_minimized() == Some(true) {
|
if window.is_minimized() == Some(true) {
|
||||||
// On Mac, a minimized Window uses up all CPU:
|
// On Mac, a minimized Window uses up all CPU:
|
||||||
// https://github.com/emilk/egui/issues/325
|
// https://github.com/emilk/egui/issues/325
|
||||||
crate::profile_scope!("minimized_sleep");
|
profiling::scope!("minimized_sleep");
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -857,7 +855,7 @@ fn change_gl_context(
|
|||||||
not_current_gl_context: &mut Option<glutin::context::NotCurrentContext>,
|
not_current_gl_context: &mut Option<glutin::context::NotCurrentContext>,
|
||||||
gl_surface: &glutin::surface::Surface<glutin::surface::WindowSurface>,
|
gl_surface: &glutin::surface::Surface<glutin::surface::WindowSurface>,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if !cfg!(target_os = "windows") {
|
if !cfg!(target_os = "windows") {
|
||||||
// According to https://github.com/emilk/egui/issues/4289
|
// According to https://github.com/emilk/egui/issues/4289
|
||||||
@@ -866,7 +864,7 @@ fn change_gl_context(
|
|||||||
// See https://github.com/emilk/egui/issues/4173
|
// See https://github.com/emilk/egui/issues/4173
|
||||||
|
|
||||||
if let Some(current_gl_context) = current_gl_context {
|
if let Some(current_gl_context) = current_gl_context {
|
||||||
crate::profile_scope!("is_current");
|
profiling::scope!("is_current");
|
||||||
if gl_surface.is_current(current_gl_context) {
|
if gl_surface.is_current(current_gl_context) {
|
||||||
return; // Early-out to save a lot of time.
|
return; // Early-out to save a lot of time.
|
||||||
}
|
}
|
||||||
@@ -876,7 +874,7 @@ fn change_gl_context(
|
|||||||
let not_current = if let Some(not_current_context) = not_current_gl_context.take() {
|
let not_current = if let Some(not_current_context) = not_current_gl_context.take() {
|
||||||
not_current_context
|
not_current_context
|
||||||
} else {
|
} else {
|
||||||
crate::profile_scope!("make_not_current");
|
profiling::scope!("make_not_current");
|
||||||
current_gl_context
|
current_gl_context
|
||||||
.take()
|
.take()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -884,7 +882,7 @@ fn change_gl_context(
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
crate::profile_scope!("make_current");
|
profiling::scope!("make_current");
|
||||||
*current_gl_context = Some(not_current.make_current(gl_surface).unwrap());
|
*current_gl_context = Some(not_current.make_current(gl_surface).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -896,7 +894,7 @@ impl GlutinWindowContext {
|
|||||||
native_options: &NativeOptions,
|
native_options: &NativeOptions,
|
||||||
event_loop: &ActiveEventLoop,
|
event_loop: &ActiveEventLoop,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
// There is a lot of complexity with opengl creation,
|
// There is a lot of complexity with opengl creation,
|
||||||
// so prefer extensive logging to get all the help we can to debug issues.
|
// so prefer extensive logging to get all the help we can to debug issues.
|
||||||
@@ -952,7 +950,7 @@ impl GlutinWindowContext {
|
|||||||
)));
|
)));
|
||||||
|
|
||||||
let (window, gl_config) = {
|
let (window, gl_config) = {
|
||||||
crate::profile_scope!("DisplayBuilder::build");
|
profiling::scope!("DisplayBuilder::build");
|
||||||
|
|
||||||
display_builder
|
display_builder
|
||||||
.build(
|
.build(
|
||||||
@@ -995,7 +993,7 @@ impl GlutinWindowContext {
|
|||||||
.build(glutin_raw_window_handle);
|
.build(glutin_raw_window_handle);
|
||||||
|
|
||||||
let gl_context_result = unsafe {
|
let gl_context_result = unsafe {
|
||||||
crate::profile_scope!("create_context");
|
profiling::scope!("create_context");
|
||||||
gl_config
|
gl_config
|
||||||
.display()
|
.display()
|
||||||
.create_context(&gl_config, &context_attributes)
|
.create_context(&gl_config, &context_attributes)
|
||||||
@@ -1070,7 +1068,7 @@ impl GlutinWindowContext {
|
|||||||
///
|
///
|
||||||
/// Errors will be logged.
|
/// Errors will be logged.
|
||||||
fn initialize_all_windows(&mut self, event_loop: &ActiveEventLoop) {
|
fn initialize_all_windows(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let viewports: Vec<ViewportId> = self.viewports.keys().copied().collect();
|
let viewports: Vec<ViewportId> = self.viewports.keys().copied().collect();
|
||||||
|
|
||||||
@@ -1088,7 +1086,7 @@ impl GlutinWindowContext {
|
|||||||
viewport_id: ViewportId,
|
viewport_id: ViewportId,
|
||||||
event_loop: &ActiveEventLoop,
|
event_loop: &ActiveEventLoop,
|
||||||
) -> Result {
|
) -> Result {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let viewport = self
|
let viewport = self
|
||||||
.viewports
|
.viewports
|
||||||
@@ -1268,7 +1266,7 @@ impl GlutinWindowContext {
|
|||||||
egui_ctx: &egui::Context,
|
egui_ctx: &egui::Context,
|
||||||
viewport_output: &ViewportIdMap<ViewportOutput>,
|
viewport_output: &ViewportIdMap<ViewportOutput>,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
for (
|
for (
|
||||||
viewport_id,
|
viewport_id,
|
||||||
@@ -1329,7 +1327,7 @@ fn initialize_or_update_viewport(
|
|||||||
mut builder: ViewportBuilder,
|
mut builder: ViewportBuilder,
|
||||||
viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
|
viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
|
||||||
) -> &mut Viewport {
|
) -> &mut Viewport {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if builder.icon.is_none() {
|
if builder.icon.is_none() {
|
||||||
// Inherit icon from parent
|
// Inherit icon from parent
|
||||||
@@ -1393,7 +1391,7 @@ fn render_immediate_viewport(
|
|||||||
beginning: Instant,
|
beginning: Instant,
|
||||||
immediate_viewport: ImmediateViewport<'_>,
|
immediate_viewport: ImmediateViewport<'_>,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let ImmediateViewport {
|
let ImmediateViewport {
|
||||||
ids,
|
ids,
|
||||||
@@ -1516,7 +1514,7 @@ fn render_immediate_viewport(
|
|||||||
);
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
crate::profile_scope!("swap_buffers");
|
profiling::scope!("swap_buffers");
|
||||||
if let Err(err) = gl_surface.swap_buffers(current_gl_context) {
|
if let Err(err) = gl_surface.swap_buffers(current_gl_context) {
|
||||||
log::error!("swap_buffers failed: {err}");
|
log::error!("swap_buffers failed: {err}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,25 @@ use crate::{
|
|||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result<EventLoop<UserEvent>> {
|
fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result<EventLoop<UserEvent>> {
|
||||||
crate::profile_function!();
|
#[cfg(target_os = "android")]
|
||||||
|
use winit::platform::android::EventLoopBuilderExtAndroid as _;
|
||||||
|
|
||||||
|
profiling::function_scope!();
|
||||||
let mut builder = winit::event_loop::EventLoop::with_user_event();
|
let mut builder = winit::event_loop::EventLoop::with_user_event();
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
let mut builder =
|
||||||
|
builder.with_android_app(native_options.android_app.take().ok_or_else(|| {
|
||||||
|
crate::Error::AppCreation(Box::from(
|
||||||
|
"`NativeOptions` is missing required `android_app`",
|
||||||
|
))
|
||||||
|
})?);
|
||||||
|
|
||||||
if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) {
|
if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) {
|
||||||
hook(&mut builder);
|
hook(&mut builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::profile_scope!("EventLoopBuilder::build");
|
profiling::scope!("EventLoopBuilder::build");
|
||||||
Ok(builder.build()?)
|
Ok(builder.build()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +186,7 @@ impl<T: WinitApp> WinitAppWrapper<T> {
|
|||||||
|
|
||||||
impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
|
impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
|
||||||
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
|
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
crate::profile_function!("Event::Suspended");
|
profiling::scope!("Event::Suspended");
|
||||||
|
|
||||||
event_loop_context::with_event_loop_context(event_loop, move || {
|
event_loop_context::with_event_loop_context(event_loop, move || {
|
||||||
let event_result = self.winit_app.suspended(event_loop);
|
let event_result = self.winit_app.suspended(event_loop);
|
||||||
@@ -184,7 +195,7 @@ impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
crate::profile_function!("Event::Resumed");
|
profiling::scope!("Event::Resumed");
|
||||||
|
|
||||||
// Nb: Make sure this guard is dropped after this function returns.
|
// Nb: Make sure this guard is dropped after this function returns.
|
||||||
event_loop_context::with_event_loop_context(event_loop, move || {
|
event_loop_context::with_event_loop_context(event_loop, move || {
|
||||||
@@ -208,7 +219,7 @@ impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
|
|||||||
device_id: winit::event::DeviceId,
|
device_id: winit::event::DeviceId,
|
||||||
event: winit::event::DeviceEvent,
|
event: winit::event::DeviceEvent,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!(egui_winit::short_device_event_description(&event));
|
profiling::function_scope!(egui_winit::short_device_event_description(&event));
|
||||||
|
|
||||||
// Nb: Make sure this guard is dropped after this function returns.
|
// Nb: Make sure this guard is dropped after this function returns.
|
||||||
event_loop_context::with_event_loop_context(event_loop, move || {
|
event_loop_context::with_event_loop_context(event_loop, move || {
|
||||||
@@ -218,7 +229,7 @@ impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
|
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
|
||||||
crate::profile_function!(match &event {
|
profiling::function_scope!(match &event {
|
||||||
UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint",
|
UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint",
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest",
|
UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest",
|
||||||
@@ -274,7 +285,7 @@ impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
|
|||||||
window_id: WindowId,
|
window_id: WindowId,
|
||||||
event: winit::event::WindowEvent,
|
event: winit::event::WindowEvent,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!(egui_winit::short_window_event_description(&event));
|
profiling::function_scope!(egui_winit::short_window_event_description(&event));
|
||||||
|
|
||||||
// Nb: Make sure this guard is dropped after this function returns.
|
// Nb: Make sure this guard is dropped after this function returns.
|
||||||
event_loop_context::with_event_loop_context(event_loop, move || {
|
event_loop_context::with_event_loop_context(event_loop, move || {
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ impl<'app> WgpuWinitApp<'app> {
|
|||||||
native_options: NativeOptions,
|
native_options: NativeOptions,
|
||||||
app_creator: AppCreator<'app>,
|
app_creator: AppCreator<'app>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
#[cfg(feature = "__screenshot")]
|
#[cfg(feature = "__screenshot")]
|
||||||
assert!(
|
assert!(
|
||||||
@@ -181,10 +181,10 @@ impl<'app> WgpuWinitApp<'app> {
|
|||||||
window: Window,
|
window: Window,
|
||||||
builder: ViewportBuilder,
|
builder: ViewportBuilder,
|
||||||
) -> crate::Result<&mut WgpuWinitRunning<'app>> {
|
) -> crate::Result<&mut WgpuWinitRunning<'app>> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
#[allow(unsafe_code, unused_mut, unused_unsafe)]
|
#[allow(unsafe_code, unused_mut, unused_unsafe)]
|
||||||
let mut painter = egui_wgpu::winit::Painter::new(
|
let mut painter = egui_wgpu::winit::Painter::new(
|
||||||
|
egui_ctx.clone(),
|
||||||
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 _,
|
||||||
egui_wgpu::depth_format_from_bits(
|
egui_wgpu::depth_format_from_bits(
|
||||||
@@ -198,7 +198,7 @@ impl<'app> WgpuWinitApp<'app> {
|
|||||||
let window = Arc::new(window);
|
let window = Arc::new(window);
|
||||||
|
|
||||||
{
|
{
|
||||||
crate::profile_scope!("set_window");
|
profiling::scope!("set_window");
|
||||||
pollster::block_on(painter.set_window(ViewportId::ROOT, Some(window.clone())))?;
|
pollster::block_on(painter.set_window(ViewportId::ROOT, Some(window.clone())))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ impl<'app> WgpuWinitApp<'app> {
|
|||||||
raw_window_handle: window.window_handle().map(|h| h.as_raw()),
|
raw_window_handle: window.window_handle().map(|h| h.as_raw()),
|
||||||
};
|
};
|
||||||
let app = {
|
let app = {
|
||||||
crate::profile_scope!("user_app_creator");
|
profiling::scope!("user_app_creator");
|
||||||
app_creator(&cc).map_err(crate::Error::AppCreation)?
|
app_creator(&cc).map_err(crate::Error::AppCreation)?
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -489,7 +489,7 @@ impl<'app> WinitApp for WgpuWinitApp<'app> {
|
|||||||
|
|
||||||
impl<'app> WgpuWinitRunning<'app> {
|
impl<'app> WgpuWinitRunning<'app> {
|
||||||
fn save_and_destroy(&mut self) {
|
fn save_and_destroy(&mut self) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let mut shared = self.shared.borrow_mut();
|
let mut shared = self.shared.borrow_mut();
|
||||||
if let Some(Viewport { window, .. }) = shared.viewports.get(&ViewportId::ROOT) {
|
if let Some(Viewport { window, .. }) = shared.viewports.get(&ViewportId::ROOT) {
|
||||||
@@ -507,7 +507,7 @@ impl<'app> WgpuWinitRunning<'app> {
|
|||||||
|
|
||||||
/// This is called both for the root viewport, and all deferred viewports
|
/// This is called both for the root viewport, and all deferred viewports
|
||||||
fn run_ui_and_paint(&mut self, window_id: WindowId) -> Result<EventResult> {
|
fn run_ui_and_paint(&mut self, window_id: WindowId) -> Result<EventResult> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let Some(viewport_id) = self
|
let Some(viewport_id) = self
|
||||||
.shared
|
.shared
|
||||||
@@ -519,8 +519,7 @@ impl<'app> WgpuWinitRunning<'app> {
|
|||||||
return Ok(EventResult::Wait);
|
return Ok(EventResult::Wait);
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "puffin")]
|
profiling::finish_frame!();
|
||||||
puffin::GlobalProfiler::lock().new_frame();
|
|
||||||
|
|
||||||
let Self {
|
let Self {
|
||||||
app,
|
app,
|
||||||
@@ -532,7 +531,7 @@ impl<'app> WgpuWinitRunning<'app> {
|
|||||||
frame_timer.start();
|
frame_timer.start();
|
||||||
|
|
||||||
let (viewport_ui_cb, raw_input) = {
|
let (viewport_ui_cb, raw_input) = {
|
||||||
crate::profile_scope!("Prepare");
|
profiling::scope!("Prepare");
|
||||||
let mut shared_lock = shared.borrow_mut();
|
let mut shared_lock = shared.borrow_mut();
|
||||||
|
|
||||||
let SharedState {
|
let SharedState {
|
||||||
@@ -576,7 +575,7 @@ impl<'app> WgpuWinitRunning<'app> {
|
|||||||
egui_winit::update_viewport_info(info, &integration.egui_ctx, window, false);
|
egui_winit::update_viewport_info(info, &integration.egui_ctx, window, false);
|
||||||
|
|
||||||
{
|
{
|
||||||
crate::profile_scope!("set_window");
|
profiling::scope!("set_window");
|
||||||
pollster::block_on(painter.set_window(viewport_id, Some(window.clone())))?;
|
pollster::block_on(painter.set_window(viewport_id, Some(window.clone())))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -593,6 +592,8 @@ impl<'app> WgpuWinitRunning<'app> {
|
|||||||
.map(|(id, viewport)| (*id, viewport.info.clone()))
|
.map(|(id, viewport)| (*id, viewport.info.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
painter.handle_screenshots(&mut raw_input.events);
|
||||||
|
|
||||||
(viewport_ui_cb, raw_input)
|
(viewport_ui_cb, raw_input)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -652,37 +653,14 @@ impl<'app> WgpuWinitRunning<'app> {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let screenshot_requested = !screenshot_commands.is_empty();
|
let vsync_secs = painter.paint_and_update_textures(
|
||||||
let (vsync_secs, screenshot) = painter.paint_and_update_textures(
|
|
||||||
viewport_id,
|
viewport_id,
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
app.clear_color(&egui_ctx.style().visuals),
|
app.clear_color(&egui_ctx.style().visuals),
|
||||||
&clipped_primitives,
|
&clipped_primitives,
|
||||||
&textures_delta,
|
&textures_delta,
|
||||||
screenshot_requested,
|
screenshot_commands,
|
||||||
);
|
);
|
||||||
match (screenshot_requested, screenshot) {
|
|
||||||
(false, None) => {}
|
|
||||||
(true, Some(screenshot)) => {
|
|
||||||
let screenshot = Arc::new(screenshot);
|
|
||||||
for user_data in screenshot_commands {
|
|
||||||
egui_winit
|
|
||||||
.egui_input_mut()
|
|
||||||
.events
|
|
||||||
.push(egui::Event::Screenshot {
|
|
||||||
viewport_id,
|
|
||||||
user_data,
|
|
||||||
image: screenshot.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(true, None) => {
|
|
||||||
log::error!("Bug in egui_wgpu: screenshot requested, but no screenshot was taken");
|
|
||||||
}
|
|
||||||
(false, Some(_)) => {
|
|
||||||
log::warn!("Bug in egui_wgpu: Got screenshot without requesting it");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for action in viewport.actions_requested.drain() {
|
for action in viewport.actions_requested.drain() {
|
||||||
match action {
|
match action {
|
||||||
@@ -739,7 +717,7 @@ impl<'app> WgpuWinitRunning<'app> {
|
|||||||
if window.is_minimized() == Some(true) {
|
if window.is_minimized() == Some(true) {
|
||||||
// On Mac, a minimized Window uses up all CPU:
|
// On Mac, a minimized Window uses up all CPU:
|
||||||
// https://github.com/emilk/egui/issues/325
|
// https://github.com/emilk/egui/issues/325
|
||||||
crate::profile_scope!("minimized_sleep");
|
profiling::scope!("minimized_sleep");
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -866,7 +844,7 @@ impl Viewport {
|
|||||||
return; // we already have one
|
return; // we already have one
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let viewport_id = self.ids.this;
|
let viewport_id = self.ids.this;
|
||||||
|
|
||||||
@@ -907,7 +885,7 @@ fn create_window(
|
|||||||
storage: Option<&dyn Storage>,
|
storage: Option<&dyn Storage>,
|
||||||
native_options: &mut NativeOptions,
|
native_options: &mut NativeOptions,
|
||||||
) -> Result<(Window, ViewportBuilder), winit::error::OsError> {
|
) -> Result<(Window, ViewportBuilder), winit::error::OsError> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let window_settings = epi_integration::load_window_settings(storage);
|
let window_settings = epi_integration::load_window_settings(storage);
|
||||||
let viewport_builder = epi_integration::viewport_builder(
|
let viewport_builder = epi_integration::viewport_builder(
|
||||||
@@ -928,7 +906,7 @@ fn render_immediate_viewport(
|
|||||||
shared: &RefCell<SharedState>,
|
shared: &RefCell<SharedState>,
|
||||||
immediate_viewport: ImmediateViewport<'_>,
|
immediate_viewport: ImmediateViewport<'_>,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let ImmediateViewport {
|
let ImmediateViewport {
|
||||||
ids,
|
ids,
|
||||||
@@ -1008,7 +986,7 @@ fn render_immediate_viewport(
|
|||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
crate::profile_scope!("set_window");
|
profiling::scope!("set_window");
|
||||||
if let Err(err) = pollster::block_on(painter.set_window(ids.this, Some(window.clone()))) {
|
if let Err(err) = pollster::block_on(painter.set_window(ids.this, Some(window.clone()))) {
|
||||||
log::error!(
|
log::error!(
|
||||||
"when rendering viewport_id={:?}, set_window Error {err}",
|
"when rendering viewport_id={:?}, set_window Error {err}",
|
||||||
@@ -1024,7 +1002,7 @@ fn render_immediate_viewport(
|
|||||||
[0.0, 0.0, 0.0, 0.0],
|
[0.0, 0.0, 0.0, 0.0],
|
||||||
&clipped_primitives,
|
&clipped_primitives,
|
||||||
&textures_delta,
|
&textures_delta,
|
||||||
false,
|
vec![],
|
||||||
);
|
);
|
||||||
|
|
||||||
egui_winit.handle_platform_output(window, platform_output);
|
egui_winit.handle_platform_output(window, platform_output);
|
||||||
@@ -1116,7 +1094,7 @@ fn initialize_or_update_viewport<'a>(
|
|||||||
viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
|
viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
|
||||||
painter: &mut egui_wgpu::winit::Painter,
|
painter: &mut egui_wgpu::winit::Painter,
|
||||||
) -> &'a mut Viewport {
|
) -> &'a mut Viewport {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if builder.icon.is_none() {
|
if builder.icon.is_none() {
|
||||||
// Inherit icon from parent
|
// Inherit icon from parent
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use egui_winit::accesskit_winit;
|
|||||||
|
|
||||||
/// Create an egui context, restoring it from storage if possible.
|
/// Create an egui context, restoring it from storage if possible.
|
||||||
pub fn create_egui_context(storage: Option<&dyn crate::Storage>) -> egui::Context {
|
pub fn create_egui_context(storage: Option<&dyn crate::Storage>) -> egui::Context {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
pub const IS_DESKTOP: bool = cfg!(any(
|
pub const IS_DESKTOP: bool = cfg!(any(
|
||||||
target_os = "freebsd",
|
target_os = "freebsd",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use egui::TexturesDelta;
|
use egui::{TexturesDelta, UserData, ViewportCommand};
|
||||||
|
|
||||||
use crate::{epi, App};
|
use crate::{epi, App};
|
||||||
|
|
||||||
@@ -16,6 +16,10 @@ pub struct AppRunner {
|
|||||||
last_save_time: f64,
|
last_save_time: f64,
|
||||||
pub(crate) text_agent: TextAgent,
|
pub(crate) text_agent: TextAgent,
|
||||||
|
|
||||||
|
// If not empty, the painter should capture n frames from now.
|
||||||
|
// zero means capture the exact next frame.
|
||||||
|
screenshot_commands_with_frame_delay: Vec<(UserData, usize)>,
|
||||||
|
|
||||||
// Output for the last run:
|
// Output for the last run:
|
||||||
textures_delta: TexturesDelta,
|
textures_delta: TexturesDelta,
|
||||||
clipped_primitives: Option<Vec<egui::ClippedPrimitive>>,
|
clipped_primitives: Option<Vec<egui::ClippedPrimitive>>,
|
||||||
@@ -36,7 +40,8 @@ impl AppRunner {
|
|||||||
app_creator: epi::AppCreator<'static>,
|
app_creator: epi::AppCreator<'static>,
|
||||||
text_agent: TextAgent,
|
text_agent: TextAgent,
|
||||||
) -> Result<Self, String> {
|
) -> Result<Self, String> {
|
||||||
let painter = super::ActiveWebPainter::new(canvas, &web_options).await?;
|
let egui_ctx = egui::Context::default();
|
||||||
|
let painter = super::ActiveWebPainter::new(egui_ctx.clone(), canvas, &web_options).await?;
|
||||||
|
|
||||||
let info = epi::IntegrationInfo {
|
let info = epi::IntegrationInfo {
|
||||||
web_info: epi::WebInfo {
|
web_info: epi::WebInfo {
|
||||||
@@ -47,7 +52,6 @@ impl AppRunner {
|
|||||||
};
|
};
|
||||||
let storage = LocalStorage::default();
|
let storage = LocalStorage::default();
|
||||||
|
|
||||||
let egui_ctx = egui::Context::default();
|
|
||||||
egui_ctx.set_os(egui::os::OperatingSystem::from_user_agent(
|
egui_ctx.set_os(egui::os::OperatingSystem::from_user_agent(
|
||||||
&super::user_agent().unwrap_or_default(),
|
&super::user_agent().unwrap_or_default(),
|
||||||
));
|
));
|
||||||
@@ -110,6 +114,7 @@ impl AppRunner {
|
|||||||
needs_repaint,
|
needs_repaint,
|
||||||
last_save_time: now_sec(),
|
last_save_time: now_sec(),
|
||||||
text_agent,
|
text_agent,
|
||||||
|
screenshot_commands_with_frame_delay: vec![],
|
||||||
textures_delta: Default::default(),
|
textures_delta: Default::default(),
|
||||||
clipped_primitives: None,
|
clipped_primitives: None,
|
||||||
};
|
};
|
||||||
@@ -205,6 +210,8 @@ impl AppRunner {
|
|||||||
pub fn logic(&mut self) {
|
pub fn logic(&mut self) {
|
||||||
// We sometimes miss blur/focus events due to the text agent, so let's just poll each frame:
|
// We sometimes miss blur/focus events due to the text agent, so let's just poll each frame:
|
||||||
self.update_focus();
|
self.update_focus();
|
||||||
|
// We might have received a screenshot
|
||||||
|
self.painter.handle_screenshots(&mut self.input.raw.events);
|
||||||
|
|
||||||
let canvas_size = super::canvas_size_in_points(self.canvas(), self.egui_ctx());
|
let canvas_size = super::canvas_size_in_points(self.canvas(), self.egui_ctx());
|
||||||
let mut raw_input = self.input.new_frame(canvas_size);
|
let mut raw_input = self.input.new_frame(canvas_size);
|
||||||
@@ -225,14 +232,22 @@ impl AppRunner {
|
|||||||
if viewport_output.len() > 1 {
|
if viewport_output.len() > 1 {
|
||||||
log::warn!("Multiple viewports not yet supported on the web");
|
log::warn!("Multiple viewports not yet supported on the web");
|
||||||
}
|
}
|
||||||
for viewport_output in viewport_output.values() {
|
for (_viewport_id, viewport_output) in viewport_output {
|
||||||
for command in &viewport_output.commands {
|
for command in viewport_output.commands {
|
||||||
|
match command {
|
||||||
|
ViewportCommand::Screenshot(user_data) => {
|
||||||
|
self.screenshot_commands_with_frame_delay
|
||||||
|
.push((user_data, 1));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
// TODO(emilk): handle some of the commands
|
// TODO(emilk): handle some of the commands
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"Unhandled egui viewport command: {command:?} - not implemented in web backend"
|
"Unhandled egui viewport command: {command:?} - not implemented in web backend"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.handle_platform_output(platform_output);
|
self.handle_platform_output(platform_output);
|
||||||
self.textures_delta.append(textures_delta);
|
self.textures_delta.append(textures_delta);
|
||||||
@@ -245,11 +260,27 @@ impl AppRunner {
|
|||||||
let clipped_primitives = std::mem::take(&mut self.clipped_primitives);
|
let clipped_primitives = std::mem::take(&mut self.clipped_primitives);
|
||||||
|
|
||||||
if let Some(clipped_primitives) = clipped_primitives {
|
if let Some(clipped_primitives) = clipped_primitives {
|
||||||
|
let mut screenshot_commands = vec![];
|
||||||
|
self.screenshot_commands_with_frame_delay
|
||||||
|
.retain_mut(|(user_data, frame_delay)| {
|
||||||
|
if *frame_delay == 0 {
|
||||||
|
screenshot_commands.push(user_data.clone());
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
*frame_delay -= 1;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if !self.screenshot_commands_with_frame_delay.is_empty() {
|
||||||
|
self.egui_ctx().request_repaint();
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(err) = self.painter.paint_and_update_textures(
|
if let Err(err) = self.painter.paint_and_update_textures(
|
||||||
self.app.clear_color(&self.egui_ctx.style().visuals),
|
self.app.clear_color(&self.egui_ctx.style().visuals),
|
||||||
&clipped_primitives,
|
&clipped_primitives,
|
||||||
self.egui_ctx.pixels_per_point(),
|
self.egui_ctx.pixels_per_point(),
|
||||||
&textures_delta,
|
&textures_delta,
|
||||||
|
screenshot_commands,
|
||||||
) {
|
) {
|
||||||
log::error!("Failed to paint: {}", super::string_from_js_value(&err));
|
log::error!("Failed to paint: {}", super::string_from_js_value(&err));
|
||||||
}
|
}
|
||||||
@@ -260,7 +291,7 @@ impl AppRunner {
|
|||||||
self.frame.info.cpu_usage = Some(cpu_usage_seconds);
|
self.frame.info.cpu_usage = Some(cpu_usage_seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
|
fn handle_platform_output(&self, platform_output: egui::PlatformOutput) {
|
||||||
#[cfg(feature = "web_screen_reader")]
|
#[cfg(feature = "web_screen_reader")]
|
||||||
if self.egui_ctx.options(|o| o.screen_reader) {
|
if self.egui_ctx.options(|o| o.screen_reader) {
|
||||||
super::screen_reader::speak(&platform_output.events_description());
|
super::screen_reader::speak(&platform_output.events_description());
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use egui::{Event, UserData};
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
|
|
||||||
/// Renderer for a browser canvas.
|
/// Renderer for a browser canvas.
|
||||||
@@ -16,14 +17,19 @@ pub(crate) trait WebPainter {
|
|||||||
fn max_texture_side(&self) -> usize;
|
fn max_texture_side(&self) -> usize;
|
||||||
|
|
||||||
/// Update all internal textures and paint gui.
|
/// Update all internal textures and paint gui.
|
||||||
|
/// When `capture` isn't empty, the rendered screen should be captured.
|
||||||
|
/// Once the screenshot is ready, the screenshot should be returned via [`Self::handle_screenshots`].
|
||||||
fn paint_and_update_textures(
|
fn paint_and_update_textures(
|
||||||
&mut self,
|
&mut self,
|
||||||
clear_color: [f32; 4],
|
clear_color: [f32; 4],
|
||||||
clipped_primitives: &[egui::ClippedPrimitive],
|
clipped_primitives: &[egui::ClippedPrimitive],
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
textures_delta: &egui::TexturesDelta,
|
textures_delta: &egui::TexturesDelta,
|
||||||
|
capture: Vec<UserData>,
|
||||||
) -> Result<(), JsValue>;
|
) -> Result<(), JsValue>;
|
||||||
|
|
||||||
|
fn handle_screenshots(&mut self, events: &mut Vec<Event>);
|
||||||
|
|
||||||
/// Destroy all resources.
|
/// Destroy all resources.
|
||||||
fn destroy(&mut self);
|
fn destroy(&mut self);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
use egui::{Event, UserData, ViewportId};
|
||||||
|
use egui_glow::glow;
|
||||||
|
use std::sync::Arc;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
use web_sys::HtmlCanvasElement;
|
use web_sys::HtmlCanvasElement;
|
||||||
|
|
||||||
use egui_glow::glow;
|
|
||||||
|
|
||||||
use crate::{WebGlContextOption, WebOptions};
|
use crate::{WebGlContextOption, WebOptions};
|
||||||
|
|
||||||
use super::web_painter::WebPainter;
|
use super::web_painter::WebPainter;
|
||||||
@@ -11,6 +12,7 @@ use super::web_painter::WebPainter;
|
|||||||
pub(crate) struct WebPainterGlow {
|
pub(crate) struct WebPainterGlow {
|
||||||
canvas: HtmlCanvasElement,
|
canvas: HtmlCanvasElement,
|
||||||
painter: egui_glow::Painter,
|
painter: egui_glow::Painter,
|
||||||
|
screenshots: Vec<(egui::ColorImage, Vec<UserData>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebPainterGlow {
|
impl WebPainterGlow {
|
||||||
@@ -18,7 +20,11 @@ impl WebPainterGlow {
|
|||||||
self.painter.gl()
|
self.painter.gl()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new(canvas: HtmlCanvasElement, options: &WebOptions) -> Result<Self, String> {
|
pub async fn new(
|
||||||
|
_ctx: egui::Context,
|
||||||
|
canvas: HtmlCanvasElement,
|
||||||
|
options: &WebOptions,
|
||||||
|
) -> Result<Self, String> {
|
||||||
let (gl, shader_prefix) =
|
let (gl, shader_prefix) =
|
||||||
init_glow_context_from_canvas(&canvas, options.webgl_context_option)?;
|
init_glow_context_from_canvas(&canvas, options.webgl_context_option)?;
|
||||||
#[allow(clippy::arc_with_non_send_sync)]
|
#[allow(clippy::arc_with_non_send_sync)]
|
||||||
@@ -27,7 +33,11 @@ impl WebPainterGlow {
|
|||||||
let painter = egui_glow::Painter::new(gl, shader_prefix, None, options.dithering)
|
let painter = egui_glow::Painter::new(gl, shader_prefix, None, options.dithering)
|
||||||
.map_err(|err| format!("Error starting glow painter: {err}"))?;
|
.map_err(|err| format!("Error starting glow painter: {err}"))?;
|
||||||
|
|
||||||
Ok(Self { canvas, painter })
|
Ok(Self {
|
||||||
|
canvas,
|
||||||
|
painter,
|
||||||
|
screenshots: Vec::new(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +56,7 @@ impl WebPainter for WebPainterGlow {
|
|||||||
clipped_primitives: &[egui::ClippedPrimitive],
|
clipped_primitives: &[egui::ClippedPrimitive],
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
textures_delta: &egui::TexturesDelta,
|
textures_delta: &egui::TexturesDelta,
|
||||||
|
capture: Vec<UserData>,
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
||||||
|
|
||||||
@@ -57,6 +68,11 @@ impl WebPainter for WebPainterGlow {
|
|||||||
self.painter
|
self.painter
|
||||||
.paint_primitives(canvas_dimension, pixels_per_point, clipped_primitives);
|
.paint_primitives(canvas_dimension, pixels_per_point, clipped_primitives);
|
||||||
|
|
||||||
|
if !capture.is_empty() {
|
||||||
|
let image = self.painter.read_screen_rgba(canvas_dimension);
|
||||||
|
self.screenshots.push((image, capture));
|
||||||
|
}
|
||||||
|
|
||||||
for &id in &textures_delta.free {
|
for &id in &textures_delta.free {
|
||||||
self.painter.free_texture(id);
|
self.painter.free_texture(id);
|
||||||
}
|
}
|
||||||
@@ -67,6 +83,19 @@ impl WebPainter for WebPainterGlow {
|
|||||||
fn destroy(&mut self) {
|
fn destroy(&mut self) {
|
||||||
self.painter.destroy();
|
self.painter.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_screenshots(&mut self, events: &mut Vec<Event>) {
|
||||||
|
for (image, data) in self.screenshots.drain(..) {
|
||||||
|
let image = Arc::new(image);
|
||||||
|
for data in data {
|
||||||
|
events.push(Event::Screenshot {
|
||||||
|
viewport_id: ViewportId::default(),
|
||||||
|
image: image.clone(),
|
||||||
|
user_data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns glow context and shader prefix.
|
/// Returns glow context and shader prefix.
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::web_painter::WebPainter;
|
||||||
|
use crate::WebOptions;
|
||||||
|
use egui::{Event, UserData, ViewportId};
|
||||||
|
use egui_wgpu::capture::{capture_channel, CaptureReceiver, CaptureSender, CaptureState};
|
||||||
|
use egui_wgpu::{RenderState, SurfaceErrorAction, WgpuSetup};
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
use web_sys::HtmlCanvasElement;
|
use web_sys::HtmlCanvasElement;
|
||||||
|
|
||||||
use egui_wgpu::{RenderState, SurfaceErrorAction, WgpuSetup};
|
|
||||||
|
|
||||||
use crate::WebOptions;
|
|
||||||
|
|
||||||
use super::web_painter::WebPainter;
|
|
||||||
|
|
||||||
pub(crate) struct WebPainterWgpu {
|
pub(crate) struct WebPainterWgpu {
|
||||||
canvas: HtmlCanvasElement,
|
canvas: HtmlCanvasElement,
|
||||||
surface: wgpu::Surface<'static>,
|
surface: wgpu::Surface<'static>,
|
||||||
@@ -17,6 +16,10 @@ pub(crate) struct WebPainterWgpu {
|
|||||||
on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction>,
|
on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction>,
|
||||||
depth_format: Option<wgpu::TextureFormat>,
|
depth_format: Option<wgpu::TextureFormat>,
|
||||||
depth_texture_view: Option<wgpu::TextureView>,
|
depth_texture_view: Option<wgpu::TextureView>,
|
||||||
|
screen_capture_state: Option<CaptureState>,
|
||||||
|
capture_tx: CaptureSender,
|
||||||
|
capture_rx: CaptureReceiver,
|
||||||
|
ctx: egui::Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebPainterWgpu {
|
impl WebPainterWgpu {
|
||||||
@@ -54,6 +57,7 @@ impl WebPainterWgpu {
|
|||||||
|
|
||||||
#[allow(unused)] // only used if `wgpu` is the only active feature.
|
#[allow(unused)] // only used if `wgpu` is the only active feature.
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
|
ctx: egui::Context,
|
||||||
canvas: web_sys::HtmlCanvasElement,
|
canvas: web_sys::HtmlCanvasElement,
|
||||||
options: &WebOptions,
|
options: &WebOptions,
|
||||||
) -> Result<Self, String> {
|
) -> Result<Self, String> {
|
||||||
@@ -119,17 +123,21 @@ impl WebPainterWgpu {
|
|||||||
.await
|
.await
|
||||||
.map_err(|err| err.to_string())?;
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
let default_configuration = surface
|
||||||
|
.get_default_config(&render_state.adapter, 0, 0) // Width/height is set later.
|
||||||
|
.ok_or("The surface isn't supported by this adapter")?;
|
||||||
|
|
||||||
let surface_configuration = wgpu::SurfaceConfiguration {
|
let surface_configuration = wgpu::SurfaceConfiguration {
|
||||||
format: render_state.target_format,
|
format: render_state.target_format,
|
||||||
present_mode: options.wgpu_options.present_mode,
|
present_mode: options.wgpu_options.present_mode,
|
||||||
view_formats: vec![render_state.target_format],
|
view_formats: vec![render_state.target_format],
|
||||||
..surface
|
..default_configuration
|
||||||
.get_default_config(&render_state.adapter, 0, 0) // Width/height is set later.
|
|
||||||
.ok_or("The surface isn't supported by this adapter")?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
log::debug!("wgpu painter initialized.");
|
log::debug!("wgpu painter initialized.");
|
||||||
|
|
||||||
|
let (capture_tx, capture_rx) = capture_channel();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
canvas,
|
canvas,
|
||||||
render_state: Some(render_state),
|
render_state: Some(render_state),
|
||||||
@@ -138,6 +146,10 @@ impl WebPainterWgpu {
|
|||||||
depth_format,
|
depth_format,
|
||||||
depth_texture_view: None,
|
depth_texture_view: None,
|
||||||
on_surface_error: options.wgpu_options.on_surface_error.clone(),
|
on_surface_error: options.wgpu_options.on_surface_error.clone(),
|
||||||
|
screen_capture_state: None,
|
||||||
|
capture_tx,
|
||||||
|
capture_rx,
|
||||||
|
ctx,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +171,10 @@ impl WebPainter for WebPainterWgpu {
|
|||||||
clipped_primitives: &[egui::ClippedPrimitive],
|
clipped_primitives: &[egui::ClippedPrimitive],
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
textures_delta: &egui::TexturesDelta,
|
textures_delta: &egui::TexturesDelta,
|
||||||
|
capture_data: Vec<UserData>,
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
|
let capture = !capture_data.is_empty();
|
||||||
|
|
||||||
let size_in_pixels = [self.canvas.width(), self.canvas.height()];
|
let size_in_pixels = [self.canvas.width(), self.canvas.height()];
|
||||||
|
|
||||||
let Some(render_state) = &self.render_state else {
|
let Some(render_state) = &self.render_state else {
|
||||||
@@ -203,7 +218,7 @@ impl WebPainter for WebPainterWgpu {
|
|||||||
|
|
||||||
// Resize surface if needed
|
// Resize surface if needed
|
||||||
let is_zero_sized_surface = size_in_pixels[0] == 0 || size_in_pixels[1] == 0;
|
let is_zero_sized_surface = size_in_pixels[0] == 0 || size_in_pixels[1] == 0;
|
||||||
let frame = if is_zero_sized_surface {
|
let frame_and_capture_buffer = if is_zero_sized_surface {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
if size_in_pixels[0] != self.surface_configuration.width
|
if size_in_pixels[0] != self.surface_configuration.width
|
||||||
@@ -220,7 +235,7 @@ impl WebPainter for WebPainterWgpu {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame = match self.surface.get_current_texture() {
|
let output_frame = match self.surface.get_current_texture() {
|
||||||
Ok(frame) => frame,
|
Ok(frame) => frame,
|
||||||
Err(err) => match (*self.on_surface_error)(err) {
|
Err(err) => match (*self.on_surface_error)(err) {
|
||||||
SurfaceErrorAction::RecreateSurface => {
|
SurfaceErrorAction::RecreateSurface => {
|
||||||
@@ -236,12 +251,23 @@ impl WebPainter for WebPainterWgpu {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let renderer = render_state.renderer.read();
|
let renderer = render_state.renderer.read();
|
||||||
let frame_view = frame
|
|
||||||
.texture
|
let target_texture = if capture {
|
||||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
let capture_state = self.screen_capture_state.get_or_insert_with(|| {
|
||||||
|
CaptureState::new(&render_state.device, &output_frame.texture)
|
||||||
|
});
|
||||||
|
capture_state.update(&render_state.device, &output_frame.texture);
|
||||||
|
|
||||||
|
&capture_state.texture
|
||||||
|
} else {
|
||||||
|
&output_frame.texture
|
||||||
|
};
|
||||||
|
let target_view =
|
||||||
|
target_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: &frame_view,
|
view: &target_view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||||
@@ -280,7 +306,19 @@ impl WebPainter for WebPainterWgpu {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(frame)
|
let mut capture_buffer = None;
|
||||||
|
|
||||||
|
if capture {
|
||||||
|
if let Some(capture_state) = &mut self.screen_capture_state {
|
||||||
|
capture_buffer = Some(capture_state.copy_textures(
|
||||||
|
&render_state.device,
|
||||||
|
&output_frame,
|
||||||
|
&mut encoder,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((output_frame, capture_buffer))
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -295,13 +333,38 @@ impl WebPainter for WebPainterWgpu {
|
|||||||
.queue
|
.queue
|
||||||
.submit(user_cmd_bufs.into_iter().chain([encoder.finish()]));
|
.submit(user_cmd_bufs.into_iter().chain([encoder.finish()]));
|
||||||
|
|
||||||
if let Some(frame) = frame {
|
if let Some((frame, capture_buffer)) = frame_and_capture_buffer {
|
||||||
|
if let Some(capture_buffer) = capture_buffer {
|
||||||
|
if let Some(capture_state) = &self.screen_capture_state {
|
||||||
|
capture_state.read_screen_rgba(
|
||||||
|
self.ctx.clone(),
|
||||||
|
capture_buffer,
|
||||||
|
capture_data,
|
||||||
|
self.capture_tx.clone(),
|
||||||
|
ViewportId::ROOT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
frame.present();
|
frame.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_screenshots(&mut self, events: &mut Vec<Event>) {
|
||||||
|
for (viewport_id, user_data, screenshot) in self.capture_rx.try_iter() {
|
||||||
|
let screenshot = Arc::new(screenshot);
|
||||||
|
for data in user_data {
|
||||||
|
events.push(Event::Screenshot {
|
||||||
|
viewport_id,
|
||||||
|
user_data: data,
|
||||||
|
image: screenshot.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn destroy(&mut self) {
|
fn destroy(&mut self) {
|
||||||
self.render_state = None;
|
self.render_state = None;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ This file is updated upon each release.
|
|||||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.30.0 - 2024-12-16
|
||||||
|
* Fix docs.rs build [#5204](https://github.com/emilk/egui/pull/5204) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||||
|
* Free textures after submitting queue instead of before with wgpu renderer [#5226](https://github.com/emilk/egui/pull/5226) by [@Rusty-Cube](https://github.com/Rusty-Cube)
|
||||||
|
* Add option to initialize with existing wgpu instance/adapter/device/queue [#5319](https://github.com/emilk/egui/pull/5319) by [@ArthurBrussee](https://github.com/ArthurBrussee)
|
||||||
|
* Updare to `wgpu` 23.0.0 and `wasm-bindgen` to 0.2.95 [#5330](https://github.com/emilk/egui/pull/5330) by [@torokati44](https://github.com/torokati44)
|
||||||
|
* Support wgpu-tracing with same mechanism as wgpu examples [#5450](https://github.com/emilk/egui/pull/5450) by [@EriKWDev](https://github.com/EriKWDev)
|
||||||
|
|
||||||
|
|
||||||
## 0.29.1 - 2024-10-01
|
## 0.29.1 - 2024-10-01
|
||||||
Nothing new
|
Nothing new
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ rustdoc-args = ["--generate-link-to-definition"]
|
|||||||
[features]
|
[features]
|
||||||
default = ["fragile-send-sync-non-atomic-wasm"]
|
default = ["fragile-send-sync-non-atomic-wasm"]
|
||||||
|
|
||||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
|
||||||
puffin = ["dep:puffin"]
|
|
||||||
|
|
||||||
## Enable [`winit`](https://docs.rs/winit) integration. On Linux, requires either `wayland` or `x11`
|
## Enable [`winit`](https://docs.rs/winit) integration. On Linux, requires either `wayland` or `x11`
|
||||||
winit = ["dep:winit", "winit/rwh_06"]
|
winit = ["dep:winit", "winit/rwh_06"]
|
||||||
|
|
||||||
@@ -60,6 +57,7 @@ ahash.workspace = true
|
|||||||
bytemuck.workspace = true
|
bytemuck.workspace = true
|
||||||
document-features.workspace = true
|
document-features.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
profiling.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
type-map.workspace = true
|
type-map.workspace = true
|
||||||
web-time.workspace = true
|
web-time.workspace = true
|
||||||
@@ -68,7 +66,3 @@ wgpu = { workspace = true, features = ["wgsl"] }
|
|||||||
# Optional dependencies:
|
# Optional dependencies:
|
||||||
|
|
||||||
winit = { workspace = true, optional = true, default-features = false }
|
winit = { workspace = true, optional = true, default-features = false }
|
||||||
|
|
||||||
# Native:
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
|
||||||
puffin = { workspace = true, optional = true }
|
|
||||||
|
|||||||
257
crates/egui-wgpu/src/capture.rs
Normal file
257
crates/egui-wgpu/src/capture.rs
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
use egui::{UserData, ViewportId};
|
||||||
|
use epaint::ColorImage;
|
||||||
|
use std::sync::{mpsc, Arc};
|
||||||
|
use wgpu::{BindGroupLayout, MultisampleState, StoreOp};
|
||||||
|
|
||||||
|
/// A texture and a buffer for reading the rendered frame back to the cpu.
|
||||||
|
/// The texture is required since [`wgpu::TextureUsages::COPY_SRC`] is not an allowed
|
||||||
|
/// flag for the surface texture on all platforms. This means that anytime we want to
|
||||||
|
/// capture the frame, we first render it to this texture, and then we can copy it to
|
||||||
|
/// both the surface texture (via a render pass) and the buffer (via a texture to buffer copy),
|
||||||
|
/// from where we can pull it back
|
||||||
|
/// to the cpu.
|
||||||
|
pub struct CaptureState {
|
||||||
|
padding: BufferPadding,
|
||||||
|
pub texture: wgpu::Texture,
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
bind_group: wgpu::BindGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type CaptureReceiver = mpsc::Receiver<(ViewportId, Vec<UserData>, ColorImage)>;
|
||||||
|
pub type CaptureSender = mpsc::Sender<(ViewportId, Vec<UserData>, ColorImage)>;
|
||||||
|
pub use mpsc::channel as capture_channel;
|
||||||
|
|
||||||
|
impl CaptureState {
|
||||||
|
pub fn new(device: &wgpu::Device, surface_texture: &wgpu::Texture) -> Self {
|
||||||
|
let shader = device.create_shader_module(wgpu::include_wgsl!("texture_copy.wgsl"));
|
||||||
|
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("texture_copy"),
|
||||||
|
layout: None,
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("vs_main"),
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
buffers: &[],
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("fs_main"),
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
targets: &[Some(surface_texture.format().into())],
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: MultisampleState::default(),
|
||||||
|
multiview: None,
|
||||||
|
cache: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let bind_group_layout = pipeline.get_bind_group_layout(0);
|
||||||
|
|
||||||
|
let (texture, padding, bind_group) =
|
||||||
|
Self::create_texture(device, surface_texture, &bind_group_layout);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
padding,
|
||||||
|
texture,
|
||||||
|
pipeline,
|
||||||
|
bind_group,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_texture(
|
||||||
|
device: &wgpu::Device,
|
||||||
|
surface_texture: &wgpu::Texture,
|
||||||
|
layout: &BindGroupLayout,
|
||||||
|
) -> (wgpu::Texture, BufferPadding, wgpu::BindGroup) {
|
||||||
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("egui_screen_capture_texture"),
|
||||||
|
size: surface_texture.size(),
|
||||||
|
mip_level_count: surface_texture.mip_level_count(),
|
||||||
|
sample_count: surface_texture.sample_count(),
|
||||||
|
dimension: surface_texture.dimension(),
|
||||||
|
format: surface_texture.format(),
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||||
|
| wgpu::TextureUsages::TEXTURE_BINDING
|
||||||
|
| wgpu::TextureUsages::COPY_SRC,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let padding = BufferPadding::new(surface_texture.width());
|
||||||
|
|
||||||
|
let view = texture.create_view(&Default::default());
|
||||||
|
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout,
|
||||||
|
entries: &[wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&view),
|
||||||
|
}],
|
||||||
|
label: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
(texture, padding, bind_group)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the [`CaptureState`] if the size of the surface texture has changed
|
||||||
|
pub fn update(&mut self, device: &wgpu::Device, texture: &wgpu::Texture) {
|
||||||
|
if self.texture.size() != texture.size() {
|
||||||
|
let (new_texture, padding, bind_group) =
|
||||||
|
Self::create_texture(device, texture, &self.pipeline.get_bind_group_layout(0));
|
||||||
|
self.texture = new_texture;
|
||||||
|
self.padding = padding;
|
||||||
|
self.bind_group = bind_group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles copying from the [`CaptureState`] texture to the surface texture and the buffer.
|
||||||
|
/// Pass the returned buffer to [`CaptureState::read_screen_rgba`] to read the data back to the cpu.
|
||||||
|
pub fn copy_textures(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
output_frame: &wgpu::SurfaceTexture,
|
||||||
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
|
) -> wgpu::Buffer {
|
||||||
|
debug_assert_eq!(
|
||||||
|
self.texture.size(),
|
||||||
|
output_frame.texture.size(),
|
||||||
|
"Texture sizes must match, `CaptureState::update` was probably not called"
|
||||||
|
);
|
||||||
|
|
||||||
|
// It would be more efficient to reuse the Buffer, e.g. via some kind of ring buffer, but
|
||||||
|
// for most screenshot use cases this should be fine. When taking many screenshots (e.g. for a video)
|
||||||
|
// it might make sense to revisit this and implement a more efficient solution.
|
||||||
|
#[allow(clippy::arc_with_non_send_sync)]
|
||||||
|
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("egui_screen_capture_buffer"),
|
||||||
|
size: (self.padding.padded_bytes_per_row * self.texture.height()) as u64,
|
||||||
|
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
let padding = self.padding;
|
||||||
|
let tex = &mut self.texture;
|
||||||
|
|
||||||
|
let tex_extent = tex.size();
|
||||||
|
|
||||||
|
encoder.copy_texture_to_buffer(
|
||||||
|
tex.as_image_copy(),
|
||||||
|
wgpu::ImageCopyBuffer {
|
||||||
|
buffer: &buffer,
|
||||||
|
layout: wgpu::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(padding.padded_bytes_per_row),
|
||||||
|
rows_per_image: None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tex_extent,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("texture_copy"),
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: &output_frame.texture.create_view(&Default::default()),
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||||
|
store: StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
pass.set_pipeline(&self.pipeline);
|
||||||
|
pass.set_bind_group(0, &self.bind_group, &[]);
|
||||||
|
pass.draw(0..3, 0..1);
|
||||||
|
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles copying from the [`CaptureState`] texture to the surface texture and the cpu
|
||||||
|
/// This function is non-blocking and will send the data to the given sender when it's ready.
|
||||||
|
/// Pass in the buffer returned from [`CaptureState::copy_textures`].
|
||||||
|
/// Make sure to call this after the encoder has been submitted.
|
||||||
|
pub fn read_screen_rgba(
|
||||||
|
&self,
|
||||||
|
ctx: egui::Context,
|
||||||
|
buffer: wgpu::Buffer,
|
||||||
|
data: Vec<UserData>,
|
||||||
|
tx: CaptureSender,
|
||||||
|
viewport_id: ViewportId,
|
||||||
|
) {
|
||||||
|
#[allow(clippy::arc_with_non_send_sync)]
|
||||||
|
let buffer = Arc::new(buffer);
|
||||||
|
let buffer_clone = buffer.clone();
|
||||||
|
let buffer_slice = buffer_clone.slice(..);
|
||||||
|
let format = self.texture.format();
|
||||||
|
let tex_extent = self.texture.size();
|
||||||
|
let padding = self.padding;
|
||||||
|
let to_rgba = match format {
|
||||||
|
wgpu::TextureFormat::Rgba8Unorm => [0, 1, 2, 3],
|
||||||
|
wgpu::TextureFormat::Bgra8Unorm => [2, 1, 0, 3],
|
||||||
|
_ => {
|
||||||
|
log::error!("Screen can't be captured unless the surface format is Rgba8Unorm or Bgra8Unorm. Current surface format is {:?}", format);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
|
||||||
|
if let Err(err) = result {
|
||||||
|
log::error!("Failed to map buffer for reading: {:?}", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let buffer_slice = buffer.slice(..);
|
||||||
|
|
||||||
|
let mut pixels = Vec::with_capacity((tex_extent.width * tex_extent.height) as usize);
|
||||||
|
for padded_row in buffer_slice
|
||||||
|
.get_mapped_range()
|
||||||
|
.chunks(padding.padded_bytes_per_row as usize)
|
||||||
|
{
|
||||||
|
let row = &padded_row[..padding.unpadded_bytes_per_row as usize];
|
||||||
|
for color in row.chunks(4) {
|
||||||
|
pixels.push(epaint::Color32::from_rgba_premultiplied(
|
||||||
|
color[to_rgba[0]],
|
||||||
|
color[to_rgba[1]],
|
||||||
|
color[to_rgba[2]],
|
||||||
|
color[to_rgba[3]],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer.unmap();
|
||||||
|
|
||||||
|
tx.send((
|
||||||
|
viewport_id,
|
||||||
|
data,
|
||||||
|
ColorImage {
|
||||||
|
size: [tex_extent.width as usize, tex_extent.height as usize],
|
||||||
|
pixels,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.ok();
|
||||||
|
ctx.request_repaint();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct BufferPadding {
|
||||||
|
unpadded_bytes_per_row: u32,
|
||||||
|
padded_bytes_per_row: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BufferPadding {
|
||||||
|
fn new(width: u32) -> Self {
|
||||||
|
let bytes_per_pixel = std::mem::size_of::<u32>() as u32;
|
||||||
|
let unpadded_bytes_per_row = width * bytes_per_pixel;
|
||||||
|
let padded_bytes_per_row =
|
||||||
|
wgpu::util::align_to(unpadded_bytes_per_row, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
|
||||||
|
Self {
|
||||||
|
unpadded_bytes_per_row,
|
||||||
|
padded_bytes_per_row,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,9 @@ mod renderer;
|
|||||||
pub use renderer::*;
|
pub use renderer::*;
|
||||||
use wgpu::{Adapter, Device, Instance, Queue};
|
use wgpu::{Adapter, Device, Instance, Queue};
|
||||||
|
|
||||||
|
/// Helpers for capturing screenshots of the UI.
|
||||||
|
pub mod capture;
|
||||||
|
|
||||||
/// Module for painting [`egui`](https://github.com/emilk/egui) with [`wgpu`] on [`winit`].
|
/// Module for painting [`egui`](https://github.com/emilk/egui) with [`wgpu`] on [`winit`].
|
||||||
#[cfg(feature = "winit")]
|
#[cfg(feature = "winit")]
|
||||||
pub mod winit;
|
pub mod winit;
|
||||||
@@ -93,7 +96,7 @@ impl RenderState {
|
|||||||
msaa_samples: u32,
|
msaa_samples: u32,
|
||||||
dithering: bool,
|
dithering: bool,
|
||||||
) -> Result<Self, WgpuError> {
|
) -> Result<Self, WgpuError> {
|
||||||
crate::profile_scope!("RenderState::create"); // async yield give bad names using `profile_function`
|
profiling::scope!("RenderState::create"); // async yield give bad names using `profile_function`
|
||||||
|
|
||||||
// This is always an empty list on web.
|
// This is always an empty list on web.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -106,7 +109,7 @@ impl RenderState {
|
|||||||
device_descriptor,
|
device_descriptor,
|
||||||
} => {
|
} => {
|
||||||
let adapter = {
|
let adapter = {
|
||||||
crate::profile_scope!("request_adapter");
|
profiling::scope!("request_adapter");
|
||||||
instance
|
instance
|
||||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||||
power_preference,
|
power_preference,
|
||||||
@@ -159,10 +162,14 @@ impl RenderState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let trace_path = std::env::var("WGPU_TRACE");
|
||||||
let (device, queue) = {
|
let (device, queue) = {
|
||||||
crate::profile_scope!("request_device");
|
profiling::scope!("request_device");
|
||||||
adapter
|
adapter
|
||||||
.request_device(&(*device_descriptor)(&adapter), None)
|
.request_device(
|
||||||
|
&(*device_descriptor)(&adapter),
|
||||||
|
trace_path.ok().as_ref().map(std::path::Path::new),
|
||||||
|
)
|
||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -180,7 +187,7 @@ impl RenderState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let capabilities = {
|
let capabilities = {
|
||||||
crate::profile_scope!("get_capabilities");
|
profiling::scope!("get_capabilities");
|
||||||
surface.get_capabilities(&adapter).formats
|
surface.get_capabilities(&adapter).formats
|
||||||
};
|
};
|
||||||
let target_format = crate::preferred_framebuffer_format(&capabilities)?;
|
let target_format = crate::preferred_framebuffer_format(&capabilities)?;
|
||||||
@@ -467,33 +474,3 @@ pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
|
|||||||
|
|
||||||
summary
|
summary
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
mod profiling_scopes {
|
|
||||||
#![allow(unused_macros)]
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_function {
|
|
||||||
($($arg: tt)*) => {
|
|
||||||
#[cfg(feature = "puffin")]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
|
||||||
puffin::profile_function!($($arg)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use profile_function;
|
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_scope {
|
|
||||||
($($arg: tt)*) => {
|
|
||||||
#[cfg(feature = "puffin")]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
|
||||||
puffin::profile_scope!($($arg)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use profile_scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub(crate) use profiling_scopes::{profile_function, profile_scope};
|
|
||||||
|
|||||||
@@ -214,14 +214,14 @@ impl Renderer {
|
|||||||
msaa_samples: u32,
|
msaa_samples: u32,
|
||||||
dithering: bool,
|
dithering: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let shader = wgpu::ShaderModuleDescriptor {
|
let shader = wgpu::ShaderModuleDescriptor {
|
||||||
label: Some("egui"),
|
label: Some("egui"),
|
||||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("egui.wgsl"))),
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("egui.wgsl"))),
|
||||||
};
|
};
|
||||||
let module = {
|
let module = {
|
||||||
crate::profile_scope!("create_shader_module");
|
profiling::scope!("create_shader_module");
|
||||||
device.create_shader_module(shader)
|
device.create_shader_module(shader)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ impl Renderer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let uniform_bind_group_layout = {
|
let uniform_bind_group_layout = {
|
||||||
crate::profile_scope!("create_bind_group_layout");
|
profiling::scope!("create_bind_group_layout");
|
||||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
label: Some("egui_uniform_bind_group_layout"),
|
label: Some("egui_uniform_bind_group_layout"),
|
||||||
entries: &[wgpu::BindGroupLayoutEntry {
|
entries: &[wgpu::BindGroupLayoutEntry {
|
||||||
@@ -253,7 +253,7 @@ impl Renderer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let uniform_bind_group = {
|
let uniform_bind_group = {
|
||||||
crate::profile_scope!("create_bind_group");
|
profiling::scope!("create_bind_group");
|
||||||
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: Some("egui_uniform_bind_group"),
|
label: Some("egui_uniform_bind_group"),
|
||||||
layout: &uniform_bind_group_layout,
|
layout: &uniform_bind_group_layout,
|
||||||
@@ -269,7 +269,7 @@ impl Renderer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let texture_bind_group_layout = {
|
let texture_bind_group_layout = {
|
||||||
crate::profile_scope!("create_bind_group_layout");
|
profiling::scope!("create_bind_group_layout");
|
||||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
label: Some("egui_texture_bind_group_layout"),
|
label: Some("egui_texture_bind_group_layout"),
|
||||||
entries: &[
|
entries: &[
|
||||||
@@ -308,7 +308,7 @@ impl Renderer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let pipeline = {
|
let pipeline = {
|
||||||
crate::profile_scope!("create_render_pipeline");
|
profiling::scope!("create_render_pipeline");
|
||||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
label: Some("egui_pipeline"),
|
label: Some("egui_pipeline"),
|
||||||
layout: Some(&pipeline_layout),
|
layout: Some(&pipeline_layout),
|
||||||
@@ -420,7 +420,7 @@ impl Renderer {
|
|||||||
paint_jobs: &[epaint::ClippedPrimitive],
|
paint_jobs: &[epaint::ClippedPrimitive],
|
||||||
screen_descriptor: &ScreenDescriptor,
|
screen_descriptor: &ScreenDescriptor,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let pixels_per_point = screen_descriptor.pixels_per_point;
|
let pixels_per_point = screen_descriptor.pixels_per_point;
|
||||||
let size_in_pixels = screen_descriptor.size_in_pixels;
|
let size_in_pixels = screen_descriptor.size_in_pixels;
|
||||||
@@ -506,7 +506,7 @@ impl Renderer {
|
|||||||
|
|
||||||
let viewport_px = info.viewport_in_pixels();
|
let viewport_px = info.viewport_in_pixels();
|
||||||
if viewport_px.width_px > 0 && viewport_px.height_px > 0 {
|
if viewport_px.width_px > 0 && viewport_px.height_px > 0 {
|
||||||
crate::profile_scope!("callback");
|
profiling::scope!("callback");
|
||||||
|
|
||||||
needs_reset = true;
|
needs_reset = true;
|
||||||
|
|
||||||
@@ -544,7 +544,7 @@ impl Renderer {
|
|||||||
id: epaint::TextureId,
|
id: epaint::TextureId,
|
||||||
image_delta: &epaint::ImageDelta,
|
image_delta: &epaint::ImageDelta,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let width = image_delta.image.width() as u32;
|
let width = image_delta.image.width() as u32;
|
||||||
let height = image_delta.image.height() as u32;
|
let height = image_delta.image.height() as u32;
|
||||||
@@ -570,14 +570,14 @@ impl Renderer {
|
|||||||
image.pixels.len(),
|
image.pixels.len(),
|
||||||
"Mismatch between texture size and texel count"
|
"Mismatch between texture size and texel count"
|
||||||
);
|
);
|
||||||
crate::profile_scope!("font -> sRGBA");
|
profiling::scope!("font -> sRGBA");
|
||||||
Cow::Owned(image.srgba_pixels(None).collect::<Vec<epaint::Color32>>())
|
Cow::Owned(image.srgba_pixels(None).collect::<Vec<epaint::Color32>>())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());
|
let data_bytes: &[u8] = bytemuck::cast_slice(data_color32.as_slice());
|
||||||
|
|
||||||
let queue_write_data_to_texture = |texture, origin| {
|
let queue_write_data_to_texture = |texture, origin| {
|
||||||
crate::profile_scope!("write_texture");
|
profiling::scope!("write_texture");
|
||||||
queue.write_texture(
|
queue.write_texture(
|
||||||
wgpu::ImageCopyTexture {
|
wgpu::ImageCopyTexture {
|
||||||
texture,
|
texture,
|
||||||
@@ -631,7 +631,7 @@ impl Renderer {
|
|||||||
} else {
|
} else {
|
||||||
// allocate a new texture
|
// allocate a new texture
|
||||||
let texture = {
|
let texture = {
|
||||||
crate::profile_scope!("create_texture");
|
profiling::scope!("create_texture");
|
||||||
device.create_texture(&wgpu::TextureDescriptor {
|
device.create_texture(&wgpu::TextureDescriptor {
|
||||||
label,
|
label,
|
||||||
size,
|
size,
|
||||||
@@ -756,7 +756,7 @@ impl Renderer {
|
|||||||
texture: &wgpu::TextureView,
|
texture: &wgpu::TextureView,
|
||||||
sampler_descriptor: wgpu::SamplerDescriptor<'_>,
|
sampler_descriptor: wgpu::SamplerDescriptor<'_>,
|
||||||
) -> epaint::TextureId {
|
) -> epaint::TextureId {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
compare: None,
|
compare: None,
|
||||||
@@ -804,7 +804,7 @@ impl Renderer {
|
|||||||
sampler_descriptor: wgpu::SamplerDescriptor<'_>,
|
sampler_descriptor: wgpu::SamplerDescriptor<'_>,
|
||||||
id: epaint::TextureId,
|
id: epaint::TextureId,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let Texture {
|
let Texture {
|
||||||
bind_group: user_texture_binding,
|
bind_group: user_texture_binding,
|
||||||
@@ -849,7 +849,7 @@ impl Renderer {
|
|||||||
paint_jobs: &[epaint::ClippedPrimitive],
|
paint_jobs: &[epaint::ClippedPrimitive],
|
||||||
screen_descriptor: &ScreenDescriptor,
|
screen_descriptor: &ScreenDescriptor,
|
||||||
) -> Vec<wgpu::CommandBuffer> {
|
) -> Vec<wgpu::CommandBuffer> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let screen_size_in_points = screen_descriptor.screen_size_in_points();
|
let screen_size_in_points = screen_descriptor.screen_size_in_points();
|
||||||
|
|
||||||
@@ -859,7 +859,7 @@ impl Renderer {
|
|||||||
_padding: Default::default(),
|
_padding: Default::default(),
|
||||||
};
|
};
|
||||||
if uniform_buffer_content != self.previous_uniform_buffer_content {
|
if uniform_buffer_content != self.previous_uniform_buffer_content {
|
||||||
crate::profile_scope!("update uniforms");
|
profiling::scope!("update uniforms");
|
||||||
queue.write_buffer(
|
queue.write_buffer(
|
||||||
&self.uniform_buffer,
|
&self.uniform_buffer,
|
||||||
0,
|
0,
|
||||||
@@ -871,7 +871,7 @@ impl Renderer {
|
|||||||
// Determine how many vertices & indices need to be rendered, and gather prepare callbacks
|
// Determine how many vertices & indices need to be rendered, and gather prepare callbacks
|
||||||
let mut callbacks = Vec::new();
|
let mut callbacks = Vec::new();
|
||||||
let (vertex_count, index_count) = {
|
let (vertex_count, index_count) = {
|
||||||
crate::profile_scope!("count_vertices_indices");
|
profiling::scope!("count_vertices_indices");
|
||||||
paint_jobs.iter().fold((0, 0), |acc, clipped_primitive| {
|
paint_jobs.iter().fold((0, 0), |acc, clipped_primitive| {
|
||||||
match &clipped_primitive.primitive {
|
match &clipped_primitive.primitive {
|
||||||
Primitive::Mesh(mesh) => {
|
Primitive::Mesh(mesh) => {
|
||||||
@@ -890,7 +890,7 @@ impl Renderer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if index_count > 0 {
|
if index_count > 0 {
|
||||||
crate::profile_scope!("indices", index_count.to_string());
|
profiling::scope!("indices", index_count.to_string().as_str());
|
||||||
|
|
||||||
self.index_buffer.slices.clear();
|
self.index_buffer.slices.clear();
|
||||||
|
|
||||||
@@ -928,7 +928,7 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if vertex_count > 0 {
|
if vertex_count > 0 {
|
||||||
crate::profile_scope!("vertices", vertex_count.to_string());
|
profiling::scope!("vertices", vertex_count.to_string().as_str());
|
||||||
|
|
||||||
self.vertex_buffer.slices.clear();
|
self.vertex_buffer.slices.clear();
|
||||||
|
|
||||||
@@ -969,7 +969,7 @@ impl Renderer {
|
|||||||
|
|
||||||
let mut user_cmd_bufs = Vec::new();
|
let mut user_cmd_bufs = Vec::new();
|
||||||
{
|
{
|
||||||
crate::profile_scope!("prepare callbacks");
|
profiling::scope!("prepare callbacks");
|
||||||
for callback in &callbacks {
|
for callback in &callbacks {
|
||||||
user_cmd_bufs.extend(callback.prepare(
|
user_cmd_bufs.extend(callback.prepare(
|
||||||
device,
|
device,
|
||||||
@@ -981,7 +981,7 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
crate::profile_scope!("finish prepare callbacks");
|
profiling::scope!("finish prepare callbacks");
|
||||||
for callback in &callbacks {
|
for callback in &callbacks {
|
||||||
user_cmd_bufs.extend(callback.finish_prepare(
|
user_cmd_bufs.extend(callback.finish_prepare(
|
||||||
device,
|
device,
|
||||||
@@ -1026,7 +1026,7 @@ fn create_sampler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_vertex_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
|
fn create_vertex_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
device.create_buffer(&wgpu::BufferDescriptor {
|
device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
label: Some("egui_vertex_buffer"),
|
label: Some("egui_vertex_buffer"),
|
||||||
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
||||||
@@ -1036,7 +1036,7 @@ fn create_vertex_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_index_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
|
fn create_index_buffer(device: &wgpu::Device, size: u64) -> wgpu::Buffer {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
device.create_buffer(&wgpu::BufferDescriptor {
|
device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
label: Some("egui_index_buffer"),
|
label: Some("egui_index_buffer"),
|
||||||
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
|
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
|
||||||
|
|||||||
43
crates/egui-wgpu/src/texture_copy.wgsl
Normal file
43
crates/egui-wgpu/src/texture_copy.wgsl
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) position: vec4<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
var<private> positions: array<vec2f, 3> = array<vec2f, 3>(
|
||||||
|
vec2f(-1.0, -3.0),
|
||||||
|
vec2f(-1.0, 1.0),
|
||||||
|
vec2f(3.0, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
// meant to be called with 3 vertex indices: 0, 1, 2
|
||||||
|
// draws one large triangle over the clip space like this:
|
||||||
|
// (the asterisks represent the clip space bounds)
|
||||||
|
//-1,1 1,1
|
||||||
|
// ---------------------------------
|
||||||
|
// | * .
|
||||||
|
// | * .
|
||||||
|
// | * .
|
||||||
|
// | * .
|
||||||
|
// | * .
|
||||||
|
// | * .
|
||||||
|
// |***************
|
||||||
|
// | . 1,-1
|
||||||
|
// | .
|
||||||
|
// | .
|
||||||
|
// | .
|
||||||
|
// | .
|
||||||
|
// |.
|
||||||
|
@vertex
|
||||||
|
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||||
|
var result: VertexOutput;
|
||||||
|
result.position = vec4f(positions[vertex_index], 0.0, 1.0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(0)
|
||||||
|
@binding(0)
|
||||||
|
var r_color: texture_2d<f32>;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(vertex: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
return textureLoad(r_color, vec2i(vertex.position.xy), 0);
|
||||||
|
}
|
||||||
@@ -1,77 +1,16 @@
|
|||||||
#![allow(clippy::missing_errors_doc)]
|
#![allow(clippy::missing_errors_doc)]
|
||||||
#![allow(clippy::undocumented_unsafe_blocks)]
|
#![allow(clippy::undocumented_unsafe_blocks)]
|
||||||
|
|
||||||
use std::{num::NonZeroU32, sync::Arc};
|
use crate::capture::{capture_channel, CaptureReceiver, CaptureSender, CaptureState};
|
||||||
|
|
||||||
use egui::{ViewportId, ViewportIdMap, ViewportIdSet};
|
|
||||||
|
|
||||||
use crate::{renderer, RenderState, SurfaceErrorAction, WgpuConfiguration};
|
use crate::{renderer, RenderState, SurfaceErrorAction, WgpuConfiguration};
|
||||||
|
use egui::{Context, Event, UserData, ViewportId, ViewportIdMap, ViewportIdSet};
|
||||||
|
use std::{num::NonZeroU32, sync::Arc};
|
||||||
|
|
||||||
struct SurfaceState {
|
struct SurfaceState {
|
||||||
surface: wgpu::Surface<'static>,
|
surface: wgpu::Surface<'static>,
|
||||||
alpha_mode: wgpu::CompositeAlphaMode,
|
alpha_mode: wgpu::CompositeAlphaMode,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
supports_screenshot: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A texture and a buffer for reading the rendered frame back to the cpu.
|
|
||||||
/// The texture is required since [`wgpu::TextureUsages::COPY_DST`] is not an allowed
|
|
||||||
/// flag for the surface texture on all platforms. This means that anytime we want to
|
|
||||||
/// capture the frame, we first render it to this texture, and then we can copy it to
|
|
||||||
/// both the surface texture and the buffer, from where we can pull it back to the cpu.
|
|
||||||
struct CaptureState {
|
|
||||||
texture: wgpu::Texture,
|
|
||||||
buffer: wgpu::Buffer,
|
|
||||||
padding: BufferPadding,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CaptureState {
|
|
||||||
fn new(device: &Arc<wgpu::Device>, surface_texture: &wgpu::Texture) -> Self {
|
|
||||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
|
||||||
label: Some("egui_screen_capture_texture"),
|
|
||||||
size: surface_texture.size(),
|
|
||||||
mip_level_count: surface_texture.mip_level_count(),
|
|
||||||
sample_count: surface_texture.sample_count(),
|
|
||||||
dimension: surface_texture.dimension(),
|
|
||||||
format: surface_texture.format(),
|
|
||||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
|
||||||
view_formats: &[],
|
|
||||||
});
|
|
||||||
|
|
||||||
let padding = BufferPadding::new(surface_texture.width());
|
|
||||||
|
|
||||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
|
||||||
label: Some("egui_screen_capture_buffer"),
|
|
||||||
size: (padding.padded_bytes_per_row * texture.height()) as u64,
|
|
||||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
|
||||||
mapped_at_creation: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
texture,
|
|
||||||
buffer,
|
|
||||||
padding,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BufferPadding {
|
|
||||||
unpadded_bytes_per_row: u32,
|
|
||||||
padded_bytes_per_row: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BufferPadding {
|
|
||||||
fn new(width: u32) -> Self {
|
|
||||||
let bytes_per_pixel = std::mem::size_of::<u32>() as u32;
|
|
||||||
let unpadded_bytes_per_row = width * bytes_per_pixel;
|
|
||||||
let padded_bytes_per_row =
|
|
||||||
wgpu::util::align_to(unpadded_bytes_per_row, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
|
|
||||||
Self {
|
|
||||||
unpadded_bytes_per_row,
|
|
||||||
padded_bytes_per_row,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Everything you need to paint egui with [`wgpu`] on [`winit`].
|
/// Everything you need to paint egui with [`wgpu`] on [`winit`].
|
||||||
@@ -80,6 +19,7 @@ impl BufferPadding {
|
|||||||
///
|
///
|
||||||
/// NOTE: all egui viewports share the same painter.
|
/// NOTE: all egui viewports share the same painter.
|
||||||
pub struct Painter {
|
pub struct Painter {
|
||||||
|
context: Context,
|
||||||
configuration: WgpuConfiguration,
|
configuration: WgpuConfiguration,
|
||||||
msaa_samples: u32,
|
msaa_samples: u32,
|
||||||
support_transparent_backbuffer: bool,
|
support_transparent_backbuffer: bool,
|
||||||
@@ -94,6 +34,8 @@ pub struct Painter {
|
|||||||
depth_texture_view: ViewportIdMap<wgpu::TextureView>,
|
depth_texture_view: ViewportIdMap<wgpu::TextureView>,
|
||||||
msaa_texture_view: ViewportIdMap<wgpu::TextureView>,
|
msaa_texture_view: ViewportIdMap<wgpu::TextureView>,
|
||||||
surfaces: ViewportIdMap<SurfaceState>,
|
surfaces: ViewportIdMap<SurfaceState>,
|
||||||
|
capture_tx: CaptureSender,
|
||||||
|
capture_rx: CaptureReceiver,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Painter {
|
impl Painter {
|
||||||
@@ -110,6 +52,7 @@ impl Painter {
|
|||||||
/// 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(
|
||||||
|
context: Context,
|
||||||
configuration: WgpuConfiguration,
|
configuration: WgpuConfiguration,
|
||||||
msaa_samples: u32,
|
msaa_samples: u32,
|
||||||
depth_format: Option<wgpu::TextureFormat>,
|
depth_format: Option<wgpu::TextureFormat>,
|
||||||
@@ -126,7 +69,10 @@ impl Painter {
|
|||||||
crate::WgpuSetup::Existing { instance, .. } => instance.clone(),
|
crate::WgpuSetup::Existing { instance, .. } => instance.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (capture_tx, capture_rx) = capture_channel();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
context,
|
||||||
configuration,
|
configuration,
|
||||||
msaa_samples,
|
msaa_samples,
|
||||||
support_transparent_backbuffer,
|
support_transparent_backbuffer,
|
||||||
@@ -140,6 +86,9 @@ impl Painter {
|
|||||||
depth_texture_view: Default::default(),
|
depth_texture_view: Default::default(),
|
||||||
surfaces: Default::default(),
|
surfaces: Default::default(),
|
||||||
msaa_texture_view: Default::default(),
|
msaa_texture_view: Default::default(),
|
||||||
|
|
||||||
|
capture_tx,
|
||||||
|
capture_rx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,19 +104,13 @@ impl Painter {
|
|||||||
render_state: &RenderState,
|
render_state: &RenderState,
|
||||||
config: &WgpuConfiguration,
|
config: &WgpuConfiguration,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let usage = if surface_state.supports_screenshot {
|
|
||||||
wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST
|
|
||||||
} else {
|
|
||||||
wgpu::TextureUsages::RENDER_ATTACHMENT
|
|
||||||
};
|
|
||||||
|
|
||||||
let width = surface_state.width;
|
let width = surface_state.width;
|
||||||
let height = surface_state.height;
|
let height = surface_state.height;
|
||||||
|
|
||||||
let mut surf_config = wgpu::SurfaceConfiguration {
|
let mut surf_config = wgpu::SurfaceConfiguration {
|
||||||
usage,
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
format: render_state.target_format,
|
format: render_state.target_format,
|
||||||
present_mode: config.present_mode,
|
present_mode: config.present_mode,
|
||||||
alpha_mode: surface_state.alpha_mode,
|
alpha_mode: surface_state.alpha_mode,
|
||||||
@@ -213,7 +156,7 @@ impl Painter {
|
|||||||
viewport_id: ViewportId,
|
viewport_id: ViewportId,
|
||||||
window: Option<Arc<winit::window::Window>>,
|
window: Option<Arc<winit::window::Window>>,
|
||||||
) -> Result<(), crate::WgpuError> {
|
) -> Result<(), crate::WgpuError> {
|
||||||
crate::profile_scope!("Painter::set_window"); // profile_function gives bad names for async functions
|
profiling::scope!("Painter::set_window"); // profile_function gives bad names for async functions
|
||||||
|
|
||||||
if let Some(window) = window {
|
if let Some(window) = window {
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
@@ -239,7 +182,7 @@ impl Painter {
|
|||||||
viewport_id: ViewportId,
|
viewport_id: ViewportId,
|
||||||
window: Option<&winit::window::Window>,
|
window: Option<&winit::window::Window>,
|
||||||
) -> Result<(), crate::WgpuError> {
|
) -> Result<(), crate::WgpuError> {
|
||||||
crate::profile_scope!("Painter::set_window_unsafe"); // profile_function gives bad names for async functions
|
profiling::scope!("Painter::set_window_unsafe"); // profile_function gives bad names for async functions
|
||||||
|
|
||||||
if let Some(window) = window {
|
if let Some(window) = window {
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
@@ -292,8 +235,6 @@ impl Painter {
|
|||||||
} else {
|
} else {
|
||||||
wgpu::CompositeAlphaMode::Auto
|
wgpu::CompositeAlphaMode::Auto
|
||||||
};
|
};
|
||||||
let supports_screenshot =
|
|
||||||
!matches!(render_state.adapter.get_info().backend, wgpu::Backend::Gl);
|
|
||||||
self.surfaces.insert(
|
self.surfaces.insert(
|
||||||
viewport_id,
|
viewport_id,
|
||||||
SurfaceState {
|
SurfaceState {
|
||||||
@@ -301,7 +242,6 @@ impl Painter {
|
|||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
alpha_mode,
|
alpha_mode,
|
||||||
supports_screenshot,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let Some(width) = NonZeroU32::new(size.width) else {
|
let Some(width) = NonZeroU32::new(size.width) else {
|
||||||
@@ -333,7 +273,7 @@ impl Painter {
|
|||||||
width_in_pixels: NonZeroU32,
|
width_in_pixels: NonZeroU32,
|
||||||
height_in_pixels: NonZeroU32,
|
height_in_pixels: NonZeroU32,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let width = width_in_pixels.get();
|
let width = width_in_pixels.get();
|
||||||
let height = height_in_pixels.get();
|
let height = height_in_pixels.get();
|
||||||
@@ -404,7 +344,7 @@ impl Painter {
|
|||||||
width_in_pixels: NonZeroU32,
|
width_in_pixels: NonZeroU32,
|
||||||
height_in_pixels: NonZeroU32,
|
height_in_pixels: NonZeroU32,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if self.surfaces.contains_key(&viewport_id) {
|
if self.surfaces.contains_key(&viewport_id) {
|
||||||
self.resize_and_generate_depth_texture_view_and_msaa_view(
|
self.resize_and_generate_depth_texture_view_and_msaa_view(
|
||||||
@@ -417,109 +357,12 @@ impl Painter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaptureState only needs to be updated when the size of the two textures don't match and we want to
|
|
||||||
// capture a frame
|
|
||||||
fn update_capture_state(
|
|
||||||
screen_capture_state: &mut Option<CaptureState>,
|
|
||||||
surface_texture: &wgpu::SurfaceTexture,
|
|
||||||
render_state: &RenderState,
|
|
||||||
) {
|
|
||||||
let surface_texture = &surface_texture.texture;
|
|
||||||
match screen_capture_state {
|
|
||||||
Some(capture_state) => {
|
|
||||||
if capture_state.texture.size() != surface_texture.size() {
|
|
||||||
*capture_state = CaptureState::new(&render_state.device, surface_texture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
*screen_capture_state =
|
|
||||||
Some(CaptureState::new(&render_state.device, surface_texture));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handles copying from the CaptureState texture to the surface texture and the cpu
|
|
||||||
fn read_screen_rgba(
|
|
||||||
screen_capture_state: &CaptureState,
|
|
||||||
render_state: &RenderState,
|
|
||||||
output_frame: &wgpu::SurfaceTexture,
|
|
||||||
) -> Option<epaint::ColorImage> {
|
|
||||||
let CaptureState {
|
|
||||||
texture: tex,
|
|
||||||
buffer,
|
|
||||||
padding,
|
|
||||||
} = screen_capture_state;
|
|
||||||
|
|
||||||
let device = &render_state.device;
|
|
||||||
let queue = &render_state.queue;
|
|
||||||
|
|
||||||
let tex_extent = tex.size();
|
|
||||||
|
|
||||||
let mut encoder = device.create_command_encoder(&Default::default());
|
|
||||||
encoder.copy_texture_to_buffer(
|
|
||||||
tex.as_image_copy(),
|
|
||||||
wgpu::ImageCopyBuffer {
|
|
||||||
buffer,
|
|
||||||
layout: wgpu::ImageDataLayout {
|
|
||||||
offset: 0,
|
|
||||||
bytes_per_row: Some(padding.padded_bytes_per_row),
|
|
||||||
rows_per_image: None,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tex_extent,
|
|
||||||
);
|
|
||||||
|
|
||||||
encoder.copy_texture_to_texture(
|
|
||||||
tex.as_image_copy(),
|
|
||||||
output_frame.texture.as_image_copy(),
|
|
||||||
tex.size(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let id = queue.submit(Some(encoder.finish()));
|
|
||||||
let buffer_slice = buffer.slice(..);
|
|
||||||
let (sender, receiver) = std::sync::mpsc::channel();
|
|
||||||
buffer_slice.map_async(wgpu::MapMode::Read, move |v| {
|
|
||||||
drop(sender.send(v));
|
|
||||||
});
|
|
||||||
device.poll(wgpu::Maintain::WaitForSubmissionIndex(id));
|
|
||||||
receiver.recv().ok()?.ok()?;
|
|
||||||
|
|
||||||
let to_rgba = match tex.format() {
|
|
||||||
wgpu::TextureFormat::Rgba8Unorm => [0, 1, 2, 3],
|
|
||||||
wgpu::TextureFormat::Bgra8Unorm => [2, 1, 0, 3],
|
|
||||||
_ => {
|
|
||||||
log::error!("Screen can't be captured unless the surface format is Rgba8Unorm or Bgra8Unorm. Current surface format is {:?}", tex.format());
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut pixels = Vec::with_capacity((tex.width() * tex.height()) as usize);
|
|
||||||
for padded_row in buffer_slice
|
|
||||||
.get_mapped_range()
|
|
||||||
.chunks(padding.padded_bytes_per_row as usize)
|
|
||||||
{
|
|
||||||
let row = &padded_row[..padding.unpadded_bytes_per_row as usize];
|
|
||||||
for color in row.chunks(4) {
|
|
||||||
pixels.push(epaint::Color32::from_rgba_premultiplied(
|
|
||||||
color[to_rgba[0]],
|
|
||||||
color[to_rgba[1]],
|
|
||||||
color[to_rgba[2]],
|
|
||||||
color[to_rgba[3]],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.unmap();
|
|
||||||
|
|
||||||
Some(epaint::ColorImage {
|
|
||||||
size: [tex.width() as usize, tex.height() as usize],
|
|
||||||
pixels,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns two things:
|
/// Returns two things:
|
||||||
///
|
///
|
||||||
/// The approximate number of seconds spent on vsync-waiting (if any),
|
/// The approximate number of seconds spent on vsync-waiting (if any),
|
||||||
/// and the captures captured screenshot if it was requested.
|
/// and the captures captured screenshot if it was requested.
|
||||||
|
///
|
||||||
|
/// If `capture_data` isn't empty, a screenshot will be captured.
|
||||||
pub fn paint_and_update_textures(
|
pub fn paint_and_update_textures(
|
||||||
&mut self,
|
&mut self,
|
||||||
viewport_id: ViewportId,
|
viewport_id: ViewportId,
|
||||||
@@ -527,17 +370,18 @@ impl Painter {
|
|||||||
clear_color: [f32; 4],
|
clear_color: [f32; 4],
|
||||||
clipped_primitives: &[epaint::ClippedPrimitive],
|
clipped_primitives: &[epaint::ClippedPrimitive],
|
||||||
textures_delta: &epaint::textures::TexturesDelta,
|
textures_delta: &epaint::textures::TexturesDelta,
|
||||||
capture: bool,
|
capture_data: Vec<UserData>,
|
||||||
) -> (f32, Option<epaint::ColorImage>) {
|
) -> f32 {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
|
let capture = !capture_data.is_empty();
|
||||||
let mut vsync_sec = 0.0;
|
let mut vsync_sec = 0.0;
|
||||||
|
|
||||||
let Some(render_state) = self.render_state.as_mut() else {
|
let Some(render_state) = self.render_state.as_mut() else {
|
||||||
return (vsync_sec, None);
|
return vsync_sec;
|
||||||
};
|
};
|
||||||
let Some(surface_state) = self.surfaces.get(&viewport_id) else {
|
let Some(surface_state) = self.surfaces.get(&viewport_id) else {
|
||||||
return (vsync_sec, None);
|
return vsync_sec;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut encoder =
|
let mut encoder =
|
||||||
@@ -573,17 +417,8 @@ impl Painter {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let capture = match (capture, surface_state.supports_screenshot) {
|
|
||||||
(false, _) => false,
|
|
||||||
(true, true) => true,
|
|
||||||
(true, false) => {
|
|
||||||
log::error!("The active render surface doesn't support taking screenshots.");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let output_frame = {
|
let output_frame = {
|
||||||
crate::profile_scope!("get_current_texture");
|
profiling::scope!("get_current_texture");
|
||||||
// This is what vsync-waiting happens on my Mac.
|
// This is what vsync-waiting happens on my Mac.
|
||||||
let start = web_time::Instant::now();
|
let start = web_time::Instant::now();
|
||||||
let output_frame = surface_state.surface.get_current_texture();
|
let output_frame = surface_state.surface.get_current_texture();
|
||||||
@@ -596,40 +431,35 @@ impl Painter {
|
|||||||
Err(err) => match (*self.configuration.on_surface_error)(err) {
|
Err(err) => match (*self.configuration.on_surface_error)(err) {
|
||||||
SurfaceErrorAction::RecreateSurface => {
|
SurfaceErrorAction::RecreateSurface => {
|
||||||
Self::configure_surface(surface_state, render_state, &self.configuration);
|
Self::configure_surface(surface_state, render_state, &self.configuration);
|
||||||
return (vsync_sec, None);
|
return vsync_sec;
|
||||||
}
|
}
|
||||||
SurfaceErrorAction::SkipFrame => {
|
SurfaceErrorAction::SkipFrame => {
|
||||||
return (vsync_sec, None);
|
return vsync_sec;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut capture_buffer = None;
|
||||||
{
|
{
|
||||||
let renderer = render_state.renderer.read();
|
let renderer = render_state.renderer.read();
|
||||||
let frame_view = if capture {
|
|
||||||
Self::update_capture_state(
|
let target_texture = if capture {
|
||||||
&mut self.screen_capture_state,
|
let capture_state = self.screen_capture_state.get_or_insert_with(|| {
|
||||||
&output_frame,
|
CaptureState::new(&render_state.device, &output_frame.texture)
|
||||||
render_state,
|
});
|
||||||
);
|
capture_state.update(&render_state.device, &output_frame.texture);
|
||||||
self.screen_capture_state
|
|
||||||
.as_ref()
|
&capture_state.texture
|
||||||
.map_or_else(
|
|
||||||
|| &output_frame.texture,
|
|
||||||
|capture_state| &capture_state.texture,
|
|
||||||
)
|
|
||||||
.create_view(&wgpu::TextureViewDescriptor::default())
|
|
||||||
} else {
|
} else {
|
||||||
output_frame
|
&output_frame.texture
|
||||||
.texture
|
|
||||||
.create_view(&wgpu::TextureViewDescriptor::default())
|
|
||||||
};
|
};
|
||||||
|
let target_view = target_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
let (view, resolve_target) = (self.msaa_samples > 1)
|
let (view, resolve_target) = (self.msaa_samples > 1)
|
||||||
.then_some(self.msaa_texture_view.get(&viewport_id))
|
.then_some(self.msaa_texture_view.get(&viewport_id))
|
||||||
.flatten()
|
.flatten()
|
||||||
.map_or((&frame_view, None), |texture_view| {
|
.map_or((&target_view, None), |texture_view| {
|
||||||
(texture_view, Some(&frame_view))
|
(texture_view, Some(&target_view))
|
||||||
});
|
});
|
||||||
|
|
||||||
let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
@@ -671,16 +501,26 @@ impl Painter {
|
|||||||
clipped_primitives,
|
clipped_primitives,
|
||||||
&screen_descriptor,
|
&screen_descriptor,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if capture {
|
||||||
|
if let Some(capture_state) = &mut self.screen_capture_state {
|
||||||
|
capture_buffer = Some(capture_state.copy_textures(
|
||||||
|
&render_state.device,
|
||||||
|
&output_frame,
|
||||||
|
&mut encoder,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let encoded = {
|
let encoded = {
|
||||||
crate::profile_scope!("CommandEncoder::finish");
|
profiling::scope!("CommandEncoder::finish");
|
||||||
encoder.finish()
|
encoder.finish()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Submit the commands: both the main buffer and user-defined ones.
|
// Submit the commands: both the main buffer and user-defined ones.
|
||||||
{
|
{
|
||||||
crate::profile_scope!("Queue::submit");
|
profiling::scope!("Queue::submit");
|
||||||
// wgpu doesn't document where vsync can happen. Maybe here?
|
// wgpu doesn't document where vsync can happen. Maybe here?
|
||||||
let start = web_time::Instant::now();
|
let start = web_time::Instant::now();
|
||||||
render_state
|
render_state
|
||||||
@@ -699,25 +539,41 @@ impl Painter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let screenshot = if capture {
|
if let Some(capture_buffer) = capture_buffer {
|
||||||
self.screen_capture_state
|
if let Some(screen_capture_state) = &mut self.screen_capture_state {
|
||||||
.as_ref()
|
screen_capture_state.read_screen_rgba(
|
||||||
.and_then(|screen_capture_state| {
|
self.context.clone(),
|
||||||
Self::read_screen_rgba(screen_capture_state, render_state, &output_frame)
|
capture_buffer,
|
||||||
})
|
capture_data,
|
||||||
} else {
|
self.capture_tx.clone(),
|
||||||
None
|
viewport_id,
|
||||||
};
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
crate::profile_scope!("present");
|
profiling::scope!("present");
|
||||||
// wgpu doesn't document where vsync can happen. Maybe here?
|
// wgpu doesn't document where vsync can happen. Maybe here?
|
||||||
let start = web_time::Instant::now();
|
let start = web_time::Instant::now();
|
||||||
output_frame.present();
|
output_frame.present();
|
||||||
vsync_sec += start.elapsed().as_secs_f32();
|
vsync_sec += start.elapsed().as_secs_f32();
|
||||||
}
|
}
|
||||||
|
|
||||||
(vsync_sec, screenshot)
|
vsync_sec
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call this at the beginning of each frame to receive the requested screenshots.
|
||||||
|
pub fn handle_screenshots(&self, events: &mut Vec<Event>) {
|
||||||
|
for (viewport_id, user_data, screenshot) in self.capture_rx.try_iter() {
|
||||||
|
let screenshot = Arc::new(screenshot);
|
||||||
|
for data in user_data {
|
||||||
|
events.push(Event::Screenshot {
|
||||||
|
viewport_id,
|
||||||
|
user_data: data,
|
||||||
|
image: screenshot.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gc_viewports(&mut self, active_viewports: &ViewportIdSet) {
|
pub fn gc_viewports(&mut self, active_viewports: &ViewportIdSet) {
|
||||||
@@ -728,7 +584,7 @@ impl Painter {
|
|||||||
.retain(|id, _| active_viewports.contains(id));
|
.retain(|id, _| active_viewports.contains(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unused_self)]
|
#[allow(clippy::needless_pass_by_ref_mut, clippy::unused_self)]
|
||||||
pub fn destroy(&mut self) {
|
pub fn destroy(&mut self) {
|
||||||
// TODO(emilk): something here?
|
// TODO(emilk): something here?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ This file is updated upon each release.
|
|||||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.30.0 - 2024-12-16
|
||||||
|
* iOS: Support putting UI next to the dynamic island [#5211](https://github.com/emilk/egui/pull/5211) by [@frederik-uni](https://github.com/frederik-uni)
|
||||||
|
* Remove implicit `accesskit_winit` feature [#5316](https://github.com/emilk/egui/pull/5316) by [@waywardmonkeys](https://github.com/waywardmonkeys)
|
||||||
|
|
||||||
|
|
||||||
## 0.29.1 - 2024-10-01 - Fix backspace/arrow keys on X11
|
## 0.29.1 - 2024-10-01 - Fix backspace/arrow keys on X11
|
||||||
* Linux: Disable IME to fix backspace/arrow keys [#5188](https://github.com/emilk/egui/pull/5188) by [@emilk](https://github.com/emilk)
|
* Linux: Disable IME to fix backspace/arrow keys [#5188](https://github.com/emilk/egui/pull/5188) by [@emilk](https://github.com/emilk)
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,6 @@ clipboard = ["arboard", "smithay-clipboard"]
|
|||||||
## Enable opening links in a browser when an egui hyperlink is clicked.
|
## Enable opening links in a browser when an egui hyperlink is clicked.
|
||||||
links = ["webbrowser"]
|
links = ["webbrowser"]
|
||||||
|
|
||||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
|
||||||
puffin = ["dep:puffin", "egui/puffin"]
|
|
||||||
|
|
||||||
## Allow serialization of [`WindowSettings`] using [`serde`](https://docs.rs/serde).
|
## Allow serialization of [`WindowSettings`] using [`serde`](https://docs.rs/serde).
|
||||||
serde = ["egui/serde", "dep:serde"]
|
serde = ["egui/serde", "dep:serde"]
|
||||||
|
|
||||||
@@ -62,6 +59,7 @@ egui = { workspace = true, default-features = false, features = ["log"] }
|
|||||||
|
|
||||||
ahash.workspace = true
|
ahash.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
profiling.workspace = true
|
||||||
raw-window-handle.workspace = true
|
raw-window-handle.workspace = true
|
||||||
web-time.workspace = true
|
web-time.workspace = true
|
||||||
winit = { workspace = true, default-features = false }
|
winit = { workspace = true, default-features = false }
|
||||||
@@ -74,7 +72,6 @@ accesskit_winit = { version = "0.23", optional = true }
|
|||||||
## Enable this when generating docs.
|
## Enable this when generating docs.
|
||||||
document-features = { workspace = true, optional = true }
|
document-features = { workspace = true, optional = true }
|
||||||
|
|
||||||
puffin = { workspace = true, optional = true }
|
|
||||||
serde = { workspace = true, optional = true }
|
serde = { workspace = true, optional = true }
|
||||||
webbrowser = { version = "1.0.0", optional = true }
|
webbrowser = { version = "1.0.0", optional = true }
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ impl Clipboard {
|
|||||||
|
|
||||||
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
#[cfg(all(feature = "arboard", not(target_os = "android")))]
|
||||||
fn init_arboard() -> Option<arboard::Clipboard> {
|
fn init_arboard() -> Option<arboard::Clipboard> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
log::trace!("Initializing arboard clipboard…");
|
log::trace!("Initializing arboard clipboard…");
|
||||||
match arboard::Clipboard::new() {
|
match arboard::Clipboard::new() {
|
||||||
@@ -139,7 +139,7 @@ fn init_smithay_clipboard(
|
|||||||
) -> Option<smithay_clipboard::Clipboard> {
|
) -> Option<smithay_clipboard::Clipboard> {
|
||||||
#![allow(clippy::undocumented_unsafe_blocks)]
|
#![allow(clippy::undocumented_unsafe_blocks)]
|
||||||
|
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if let Some(RawDisplayHandle::Wayland(display)) = raw_display_handle {
|
if let Some(RawDisplayHandle::Wayland(display)) = raw_display_handle {
|
||||||
log::trace!("Initializing smithay clipboard…");
|
log::trace!("Initializing smithay clipboard…");
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ pub use window_settings::WindowSettings;
|
|||||||
use ahash::HashSet;
|
use ahash::HashSet;
|
||||||
use raw_window_handle::HasDisplayHandle;
|
use raw_window_handle::HasDisplayHandle;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub(crate) use profiling_scopes::{profile_function, profile_scope};
|
|
||||||
|
|
||||||
use winit::{
|
use winit::{
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
event::ElementState,
|
event::ElementState,
|
||||||
@@ -121,7 +118,7 @@ impl State {
|
|||||||
theme: Option<winit::window::Theme>,
|
theme: Option<winit::window::Theme>,
|
||||||
max_texture_side: Option<usize>,
|
max_texture_side: Option<usize>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let egui_input = egui::RawInput {
|
let egui_input = egui::RawInput {
|
||||||
focused: false, // winit will tell us when we have focus
|
focused: false, // winit will tell us when we have focus
|
||||||
@@ -172,7 +169,7 @@ impl State {
|
|||||||
window: &Window,
|
window: &Window,
|
||||||
event_loop_proxy: winit::event_loop::EventLoopProxy<T>,
|
event_loop_proxy: winit::event_loop::EventLoopProxy<T>,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
self.accesskit = Some(accesskit_winit::Adapter::with_event_loop_proxy(
|
self.accesskit = Some(accesskit_winit::Adapter::with_event_loop_proxy(
|
||||||
window,
|
window,
|
||||||
@@ -233,7 +230,7 @@ impl State {
|
|||||||
/// Use [`update_viewport_info`] to update the info for each
|
/// Use [`update_viewport_info`] to update the info for each
|
||||||
/// viewport.
|
/// viewport.
|
||||||
pub fn take_egui_input(&mut self, window: &Window) -> egui::RawInput {
|
pub fn take_egui_input(&mut self, window: &Window) -> egui::RawInput {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
|
self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
|
||||||
|
|
||||||
@@ -268,7 +265,7 @@ impl State {
|
|||||||
window: &Window,
|
window: &Window,
|
||||||
event: &winit::event::WindowEvent,
|
event: &winit::event::WindowEvent,
|
||||||
) -> EventResponse {
|
) -> EventResponse {
|
||||||
crate::profile_function!(short_window_event_description(event));
|
profiling::function_scope!(short_window_event_description(event));
|
||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
if let Some(accesskit) = self.accesskit.as_mut() {
|
if let Some(accesskit) = self.accesskit.as_mut() {
|
||||||
@@ -823,7 +820,7 @@ impl State {
|
|||||||
window: &Window,
|
window: &Window,
|
||||||
platform_output: egui::PlatformOutput,
|
platform_output: egui::PlatformOutput,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let egui::PlatformOutput {
|
let egui::PlatformOutput {
|
||||||
cursor_icon,
|
cursor_icon,
|
||||||
@@ -851,7 +848,7 @@ impl State {
|
|||||||
let allow_ime = ime.is_some();
|
let allow_ime = ime.is_some();
|
||||||
if self.allow_ime != allow_ime {
|
if self.allow_ime != allow_ime {
|
||||||
self.allow_ime = allow_ime;
|
self.allow_ime = allow_ime;
|
||||||
crate::profile_scope!("set_ime_allowed");
|
profiling::scope!("set_ime_allowed");
|
||||||
window.set_ime_allowed(allow_ime);
|
window.set_ime_allowed(allow_ime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -862,7 +859,7 @@ impl State {
|
|||||||
|| self.egui_ctx.input(|i| !i.events.is_empty())
|
|| self.egui_ctx.input(|i| !i.events.is_empty())
|
||||||
{
|
{
|
||||||
self.ime_rect_px = Some(ime_rect_px);
|
self.ime_rect_px = Some(ime_rect_px);
|
||||||
crate::profile_scope!("set_ime_cursor_area");
|
profiling::scope!("set_ime_cursor_area");
|
||||||
window.set_ime_cursor_area(
|
window.set_ime_cursor_area(
|
||||||
winit::dpi::PhysicalPosition {
|
winit::dpi::PhysicalPosition {
|
||||||
x: ime_rect_px.min.x,
|
x: ime_rect_px.min.x,
|
||||||
@@ -881,7 +878,7 @@ impl State {
|
|||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
if let Some(accesskit) = self.accesskit.as_mut() {
|
if let Some(accesskit) = self.accesskit.as_mut() {
|
||||||
if let Some(update) = accesskit_update {
|
if let Some(update) = accesskit_update {
|
||||||
crate::profile_scope!("accesskit");
|
profiling::scope!("accesskit");
|
||||||
accesskit.update_if_active(|| update);
|
accesskit.update_if_active(|| update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -953,8 +950,7 @@ pub fn update_viewport_info(
|
|||||||
window: &Window,
|
window: &Window,
|
||||||
is_init: bool,
|
is_init: bool,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let pixels_per_point = pixels_per_point(egui_ctx, window);
|
let pixels_per_point = pixels_per_point(egui_ctx, window);
|
||||||
|
|
||||||
let has_a_position = match window.is_minimized() {
|
let has_a_position = match window.is_minimized() {
|
||||||
@@ -975,7 +971,7 @@ pub fn update_viewport_info(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let monitor_size = {
|
let monitor_size = {
|
||||||
crate::profile_scope!("monitor_size");
|
profiling::scope!("monitor_size");
|
||||||
if let Some(monitor) = window.current_monitor() {
|
if let Some(monitor) = window.current_monitor() {
|
||||||
let size = monitor.size().to_logical::<f32>(pixels_per_point.into());
|
let size = monitor.size().to_logical::<f32>(pixels_per_point.into());
|
||||||
Some(egui::vec2(size.width, size.height))
|
Some(egui::vec2(size.width, size.height))
|
||||||
@@ -1326,7 +1322,7 @@ fn process_viewport_command(
|
|||||||
info: &mut ViewportInfo,
|
info: &mut ViewportInfo,
|
||||||
actions_requested: &mut HashSet<ActionRequested>,
|
actions_requested: &mut HashSet<ActionRequested>,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
use winit::window::ResizeDirection;
|
use winit::window::ResizeDirection;
|
||||||
|
|
||||||
@@ -1542,7 +1538,7 @@ pub fn create_window(
|
|||||||
event_loop: &ActiveEventLoop,
|
event_loop: &ActiveEventLoop,
|
||||||
viewport_builder: &ViewportBuilder,
|
viewport_builder: &ViewportBuilder,
|
||||||
) -> Result<Window, winit::error::OsError> {
|
) -> Result<Window, winit::error::OsError> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let window_attributes =
|
let window_attributes =
|
||||||
create_winit_window_attributes(egui_ctx, event_loop, viewport_builder.clone());
|
create_winit_window_attributes(egui_ctx, event_loop, viewport_builder.clone());
|
||||||
@@ -1556,7 +1552,7 @@ pub fn create_winit_window_attributes(
|
|||||||
event_loop: &ActiveEventLoop,
|
event_loop: &ActiveEventLoop,
|
||||||
viewport_builder: ViewportBuilder,
|
viewport_builder: ViewportBuilder,
|
||||||
) -> winit::window::WindowAttributes {
|
) -> winit::window::WindowAttributes {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
// We set sizes and positions in egui:s own ui points, which depends on the egui
|
// We set sizes and positions in egui:s own ui points, which depends on the egui
|
||||||
// zoom_factor and the native pixels per point, so we need to know that here.
|
// zoom_factor and the native pixels per point, so we need to know that here.
|
||||||
@@ -1752,7 +1748,7 @@ fn to_winit_icon(icon: &egui::IconData) -> Option<winit::window::Icon> {
|
|||||||
if icon.is_empty() {
|
if icon.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
match winit::window::Icon::from_rgba(icon.rgba.clone(), icon.width, icon.height) {
|
match winit::window::Icon::from_rgba(icon.rgba.clone(), icon.width, icon.height) {
|
||||||
Ok(winit_icon) => Some(winit_icon),
|
Ok(winit_icon) => Some(winit_icon),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -1867,30 +1863,3 @@ pub fn short_window_event_description(event: &winit::event::WindowEvent) -> &'st
|
|||||||
WindowEvent::PanGesture { .. } => "WindowEvent::PanGesture",
|
WindowEvent::PanGesture { .. } => "WindowEvent::PanGesture",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
mod profiling_scopes {
|
|
||||||
#![allow(unused_macros)]
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_function {
|
|
||||||
($($arg: tt)*) => {
|
|
||||||
#[cfg(feature = "puffin")]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
|
||||||
puffin::profile_function!($($arg)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use profile_function;
|
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_scope {
|
|
||||||
($($arg: tt)*) => {
|
|
||||||
#[cfg(feature = "puffin")]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
|
||||||
puffin::profile_scope!($($arg)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use profile_scope;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ impl WindowSettings {
|
|||||||
event_loop: &winit::event_loop::ActiveEventLoop,
|
event_loop: &winit::event_loop::ActiveEventLoop,
|
||||||
mut viewport_builder: ViewportBuilder,
|
mut viewport_builder: ViewportBuilder,
|
||||||
) -> ViewportBuilder {
|
) -> ViewportBuilder {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
// `WindowBuilder::with_position` expects inner position in Macos, and outer position elsewhere
|
// `WindowBuilder::with_position` expects inner position in Macos, and outer position elsewhere
|
||||||
// See [`winit::window::WindowBuilder::with_position`] for details.
|
// See [`winit::window::WindowBuilder::with_position`] for details.
|
||||||
@@ -143,8 +143,7 @@ fn find_active_monitor(
|
|||||||
window_size_pts: egui::Vec2,
|
window_size_pts: egui::Vec2,
|
||||||
position_px: &egui::Pos2,
|
position_px: &egui::Pos2,
|
||||||
) -> Option<winit::monitor::MonitorHandle> {
|
) -> Option<winit::monitor::MonitorHandle> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let monitors = event_loop.available_monitors();
|
let monitors = event_loop.available_monitors();
|
||||||
|
|
||||||
// default to primary monitor, in case the correct monitor was disconnected.
|
// default to primary monitor, in case the correct monitor was disconnected.
|
||||||
@@ -178,7 +177,7 @@ fn clamp_pos_to_monitors(
|
|||||||
window_size_pts: egui::Vec2,
|
window_size_pts: egui::Vec2,
|
||||||
position_px: &mut egui::Pos2,
|
position_px: &mut egui::Pos2,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let Some(active_monitor) =
|
let Some(active_monitor) =
|
||||||
find_active_monitor(egui_zoom_factor, event_loop, window_size_pts, position_px)
|
find_active_monitor(egui_zoom_factor, event_loop, window_size_pts, position_px)
|
||||||
|
|||||||
@@ -62,10 +62,6 @@ mint = ["epaint/mint"]
|
|||||||
## Enable persistence of memory (window positions etc).
|
## Enable persistence of memory (window positions etc).
|
||||||
persistence = ["serde", "epaint/serde", "ron"]
|
persistence = ["serde", "epaint/serde", "ron"]
|
||||||
|
|
||||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
|
||||||
##
|
|
||||||
## Only enabled on native, because of the low resolution (1ms) of clocks in browsers.
|
|
||||||
puffin = ["dep:puffin", "epaint/puffin"]
|
|
||||||
|
|
||||||
## Enable parallel tessellation using [`rayon`](https://docs.rs/rayon).
|
## Enable parallel tessellation using [`rayon`](https://docs.rs/rayon).
|
||||||
##
|
##
|
||||||
@@ -85,6 +81,7 @@ epaint = { workspace = true, default-features = false }
|
|||||||
|
|
||||||
ahash.workspace = true
|
ahash.workspace = true
|
||||||
nohash-hasher.workspace = true
|
nohash-hasher.workspace = true
|
||||||
|
profiling.workspace = true
|
||||||
|
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
accesskit = { version = "0.17.0", optional = true }
|
accesskit = { version = "0.17.0", optional = true }
|
||||||
@@ -95,10 +92,5 @@ backtrace = { workspace = true, optional = true }
|
|||||||
document-features = { workspace = true, optional = true }
|
document-features = { workspace = true, optional = true }
|
||||||
|
|
||||||
log = { workspace = true, optional = true }
|
log = { workspace = true, optional = true }
|
||||||
puffin = { workspace = true, optional = true }
|
|
||||||
ron = { workspace = true, optional = true }
|
ron = { workspace = true, optional = true }
|
||||||
serde = { workspace = true, optional = true, features = ["derive", "rc"] }
|
serde = { workspace = true, optional = true, features = ["derive", "rc"] }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] }
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 45 KiB |
@@ -467,7 +467,7 @@ impl Area {
|
|||||||
id: interact_id,
|
id: interact_id,
|
||||||
layer_id,
|
layer_id,
|
||||||
rect: state.rect(),
|
rect: state.rect(),
|
||||||
interact_rect: state.rect(),
|
interact_rect: state.rect().intersect(constrain_rect),
|
||||||
sense,
|
sense,
|
||||||
enabled,
|
enabled,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -93,8 +93,8 @@ pub fn show_tooltip_at_pointer<R>(
|
|||||||
pointer_rect.min.x = pointer_pos.x;
|
pointer_rect.min.x = pointer_pos.x;
|
||||||
|
|
||||||
// Transform global coords to layer coords:
|
// Transform global coords to layer coords:
|
||||||
if let Some(transform) = ctx.memory(|m| m.layer_transforms.get(&parent_layer).copied()) {
|
if let Some(from_global) = ctx.layer_transform_from_global(parent_layer) {
|
||||||
pointer_rect = transform.inverse() * pointer_rect;
|
pointer_rect = from_global * pointer_rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
show_tooltip_at_dyn(
|
show_tooltip_at_dyn(
|
||||||
@@ -162,8 +162,8 @@ fn show_tooltip_at_dyn<'c, R>(
|
|||||||
) -> R {
|
) -> R {
|
||||||
// Transform layer coords to global coords:
|
// Transform layer coords to global coords:
|
||||||
let mut widget_rect = *widget_rect;
|
let mut widget_rect = *widget_rect;
|
||||||
if let Some(transform) = ctx.memory(|m| m.layer_transforms.get(&parent_layer).copied()) {
|
if let Some(to_global) = ctx.layer_transform_to_global(parent_layer) {
|
||||||
widget_rect = transform * widget_rect;
|
widget_rect = to_global * widget_rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
remember_that_tooltip_was_shown(ctx);
|
remember_that_tooltip_was_shown(ctx);
|
||||||
@@ -404,11 +404,12 @@ pub fn popup_above_or_below_widget<R>(
|
|||||||
AboveOrBelow::Above => (widget_response.rect.left_top(), Align2::LEFT_BOTTOM),
|
AboveOrBelow::Above => (widget_response.rect.left_top(), Align2::LEFT_BOTTOM),
|
||||||
AboveOrBelow::Below => (widget_response.rect.left_bottom(), Align2::LEFT_TOP),
|
AboveOrBelow::Below => (widget_response.rect.left_bottom(), Align2::LEFT_TOP),
|
||||||
};
|
};
|
||||||
if let Some(transform) = parent_ui
|
|
||||||
|
if let Some(to_global) = parent_ui
|
||||||
.ctx()
|
.ctx()
|
||||||
.memory(|m| m.layer_transforms.get(&parent_ui.layer_id()).copied())
|
.layer_transform_to_global(parent_ui.layer_id())
|
||||||
{
|
{
|
||||||
pos = transform * pos;
|
pos = to_global * pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame = Frame::popup(parent_ui.style());
|
let frame = Frame::popup(parent_ui.style());
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ struct Prepared {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Resize {
|
impl Resize {
|
||||||
fn begin(&mut self, ui: &mut Ui) -> Prepared {
|
fn begin(&self, ui: &mut Ui) -> Prepared {
|
||||||
let position = ui.available_rect_before_wrap().min;
|
let position = ui.available_rect_before_wrap().min;
|
||||||
let id = self.id.unwrap_or_else(|| {
|
let id = self.id.unwrap_or_else(|| {
|
||||||
let id_salt = self.id_salt.unwrap_or_else(|| Id::new("resize"));
|
let id_salt = self.id_salt.unwrap_or_else(|| Id::new("resize"));
|
||||||
@@ -295,7 +295,7 @@ impl Resize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show<R>(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
|
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
|
||||||
let mut prepared = self.begin(ui);
|
let mut prepared = self.begin(ui);
|
||||||
let ret = add_contents(&mut prepared.content_ui);
|
let ret = add_contents(&mut prepared.content_ui);
|
||||||
self.end(ui, prepared);
|
self.end(ui, prepared);
|
||||||
|
|||||||
@@ -109,13 +109,13 @@ struct Plugins {
|
|||||||
|
|
||||||
impl Plugins {
|
impl Plugins {
|
||||||
fn call(ctx: &Context, _cb_name: &str, callbacks: &[NamedContextCallback]) {
|
fn call(ctx: &Context, _cb_name: &str, callbacks: &[NamedContextCallback]) {
|
||||||
crate::profile_scope!("plugins", _cb_name);
|
profiling::scope!("plugins", _cb_name);
|
||||||
for NamedContextCallback {
|
for NamedContextCallback {
|
||||||
debug_name: _name,
|
debug_name: _name,
|
||||||
callback,
|
callback,
|
||||||
} in callbacks
|
} in callbacks
|
||||||
{
|
{
|
||||||
crate::profile_scope!("plugin", _name);
|
profiling::scope!("plugin", _name);
|
||||||
(callback)(ctx);
|
(callback)(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -498,19 +498,8 @@ impl ContextImpl {
|
|||||||
viewport.this_pass.begin_pass(screen_rect);
|
viewport.this_pass.begin_pass(screen_rect);
|
||||||
|
|
||||||
{
|
{
|
||||||
let area_order = self.memory.areas().order_map();
|
|
||||||
|
|
||||||
let mut layers: Vec<LayerId> = viewport.prev_pass.widgets.layer_ids().collect();
|
let mut layers: Vec<LayerId> = viewport.prev_pass.widgets.layer_ids().collect();
|
||||||
|
layers.sort_by(|&a, &b| self.memory.areas().compare_order(a, b));
|
||||||
layers.sort_by(|a, b| {
|
|
||||||
if a.order == b.order {
|
|
||||||
// Maybe both are windows, so respect area order:
|
|
||||||
area_order.get(a).cmp(&area_order.get(b))
|
|
||||||
} else {
|
|
||||||
// comparing e.g. background to tooltips
|
|
||||||
a.order.cmp(&b.order)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
viewport.hits = if let Some(pos) = viewport.input.pointer.interact_pos() {
|
viewport.hits = if let Some(pos) = viewport.input.pointer.interact_pos() {
|
||||||
let interact_radius = self.memory.options.style().interaction.interact_radius;
|
let interact_radius = self.memory.options.style().interaction.interact_radius;
|
||||||
@@ -518,7 +507,7 @@ impl ContextImpl {
|
|||||||
crate::hit_test::hit_test(
|
crate::hit_test::hit_test(
|
||||||
&viewport.prev_pass.widgets,
|
&viewport.prev_pass.widgets,
|
||||||
&layers,
|
&layers,
|
||||||
&self.memory.layer_transforms,
|
&self.memory.to_global,
|
||||||
pos,
|
pos,
|
||||||
interact_radius,
|
interact_radius,
|
||||||
)
|
)
|
||||||
@@ -549,7 +538,7 @@ impl ContextImpl {
|
|||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
if self.is_accesskit_enabled {
|
if self.is_accesskit_enabled {
|
||||||
crate::profile_scope!("accesskit");
|
profiling::scope!("accesskit");
|
||||||
use crate::pass_state::AccessKitPassState;
|
use crate::pass_state::AccessKitPassState;
|
||||||
let id = crate::accesskit_root_id();
|
let id = crate::accesskit_root_id();
|
||||||
let mut root_node = accesskit::Node::new(accesskit::Role::Window);
|
let mut root_node = accesskit::Node::new(accesskit::Role::Window);
|
||||||
@@ -568,8 +557,7 @@ impl ContextImpl {
|
|||||||
|
|
||||||
/// Load fonts unless already loaded.
|
/// Load fonts unless already loaded.
|
||||||
fn update_fonts_mut(&mut self) {
|
fn update_fonts_mut(&mut self) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let input = &self.viewport().input;
|
let input = &self.viewport().input;
|
||||||
let pixels_per_point = input.pixels_per_point();
|
let pixels_per_point = input.pixels_per_point();
|
||||||
let max_texture_side = input.max_texture_side;
|
let max_texture_side = input.max_texture_side;
|
||||||
@@ -616,7 +604,7 @@ impl ContextImpl {
|
|||||||
log::trace!("Creating new Fonts for pixels_per_point={pixels_per_point}");
|
log::trace!("Creating new Fonts for pixels_per_point={pixels_per_point}");
|
||||||
|
|
||||||
is_new = true;
|
is_new = true;
|
||||||
crate::profile_scope!("Fonts::new");
|
profiling::scope!("Fonts::new");
|
||||||
Fonts::new(
|
Fonts::new(
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
max_texture_side,
|
max_texture_side,
|
||||||
@@ -625,12 +613,12 @@ impl ContextImpl {
|
|||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
crate::profile_scope!("Fonts::begin_pass");
|
profiling::scope!("Fonts::begin_pass");
|
||||||
fonts.begin_pass(pixels_per_point, max_texture_side);
|
fonts.begin_pass(pixels_per_point, max_texture_side);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_new && self.memory.options.preload_font_glyphs {
|
if is_new && self.memory.options.preload_font_glyphs {
|
||||||
crate::profile_scope!("preload_font_glyphs");
|
profiling::scope!("preload_font_glyphs");
|
||||||
// Preload the most common characters for the most common fonts.
|
// Preload the most common characters for the most common fonts.
|
||||||
// This is not very important to do, but may save a few GPU operations.
|
// This is not very important to do, but may save a few GPU operations.
|
||||||
for font_id in self.memory.options.style().text_styles.values() {
|
for font_id in self.memory.options.style().text_styles.values() {
|
||||||
@@ -812,8 +800,7 @@ impl Context {
|
|||||||
/// ```
|
/// ```
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn run(&self, mut new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput {
|
pub fn run(&self, mut new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let viewport_id = new_input.viewport_id;
|
let viewport_id = new_input.viewport_id;
|
||||||
let max_passes = self.write(|ctx| ctx.memory.options.max_passes.get());
|
let max_passes = self.write(|ctx| ctx.memory.options.max_passes.get());
|
||||||
|
|
||||||
@@ -821,9 +808,13 @@ impl Context {
|
|||||||
debug_assert_eq!(output.platform_output.num_completed_passes, 0);
|
debug_assert_eq!(output.platform_output.num_completed_passes, 0);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
crate::profile_scope!(
|
profiling::scope!(
|
||||||
"pass",
|
"pass",
|
||||||
output.platform_output.num_completed_passes.to_string()
|
output
|
||||||
|
.platform_output
|
||||||
|
.num_completed_passes
|
||||||
|
.to_string()
|
||||||
|
.as_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
// We must move the `num_passes` (back) to the viewport output so that [`Self::will_discard`]
|
// We must move the `num_passes` (back) to the viewport output so that [`Self::will_discard`]
|
||||||
@@ -886,7 +877,7 @@ impl Context {
|
|||||||
/// // handle full_output
|
/// // handle full_output
|
||||||
/// ```
|
/// ```
|
||||||
pub fn begin_pass(&self, new_input: RawInput) {
|
pub fn begin_pass(&self, new_input: RawInput) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
self.write(|ctx| ctx.begin_pass(new_input));
|
self.write(|ctx| ctx.begin_pass(new_input));
|
||||||
|
|
||||||
@@ -1329,11 +1320,11 @@ impl Context {
|
|||||||
res.is_pointer_button_down_on || res.long_touched || clicked || res.drag_stopped;
|
res.is_pointer_button_down_on || res.long_touched || clicked || res.drag_stopped;
|
||||||
if is_interacted_with {
|
if is_interacted_with {
|
||||||
res.interact_pointer_pos = input.pointer.interact_pos();
|
res.interact_pointer_pos = input.pointer.interact_pos();
|
||||||
if let (Some(transform), Some(pos)) = (
|
if let (Some(to_global), Some(pos)) = (
|
||||||
memory.layer_transforms.get(&res.layer_id),
|
memory.to_global.get(&res.layer_id),
|
||||||
&mut res.interact_pointer_pos,
|
&mut res.interact_pointer_pos,
|
||||||
) {
|
) {
|
||||||
*pos = transform.inverse() * *pos;
|
*pos = to_global.inverse() * *pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1760,7 +1751,7 @@ impl Context {
|
|||||||
/// The new fonts will become active at the start of the next pass.
|
/// The new fonts will become active at the start of the next pass.
|
||||||
/// This will overwrite the existing fonts.
|
/// This will overwrite the existing fonts.
|
||||||
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
|
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let pixels_per_point = self.pixels_per_point();
|
let pixels_per_point = self.pixels_per_point();
|
||||||
|
|
||||||
@@ -1788,7 +1779,7 @@ impl Context {
|
|||||||
/// The new font will become active at the start of the next pass.
|
/// The new font will become active at the start of the next pass.
|
||||||
/// This will keep the existing fonts.
|
/// This will keep the existing fonts.
|
||||||
pub fn add_font(&self, new_font: FontInsert) {
|
pub fn add_font(&self, new_font: FontInsert) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let pixels_per_point = self.pixels_per_point();
|
let pixels_per_point = self.pixels_per_point();
|
||||||
|
|
||||||
@@ -2152,7 +2143,7 @@ impl Context {
|
|||||||
/// Call at the end of each frame if you called [`Context::begin_pass`].
|
/// Call at the end of each frame if you called [`Context::begin_pass`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn end_pass(&self) -> FullOutput {
|
pub fn end_pass(&self) -> FullOutput {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if self.options(|o| o.zoom_with_keyboard) {
|
if self.options(|o| o.zoom_with_keyboard) {
|
||||||
crate::gui_zoom::zoom_with_keyboard(self);
|
crate::gui_zoom::zoom_with_keyboard(self);
|
||||||
@@ -2246,7 +2237,8 @@ impl Context {
|
|||||||
for id in contains_pointer {
|
for id in contains_pointer {
|
||||||
let mut widget_text = format!("{id:?}");
|
let mut widget_text = format!("{id:?}");
|
||||||
if let Some(rect) = widget_rects.get(id) {
|
if let Some(rect) = widget_rects.get(id) {
|
||||||
widget_text += &format!(" {:?} {:?}", rect.rect, rect.sense);
|
widget_text +=
|
||||||
|
&format!(" {:?} {:?} {:?}", rect.layer_id, rect.rect, rect.sense);
|
||||||
}
|
}
|
||||||
if let Some(info) = widget_rects.info(id) {
|
if let Some(info) = widget_rects.info(id) {
|
||||||
widget_text += &format!(" {info:?}");
|
widget_text += &format!(" {info:?}");
|
||||||
@@ -2272,11 +2264,17 @@ impl Context {
|
|||||||
if self.style().debug.show_widget_hits {
|
if self.style().debug.show_widget_hits {
|
||||||
let hits = self.write(|ctx| ctx.viewport().hits.clone());
|
let hits = self.write(|ctx| ctx.viewport().hits.clone());
|
||||||
let WidgetHits {
|
let WidgetHits {
|
||||||
|
close,
|
||||||
contains_pointer,
|
contains_pointer,
|
||||||
click,
|
click,
|
||||||
drag,
|
drag,
|
||||||
} = hits;
|
} = hits;
|
||||||
|
|
||||||
|
if false {
|
||||||
|
for widget in &close {
|
||||||
|
paint_widget(widget, "close", Color32::from_gray(70));
|
||||||
|
}
|
||||||
|
}
|
||||||
if true {
|
if true {
|
||||||
for widget in &contains_pointer {
|
for widget in &contains_pointer {
|
||||||
paint_widget(widget, "contains_pointer", Color32::BLUE);
|
paint_widget(widget, "contains_pointer", Color32::BLUE);
|
||||||
@@ -2342,7 +2340,7 @@ impl ContextImpl {
|
|||||||
// https://github.com/emilk/egui/issues/3664
|
// https://github.com/emilk/egui/issues/3664
|
||||||
// at the cost of a lot of performance.
|
// at the cost of a lot of performance.
|
||||||
// (This will override any smaller delta that was uploaded above.)
|
// (This will override any smaller delta that was uploaded above.)
|
||||||
crate::profile_scope!("full_font_atlas_update");
|
profiling::scope!("full_font_atlas_update");
|
||||||
let full_delta = ImageDelta::full(fonts.image(), TextureAtlas::texture_options());
|
let full_delta = ImageDelta::full(fonts.image(), TextureAtlas::texture_options());
|
||||||
tex_mngr.set(TextureId::default(), full_delta);
|
tex_mngr.set(TextureId::default(), full_delta);
|
||||||
}
|
}
|
||||||
@@ -2356,7 +2354,7 @@ impl ContextImpl {
|
|||||||
|
|
||||||
#[cfg(feature = "accesskit")]
|
#[cfg(feature = "accesskit")]
|
||||||
{
|
{
|
||||||
crate::profile_scope!("accesskit");
|
profiling::scope!("accesskit");
|
||||||
let state = viewport.this_pass.accesskit_state.take();
|
let state = viewport.this_pass.accesskit_state.take();
|
||||||
if let Some(state) = state {
|
if let Some(state) = state {
|
||||||
let root_id = crate::accesskit_root_id().accesskit_id();
|
let root_id = crate::accesskit_root_id().accesskit_id();
|
||||||
@@ -2381,12 +2379,12 @@ impl ContextImpl {
|
|||||||
|
|
||||||
let shapes = viewport
|
let shapes = viewport
|
||||||
.graphics
|
.graphics
|
||||||
.drain(self.memory.areas().order(), &self.memory.layer_transforms);
|
.drain(self.memory.areas().order(), &self.memory.to_global);
|
||||||
|
|
||||||
let mut repaint_needed = false;
|
let mut repaint_needed = false;
|
||||||
|
|
||||||
if self.memory.options.repaint_on_widget_change {
|
if self.memory.options.repaint_on_widget_change {
|
||||||
crate::profile_function!("compare-widget-rects");
|
profiling::scope!("compare-widget-rects");
|
||||||
if viewport.prev_pass.widgets != viewport.this_pass.widgets {
|
if viewport.prev_pass.widgets != viewport.this_pass.widgets {
|
||||||
repaint_needed = true; // Some widget has moved
|
repaint_needed = true; // Some widget has moved
|
||||||
}
|
}
|
||||||
@@ -2525,7 +2523,7 @@ impl Context {
|
|||||||
shapes: Vec<ClippedShape>,
|
shapes: Vec<ClippedShape>,
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
) -> Vec<ClippedPrimitive> {
|
) -> Vec<ClippedPrimitive> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
// A tempting optimization is to reuse the tessellation from last frame if the
|
// A tempting optimization is to reuse the tessellation from last frame if the
|
||||||
// shapes are the same, but just comparing the shapes takes about 50% of the time
|
// shapes are the same, but just comparing the shapes takes about 50% of the time
|
||||||
@@ -2552,7 +2550,7 @@ impl Context {
|
|||||||
|
|
||||||
let paint_stats = PaintStats::from_shapes(&shapes);
|
let paint_stats = PaintStats::from_shapes(&shapes);
|
||||||
let clipped_primitives = {
|
let clipped_primitives = {
|
||||||
crate::profile_scope!("tessellator::tessellate_shapes");
|
profiling::scope!("tessellator::tessellate_shapes");
|
||||||
tessellator::Tessellator::new(
|
tessellator::Tessellator::new(
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
tessellation_options,
|
tessellation_options,
|
||||||
@@ -2697,6 +2695,7 @@ impl Context {
|
|||||||
/// Transform the graphics of the given layer.
|
/// Transform the graphics of the given layer.
|
||||||
///
|
///
|
||||||
/// This will also affect input.
|
/// This will also affect input.
|
||||||
|
/// The direction of the given transform is "into the global coordinate system".
|
||||||
///
|
///
|
||||||
/// This is a sticky setting, remembered from one frame to the next.
|
/// This is a sticky setting, remembered from one frame to the next.
|
||||||
///
|
///
|
||||||
@@ -2706,13 +2705,28 @@ impl Context {
|
|||||||
pub fn set_transform_layer(&self, layer_id: LayerId, transform: TSTransform) {
|
pub fn set_transform_layer(&self, layer_id: LayerId, transform: TSTransform) {
|
||||||
self.memory_mut(|m| {
|
self.memory_mut(|m| {
|
||||||
if transform == TSTransform::IDENTITY {
|
if transform == TSTransform::IDENTITY {
|
||||||
m.layer_transforms.remove(&layer_id)
|
m.to_global.remove(&layer_id)
|
||||||
} else {
|
} else {
|
||||||
m.layer_transforms.insert(layer_id, transform)
|
m.to_global.insert(layer_id, transform)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return how to transform the graphics of the given layer into the global coordinate system.
|
||||||
|
///
|
||||||
|
/// Set this with [`Self::layer_transform_to_global`].
|
||||||
|
pub fn layer_transform_to_global(&self, layer_id: LayerId) -> Option<TSTransform> {
|
||||||
|
self.memory(|m| m.to_global.get(&layer_id).copied())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return how to transform the graphics of the global coordinate system into the local coordinate system of the given layer.
|
||||||
|
///
|
||||||
|
/// This returns the inverse of [`Self::layer_transform_to_global`].
|
||||||
|
pub fn layer_transform_from_global(&self, layer_id: LayerId) -> Option<TSTransform> {
|
||||||
|
self.layer_transform_to_global(layer_id)
|
||||||
|
.map(|t| t.inverse())
|
||||||
|
}
|
||||||
|
|
||||||
/// Move all the graphics at the given layer.
|
/// Move all the graphics at the given layer.
|
||||||
///
|
///
|
||||||
/// Is used to implement drag-and-drop preview.
|
/// Is used to implement drag-and-drop preview.
|
||||||
@@ -2777,9 +2791,8 @@ impl Context {
|
|||||||
///
|
///
|
||||||
/// See also [`Response::contains_pointer`].
|
/// See also [`Response::contains_pointer`].
|
||||||
pub fn rect_contains_pointer(&self, layer_id: LayerId, rect: Rect) -> bool {
|
pub fn rect_contains_pointer(&self, layer_id: LayerId, rect: Rect) -> bool {
|
||||||
let rect =
|
let rect = if let Some(to_global) = self.layer_transform_to_global(layer_id) {
|
||||||
if let Some(transform) = self.memory(|m| m.layer_transforms.get(&layer_id).copied()) {
|
to_global * rect
|
||||||
transform * rect
|
|
||||||
} else {
|
} else {
|
||||||
rect
|
rect
|
||||||
};
|
};
|
||||||
@@ -3144,28 +3157,26 @@ impl Context {
|
|||||||
self.memory_mut(|mem| *mem.areas_mut() = Default::default());
|
self.memory_mut(|mem| *mem.areas_mut() = Default::default());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.indent("areas", |ui| {
|
ui.indent("layers", |ui| {
|
||||||
ui.label("Visible areas, ordered back to front.");
|
ui.label("Layers, ordered back to front.");
|
||||||
ui.label("Hover to highlight");
|
|
||||||
let layers_ids: Vec<LayerId> = self.memory(|mem| mem.areas().order().to_vec());
|
let layers_ids: Vec<LayerId> = self.memory(|mem| mem.areas().order().to_vec());
|
||||||
for layer_id in layers_ids {
|
for layer_id in layers_ids {
|
||||||
let area = AreaState::load(self, layer_id.id);
|
if let Some(area) = AreaState::load(self, layer_id.id) {
|
||||||
if let Some(area) = area {
|
|
||||||
let is_visible = self.memory(|mem| mem.areas().is_visible(&layer_id));
|
let is_visible = self.memory(|mem| mem.areas().is_visible(&layer_id));
|
||||||
if !is_visible {
|
if !is_visible {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let text = format!("{} - {:?}", layer_id.short_debug_format(), area.rect(),);
|
let text = format!("{} - {:?}", layer_id.short_debug_format(), area.rect(),);
|
||||||
// TODO(emilk): `Sense::hover_highlight()`
|
// TODO(emilk): `Sense::hover_highlight()`
|
||||||
if ui
|
let response =
|
||||||
.add(Label::new(RichText::new(text).monospace()).sense(Sense::click()))
|
ui.add(Label::new(RichText::new(text).monospace()).sense(Sense::click()));
|
||||||
.hovered
|
if response.hovered && is_visible {
|
||||||
&& is_visible
|
|
||||||
{
|
|
||||||
ui.ctx()
|
ui.ctx()
|
||||||
.debug_painter()
|
.debug_painter()
|
||||||
.debug_rect(area.rect(), Color32::RED, "");
|
.debug_rect(area.rect(), Color32::RED, "");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ui.monospace(layer_id.short_debug_format());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -3353,7 +3364,7 @@ impl Context {
|
|||||||
pub fn forget_image(&self, uri: &str) {
|
pub fn forget_image(&self, uri: &str) {
|
||||||
use load::BytesLoader as _;
|
use load::BytesLoader as _;
|
||||||
|
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let loaders = self.loaders();
|
let loaders = self.loaders();
|
||||||
|
|
||||||
@@ -3375,7 +3386,7 @@ impl Context {
|
|||||||
pub fn forget_all_images(&self) {
|
pub fn forget_all_images(&self) {
|
||||||
use load::BytesLoader as _;
|
use load::BytesLoader as _;
|
||||||
|
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let loaders = self.loaders();
|
let loaders = self.loaders();
|
||||||
|
|
||||||
@@ -3410,7 +3421,7 @@ impl Context {
|
|||||||
/// [not_supported]: crate::load::LoadError::NotSupported
|
/// [not_supported]: crate::load::LoadError::NotSupported
|
||||||
/// [custom]: crate::load::LoadError::Loading
|
/// [custom]: crate::load::LoadError::Loading
|
||||||
pub fn try_load_bytes(&self, uri: &str) -> load::BytesLoadResult {
|
pub fn try_load_bytes(&self, uri: &str) -> load::BytesLoadResult {
|
||||||
crate::profile_function!(uri);
|
profiling::function_scope!(uri);
|
||||||
|
|
||||||
let loaders = self.loaders();
|
let loaders = self.loaders();
|
||||||
let bytes_loaders = loaders.bytes.lock();
|
let bytes_loaders = loaders.bytes.lock();
|
||||||
@@ -3447,7 +3458,7 @@ impl Context {
|
|||||||
/// [not_supported]: crate::load::LoadError::NotSupported
|
/// [not_supported]: crate::load::LoadError::NotSupported
|
||||||
/// [custom]: crate::load::LoadError::Loading
|
/// [custom]: crate::load::LoadError::Loading
|
||||||
pub fn try_load_image(&self, uri: &str, size_hint: load::SizeHint) -> load::ImageLoadResult {
|
pub fn try_load_image(&self, uri: &str, size_hint: load::SizeHint) -> load::ImageLoadResult {
|
||||||
crate::profile_function!(uri);
|
profiling::function_scope!(uri);
|
||||||
|
|
||||||
let loaders = self.loaders();
|
let loaders = self.loaders();
|
||||||
let image_loaders = loaders.image.lock();
|
let image_loaders = loaders.image.lock();
|
||||||
@@ -3498,7 +3509,7 @@ impl Context {
|
|||||||
texture_options: TextureOptions,
|
texture_options: TextureOptions,
|
||||||
size_hint: load::SizeHint,
|
size_hint: load::SizeHint,
|
||||||
) -> load::TextureLoadResult {
|
) -> load::TextureLoadResult {
|
||||||
crate::profile_function!(uri);
|
profiling::function_scope!(uri);
|
||||||
|
|
||||||
let loaders = self.loaders();
|
let loaders = self.loaders();
|
||||||
let texture_loaders = loaders.texture.lock();
|
let texture_loaders = loaders.texture.lock();
|
||||||
@@ -3516,7 +3527,7 @@ impl Context {
|
|||||||
|
|
||||||
/// The loaders of bytes, images, and textures.
|
/// The loaders of bytes, images, and textures.
|
||||||
pub fn loaders(&self) -> Arc<Loaders> {
|
pub fn loaders(&self) -> Arc<Loaders> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
self.read(|this| this.loaders.clone())
|
self.read(|this| this.loaders.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3648,7 +3659,7 @@ impl Context {
|
|||||||
viewport_builder: ViewportBuilder,
|
viewport_builder: ViewportBuilder,
|
||||||
viewport_ui_cb: impl Fn(&Self, ViewportClass) + Send + Sync + 'static,
|
viewport_ui_cb: impl Fn(&Self, ViewportClass) + Send + Sync + 'static,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if self.embed_viewports() {
|
if self.embed_viewports() {
|
||||||
viewport_ui_cb(self, ViewportClass::Embedded);
|
viewport_ui_cb(self, ViewportClass::Embedded);
|
||||||
@@ -3700,7 +3711,7 @@ impl Context {
|
|||||||
builder: ViewportBuilder,
|
builder: ViewportBuilder,
|
||||||
mut viewport_ui_cb: impl FnMut(&Self, ViewportClass) -> T,
|
mut viewport_ui_cb: impl FnMut(&Self, ViewportClass) -> T,
|
||||||
) -> T {
|
) -> T {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if self.embed_viewports() {
|
if self.embed_viewports() {
|
||||||
return viewport_ui_cb(self, ViewportClass::Embedded);
|
return viewport_ui_cb(self, ViewportClass::Embedded);
|
||||||
|
|||||||
@@ -27,31 +27,44 @@ impl DragAndDrop {
|
|||||||
ctx.on_end_pass("drag_and_drop_end_pass", Arc::new(Self::end_pass));
|
ctx.on_end_pass("drag_and_drop_end_pass", Arc::new(Self::end_pass));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Interrupt drag-and-drop if the user presses the escape key.
|
||||||
|
///
|
||||||
|
/// This needs to happen at frame start so we can properly capture the escape key.
|
||||||
fn begin_pass(ctx: &Context) {
|
fn begin_pass(ctx: &Context) {
|
||||||
let has_any_payload = Self::has_any_payload(ctx);
|
let has_any_payload = Self::has_any_payload(ctx);
|
||||||
|
|
||||||
if has_any_payload {
|
if has_any_payload {
|
||||||
let abort_dnd = ctx.input_mut(|i| {
|
let abort_dnd_due_to_escape_key =
|
||||||
i.pointer.any_released()
|
ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
|
||||||
|| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape)
|
|
||||||
});
|
|
||||||
|
|
||||||
if abort_dnd {
|
if abort_dnd_due_to_escape_key {
|
||||||
Self::clear_payload(ctx);
|
Self::clear_payload(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Interrupt drag-and-drop if the user releases the mouse button.
|
||||||
|
///
|
||||||
|
/// This is a catch-all safety net in case user code doesn't capture the drag payload itself.
|
||||||
|
/// This must happen at end-of-frame such that we don't shadow the mouse release event from user
|
||||||
|
/// code.
|
||||||
fn end_pass(ctx: &Context) {
|
fn end_pass(ctx: &Context) {
|
||||||
let mut is_dragging = false;
|
let has_any_payload = Self::has_any_payload(ctx);
|
||||||
|
|
||||||
ctx.data_mut(|data| {
|
if has_any_payload {
|
||||||
let state = data.get_temp_mut_or_default::<Self>(Id::NULL);
|
let abort_dnd_due_to_mouse_release = ctx.input_mut(|i| i.pointer.any_released());
|
||||||
is_dragging = state.payload.is_some();
|
|
||||||
|
if abort_dnd_due_to_mouse_release {
|
||||||
|
Self::clear_payload(ctx);
|
||||||
|
} else {
|
||||||
|
// We set the cursor icon only if its default, as the user code might have
|
||||||
|
// explicitly set it already.
|
||||||
|
ctx.output_mut(|o| {
|
||||||
|
if o.cursor_icon == CursorIcon::Default {
|
||||||
|
o.cursor_icon = CursorIcon::Grabbing;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
if is_dragging {
|
|
||||||
ctx.set_cursor_icon(CursorIcon::Grabbing);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ impl GridLayout {
|
|||||||
self.col += 1;
|
self.col += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_row(&mut self, cursor: &Rect, painter: &Painter) {
|
fn paint_row(&self, cursor: &Rect, painter: &Painter) {
|
||||||
// handle row color painting based on color-picker function
|
// handle row color painting based on color-picker function
|
||||||
let Some(color_picker) = self.color_picker.as_ref() else {
|
let Some(color_picker) = self.color_picker.as_ref() else {
|
||||||
return;
|
return;
|
||||||
@@ -450,7 +450,7 @@ impl Grid {
|
|||||||
ui.allocate_new_ui(ui_builder, |ui| {
|
ui.allocate_new_ui(ui_builder, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let is_color = color_picker.is_some();
|
let is_color = color_picker.is_some();
|
||||||
let mut grid = GridLayout {
|
let grid = GridLayout {
|
||||||
num_columns,
|
num_columns,
|
||||||
color_picker,
|
color_picker,
|
||||||
min_cell_size: vec2(min_col_width, min_row_height),
|
min_cell_size: vec2(min_col_width, min_row_height),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use ahash::HashMap;
|
|||||||
|
|
||||||
use emath::TSTransform;
|
use emath::TSTransform;
|
||||||
|
|
||||||
use crate::{ahash, emath, LayerId, Pos2, WidgetRect, WidgetRects};
|
use crate::{ahash, emath, LayerId, Pos2, Rect, WidgetRect, WidgetRects};
|
||||||
|
|
||||||
/// Result of a hit-test against [`WidgetRects`].
|
/// Result of a hit-test against [`WidgetRects`].
|
||||||
///
|
///
|
||||||
@@ -12,11 +12,18 @@ use crate::{ahash, emath, LayerId, Pos2, WidgetRect, WidgetRects};
|
|||||||
/// or if we're currently already dragging something.
|
/// or if we're currently already dragging something.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct WidgetHits {
|
pub struct WidgetHits {
|
||||||
|
/// All widgets close to the pointer, back-to-front.
|
||||||
|
///
|
||||||
|
/// This is a superset of all other widgets in this struct.
|
||||||
|
pub close: Vec<WidgetRect>,
|
||||||
|
|
||||||
/// All widgets that contains the pointer, back-to-front.
|
/// All widgets that contains the pointer, back-to-front.
|
||||||
///
|
///
|
||||||
/// i.e. both a Window and the button in it can contain the pointer.
|
/// i.e. both a Window and the Button in it can contain the pointer.
|
||||||
///
|
///
|
||||||
/// Some of these may be widgets in a layer below the top-most layer.
|
/// Some of these may be widgets in a layer below the top-most layer.
|
||||||
|
///
|
||||||
|
/// This will be used for hovering.
|
||||||
pub contains_pointer: Vec<WidgetRect>,
|
pub contains_pointer: Vec<WidgetRect>,
|
||||||
|
|
||||||
/// If the user would start a clicking now, this is what would be clicked.
|
/// If the user would start a clicking now, this is what would be clicked.
|
||||||
@@ -35,18 +42,18 @@ pub struct WidgetHits {
|
|||||||
pub fn hit_test(
|
pub fn hit_test(
|
||||||
widgets: &WidgetRects,
|
widgets: &WidgetRects,
|
||||||
layer_order: &[LayerId],
|
layer_order: &[LayerId],
|
||||||
layer_transforms: &HashMap<LayerId, TSTransform>,
|
layer_to_global: &HashMap<LayerId, TSTransform>,
|
||||||
pos: Pos2,
|
pos: Pos2,
|
||||||
search_radius: f32,
|
search_radius: f32,
|
||||||
) -> WidgetHits {
|
) -> WidgetHits {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let search_radius_sq = search_radius * search_radius;
|
let search_radius_sq = search_radius * search_radius;
|
||||||
|
|
||||||
// Transform the position into the local coordinate space of each layer:
|
// Transform the position into the local coordinate space of each layer:
|
||||||
let pos_in_layers: HashMap<LayerId, Pos2> = layer_transforms
|
let pos_in_layers: HashMap<LayerId, Pos2> = layer_to_global
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(layer_id, t)| (*layer_id, t.inverse() * pos))
|
.map(|(layer_id, to_global)| (*layer_id, to_global.inverse() * pos))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut closest_dist_sq = f32::INFINITY;
|
let mut closest_dist_sq = f32::INFINITY;
|
||||||
@@ -63,6 +70,7 @@ pub fn hit_test(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let pos_in_layer = pos_in_layers.get(&w.layer_id).copied().unwrap_or(pos);
|
let pos_in_layer = pos_in_layers.get(&w.layer_id).copied().unwrap_or(pos);
|
||||||
|
// TODO(emilk): we should probably do the distance testing in global space instead
|
||||||
let dist_sq = w.interact_rect.distance_sq_to_pos(pos_in_layer);
|
let dist_sq = w.interact_rect.distance_sq_to_pos(pos_in_layer);
|
||||||
|
|
||||||
// In tie, pick last = topmost.
|
// In tie, pick last = topmost.
|
||||||
@@ -76,13 +84,46 @@ pub fn hit_test(
|
|||||||
.copied()
|
.copied()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// We need to pick one single layer for the interaction.
|
// Transform to global coordinates:
|
||||||
if let Some(closest_hit) = closest_hit {
|
for hit in &mut close {
|
||||||
// Select the top layer, and ignore widgets in any other layer:
|
if let Some(to_global) = layer_to_global.get(&hit.layer_id).copied() {
|
||||||
let top_layer = closest_hit.layer_id;
|
*hit = hit.transform(to_global);
|
||||||
close.retain(|w| w.layer_id == top_layer);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If the widget is disabled, treat it as if it isn't sensing anything.
|
// When using layer transforms it is common to stack layers close to each other.
|
||||||
|
// For instance, you may have a resize-separator on a panel, with two
|
||||||
|
// transform-layers on either side.
|
||||||
|
// The resize-separator is technically in a layer _behind_ the transform-layers,
|
||||||
|
// but the user doesn't perceive it as such.
|
||||||
|
// So how do we handle this case?
|
||||||
|
//
|
||||||
|
// If we just allow interactions with ALL close widgets,
|
||||||
|
// then we might accidentally allow clicks through windows and other bad stuff.
|
||||||
|
//
|
||||||
|
// Let's try this:
|
||||||
|
// * Set up a hit-area (based on search_radius)
|
||||||
|
// * Iterate over all hits top-to-bottom
|
||||||
|
// * Stop if any hit covers the whole hit-area, otherwise keep going
|
||||||
|
// * Collect the layers ids in a set
|
||||||
|
// * Remove all widgets not in the above layer set
|
||||||
|
//
|
||||||
|
// This will most often result in only one layer,
|
||||||
|
// but if the pointer is at the edge of a layer, we might include widgets in
|
||||||
|
// a layer behind it.
|
||||||
|
|
||||||
|
let mut included_layers: ahash::HashSet<LayerId> = Default::default();
|
||||||
|
for hit in close.iter().rev() {
|
||||||
|
included_layers.insert(hit.layer_id);
|
||||||
|
let hit_covers_search_area = contains_circle(hit.interact_rect, pos, search_radius);
|
||||||
|
if hit_covers_search_area {
|
||||||
|
break; // nothing behind this layer could ever be interacted with
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close.retain(|hit| included_layers.contains(&hit.layer_id));
|
||||||
|
|
||||||
|
// If a widget is disabled, treat it as if it isn't sensing anything.
|
||||||
// This simplifies the code in `hit_test_on_close` so it doesn't have to check
|
// This simplifies the code in `hit_test_on_close` so it doesn't have to check
|
||||||
// the `enabled` flag everywhere:
|
// the `enabled` flag everywhere:
|
||||||
for w in &mut close {
|
for w in &mut close {
|
||||||
@@ -92,35 +133,54 @@ pub fn hit_test(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let pos_in_layer = pos_in_layers.get(&top_layer).copied().unwrap_or(pos);
|
let mut hits = hit_test_on_close(&close, pos);
|
||||||
let hits = hit_test_on_close(&close, pos_in_layer);
|
|
||||||
|
|
||||||
if let Some(drag) = hits.drag {
|
hits.contains_pointer = close
|
||||||
debug_assert!(drag.sense.drag);
|
|
||||||
}
|
|
||||||
if let Some(click) = hits.click {
|
|
||||||
debug_assert!(click.sense.click);
|
|
||||||
}
|
|
||||||
|
|
||||||
hits
|
|
||||||
} else {
|
|
||||||
// No close widgets.
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
|
|
||||||
#![allow(clippy::collapsible_else_if)]
|
|
||||||
|
|
||||||
// Only those widgets directly under the `pos`.
|
|
||||||
let hits: Vec<WidgetRect> = close
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|widget| widget.interact_rect.contains(pos))
|
.filter(|widget| widget.interact_rect.contains(pos))
|
||||||
.copied()
|
.copied()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let hit_click = hits.iter().copied().filter(|w| w.sense.click).last();
|
hits.close = close;
|
||||||
let hit_drag = hits.iter().copied().filter(|w| w.sense.drag).last();
|
|
||||||
|
{
|
||||||
|
// Undo the to_global-transform we applied earlier,
|
||||||
|
// go back to local layer-coordinates:
|
||||||
|
|
||||||
|
let restore_widget_rect = |w: &mut WidgetRect| {
|
||||||
|
*w = widgets.get(w.id).copied().unwrap_or(*w);
|
||||||
|
};
|
||||||
|
|
||||||
|
for wr in &mut hits.close {
|
||||||
|
restore_widget_rect(wr);
|
||||||
|
}
|
||||||
|
for wr in &mut hits.contains_pointer {
|
||||||
|
restore_widget_rect(wr);
|
||||||
|
}
|
||||||
|
if let Some(wr) = &mut hits.drag {
|
||||||
|
debug_assert!(wr.sense.drag);
|
||||||
|
restore_widget_rect(wr);
|
||||||
|
}
|
||||||
|
if let Some(wr) = &mut hits.click {
|
||||||
|
debug_assert!(wr.sense.click);
|
||||||
|
restore_widget_rect(wr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hits
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the rectangle contains the whole circle.
|
||||||
|
fn contains_circle(interact_rect: emath::Rect, pos: Pos2, radius: f32) -> bool {
|
||||||
|
interact_rect.shrink(radius).contains(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
|
||||||
|
#![allow(clippy::collapsible_else_if)]
|
||||||
|
|
||||||
|
// First find the best direct hits:
|
||||||
|
let hit_click = find_closest_within(close.iter().copied().filter(|w| w.sense.click), pos, 0.0);
|
||||||
|
let hit_drag = find_closest_within(close.iter().copied().filter(|w| w.sense.drag), pos, 0.0);
|
||||||
|
|
||||||
match (hit_click, hit_drag) {
|
match (hit_click, hit_drag) {
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
@@ -136,16 +196,16 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
|
|||||||
|
|
||||||
if let Some(closest) = closest {
|
if let Some(closest) = closest {
|
||||||
WidgetHits {
|
WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: closest.sense.click.then_some(closest),
|
click: closest.sense.click.then_some(closest),
|
||||||
drag: closest.sense.drag.then_some(closest),
|
drag: closest.sense.drag.then_some(closest),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Found nothing
|
// Found nothing
|
||||||
WidgetHits {
|
WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: None,
|
click: None,
|
||||||
drag: None,
|
drag: None,
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,17 +230,17 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
|
|||||||
// This is a smaller thing on a big background - help the user hit it,
|
// This is a smaller thing on a big background - help the user hit it,
|
||||||
// and ignore the big drag background.
|
// and ignore the big drag background.
|
||||||
WidgetHits {
|
WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: Some(closest_click),
|
click: Some(closest_click),
|
||||||
drag: Some(closest_click),
|
drag: Some(closest_click),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The drag wiudth is separate from the click wiudth,
|
// The drag-widget is separate from the click-widget,
|
||||||
// so return only the drag widget
|
// so return only the drag-widget
|
||||||
WidgetHits {
|
WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: None,
|
click: None,
|
||||||
drag: Some(hit_drag),
|
drag: Some(hit_drag),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -194,17 +254,17 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
|
|||||||
// The drag widget is a big background thing (scroll area),
|
// The drag widget is a big background thing (scroll area),
|
||||||
// so returning a separate click widget should not be confusing
|
// so returning a separate click widget should not be confusing
|
||||||
WidgetHits {
|
WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: Some(closest_click),
|
click: Some(closest_click),
|
||||||
drag: Some(hit_drag),
|
drag: Some(hit_drag),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The two widgets are just two normal small widgets close to each other.
|
// The two widgets are just two normal small widgets close to each other.
|
||||||
// Highlighting both would be very confusing.
|
// Highlighting both would be very confusing.
|
||||||
WidgetHits {
|
WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: None,
|
click: None,
|
||||||
drag: Some(hit_drag),
|
drag: Some(hit_drag),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,17 +289,17 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
|
|||||||
// `hit_drag` is a big background thing and `closest_drag` is something small on top of it.
|
// `hit_drag` is a big background thing and `closest_drag` is something small on top of it.
|
||||||
// Be helpful and return the small things:
|
// Be helpful and return the small things:
|
||||||
return WidgetHits {
|
return WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: None,
|
click: None,
|
||||||
drag: Some(closest_drag),
|
drag: Some(closest_drag),
|
||||||
|
..Default::default()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WidgetHits {
|
WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: None,
|
click: None,
|
||||||
drag: Some(hit_drag),
|
drag: Some(hit_drag),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,57 +313,57 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
|
|||||||
// where when hovering directly over a drag-widget (like a big ScrollArea),
|
// where when hovering directly over a drag-widget (like a big ScrollArea),
|
||||||
// we look for close click-widgets (e.g. buttons).
|
// we look for close click-widgets (e.g. buttons).
|
||||||
// This is because big background drag-widgets (ScrollArea, Window) are common,
|
// This is because big background drag-widgets (ScrollArea, Window) are common,
|
||||||
// but bit clickable things aren't.
|
// but big clickable things aren't.
|
||||||
// Even if they were, I think it would be confusing for a user if clicking
|
// Even if they were, I think it would be confusing for a user if clicking
|
||||||
// a drag-only widget would click something _behind_ it.
|
// a drag-only widget would click something _behind_ it.
|
||||||
|
|
||||||
WidgetHits {
|
WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: Some(hit_click),
|
click: Some(hit_click),
|
||||||
drag: None,
|
drag: None,
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(Some(hit_click), Some(hit_drag)) => {
|
(Some(hit_click), Some(hit_drag)) => {
|
||||||
// We have a perfect hit on both click and drag. Which is the topmost?
|
// We have a perfect hit on both click and drag. Which is the topmost?
|
||||||
let click_idx = hits.iter().position(|w| *w == hit_click).unwrap();
|
let click_idx = close.iter().position(|w| *w == hit_click).unwrap();
|
||||||
let drag_idx = hits.iter().position(|w| *w == hit_drag).unwrap();
|
let drag_idx = close.iter().position(|w| *w == hit_drag).unwrap();
|
||||||
|
|
||||||
let click_is_on_top_of_drag = drag_idx < click_idx;
|
let click_is_on_top_of_drag = drag_idx < click_idx;
|
||||||
if click_is_on_top_of_drag {
|
if click_is_on_top_of_drag {
|
||||||
if hit_click.sense.drag {
|
if hit_click.sense.drag {
|
||||||
// The top thing senses both clicks and drags.
|
// The top thing senses both clicks and drags.
|
||||||
WidgetHits {
|
WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: Some(hit_click),
|
click: Some(hit_click),
|
||||||
drag: Some(hit_click),
|
drag: Some(hit_click),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// They are interested in different things,
|
// They are interested in different things,
|
||||||
// and click is on top. Report both hits,
|
// and click is on top. Report both hits,
|
||||||
// e.g. the top Button and the ScrollArea behind it.
|
// e.g. the top Button and the ScrollArea behind it.
|
||||||
WidgetHits {
|
WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: Some(hit_click),
|
click: Some(hit_click),
|
||||||
drag: Some(hit_drag),
|
drag: Some(hit_drag),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if hit_drag.sense.click {
|
if hit_drag.sense.click {
|
||||||
// The top thing senses both clicks and drags.
|
// The top thing senses both clicks and drags.
|
||||||
WidgetHits {
|
WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: Some(hit_drag),
|
click: Some(hit_drag),
|
||||||
drag: Some(hit_drag),
|
drag: Some(hit_drag),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The top things senses only drags,
|
// The top things senses only drags,
|
||||||
// so we ignore the click-widget, because it would be confusing
|
// so we ignore the click-widget, because it would be confusing
|
||||||
// if clicking a drag-widget would actually click something else below it.
|
// if clicking a drag-widget would actually click something else below it.
|
||||||
WidgetHits {
|
WidgetHits {
|
||||||
contains_pointer: hits,
|
|
||||||
click: None,
|
click: None,
|
||||||
drag: Some(hit_drag),
|
drag: Some(hit_drag),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,8 +372,16 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn find_closest(widgets: impl Iterator<Item = WidgetRect>, pos: Pos2) -> Option<WidgetRect> {
|
fn find_closest(widgets: impl Iterator<Item = WidgetRect>, pos: Pos2) -> Option<WidgetRect> {
|
||||||
let mut closest = None;
|
find_closest_within(widgets, pos, f32::INFINITY)
|
||||||
let mut closest_dist_sq = f32::INFINITY;
|
}
|
||||||
|
|
||||||
|
fn find_closest_within(
|
||||||
|
widgets: impl Iterator<Item = WidgetRect>,
|
||||||
|
pos: Pos2,
|
||||||
|
max_dist: f32,
|
||||||
|
) -> Option<WidgetRect> {
|
||||||
|
let mut closest: Option<WidgetRect> = None;
|
||||||
|
let mut closest_dist_sq = max_dist * max_dist;
|
||||||
for widget in widgets {
|
for widget in widgets {
|
||||||
if widget.interact_rect.is_negative() {
|
if widget.interact_rect.is_negative() {
|
||||||
continue;
|
continue;
|
||||||
@@ -321,6 +389,16 @@ fn find_closest(widgets: impl Iterator<Item = WidgetRect>, pos: Pos2) -> Option<
|
|||||||
|
|
||||||
let dist_sq = widget.interact_rect.distance_sq_to_pos(pos);
|
let dist_sq = widget.interact_rect.distance_sq_to_pos(pos);
|
||||||
|
|
||||||
|
if let Some(closest) = closest {
|
||||||
|
if dist_sq == closest_dist_sq {
|
||||||
|
// It's a tie! Pick the thin candidate over the thick one.
|
||||||
|
// This makes it easier to hit a thin resize-handle, for instance:
|
||||||
|
if should_prioritizie_hits_on_back(closest.interact_rect, widget.interact_rect) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// In case of a tie, take the last one = the one on top.
|
// In case of a tie, take the last one = the one on top.
|
||||||
if dist_sq <= closest_dist_sq {
|
if dist_sq <= closest_dist_sq {
|
||||||
closest_dist_sq = dist_sq;
|
closest_dist_sq = dist_sq;
|
||||||
@@ -331,6 +409,27 @@ fn find_closest(widgets: impl Iterator<Item = WidgetRect>, pos: Pos2) -> Option<
|
|||||||
closest
|
closest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Should we prioritizie hits on `back` over those on `front`?
|
||||||
|
///
|
||||||
|
/// `back` should be behind the `front` widget.
|
||||||
|
///
|
||||||
|
/// Returns true if `back` is a small hit-target and `front` is not.
|
||||||
|
fn should_prioritizie_hits_on_back(back: Rect, front: Rect) -> bool {
|
||||||
|
if front.contains_rect(back) {
|
||||||
|
return false; // back widget is fully occluded; no way to hit it
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce each rect to its width or height, whichever is smaller:
|
||||||
|
let back = back.width().min(back.height());
|
||||||
|
let front = front.width().min(front.height());
|
||||||
|
|
||||||
|
// These are hard-coded heuristics that could surely be improved.
|
||||||
|
let back_is_much_thinner = back <= 0.5 * front;
|
||||||
|
let back_is_thin = back <= 16.0;
|
||||||
|
|
||||||
|
back_is_much_thinner && back_is_thin
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use emath::{pos2, vec2, Rect};
|
use emath::{pos2, vec2, Rect};
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ impl InputState {
|
|||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
options: &crate::Options,
|
options: &crate::Options,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let time = new.time.unwrap_or(self.time + new.predicted_dt as f64);
|
let time = new.time.unwrap_or(self.time + new.predicted_dt as f64);
|
||||||
let unstable_dt = (time - self.time) as f32;
|
let unstable_dt = (time - self.time) as f32;
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ pub(crate) fn interact(
|
|||||||
input: &InputState,
|
input: &InputState,
|
||||||
interaction: &mut InteractionState,
|
interaction: &mut InteractionState,
|
||||||
) -> InteractionSnapshot {
|
) -> InteractionSnapshot {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if let Some(id) = interaction.potential_click_id {
|
if let Some(id) = interaction.potential_click_id {
|
||||||
if !widgets.contains(id) {
|
if !widgets.contains(id) {
|
||||||
@@ -249,7 +249,7 @@ pub(crate) fn interact(
|
|||||||
.copied()
|
.copied()
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
// We may be hovering a an interactive widget or two.
|
// We may be hovering an interactive widget or two.
|
||||||
// We must also consider the case where non-interactive widgets
|
// We must also consider the case where non-interactive widgets
|
||||||
// are _on top_ of an interactive widget.
|
// are _on top_ of an interactive widget.
|
||||||
// For instance: a label in a draggable window.
|
// For instance: a label in a draggable window.
|
||||||
@@ -264,9 +264,9 @@ pub(crate) fn interact(
|
|||||||
// but none below it (an interactive widget stops the hover search).
|
// but none below it (an interactive widget stops the hover search).
|
||||||
//
|
//
|
||||||
// To know when to stop we need to first know the order of the widgets,
|
// To know when to stop we need to first know the order of the widgets,
|
||||||
// which luckily we have in the `WidgetRects`.
|
// which luckily we already have in `hits.close`.
|
||||||
|
|
||||||
let order = |id| widgets.order(id).map(|(_layer, order)| order); // we ignore the layer, since all widgets at this point is in the same layer
|
let order = |id| hits.close.iter().position(|w| w.id == id);
|
||||||
|
|
||||||
let click_order = hits.click.and_then(|w| order(w.id)).unwrap_or(0);
|
let click_order = hits.click.and_then(|w| order(w.id)).unwrap_or(0);
|
||||||
let drag_order = hits.drag.and_then(|w| order(w.id)).unwrap_or(0);
|
let drag_order = hits.drag.and_then(|w| order(w.id)).unwrap_or(0);
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ pub enum Order {
|
|||||||
/// Painted behind all floating windows
|
/// Painted behind all floating windows
|
||||||
Background,
|
Background,
|
||||||
|
|
||||||
/// Special layer between panels and windows
|
|
||||||
PanelResizeLine,
|
|
||||||
|
|
||||||
/// Normal moveable windows that you reorder by click
|
/// Normal moveable windows that you reorder by click
|
||||||
Middle,
|
Middle,
|
||||||
|
|
||||||
@@ -30,10 +27,9 @@ pub enum Order {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Order {
|
impl Order {
|
||||||
const COUNT: usize = 6;
|
const COUNT: usize = 5;
|
||||||
const ALL: [Self; Self::COUNT] = [
|
const ALL: [Self; Self::COUNT] = [
|
||||||
Self::Background,
|
Self::Background,
|
||||||
Self::PanelResizeLine,
|
|
||||||
Self::Middle,
|
Self::Middle,
|
||||||
Self::Foreground,
|
Self::Foreground,
|
||||||
Self::Tooltip,
|
Self::Tooltip,
|
||||||
@@ -44,12 +40,9 @@ impl Order {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_interaction(&self) -> bool {
|
pub fn allow_interaction(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Background
|
Self::Background | Self::Middle | Self::Foreground | Self::Tooltip | Self::Debug => {
|
||||||
| Self::PanelResizeLine
|
true
|
||||||
| Self::Middle
|
}
|
||||||
| Self::Foreground
|
|
||||||
| Self::Tooltip
|
|
||||||
| Self::Debug => true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +50,6 @@ impl Order {
|
|||||||
pub fn short_debug_format(&self) -> &'static str {
|
pub fn short_debug_format(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Background => "backg",
|
Self::Background => "backg",
|
||||||
Self::PanelResizeLine => "panel",
|
|
||||||
Self::Middle => "middl",
|
Self::Middle => "middl",
|
||||||
Self::Foreground => "foreg",
|
Self::Foreground => "foreg",
|
||||||
Self::Tooltip => "toolt",
|
Self::Tooltip => "toolt",
|
||||||
@@ -68,7 +60,7 @@ impl Order {
|
|||||||
|
|
||||||
/// An identifier for a paint layer.
|
/// An identifier for a paint layer.
|
||||||
/// Also acts as an identifier for [`crate::Area`]:s.
|
/// Also acts as an identifier for [`crate::Area`]:s.
|
||||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
|
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct LayerId {
|
pub struct LayerId {
|
||||||
pub order: Order,
|
pub order: Order,
|
||||||
@@ -110,6 +102,13 @@ impl LayerId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for LayerId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let Self { order, id } = self;
|
||||||
|
write!(f, "LayerId {{ {order:?} {id:?} }}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A unique identifier of a specific [`Shape`] in a [`PaintList`].
|
/// A unique identifier of a specific [`Shape`] in a [`PaintList`].
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
@@ -221,9 +220,9 @@ impl GraphicLayers {
|
|||||||
pub fn drain(
|
pub fn drain(
|
||||||
&mut self,
|
&mut self,
|
||||||
area_order: &[LayerId],
|
area_order: &[LayerId],
|
||||||
transforms: &ahash::HashMap<LayerId, TSTransform>,
|
to_global: &ahash::HashMap<LayerId, TSTransform>,
|
||||||
) -> Vec<ClippedShape> {
|
) -> Vec<ClippedShape> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let mut all_shapes: Vec<_> = Default::default();
|
let mut all_shapes: Vec<_> = Default::default();
|
||||||
|
|
||||||
@@ -239,10 +238,10 @@ impl GraphicLayers {
|
|||||||
for layer_id in area_order {
|
for layer_id in area_order {
|
||||||
if layer_id.order == order {
|
if layer_id.order == order {
|
||||||
if let Some(list) = order_map.get_mut(&layer_id.id) {
|
if let Some(list) = order_map.get_mut(&layer_id.id) {
|
||||||
if let Some(transform) = transforms.get(layer_id) {
|
if let Some(to_global) = to_global.get(layer_id) {
|
||||||
for clipped_shape in &mut list.0 {
|
for clipped_shape in &mut list.0 {
|
||||||
clipped_shape.clip_rect = *transform * clipped_shape.clip_rect;
|
clipped_shape.clip_rect = *to_global * clipped_shape.clip_rect;
|
||||||
clipped_shape.shape.transform(*transform);
|
clipped_shape.shape.transform(*to_global);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
all_shapes.append(&mut list.0);
|
all_shapes.append(&mut list.0);
|
||||||
@@ -254,10 +253,10 @@ impl GraphicLayers {
|
|||||||
for (id, list) in order_map {
|
for (id, list) in order_map {
|
||||||
let layer_id = LayerId::new(order, *id);
|
let layer_id = LayerId::new(order, *id);
|
||||||
|
|
||||||
if let Some(transform) = transforms.get(&layer_id) {
|
if let Some(to_global) = to_global.get(&layer_id) {
|
||||||
for clipped_shape in &mut list.0 {
|
for clipped_shape in &mut list.0 {
|
||||||
clipped_shape.clip_rect = *transform * clipped_shape.clip_rect;
|
clipped_shape.clip_rect = *to_global * clipped_shape.clip_rect;
|
||||||
clipped_shape.shape.transform(*transform);
|
clipped_shape.shape.transform(*to_global);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//! Try the live web demo: <https://www.egui.rs/#demo>. Read more about egui at <https://github.com/emilk/egui>.
|
//! Try the live web demo: <https://www.egui.rs/#demo>. Read more about egui at <https://github.com/emilk/egui>.
|
||||||
//!
|
//!
|
||||||
//! `egui` is in heavy development, with each new version having breaking changes.
|
//! `egui` is in heavy development, with each new version having breaking changes.
|
||||||
//! You need to have rust 1.79.0 or later to use `egui`.
|
//! You need to have rust 1.80.0 or later to use `egui`.
|
||||||
//!
|
//!
|
||||||
//! To quickly get started with egui, you can take a look at [`eframe_template`](https://github.com/emilk/eframe_template)
|
//! To quickly get started with egui, you can take a look at [`eframe_template`](https://github.com/emilk/eframe_template)
|
||||||
//! which uses [`eframe`](https://docs.rs/eframe).
|
//! which uses [`eframe`](https://docs.rs/eframe).
|
||||||
@@ -388,6 +388,18 @@
|
|||||||
//! ## Installing additional fonts
|
//! ## Installing additional fonts
|
||||||
//! The default egui fonts only support latin and cryllic characters, and some emojis.
|
//! The default egui fonts only support latin and cryllic characters, and some emojis.
|
||||||
//! To use egui with e.g. asian characters you need to install your own font (`.ttf` or `.otf`) using [`Context::set_fonts`].
|
//! To use egui with e.g. asian characters you need to install your own font (`.ttf` or `.otf`) using [`Context::set_fonts`].
|
||||||
|
//!
|
||||||
|
//! ## Instrumentation
|
||||||
|
//! This crate supports using the [profiling](https://crates.io/crates/profiling) crate for instrumentation.
|
||||||
|
//! You can enable features on the profiling crates in your application to add instrumentation for all
|
||||||
|
//! crates that support it, including egui. See the profiling crate docs for more information.
|
||||||
|
//! ```toml
|
||||||
|
//! [dependencies]
|
||||||
|
//! profiling = "1.0"
|
||||||
|
//! [features]
|
||||||
|
//! profile-with-puffin = ["profiling/profile-with-puffin"]
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
|
||||||
#![allow(clippy::float_cmp)]
|
#![allow(clippy::float_cmp)]
|
||||||
#![allow(clippy::manual_range_contains)]
|
#![allow(clippy::manual_range_contains)]
|
||||||
@@ -691,33 +703,3 @@ pub fn __run_test_ui(add_contents: impl Fn(&mut Ui)) {
|
|||||||
pub fn accesskit_root_id() -> Id {
|
pub fn accesskit_root_id() -> Id {
|
||||||
Id::new("accesskit_root")
|
Id::new("accesskit_root")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
mod profiling_scopes {
|
|
||||||
#![allow(unused_macros)]
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_function {
|
|
||||||
($($arg: tt)*) => {
|
|
||||||
#[cfg(feature = "puffin")]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
|
||||||
puffin::profile_function!($($arg)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use profile_function;
|
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_scope {
|
|
||||||
($($arg: tt)*) => {
|
|
||||||
#[cfg(feature = "puffin")]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
|
||||||
puffin::profile_scope!($($arg)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use profile_scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub(crate) use profiling_scopes::{profile_function, profile_scope};
|
|
||||||
|
|||||||
@@ -95,8 +95,13 @@ pub struct Memory {
|
|||||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||||
everything_is_visible: bool,
|
everything_is_visible: bool,
|
||||||
|
|
||||||
/// Transforms per layer
|
/// Transforms per layer.
|
||||||
pub layer_transforms: HashMap<LayerId, TSTransform>,
|
///
|
||||||
|
/// Instead of using this directly, use:
|
||||||
|
/// * [`crate::Context::set_transform_layer`]
|
||||||
|
/// * [`crate::Context::layer_transform_to_global`]
|
||||||
|
/// * [`crate::Context::layer_transform_from_global`]
|
||||||
|
pub to_global: HashMap<LayerId, TSTransform>,
|
||||||
|
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
// Per-viewport:
|
// Per-viewport:
|
||||||
@@ -120,7 +125,7 @@ impl Default for Memory {
|
|||||||
focus: Default::default(),
|
focus: Default::default(),
|
||||||
viewport_id: Default::default(),
|
viewport_id: Default::default(),
|
||||||
areas: Default::default(),
|
areas: Default::default(),
|
||||||
layer_transforms: Default::default(),
|
to_global: Default::default(),
|
||||||
popup: Default::default(),
|
popup: Default::default(),
|
||||||
everything_is_visible: Default::default(),
|
everything_is_visible: Default::default(),
|
||||||
add_fonts: Default::default(),
|
add_fonts: Default::default(),
|
||||||
@@ -774,7 +779,7 @@ impl Focus {
|
|||||||
|
|
||||||
impl Memory {
|
impl Memory {
|
||||||
pub(crate) fn begin_pass(&mut self, new_raw_input: &RawInput, viewports: &ViewportIdSet) {
|
pub(crate) fn begin_pass(&mut self, new_raw_input: &RawInput, viewports: &ViewportIdSet) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
self.viewport_id = new_raw_input.viewport_id;
|
self.viewport_id = new_raw_input.viewport_id;
|
||||||
|
|
||||||
@@ -819,7 +824,7 @@ impl Memory {
|
|||||||
/// Top-most layer at the given position.
|
/// Top-most layer at the given position.
|
||||||
pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> {
|
pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> {
|
||||||
self.areas()
|
self.areas()
|
||||||
.layer_id_at(pos, &self.layer_transforms)
|
.layer_id_at(pos, &self.to_global)
|
||||||
.and_then(|layer_id| {
|
.and_then(|layer_id| {
|
||||||
if self.is_above_modal_layer(layer_id) {
|
if self.is_above_modal_layer(layer_id) {
|
||||||
Some(layer_id)
|
Some(layer_id)
|
||||||
@@ -829,6 +834,12 @@ impl Memory {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The currently set transform of a layer.
|
||||||
|
#[deprecated = "Use `Context::layer_transform_to_global` instead"]
|
||||||
|
pub fn layer_transforms(&self, layer_id: LayerId) -> Option<TSTransform> {
|
||||||
|
self.to_global.get(&layer_id).copied()
|
||||||
|
}
|
||||||
|
|
||||||
/// An iterator over all layers. Back-to-front, top is last.
|
/// An iterator over all layers. Back-to-front, top is last.
|
||||||
pub fn layer_ids(&self) -> impl ExactSizeIterator<Item = LayerId> + '_ {
|
pub fn layer_ids(&self) -> impl ExactSizeIterator<Item = LayerId> + '_ {
|
||||||
self.areas().order().iter().copied()
|
self.areas().order().iter().copied()
|
||||||
@@ -1121,15 +1132,18 @@ type OrderMap = HashMap<LayerId, usize>;
|
|||||||
pub struct Areas {
|
pub struct Areas {
|
||||||
areas: IdMap<area::AreaState>,
|
areas: IdMap<area::AreaState>,
|
||||||
|
|
||||||
|
visible_areas_last_frame: ahash::HashSet<LayerId>,
|
||||||
|
visible_areas_current_frame: ahash::HashSet<LayerId>,
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
// Everything below this is general to all layers, not just areas.
|
||||||
|
// TODO(emilk): move this to a separate struct.
|
||||||
/// Back-to-front, top is last.
|
/// Back-to-front, top is last.
|
||||||
order: Vec<LayerId>,
|
order: Vec<LayerId>,
|
||||||
|
|
||||||
/// Actual order of the layers, pre-calculated each frame.
|
/// Inverse of [`Self::order`], calculated at the end of the frame.
|
||||||
order_map: OrderMap,
|
order_map: OrderMap,
|
||||||
|
|
||||||
visible_last_frame: ahash::HashSet<LayerId>,
|
|
||||||
visible_current_frame: ahash::HashSet<LayerId>,
|
|
||||||
|
|
||||||
/// When an area wants to be on top, it is assigned here.
|
/// When an area wants to be on top, it is assigned here.
|
||||||
/// This is used to reorder the layers at the end of the frame.
|
/// This is used to reorder the layers at the end of the frame.
|
||||||
/// If several layers want to be on top, they will keep their relative order.
|
/// If several layers want to be on top, they will keep their relative order.
|
||||||
@@ -1137,9 +1151,9 @@ pub struct Areas {
|
|||||||
/// results in them being sent to the top and keeping their previous internal order.
|
/// results in them being sent to the top and keeping their previous internal order.
|
||||||
wants_to_be_on_top: ahash::HashSet<LayerId>,
|
wants_to_be_on_top: ahash::HashSet<LayerId>,
|
||||||
|
|
||||||
/// List of sublayers for each layer.
|
/// The sublayers that each layer has.
|
||||||
///
|
///
|
||||||
/// When a layer has sublayers, they are moved directly above it in the ordering.
|
/// The parent sublayer is moved directly above the child sublayers in the ordering.
|
||||||
sublayers: ahash::HashMap<LayerId, HashSet<LayerId>>,
|
sublayers: ahash::HashMap<LayerId, HashSet<LayerId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1152,17 +1166,13 @@ impl Areas {
|
|||||||
self.areas.get(&id)
|
self.areas.get(&id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Back-to-front, top is last.
|
/// All layers back-to-front, top is last.
|
||||||
pub(crate) fn order(&self) -> &[LayerId] {
|
pub(crate) fn order(&self) -> &[LayerId] {
|
||||||
&self.order
|
&self.order
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For each layer, which [`Self::order`] is it in?
|
|
||||||
pub(crate) fn order_map(&self) -> &OrderMap {
|
|
||||||
&self.order_map
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compare the order of two layers, based on the order list from last frame.
|
/// Compare the order of two layers, based on the order list from last frame.
|
||||||
|
///
|
||||||
/// May return [`std::cmp::Ordering::Equal`] if the layers are not in the order list.
|
/// May return [`std::cmp::Ordering::Equal`] if the layers are not in the order list.
|
||||||
pub(crate) fn compare_order(&self, a: LayerId, b: LayerId) -> std::cmp::Ordering {
|
pub(crate) fn compare_order(&self, a: LayerId, b: LayerId) -> std::cmp::Ordering {
|
||||||
if let (Some(a), Some(b)) = (self.order_map.get(&a), self.order_map.get(&b)) {
|
if let (Some(a), Some(b)) = (self.order_map.get(&a), self.order_map.get(&b)) {
|
||||||
@@ -1172,18 +1182,8 @@ impl Areas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the order map.
|
|
||||||
fn calculate_order_map(&mut self) {
|
|
||||||
self.order_map = self
|
|
||||||
.order
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, id)| (*id, i))
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn set_state(&mut self, layer_id: LayerId, state: area::AreaState) {
|
pub(crate) fn set_state(&mut self, layer_id: LayerId, state: area::AreaState) {
|
||||||
self.visible_current_frame.insert(layer_id);
|
self.visible_areas_current_frame.insert(layer_id);
|
||||||
self.areas.insert(layer_id.id, state);
|
self.areas.insert(layer_id.id, state);
|
||||||
if !self.order.iter().any(|x| *x == layer_id) {
|
if !self.order.iter().any(|x| *x == layer_id) {
|
||||||
self.order.push(layer_id);
|
self.order.push(layer_id);
|
||||||
@@ -1194,15 +1194,15 @@ impl Areas {
|
|||||||
pub fn layer_id_at(
|
pub fn layer_id_at(
|
||||||
&self,
|
&self,
|
||||||
pos: Pos2,
|
pos: Pos2,
|
||||||
layer_transforms: &HashMap<LayerId, TSTransform>,
|
layer_to_global: &HashMap<LayerId, TSTransform>,
|
||||||
) -> Option<LayerId> {
|
) -> Option<LayerId> {
|
||||||
for layer in self.order.iter().rev() {
|
for layer in self.order.iter().rev() {
|
||||||
if self.is_visible(layer) {
|
if self.is_visible(layer) {
|
||||||
if let Some(state) = self.areas.get(&layer.id) {
|
if let Some(state) = self.areas.get(&layer.id) {
|
||||||
let mut rect = state.rect();
|
let mut rect = state.rect();
|
||||||
if state.interactable {
|
if state.interactable {
|
||||||
if let Some(transform) = layer_transforms.get(layer) {
|
if let Some(to_global) = layer_to_global.get(layer) {
|
||||||
rect = *transform * rect;
|
rect = *to_global * rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
if rect.contains(pos) {
|
if rect.contains(pos) {
|
||||||
@@ -1216,18 +1216,19 @@ impl Areas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn visible_last_frame(&self, layer_id: &LayerId) -> bool {
|
pub fn visible_last_frame(&self, layer_id: &LayerId) -> bool {
|
||||||
self.visible_last_frame.contains(layer_id)
|
self.visible_areas_last_frame.contains(layer_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_visible(&self, layer_id: &LayerId) -> bool {
|
pub fn is_visible(&self, layer_id: &LayerId) -> bool {
|
||||||
self.visible_last_frame.contains(layer_id) || self.visible_current_frame.contains(layer_id)
|
self.visible_areas_last_frame.contains(layer_id)
|
||||||
|
|| self.visible_areas_current_frame.contains(layer_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visible_layer_ids(&self) -> ahash::HashSet<LayerId> {
|
pub fn visible_layer_ids(&self) -> ahash::HashSet<LayerId> {
|
||||||
self.visible_last_frame
|
self.visible_areas_last_frame
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.chain(self.visible_current_frame.iter().copied())
|
.chain(self.visible_areas_current_frame.iter().copied())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1240,7 +1241,7 @@ impl Areas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_to_top(&mut self, layer_id: LayerId) {
|
pub fn move_to_top(&mut self, layer_id: LayerId) {
|
||||||
self.visible_current_frame.insert(layer_id);
|
self.visible_areas_current_frame.insert(layer_id);
|
||||||
self.wants_to_be_on_top.insert(layer_id);
|
self.wants_to_be_on_top.insert(layer_id);
|
||||||
|
|
||||||
if !self.order.iter().any(|x| *x == layer_id) {
|
if !self.order.iter().any(|x| *x == layer_id) {
|
||||||
@@ -1255,8 +1256,21 @@ impl Areas {
|
|||||||
///
|
///
|
||||||
/// This currently only supports one level of nesting. If `parent` is a sublayer of another
|
/// This currently only supports one level of nesting. If `parent` is a sublayer of another
|
||||||
/// layer, the behavior is unspecified.
|
/// layer, the behavior is unspecified.
|
||||||
|
///
|
||||||
|
/// The two layers must have the same [`LayerId::order`].
|
||||||
pub fn set_sublayer(&mut self, parent: LayerId, child: LayerId) {
|
pub fn set_sublayer(&mut self, parent: LayerId, child: LayerId) {
|
||||||
|
debug_assert_eq!(parent.order, child.order,
|
||||||
|
"DEBUG ASSERT: Trying to set sublayers across layers of different order ({:?}, {:?}), which is currently undefined behavior in egui", parent.order, child.order);
|
||||||
|
|
||||||
self.sublayers.entry(parent).or_default().insert(child);
|
self.sublayers.entry(parent).or_default().insert(child);
|
||||||
|
|
||||||
|
// Make sure the layers are in the order list:
|
||||||
|
if !self.order.iter().any(|x| *x == parent) {
|
||||||
|
self.order.push(parent);
|
||||||
|
}
|
||||||
|
if !self.order.iter().any(|x| *x == child) {
|
||||||
|
self.order.push(child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn top_layer_id(&self, order: Order) -> Option<LayerId> {
|
pub fn top_layer_id(&self, order: Order) -> Option<LayerId> {
|
||||||
@@ -1267,26 +1281,42 @@ impl Areas {
|
|||||||
.copied()
|
.copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If this layer is the sublayer of another layer, return the parent.
|
||||||
|
pub fn parent_layer(&self, layer_id: LayerId) -> Option<LayerId> {
|
||||||
|
self.sublayers.iter().find_map(|(parent, children)| {
|
||||||
|
if children.contains(&layer_id) {
|
||||||
|
Some(*parent)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All the child layers of this layer.
|
||||||
|
pub fn child_layers(&self, layer_id: LayerId) -> impl Iterator<Item = LayerId> + '_ {
|
||||||
|
self.sublayers.get(&layer_id).into_iter().flatten().copied()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn is_sublayer(&self, layer: &LayerId) -> bool {
|
pub(crate) fn is_sublayer(&self, layer: &LayerId) -> bool {
|
||||||
self.sublayers
|
self.parent_layer(*layer).is_some()
|
||||||
.iter()
|
|
||||||
.any(|(_, children)| children.contains(layer))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn end_pass(&mut self) {
|
pub(crate) fn end_pass(&mut self) {
|
||||||
let Self {
|
let Self {
|
||||||
visible_last_frame,
|
visible_areas_last_frame,
|
||||||
visible_current_frame,
|
visible_areas_current_frame,
|
||||||
order,
|
order,
|
||||||
wants_to_be_on_top,
|
wants_to_be_on_top,
|
||||||
sublayers,
|
sublayers,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
std::mem::swap(visible_last_frame, visible_current_frame);
|
std::mem::swap(visible_areas_last_frame, visible_areas_current_frame);
|
||||||
visible_current_frame.clear();
|
visible_areas_current_frame.clear();
|
||||||
|
|
||||||
order.sort_by_key(|layer| (layer.order, wants_to_be_on_top.contains(layer)));
|
order.sort_by_key(|layer| (layer.order, wants_to_be_on_top.contains(layer)));
|
||||||
wants_to_be_on_top.clear();
|
wants_to_be_on_top.clear();
|
||||||
|
|
||||||
// For all layers with sublayers, put the sublayers directly after the parent layer:
|
// For all layers with sublayers, put the sublayers directly after the parent layer:
|
||||||
let sublayers = std::mem::take(sublayers);
|
let sublayers = std::mem::take(sublayers);
|
||||||
for (parent, children) in sublayers {
|
for (parent, children) in sublayers {
|
||||||
@@ -1304,7 +1334,13 @@ impl Areas {
|
|||||||
};
|
};
|
||||||
order.splice(parent_pos..=parent_pos, moved_layers);
|
order.splice(parent_pos..=parent_pos, moved_layers);
|
||||||
}
|
}
|
||||||
self.calculate_order_map();
|
|
||||||
|
self.order_map = self
|
||||||
|
.order
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, id)| (*id, i))
|
||||||
|
.collect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -406,11 +406,8 @@ impl MenuRoot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(transform) = button
|
if let Some(to_global) = button.ctx.layer_transform_to_global(button.layer_id) {
|
||||||
.ctx
|
pos = to_global * pos;
|
||||||
.memory(|m| m.layer_transforms.get(&button.layer_id).copied())
|
|
||||||
{
|
|
||||||
pos = transform * pos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return MenuResponse::Create(pos, id);
|
return MenuResponse::Create(pos, id);
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ impl Default for PassState {
|
|||||||
|
|
||||||
impl PassState {
|
impl PassState {
|
||||||
pub(crate) fn begin_pass(&mut self, screen_rect: Rect) {
|
pub(crate) fn begin_pass(&mut self, screen_rect: Rect) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
let Self {
|
let Self {
|
||||||
used_ids,
|
used_ids,
|
||||||
widgets,
|
widgets,
|
||||||
|
|||||||
@@ -392,11 +392,8 @@ impl Response {
|
|||||||
pub fn drag_delta(&self) -> Vec2 {
|
pub fn drag_delta(&self) -> Vec2 {
|
||||||
if self.dragged() {
|
if self.dragged() {
|
||||||
let mut delta = self.ctx.input(|i| i.pointer.delta());
|
let mut delta = self.ctx.input(|i| i.pointer.delta());
|
||||||
if let Some(scaling) = self
|
if let Some(from_global) = self.ctx.layer_transform_from_global(self.layer_id) {
|
||||||
.ctx
|
delta *= from_global.scaling;
|
||||||
.memory(|m| m.layer_transforms.get(&self.layer_id).map(|t| t.scaling))
|
|
||||||
{
|
|
||||||
delta /= scaling;
|
|
||||||
}
|
}
|
||||||
delta
|
delta
|
||||||
} else {
|
} else {
|
||||||
@@ -478,11 +475,8 @@ impl Response {
|
|||||||
pub fn hover_pos(&self) -> Option<Pos2> {
|
pub fn hover_pos(&self) -> Option<Pos2> {
|
||||||
if self.hovered() {
|
if self.hovered() {
|
||||||
let mut pos = self.ctx.input(|i| i.pointer.hover_pos())?;
|
let mut pos = self.ctx.input(|i| i.pointer.hover_pos())?;
|
||||||
if let Some(transform) = self
|
if let Some(from_global) = self.ctx.layer_transform_from_global(self.layer_id) {
|
||||||
.ctx
|
pos = from_global * pos;
|
||||||
.memory(|m| m.layer_transforms.get(&self.layer_id).copied())
|
|
||||||
{
|
|
||||||
pos = transform.inverse() * pos;
|
|
||||||
}
|
}
|
||||||
Some(pos)
|
Some(pos)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ impl Ui {
|
|||||||
min_rect: placer.min_rect(),
|
min_rect: placer.min_rect(),
|
||||||
max_rect: placer.max_rect(),
|
max_rect: placer.max_rect(),
|
||||||
};
|
};
|
||||||
let child_ui = Ui {
|
let mut child_ui = Ui {
|
||||||
id: stable_id,
|
id: stable_id,
|
||||||
unique_id,
|
unique_id,
|
||||||
next_auto_id_salt,
|
next_auto_id_salt,
|
||||||
@@ -316,6 +316,10 @@ impl Ui {
|
|||||||
min_rect_already_remembered: false,
|
min_rect_already_remembered: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if disabled {
|
||||||
|
child_ui.disable();
|
||||||
|
}
|
||||||
|
|
||||||
// Register in the widget stack early, to ensure we are behind all widgets we contain:
|
// Register in the widget stack early, to ensure we are behind all widgets we contain:
|
||||||
let start_rect = Rect::NOTHING; // This will be overwritten when `remember_min_rect` is called
|
let start_rect = Rect::NOTHING; // This will be overwritten when `remember_min_rect` is called
|
||||||
child_ui.ctx().create_widget(
|
child_ui.ctx().create_widget(
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ fn from_ron_str<T: serde::de::DeserializeOwned>(ron: &str) -> Option<T> {
|
|||||||
use crate::Id;
|
use crate::Id;
|
||||||
|
|
||||||
// TODO(emilk): make IdTypeMap generic over the key (`Id`), and make a library of IdTypeMap.
|
// TODO(emilk): make IdTypeMap generic over the key (`Id`), and make a library of IdTypeMap.
|
||||||
/// Stores values identified by an [`Id`] AND a the [`std::any::TypeId`] of the value.
|
/// Stores values identified by an [`Id`] AND the [`std::any::TypeId`] of the value.
|
||||||
///
|
///
|
||||||
/// In other words, it maps `(Id, TypeId)` to any value you want.
|
/// In other words, it maps `(Id, TypeId)` to any value you want.
|
||||||
///
|
///
|
||||||
@@ -574,7 +574,7 @@ struct PersistedMap(Vec<(u64, SerializedElement)>);
|
|||||||
#[cfg(feature = "persistence")]
|
#[cfg(feature = "persistence")]
|
||||||
impl PersistedMap {
|
impl PersistedMap {
|
||||||
fn from_map(map: &IdTypeMap) -> Self {
|
fn from_map(map: &IdTypeMap) -> Self {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
@@ -593,7 +593,7 @@ impl PersistedMap {
|
|||||||
let max_bytes_per_type = map.max_bytes_per_type;
|
let max_bytes_per_type = map.max_bytes_per_type;
|
||||||
|
|
||||||
{
|
{
|
||||||
crate::profile_scope!("gather");
|
profiling::scope!("gather");
|
||||||
for (hash, element) in &map.map {
|
for (hash, element) in &map.map {
|
||||||
if let Some(element) = element.to_serialize() {
|
if let Some(element) = element.to_serialize() {
|
||||||
let stats = types_map.entry(element.type_id).or_default();
|
let stats = types_map.entry(element.type_id).or_default();
|
||||||
@@ -610,7 +610,7 @@ impl PersistedMap {
|
|||||||
let mut persisted = vec![];
|
let mut persisted = vec![];
|
||||||
|
|
||||||
{
|
{
|
||||||
crate::profile_scope!("gc");
|
profiling::scope!("gc");
|
||||||
for stats in types_map.values() {
|
for stats in types_map.values() {
|
||||||
let mut bytes_written = 0;
|
let mut bytes_written = 0;
|
||||||
|
|
||||||
@@ -634,7 +634,7 @@ impl PersistedMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn into_map(self) -> IdTypeMap {
|
fn into_map(self) -> IdTypeMap {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
let map = self
|
let map = self
|
||||||
.0
|
.0
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -671,7 +671,7 @@ impl serde::Serialize for IdTypeMap {
|
|||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
crate::profile_scope!("IdTypeMap::serialize");
|
profiling::scope!("IdTypeMap::serialize");
|
||||||
PersistedMap::from_map(self).serialize(serializer)
|
PersistedMap::from_map(self).serialize(serializer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -682,7 +682,7 @@ impl<'de> serde::Deserialize<'de> for IdTypeMap {
|
|||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
crate::profile_scope!("IdTypeMap::deserialize");
|
profiling::scope!("IdTypeMap::deserialize");
|
||||||
<PersistedMap>::deserialize(deserializer).map(PersistedMap::into_map)
|
<PersistedMap>::deserialize(deserializer).map(PersistedMap::into_map)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ impl std::fmt::Debug for IconData {
|
|||||||
|
|
||||||
impl From<IconData> for epaint::ColorImage {
|
impl From<IconData> for epaint::ColorImage {
|
||||||
fn from(icon: IconData) -> Self {
|
fn from(icon: IconData) -> Self {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
let IconData {
|
let IconData {
|
||||||
rgba,
|
rgba,
|
||||||
width,
|
width,
|
||||||
@@ -200,7 +200,7 @@ impl From<IconData> for epaint::ColorImage {
|
|||||||
|
|
||||||
impl From<&IconData> for epaint::ColorImage {
|
impl From<&IconData> for epaint::ColorImage {
|
||||||
fn from(icon: &IconData) -> Self {
|
fn from(icon: &IconData) -> Self {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
let IconData {
|
let IconData {
|
||||||
rgba,
|
rgba,
|
||||||
width,
|
width,
|
||||||
@@ -1056,7 +1056,7 @@ pub enum ViewportCommand {
|
|||||||
/// Enable mouse pass-through: mouse clicks pass through the window, used for non-interactable overlays.
|
/// Enable mouse pass-through: mouse clicks pass through the window, used for non-interactable overlays.
|
||||||
MousePassthrough(bool),
|
MousePassthrough(bool),
|
||||||
|
|
||||||
/// Take a screenshot.
|
/// Take a screenshot of the next frame after this.
|
||||||
///
|
///
|
||||||
/// The results are returned in [`crate::Event::Screenshot`].
|
/// The results are returned in [`crate::Event::Screenshot`].
|
||||||
Screenshot(crate::UserData),
|
Screenshot(crate::UserData),
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ pub struct WidgetRect {
|
|||||||
/// What layer the widget is on.
|
/// What layer the widget is on.
|
||||||
pub layer_id: LayerId,
|
pub layer_id: LayerId,
|
||||||
|
|
||||||
/// The full widget rectangle.
|
/// The full widget rectangle, in local layer coordinates.
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
|
|
||||||
/// Where the widget is.
|
/// Where the widget is, in local layer coordinates.
|
||||||
///
|
///
|
||||||
/// This is after clipping with the parent ui clip rect.
|
/// This is after clipping with the parent ui clip rect.
|
||||||
pub interact_rect: Rect,
|
pub interact_rect: Rect,
|
||||||
@@ -42,6 +42,27 @@ pub struct WidgetRect {
|
|||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WidgetRect {
|
||||||
|
pub fn transform(self, transform: emath::TSTransform) -> Self {
|
||||||
|
let Self {
|
||||||
|
id,
|
||||||
|
layer_id,
|
||||||
|
rect,
|
||||||
|
interact_rect,
|
||||||
|
sense,
|
||||||
|
enabled,
|
||||||
|
} = self;
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
layer_id,
|
||||||
|
rect: transform * rect,
|
||||||
|
interact_rect: transform * interact_rect,
|
||||||
|
sense,
|
||||||
|
enabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Stores the [`WidgetRect`]s of all widgets generated during a single egui update/frame.
|
/// Stores the [`WidgetRect`]s of all widgets generated during a single egui update/frame.
|
||||||
///
|
///
|
||||||
/// All [`crate::Ui`]s have a [`WidgetRect`]. It is created in [`crate::Ui::new`] with [`Rect::NOTHING`]
|
/// All [`crate::Ui`]s have a [`WidgetRect`]. It is created in [`crate::Ui::new`] with [`Rect::NOTHING`]
|
||||||
|
|||||||
@@ -165,8 +165,11 @@ fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Color
|
|||||||
/// * `x_value` - X axis, either saturation or value (0.0-1.0).
|
/// * `x_value` - X axis, either saturation or value (0.0-1.0).
|
||||||
/// * `y_value` - Y axis, either saturation or value (0.0-1.0).
|
/// * `y_value` - Y axis, either saturation or value (0.0-1.0).
|
||||||
/// * `color_at` - A function that dictates how the mix of saturation and value will be displayed in the 2d slider.
|
/// * `color_at` - A function that dictates how the mix of saturation and value will be displayed in the 2d slider.
|
||||||
/// E.g.: `|x_value, y_value| HsvaGamma { h: 1.0, s: x_value, v: y_value, a: 1.0 }.into()` displays the colors as follows: top-left: white \[s: 0.0, v: 1.0], top-right: fully saturated color \[s: 1.0, v: 1.0], bottom-right: black \[s: 0.0, v: 1.0].
|
|
||||||
///
|
///
|
||||||
|
/// e.g.: `|x_value, y_value| HsvaGamma { h: 1.0, s: x_value, v: y_value, a: 1.0 }.into()` displays the colors as follows:
|
||||||
|
/// * top-left: white `[s: 0.0, v: 1.0]`
|
||||||
|
/// * top-right: fully saturated color `[s: 1.0, v: 1.0]`
|
||||||
|
/// * bottom-right: black `[s: 0.0, v: 1.0].`
|
||||||
fn color_slider_2d(
|
fn color_slider_2d(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
x_value: &mut f32,
|
x_value: &mut f32,
|
||||||
|
|||||||
@@ -766,14 +766,15 @@ impl<'t> TextEdit<'t> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set IME output (in screen coords) when text is editable and visible
|
// Set IME output (in screen coords) when text is editable and visible
|
||||||
let transform = ui
|
let to_global = ui
|
||||||
.memory(|m| m.layer_transforms.get(&ui.layer_id()).copied())
|
.ctx()
|
||||||
|
.layer_transform_to_global(ui.layer_id())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
ui.ctx().output_mut(|o| {
|
ui.ctx().output_mut(|o| {
|
||||||
o.ime = Some(crate::output::IMEOutput {
|
o.ime = Some(crate::output::IMEOutput {
|
||||||
rect: transform * rect,
|
rect: to_global * rect,
|
||||||
cursor_rect: transform * primary_cursor_rect,
|
cursor_rect: to_global * primary_cursor_rect,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ impl TextEditState {
|
|||||||
self.undoer.lock().clone()
|
self.undoer.lock().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_pass_by_ref_mut)] // Intentionally hide interiority of mutability
|
||||||
pub fn set_undoer(&mut self, undoer: TextEditUndoer) {
|
pub fn set_undoer(&mut self, undoer: TextEditUndoer) {
|
||||||
*self.undoer.lock() = undoer;
|
*self.undoer.lock() = undoer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ rust-version.workspace = true
|
|||||||
publish = false
|
publish = false
|
||||||
default-run = "egui_demo_app"
|
default-run = "egui_demo_app"
|
||||||
|
|
||||||
|
[package.metadata.cargo-machete]
|
||||||
|
ignored = ["profiling"]
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
@@ -25,10 +28,15 @@ default = ["glow", "persistence"]
|
|||||||
# image_viewer adds about 0.9 MB of WASM
|
# image_viewer adds about 0.9 MB of WASM
|
||||||
web_app = ["http", "persistence"]
|
web_app = ["http", "persistence"]
|
||||||
|
|
||||||
http = ["ehttp", "image", "poll-promise", "egui_extras/image"]
|
http = ["ehttp", "image/jpeg", "poll-promise", "egui_extras/image"]
|
||||||
image_viewer = ["image", "egui_extras/all_loaders", "rfd"]
|
image_viewer = ["image/jpeg", "egui_extras/all_loaders", "rfd"]
|
||||||
persistence = ["eframe/persistence", "egui/persistence", "serde", "egui_extras/serde"]
|
persistence = [
|
||||||
puffin = ["eframe/puffin", "dep:puffin", "dep:puffin_http"]
|
"eframe/persistence",
|
||||||
|
"egui_extras/serde",
|
||||||
|
"egui/persistence",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
puffin = ["dep:puffin", "dep:puffin_http", "profiling/profile-with-puffin"]
|
||||||
serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"]
|
serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"]
|
||||||
syntect = ["egui_demo_lib/syntect"]
|
syntect = ["egui_demo_lib/syntect"]
|
||||||
|
|
||||||
@@ -48,7 +56,12 @@ eframe = { workspace = true, default-features = false, features = [
|
|||||||
egui = { workspace = true, features = ["callstack", "default", "log"] }
|
egui = { workspace = true, features = ["callstack", "default", "log"] }
|
||||||
egui_demo_lib = { workspace = true, features = ["default", "chrono"] }
|
egui_demo_lib = { workspace = true, features = ["default", "chrono"] }
|
||||||
egui_extras = { workspace = true, features = ["default", "image"] }
|
egui_extras = { workspace = true, features = ["default", "image"] }
|
||||||
|
image = { workspace = true, default-features = false, features = [
|
||||||
|
# Ensure we can display the test images
|
||||||
|
"png",
|
||||||
|
] }
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
profiling.workspace = true
|
||||||
|
|
||||||
# Optional dependencies:
|
# Optional dependencies:
|
||||||
|
|
||||||
@@ -61,7 +74,6 @@ wgpu = { workspace = true, features = ["webgpu", "webgl"], optional = true }
|
|||||||
|
|
||||||
# feature "http":
|
# feature "http":
|
||||||
ehttp = { version = "0.5", optional = true }
|
ehttp = { version = "0.5", optional = true }
|
||||||
image = { workspace = true, optional = true, features = ["jpeg", "png"] }
|
|
||||||
poll-promise = { version = "0.3", optional = true, default-features = false }
|
poll-promise = { version = "0.3", optional = true, default-features = false }
|
||||||
|
|
||||||
# feature "persistence":
|
# feature "persistence":
|
||||||
@@ -74,7 +86,7 @@ env_logger = { version = "0.10", default-features = false, features = [
|
|||||||
"auto-color",
|
"auto-color",
|
||||||
"humantime",
|
"humantime",
|
||||||
] }
|
] }
|
||||||
rfd = { version = "0.13", optional = true }
|
rfd = { version = "0.15", optional = true }
|
||||||
|
|
||||||
# web:
|
# web:
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ impl FrameHistory {
|
|||||||
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
|
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
pub fn ui(&self, ui: &mut egui::Ui) {
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
"Mean CPU usage: {:.2} ms / frame",
|
"Mean CPU usage: {:.2} ms / frame",
|
||||||
1e3 * self.mean_frame_time()
|
1e3 * self.mean_frame_time()
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ fn main() -> eframe::Result {
|
|||||||
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
eframe::run_native(
|
eframe::run_native(
|
||||||
"egui demo app",
|
"egui demo app",
|
||||||
options,
|
options,
|
||||||
|
|||||||
@@ -111,11 +111,19 @@ impl Anchor {
|
|||||||
Self::Rendering,
|
Self::Rendering,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
fn from_str_case_insensitive(anchor: &str) -> Option<Self> {
|
||||||
|
let anchor = anchor.to_lowercase();
|
||||||
|
Self::all().into_iter().find(|x| x.to_string() == anchor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Anchor {
|
impl std::fmt::Display for Anchor {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{self:?}")
|
let mut name = format!("{self:?}");
|
||||||
|
name.make_ascii_lowercase();
|
||||||
|
f.write_str(&name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,11 +271,15 @@ impl eframe::App for WrapApp {
|
|||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
if let Some(anchor) = frame.info().web_info.location.hash.strip_prefix('#') {
|
if let Some(anchor) = frame
|
||||||
let anchor = Anchor::all().into_iter().find(|x| x.to_string() == anchor);
|
.info()
|
||||||
if let Some(v) = anchor {
|
.web_info
|
||||||
self.state.selected_anchor = v;
|
.location
|
||||||
}
|
.hash
|
||||||
|
.strip_prefix('#')
|
||||||
|
.and_then(Anchor::from_str_case_insensitive)
|
||||||
|
{
|
||||||
|
self.state.selected_anchor = anchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|||||||
@@ -55,14 +55,11 @@ serde = { workspace = true, optional = true }
|
|||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# when running tests we always want to use the `chrono` feature
|
|
||||||
egui_demo_lib = { workspace = true, features = ["chrono"] }
|
|
||||||
|
|
||||||
rand = "0.8"
|
|
||||||
criterion.workspace = true
|
criterion.workspace = true
|
||||||
egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] }
|
egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] }
|
||||||
wgpu = { workspace = true, features = ["metal"] }
|
wgpu = { workspace = true, features = ["metal"] }
|
||||||
egui = { workspace = true, features = ["default_fonts"] }
|
egui = { workspace = true, features = ["default_fonts"] }
|
||||||
|
rand = "0.8"
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "benchmark"
|
name = "benchmark"
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 129 B After Width: | Height: | Size: 2.6 KiB |
@@ -38,6 +38,7 @@ impl Default for Demos {
|
|||||||
Box::<super::painting::Painting>::default(),
|
Box::<super::painting::Painting>::default(),
|
||||||
Box::<super::pan_zoom::PanZoom>::default(),
|
Box::<super::pan_zoom::PanZoom>::default(),
|
||||||
Box::<super::panels::Panels>::default(),
|
Box::<super::panels::Panels>::default(),
|
||||||
|
Box::<super::screenshot::Screenshot>::default(),
|
||||||
Box::<super::scrolling::Scrolling>::default(),
|
Box::<super::scrolling::Scrolling>::default(),
|
||||||
Box::<super::sliders::Sliders>::default(),
|
Box::<super::sliders::Sliders>::default(),
|
||||||
Box::<super::strip_demo::StripDemo>::default(),
|
Box::<super::strip_demo::StripDemo>::default(),
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ pub mod painting;
|
|||||||
pub mod pan_zoom;
|
pub mod pan_zoom;
|
||||||
pub mod panels;
|
pub mod panels;
|
||||||
pub mod password;
|
pub mod password;
|
||||||
|
pub mod screenshot;
|
||||||
pub mod scrolling;
|
pub mod scrolling;
|
||||||
pub mod sliders;
|
pub mod sliders;
|
||||||
pub mod strip_demo;
|
pub mod strip_demo;
|
||||||
|
|||||||
84
crates/egui_demo_lib/src/demo/screenshot.rs
Normal file
84
crates/egui_demo_lib/src/demo/screenshot.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
use egui::{Image, UserData, ViewportCommand, Widget};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Showcase [`ViewportCommand::Screenshot`].
|
||||||
|
#[derive(PartialEq, Eq, Default)]
|
||||||
|
pub struct Screenshot {
|
||||||
|
image: Option<(Arc<egui::ColorImage>, egui::TextureHandle)>,
|
||||||
|
continuous: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::Demo for Screenshot {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"📷 Screenshot"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||||
|
egui::Window::new(self.name())
|
||||||
|
.open(open)
|
||||||
|
.resizable(false)
|
||||||
|
.default_width(250.0)
|
||||||
|
.show(ctx, |ui| {
|
||||||
|
use crate::View as _;
|
||||||
|
self.ui(ui);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::View for Screenshot {
|
||||||
|
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.set_width(300.0);
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
ui.add(crate::egui_github_link_file!());
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal_wrapped(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
|
ui.label("This demo showcases how to take screenshots via ");
|
||||||
|
ui.code("ViewportCommand::Screenshot");
|
||||||
|
ui.label(".");
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.horizontal_top(|ui| {
|
||||||
|
let capture = ui.button("📷 Take Screenshot").clicked();
|
||||||
|
ui.checkbox(&mut self.continuous, "Capture continuously");
|
||||||
|
if capture || self.continuous {
|
||||||
|
ui.ctx()
|
||||||
|
.send_viewport_cmd(ViewportCommand::Screenshot(UserData::default()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let image = ui.ctx().input(|i| {
|
||||||
|
i.events
|
||||||
|
.iter()
|
||||||
|
.filter_map(|e| {
|
||||||
|
if let egui::Event::Screenshot { image, .. } = e {
|
||||||
|
Some(image.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.last()
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(image) = image {
|
||||||
|
self.image = Some((
|
||||||
|
image.clone(),
|
||||||
|
ui.ctx()
|
||||||
|
.load_texture("screenshot_demo", image, Default::default()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((_, texture)) = &self.image {
|
||||||
|
Image::new(texture).shrink_to_fit().ui(ui);
|
||||||
|
} else {
|
||||||
|
ui.group(|ui| {
|
||||||
|
ui.set_width(ui.available_width());
|
||||||
|
ui.set_height(100.0);
|
||||||
|
ui.centered_and_justified(|ui| {
|
||||||
|
ui.label("No screenshot taken yet.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -286,6 +286,7 @@ fn doc_link_label_with_crate<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "chrono")]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:579a7a66f86ade628e9f469b0014e9010aa56312ad5bd1e8de2faaae7e0d1af6
|
||||||
|
size 23770
|
||||||
@@ -5,6 +5,12 @@ This file is updated upon each release.
|
|||||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.30.0 - 2024-12-16
|
||||||
|
* Use `Table::id_salt` on `ScrollArea` [#5282](https://github.com/emilk/egui/pull/5282) by [@jwhear](https://github.com/jwhear)
|
||||||
|
* Use proper `image` crate URI and MIME support detection [#5324](https://github.com/emilk/egui/pull/5324) by [@xangelix](https://github.com/xangelix)
|
||||||
|
* Support loading images with weird urls and improve error message [#5431](https://github.com/emilk/egui/pull/5431) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||||
|
|
||||||
|
|
||||||
## 0.29.1 - 2024-10-01 - Fix table interaction
|
## 0.29.1 - 2024-10-01 - Fix table interaction
|
||||||
* Bug fix: click anywhere on a `Table` row to select it [#5193](https://github.com/emilk/egui/pull/5193) by [@emilk](https://github.com/emilk)
|
* Bug fix: click anywhere on a `Table` row to select it [#5193](https://github.com/emilk/egui/pull/5193) by [@emilk](https://github.com/emilk)
|
||||||
|
|
||||||
|
|||||||
@@ -53,11 +53,6 @@ http = ["dep:ehttp"]
|
|||||||
## ```
|
## ```
|
||||||
image = ["dep:image"]
|
image = ["dep:image"]
|
||||||
|
|
||||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
|
||||||
##
|
|
||||||
## Only enabled on native, because of the low resolution (1ms) of clocks in browsers.
|
|
||||||
puffin = ["dep:puffin", "egui/puffin"]
|
|
||||||
|
|
||||||
## Derive serde Serialize/Deserialize on stateful structs
|
## Derive serde Serialize/Deserialize on stateful structs
|
||||||
serde = ["egui/serde", "dep:serde"]
|
serde = ["egui/serde", "dep:serde"]
|
||||||
|
|
||||||
@@ -74,6 +69,7 @@ egui = { workspace = true, default-features = false }
|
|||||||
ahash.workspace = true
|
ahash.workspace = true
|
||||||
enum-map = { version = "2", features = ["serde"] }
|
enum-map = { version = "2", features = ["serde"] }
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
profiling.workspace = true
|
||||||
|
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
|
|
||||||
@@ -96,7 +92,6 @@ image = { workspace = true, optional = true }
|
|||||||
# file feature
|
# file feature
|
||||||
mime_guess2 = { version = "2", optional = true, default-features = false }
|
mime_guess2 = { version = "2", optional = true, default-features = false }
|
||||||
|
|
||||||
puffin = { workspace = true, optional = true }
|
|
||||||
|
|
||||||
syntect = { version = "5", optional = true, default-features = false, features = [
|
syntect = { version = "5", optional = true, default-features = false, features = [
|
||||||
"default-fancy",
|
"default-fancy",
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ impl RetainedImage {
|
|||||||
/// On invalid image or unsupported image format.
|
/// On invalid image or unsupported image format.
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
pub fn load_image_bytes(image_bytes: &[u8]) -> Result<egui::ColorImage, egui::load::LoadError> {
|
pub fn load_image_bytes(image_bytes: &[u8]) -> Result<egui::ColorImage, egui::load::LoadError> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
let image = image::load_from_memory(image_bytes).map_err(|err| match err {
|
let image = image::load_from_memory(image_bytes).map_err(|err| match err {
|
||||||
image::ImageError::Unsupported(err) => match err.kind() {
|
image::ImageError::Unsupported(err) => match err.kind() {
|
||||||
image::error::UnsupportedErrorKind::Format(format) => {
|
image::error::UnsupportedErrorKind::Format(format) => {
|
||||||
@@ -245,7 +245,8 @@ pub fn load_svg_bytes_with_size(
|
|||||||
use resvg::tiny_skia::{IntSize, Pixmap};
|
use resvg::tiny_skia::{IntSize, Pixmap};
|
||||||
use resvg::usvg::{Options, Tree, TreeParsing};
|
use resvg::usvg::{Options, Tree, TreeParsing};
|
||||||
|
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let opt = Options::default();
|
let opt = Options::default();
|
||||||
|
|
||||||
let mut rtree = Tree::from_data(svg_bytes, &opt).map_err(|err| err.to_string())?;
|
let mut rtree = Tree::from_data(svg_bytes, &opt).map_err(|err| err.to_string())?;
|
||||||
|
|||||||
@@ -37,36 +37,6 @@ pub use loaders::install_image_loaders;
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
mod profiling_scopes {
|
|
||||||
#![allow(unused_macros)]
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_function {
|
|
||||||
($($arg: tt)*) => {
|
|
||||||
#[cfg(feature = "puffin")]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
|
||||||
puffin::profile_function!($($arg)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use profile_function;
|
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_scope {
|
|
||||||
($($arg: tt)*) => {
|
|
||||||
#[cfg(feature = "puffin")]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
|
||||||
puffin::profile_scope!($($arg)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use profile_scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub(crate) use profiling_scopes::profile_function;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Panic in debug builds, log otherwise.
|
/// Panic in debug builds, log otherwise.
|
||||||
macro_rules! log_or_panic {
|
macro_rules! log_or_panic {
|
||||||
($fmt: literal) => {$crate::log_or_panic!($fmt,)};
|
($fmt: literal) => {$crate::log_or_panic!($fmt,)};
|
||||||
|
|||||||
@@ -78,6 +78,12 @@ impl ImageLoader for ImageCrateLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bytes.starts_with(b"version https://git-lfs") {
|
||||||
|
return Err(LoadError::FormatNotSupported {
|
||||||
|
detected_format: Some("git-lfs".to_owned()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// (3)
|
// (3)
|
||||||
log::trace!("started loading {uri:?}");
|
log::trace!("started loading {uri:?}");
|
||||||
let result = crate::image::load_image_bytes(&bytes).map(Arc::new);
|
let result = crate::image::load_image_bytes(&bytes).map(Arc::new);
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ struct Highlighter {
|
|||||||
#[cfg(feature = "syntect")]
|
#[cfg(feature = "syntect")]
|
||||||
impl Default for Highlighter {
|
impl Default for Highlighter {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
Self {
|
Self {
|
||||||
ps: syntect::parsing::SyntaxSet::load_defaults_newlines(),
|
ps: syntect::parsing::SyntaxSet::load_defaults_newlines(),
|
||||||
ts: syntect::highlighting::ThemeSet::load_defaults(),
|
ts: syntect::highlighting::ThemeSet::load_defaults(),
|
||||||
@@ -437,8 +437,7 @@ impl Highlighter {
|
|||||||
|
|
||||||
#[cfg(feature = "syntect")]
|
#[cfg(feature = "syntect")]
|
||||||
fn highlight_impl(&self, theme: &CodeTheme, text: &str, language: &str) -> Option<LayoutJob> {
|
fn highlight_impl(&self, theme: &CodeTheme, text: &str, language: &str) -> Option<LayoutJob> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
use syntect::easy::HighlightLines;
|
use syntect::easy::HighlightLines;
|
||||||
use syntect::highlighting::FontStyle;
|
use syntect::highlighting::FontStyle;
|
||||||
use syntect::util::LinesWithEndings;
|
use syntect::util::LinesWithEndings;
|
||||||
@@ -512,7 +511,7 @@ impl Highlighter {
|
|||||||
mut text: &str,
|
mut text: &str,
|
||||||
language: &str,
|
language: &str,
|
||||||
) -> Option<LayoutJob> {
|
) -> Option<LayoutJob> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let language = Language::new(language)?;
|
let language = Language::new(language)?;
|
||||||
|
|
||||||
|
|||||||
@@ -1227,7 +1227,7 @@ impl<'a> TableBody<'a> {
|
|||||||
|
|
||||||
// Capture the hover information for the just created row. This is used in the next render
|
// Capture the hover information for the just created row. This is used in the next render
|
||||||
// to ensure that the entire row is highlighted.
|
// to ensure that the entire row is highlighted.
|
||||||
fn capture_hover_state(&mut self, response: &Option<Response>, row_index: usize) {
|
fn capture_hover_state(&self, response: &Option<Response>, row_index: usize) {
|
||||||
let is_row_hovered = response.as_ref().map_or(false, |r| r.hovered());
|
let is_row_hovered = response.as_ref().map_or(false, |r| r.hovered());
|
||||||
if is_row_hovered {
|
if is_row_hovered {
|
||||||
self.layout
|
self.layout
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ Changes since the last release can be found at <https://github.com/emilk/egui/co
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 0.30.0 - 2024-12-16
|
||||||
|
* Update glow to 0.16 [#5395](https://github.com/emilk/egui/pull/5395) by [@sagudev](https://github.com/sagudev)
|
||||||
|
|
||||||
|
|
||||||
## 0.29.1 - 2024-10-01
|
## 0.29.1 - 2024-10-01
|
||||||
Nothing new
|
Nothing new
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ clipboard = ["egui-winit?/clipboard"]
|
|||||||
## enable opening links in a browser when an egui hyperlink is clicked.
|
## enable opening links in a browser when an egui hyperlink is clicked.
|
||||||
links = ["egui-winit?/links"]
|
links = ["egui-winit?/links"]
|
||||||
|
|
||||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
|
||||||
puffin = ["dep:puffin", "egui-winit?/puffin", "egui/puffin"]
|
|
||||||
|
|
||||||
## Enable [`winit`](https://docs.rs/winit) integration. On Linux, requires either `wayland` or `x11`
|
## Enable [`winit`](https://docs.rs/winit) integration. On Linux, requires either `wayland` or `x11`
|
||||||
winit = ["egui-winit", "dep:winit"]
|
winit = ["egui-winit", "dep:winit"]
|
||||||
|
|
||||||
@@ -61,14 +58,13 @@ bytemuck.workspace = true
|
|||||||
glow.workspace = true
|
glow.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
memoffset = "0.9"
|
memoffset = "0.9"
|
||||||
|
profiling.workspace = true
|
||||||
|
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
## Enable this when generating docs.
|
## Enable this when generating docs.
|
||||||
document-features = { workspace = true, optional = true }
|
document-features = { workspace = true, optional = true }
|
||||||
|
|
||||||
# Native:
|
# Native:
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
|
||||||
puffin = { workspace = true, optional = true }
|
|
||||||
winit = { workspace = true, optional = true, default-features = false, features = ["rwh_06"] }
|
winit = { workspace = true, optional = true, default-features = false, features = ["rwh_06"] }
|
||||||
|
|
||||||
# Web:
|
# Web:
|
||||||
|
|||||||
@@ -110,33 +110,3 @@ pub fn check_for_gl_error_impl(gl: &glow::Context, file: &str, line: u32, contex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
mod profiling_scopes {
|
|
||||||
#![allow(unused_macros)]
|
|
||||||
#![allow(unused_imports)]
|
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_function {
|
|
||||||
($($arg: tt)*) => {
|
|
||||||
#[cfg(feature = "puffin")]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
|
||||||
puffin::profile_function!($($arg)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use profile_function;
|
|
||||||
|
|
||||||
/// Profiling macro for feature "puffin"
|
|
||||||
macro_rules! profile_scope {
|
|
||||||
($($arg: tt)*) => {
|
|
||||||
#[cfg(feature = "puffin")]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
|
||||||
puffin::profile_scope!($($arg)*);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub(crate) use profile_scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub(crate) use profiling_scopes::{profile_function, profile_scope};
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ impl Painter {
|
|||||||
shader_version: Option<ShaderVersion>,
|
shader_version: Option<ShaderVersion>,
|
||||||
dithering: bool,
|
dithering: bool,
|
||||||
) -> Result<Self, PainterError> {
|
) -> Result<Self, PainterError> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
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.
|
// some useful debug info. all three of them are present in gl 1.1.
|
||||||
@@ -366,7 +366,7 @@ impl Painter {
|
|||||||
clipped_primitives: &[egui::ClippedPrimitive],
|
clipped_primitives: &[egui::ClippedPrimitive],
|
||||||
textures_delta: &egui::TexturesDelta,
|
textures_delta: &egui::TexturesDelta,
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
for (id, image_delta) in &textures_delta.set {
|
for (id, image_delta) in &textures_delta.set {
|
||||||
self.set_texture(*id, image_delta);
|
self.set_texture(*id, image_delta);
|
||||||
@@ -405,7 +405,7 @@ impl Painter {
|
|||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
clipped_primitives: &[egui::ClippedPrimitive],
|
clipped_primitives: &[egui::ClippedPrimitive],
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
self.assert_not_destroyed();
|
self.assert_not_destroyed();
|
||||||
|
|
||||||
unsafe { self.prepare_painting(screen_size_px, pixels_per_point) };
|
unsafe { self.prepare_painting(screen_size_px, pixels_per_point) };
|
||||||
@@ -423,7 +423,7 @@ impl Painter {
|
|||||||
}
|
}
|
||||||
Primitive::Callback(callback) => {
|
Primitive::Callback(callback) => {
|
||||||
if callback.rect.is_positive() {
|
if callback.rect.is_positive() {
|
||||||
crate::profile_scope!("callback");
|
profiling::scope!("callback");
|
||||||
|
|
||||||
let info = egui::PaintCallbackInfo {
|
let info = egui::PaintCallbackInfo {
|
||||||
viewport: callback.rect,
|
viewport: callback.rect,
|
||||||
@@ -508,7 +508,7 @@ impl Painter {
|
|||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
pub fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
pub fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
self.assert_not_destroyed();
|
self.assert_not_destroyed();
|
||||||
|
|
||||||
@@ -540,7 +540,7 @@ impl Painter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let data: Vec<u8> = {
|
let data: Vec<u8> = {
|
||||||
crate::profile_scope!("font -> sRGBA");
|
profiling::scope!("font -> sRGBA");
|
||||||
image
|
image
|
||||||
.srgba_pixels(None)
|
.srgba_pixels(None)
|
||||||
.flat_map(|a| a.to_array())
|
.flat_map(|a| a.to_array())
|
||||||
@@ -559,7 +559,7 @@ impl Painter {
|
|||||||
options: egui::TextureOptions,
|
options: egui::TextureOptions,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
) {
|
) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
assert_eq!(data.len(), w * h * 4);
|
assert_eq!(data.len(), w * h * 4);
|
||||||
assert!(
|
assert!(
|
||||||
w <= self.max_texture_side && h <= self.max_texture_side,
|
w <= self.max_texture_side && h <= self.max_texture_side,
|
||||||
@@ -610,7 +610,7 @@ impl Painter {
|
|||||||
|
|
||||||
let level = 0;
|
let level = 0;
|
||||||
if let Some([x, y]) = pos {
|
if let Some([x, y]) = pos {
|
||||||
crate::profile_scope!("gl.tex_sub_image_2d");
|
profiling::scope!("gl.tex_sub_image_2d");
|
||||||
self.gl.tex_sub_image_2d(
|
self.gl.tex_sub_image_2d(
|
||||||
glow::TEXTURE_2D,
|
glow::TEXTURE_2D,
|
||||||
level,
|
level,
|
||||||
@@ -625,7 +625,7 @@ impl Painter {
|
|||||||
check_for_gl_error!(&self.gl, "tex_sub_image_2d");
|
check_for_gl_error!(&self.gl, "tex_sub_image_2d");
|
||||||
} else {
|
} else {
|
||||||
let border = 0;
|
let border = 0;
|
||||||
crate::profile_scope!("gl.tex_image_2d");
|
profiling::scope!("gl.tex_image_2d");
|
||||||
self.gl.tex_image_2d(
|
self.gl.tex_image_2d(
|
||||||
glow::TEXTURE_2D,
|
glow::TEXTURE_2D,
|
||||||
level,
|
level,
|
||||||
@@ -675,7 +675,7 @@ impl Painter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_screen_rgba(&self, [w, h]: [u32; 2]) -> egui::ColorImage {
|
pub fn read_screen_rgba(&self, [w, h]: [u32; 2]) -> egui::ColorImage {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let mut pixels = vec![0_u8; (w * h * 4) as usize];
|
let mut pixels = vec![0_u8; (w * h * 4) as usize];
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -700,8 +700,7 @@ impl Painter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_screen_rgb(&self, [w, h]: [u32; 2]) -> Vec<u8> {
|
pub fn read_screen_rgb(&self, [w, h]: [u32; 2]) -> Vec<u8> {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
let mut pixels = vec![0_u8; (w * h * 3) as usize];
|
let mut pixels = vec![0_u8; (w * h * 3) as usize];
|
||||||
unsafe {
|
unsafe {
|
||||||
self.gl.read_pixels(
|
self.gl.read_pixels(
|
||||||
@@ -748,7 +747,7 @@ impl Painter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(gl: &glow::Context, screen_size_in_pixels: [u32; 2], clear_color: [f32; 4]) {
|
pub fn clear(gl: &glow::Context, screen_size_in_pixels: [u32; 2], clear_color: [f32; 4]) {
|
||||||
crate::profile_function!();
|
profiling::function_scope!();
|
||||||
unsafe {
|
unsafe {
|
||||||
gl.disable(glow::SCISSOR_TEST);
|
gl.disable(glow::SCISSOR_TEST);
|
||||||
|
|
||||||
|
|||||||
12
crates/egui_kittest/CHANGELOG.md
Normal file
12
crates/egui_kittest/CHANGELOG.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Changelog for egui_kittest
|
||||||
|
All notable changes to the `egui_kittest` crate will be noted in this file.
|
||||||
|
|
||||||
|
|
||||||
|
This file is updated upon each release.
|
||||||
|
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.30.0 - 2024-12-16 - Initial relrease
|
||||||
|
* Support for egui 0.30.0
|
||||||
|
* Automate clicks and text input
|
||||||
|
* Automatic screenshot testing with wgpu
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "egui_kittest"
|
name = "egui_kittest"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
authors = ["Lucas Meurer <lucasmeurer96@gmail.com>", "Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
authors = [
|
||||||
|
"Lucas Meurer <lucasmeurer96@gmail.com>",
|
||||||
|
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
|
||||||
|
]
|
||||||
description = "Testing library for egui based on kittest and AccessKit"
|
description = "Testing library for egui based on kittest and AccessKit"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
rust-version.workspace = true
|
rust-version.workspace = true
|
||||||
@@ -39,10 +42,9 @@ dify = { workspace = true, optional = true }
|
|||||||
document-features = { workspace = true, optional = true }
|
document-features = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] }
|
|
||||||
wgpu = { workspace = true, features = ["metal"] }
|
|
||||||
image = { workspace = true, features = ["png"] }
|
|
||||||
egui = { workspace = true, features = ["default_fonts"] }
|
egui = { workspace = true, features = ["default_fonts"] }
|
||||||
|
image = { workspace = true, features = ["png"] }
|
||||||
|
wgpu = { workspace = true, features = ["metal"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -4,21 +4,16 @@ Ui testing library for egui, based on [kittest](https://github.com/rerun-io/kitt
|
|||||||
|
|
||||||
## Example usage
|
## Example usage
|
||||||
```rust
|
```rust
|
||||||
use egui::accesskit::{Role, Toggled};
|
use egui::accesskit::Toggled;
|
||||||
use egui::{CentralPanel, Context, TextEdit, Vec2};
|
use egui_kittest::{Harness, kittest::Queryable};
|
||||||
use egui_kittest::Harness;
|
|
||||||
use kittest::Queryable;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut checked = false;
|
let mut checked = false;
|
||||||
let app = |ctx: &Context| {
|
let app = |ui: &mut egui::Ui| {
|
||||||
CentralPanel::default().show(ctx, |ui| {
|
|
||||||
ui.checkbox(&mut checked, "Check me!");
|
ui.checkbox(&mut checked, "Check me!");
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut harness = Harness::builder().with_size(egui::Vec2::new(200.0, 100.0)).build(app);
|
let mut harness = Harness::new_ui(app);
|
||||||
|
|
||||||
let checkbox = harness.get_by_label("Check me!");
|
let checkbox = harness.get_by_label("Check me!");
|
||||||
assert_eq!(checkbox.toggled(), Some(Toggled::False));
|
assert_eq!(checkbox.toggled(), Some(Toggled::False));
|
||||||
@@ -29,6 +24,9 @@ fn main() {
|
|||||||
let checkbox = harness.get_by_label("Check me!");
|
let checkbox = harness.get_by_label("Check me!");
|
||||||
assert_eq!(checkbox.toggled(), Some(Toggled::True));
|
assert_eq!(checkbox.toggled(), Some(Toggled::True));
|
||||||
|
|
||||||
|
// Shrink the window size to the smallest size possible
|
||||||
|
harness.fit_contents();
|
||||||
|
|
||||||
// You can even render the ui and do image snapshot tests
|
// You can even render the ui and do image snapshot tests
|
||||||
#[cfg(all(feature = "wgpu", feature = "snapshot"))]
|
#[cfg(all(feature = "wgpu", feature = "snapshot"))]
|
||||||
harness.wgpu_snapshot("readme_example");
|
harness.wgpu_snapshot("readme_example");
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ impl TestRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Render the [`Harness`] and return the resulting image.
|
/// Render the [`Harness`] and return the resulting image.
|
||||||
pub fn render<State>(&mut self, harness: &Harness<'_, State>) -> RgbaImage {
|
pub fn render<State>(&self, harness: &Harness<'_, State>) -> RgbaImage {
|
||||||
// We need to create a new renderer each time we render, since the renderer stores
|
// We need to create a new renderer each time we render, since the renderer stores
|
||||||
// textures related to the Harnesses' egui Context.
|
// textures related to the Harnesses' egui Context.
|
||||||
// Calling the renderer from different Harnesses would cause problems if we store the renderer.
|
// Calling the renderer from different Harnesses would cause problems if we store the renderer.
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
//! Tests the accesskit accessibility output of egui.
|
//! Tests the accesskit accessibility output of egui.
|
||||||
#![cfg(feature = "accesskit")]
|
|
||||||
|
|
||||||
use accesskit::{NodeId, Role, TreeUpdate};
|
use egui::{
|
||||||
use egui::{CentralPanel, Context, RawInput, Window};
|
accesskit::{NodeId, Role, TreeUpdate},
|
||||||
|
CentralPanel, Context, RawInput, Window,
|
||||||
|
};
|
||||||
|
|
||||||
/// Baseline test that asserts there are no spurious nodes in the
|
/// Baseline test that asserts there are no spurious nodes in the
|
||||||
/// accesskit output when the ui is empty.
|
/// accesskit output when the ui is empty.
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
use egui::Button;
|
use egui::Button;
|
||||||
use egui_kittest::kittest::Queryable;
|
use egui_kittest::{kittest::Queryable, Harness};
|
||||||
use egui_kittest::Harness;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn focus_should_skip_over_disabled_buttons() {
|
pub fn focus_should_skip_over_disabled_buttons() {
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:36c1b432140456ea5cbb687076b1c910aea8b31affd33a0ece22218f60af2d6e
|
oid sha256:31bd906040fcc356c19dc36036fbfd2a28dfcef54c7a073f584f4a9abddbdb4c
|
||||||
size 2296
|
size 1699
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ fn test_shrink() {
|
|||||||
|
|
||||||
harness.fit_contents();
|
harness.fit_contents();
|
||||||
|
|
||||||
|
#[cfg(all(feature = "snapshot", feature = "wgpu"))]
|
||||||
harness.wgpu_snapshot("test_shrink");
|
harness.wgpu_snapshot("test_shrink");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -649,7 +649,11 @@ impl Rect {
|
|||||||
///
|
///
|
||||||
/// A ray that starts inside the rect will return `true`.
|
/// A ray that starts inside the rect will return `true`.
|
||||||
pub fn intersects_ray(&self, o: Pos2, d: Vec2) -> bool {
|
pub fn intersects_ray(&self, o: Pos2, d: Vec2) -> bool {
|
||||||
debug_assert!(d.is_normalized(), "expected normalized direction");
|
debug_assert!(
|
||||||
|
d.is_normalized(),
|
||||||
|
"expected normalized direction, but `d` has length {}",
|
||||||
|
d.length()
|
||||||
|
);
|
||||||
|
|
||||||
let mut tmin = -f32::INFINITY;
|
let mut tmin = -f32::INFINITY;
|
||||||
let mut tmax = f32::INFINITY;
|
let mut tmax = f32::INFINITY;
|
||||||
@@ -677,7 +681,11 @@ impl Rect {
|
|||||||
///
|
///
|
||||||
/// `d` is the direction of the ray and assumed to be normalized.
|
/// `d` is the direction of the ray and assumed to be normalized.
|
||||||
pub fn intersects_ray_from_center(&self, d: Vec2) -> Pos2 {
|
pub fn intersects_ray_from_center(&self, d: Vec2) -> Pos2 {
|
||||||
debug_assert!(d.is_normalized(), "expected normalized direction");
|
debug_assert!(
|
||||||
|
d.is_normalized(),
|
||||||
|
"expected normalized direction, but `d` has length {}",
|
||||||
|
d.length()
|
||||||
|
);
|
||||||
|
|
||||||
let mut tmin = f32::NEG_INFINITY;
|
let mut tmin = f32::NEG_INFINITY;
|
||||||
let mut tmax = f32::INFINITY;
|
let mut tmax = f32::INFINITY;
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ This file is updated upon each release.
|
|||||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||||
|
|
||||||
|
|
||||||
|
## 0.30.0 - 2024-12-16
|
||||||
|
* Expand max font atlas size from 8k to 16k [#5257](https://github.com/emilk/egui/pull/5257) by [@rustbasic](https://github.com/rustbasic)
|
||||||
|
* Put font data into `Arc` to reduce memory consumption [#5276](https://github.com/emilk/egui/pull/5276) by [@StarStarJ](https://github.com/StarStarJ)
|
||||||
|
* Reduce aliasing when painting thin box outlines [#5484](https://github.com/emilk/egui/pull/5484) by [@emilk](https://github.com/emilk)
|
||||||
|
* Fix zero-width strokes still affecting the feathering color of boxes [#5485](https://github.com/emilk/egui/pull/5485) by [@emilk](https://github.com/emilk)
|
||||||
|
|
||||||
|
|
||||||
## 0.29.1 - 2024-10-01
|
## 0.29.1 - 2024-10-01
|
||||||
Nothing new
|
Nothing new
|
||||||
|
|
||||||
|
|||||||
@@ -55,11 +55,6 @@ log = ["dep:log"]
|
|||||||
## [`mint`](https://docs.rs/mint) enables interoperability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra).
|
## [`mint`](https://docs.rs/mint) enables interoperability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra).
|
||||||
mint = ["emath/mint"]
|
mint = ["emath/mint"]
|
||||||
|
|
||||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
|
||||||
##
|
|
||||||
## Only enabled on native, because of the low resolution (1ms) of clocks in browsers.
|
|
||||||
puffin = ["dep:puffin"]
|
|
||||||
|
|
||||||
## Enable parallel tessellation using [`rayon`](https://docs.rs/rayon).
|
## Enable parallel tessellation using [`rayon`](https://docs.rs/rayon).
|
||||||
##
|
##
|
||||||
## This can help performance for graphics-intense applications.
|
## This can help performance for graphics-intense applications.
|
||||||
@@ -79,6 +74,7 @@ ab_glyph = "0.2.11"
|
|||||||
ahash.workspace = true
|
ahash.workspace = true
|
||||||
nohash-hasher.workspace = true
|
nohash-hasher.workspace = true
|
||||||
parking_lot.workspace = true # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.
|
parking_lot.workspace = true # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios.
|
||||||
|
profiling = { workspace = true}
|
||||||
|
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
bytemuck = { workspace = true, optional = true, features = ["derive"] }
|
bytemuck = { workspace = true, optional = true, features = ["derive"] }
|
||||||
@@ -87,7 +83,6 @@ bytemuck = { workspace = true, optional = true, features = ["derive"] }
|
|||||||
document-features = { workspace = true, optional = true }
|
document-features = { workspace = true, optional = true }
|
||||||
|
|
||||||
log = { workspace = true, optional = true }
|
log = { workspace = true, optional = true }
|
||||||
puffin = { workspace = true, optional = true }
|
|
||||||
rayon = { version = "1.7", optional = true }
|
rayon = { version = "1.7", optional = true }
|
||||||
|
|
||||||
## Allow serialization using [`serde`](https://docs.rs/serde) .
|
## Allow serialization using [`serde`](https://docs.rs/serde) .
|
||||||
|
|||||||
@@ -207,17 +207,21 @@ impl CubicBezierShape {
|
|||||||
/// B.x = (P3.x - 3 * P2.x + 3 * P1.x - P0.x) * t^3 + (3 * P2.x - 6 * P1.x + 3 * P0.x) * t^2 + (3 * P1.x - 3 * P0.x) * t + P0.x
|
/// B.x = (P3.x - 3 * P2.x + 3 * P1.x - P0.x) * t^3 + (3 * P2.x - 6 * P1.x + 3 * P0.x) * t^2 + (3 * P1.x - 3 * P0.x) * t + P0.x
|
||||||
/// B.y = (P3.y - 3 * P2.y + 3 * P1.y - P0.y) * t^3 + (3 * P2.y - 6 * P1.y + 3 * P0.y) * t^2 + (3 * P1.y - 3 * P0.y) * t + P0.y
|
/// B.y = (P3.y - 3 * P2.y + 3 * P1.y - P0.y) * t^3 + (3 * P2.y - 6 * P1.y + 3 * P0.y) * t^2 + (3 * P1.y - 3 * P0.y) * t + P0.y
|
||||||
/// Combine the above three equations and iliminate B.x and B.y, we get:
|
/// Combine the above three equations and iliminate B.x and B.y, we get:
|
||||||
|
/// ```text
|
||||||
/// t^3 * ( (P3.x - 3*P2.x + 3*P1.x - P0.x) * (P3.y - P0.y) - (P3.y - 3*P2.y + 3*P1.y - P0.y) * (P3.x - P0.x))
|
/// t^3 * ( (P3.x - 3*P2.x + 3*P1.x - P0.x) * (P3.y - P0.y) - (P3.y - 3*P2.y + 3*P1.y - P0.y) * (P3.x - P0.x))
|
||||||
/// + t^2 * ( (3 * P2.x - 6 * P1.x + 3 * P0.x) * (P3.y - P0.y) - (3 * P2.y - 6 * P1.y + 3 * P0.y) * (P3.x - P0.x))
|
/// + t^2 * ( (3 * P2.x - 6 * P1.x + 3 * P0.x) * (P3.y - P0.y) - (3 * P2.y - 6 * P1.y + 3 * P0.y) * (P3.x - P0.x))
|
||||||
/// + t^1 * ( (3 * P1.x - 3 * P0.x) * (P3.y - P0.y) - (3 * P1.y - 3 * P0.y) * (P3.x - P0.x))
|
/// + t^1 * ( (3 * P1.x - 3 * P0.x) * (P3.y - P0.y) - (3 * P1.y - 3 * P0.y) * (P3.x - P0.x))
|
||||||
/// + (P0.x * (P3.y - P0.y) - P0.y * (P3.x - P0.x)) + P0.x * (P0.y - P3.y) + P0.y * (P3.x - P0.x)
|
/// + (P0.x * (P3.y - P0.y) - P0.y * (P3.x - P0.x)) + P0.x * (P0.y - P3.y) + P0.y * (P3.x - P0.x)
|
||||||
/// = 0
|
/// = 0
|
||||||
/// or a * t^3 + b * t^2 + c * t + d = 0
|
/// ```
|
||||||
|
/// or `a * t^3 + b * t^2 + c * t + d = 0`
|
||||||
///
|
///
|
||||||
/// let x = t - b / (3 * a), then we have:
|
/// let x = t - b / (3 * a), then we have:
|
||||||
|
/// ```text
|
||||||
/// x^3 + p * x + q = 0, where:
|
/// x^3 + p * x + q = 0, where:
|
||||||
/// p = (3.0 * a * c - b^2) / (3.0 * a^2)
|
/// p = (3.0 * a * c - b^2) / (3.0 * a^2)
|
||||||
/// q = (2.0 * b^3 - 9.0 * a * b * c + 27.0 * a^2 * d) / (27.0 * a^3)
|
/// q = (2.0 * b^3 - 9.0 * a * b * c + 27.0 * a^2 * d) / (27.0 * a^3)
|
||||||
|
/// ```
|
||||||
///
|
///
|
||||||
/// when p > 0, there will be one real root, two complex roots
|
/// when p > 0, there will be one real root, two complex roots
|
||||||
/// when p = 0, there will be two real roots, when p=q=0, there will be three real roots but all 0.
|
/// when p = 0, there will be two real roots, when p=q=0, there will be three real roots but all 0.
|
||||||
|
|||||||
@@ -154,8 +154,10 @@ impl ColorImage {
|
|||||||
let max_x = (region.max.x * pixels_per_point) as usize;
|
let max_x = (region.max.x * pixels_per_point) as usize;
|
||||||
let min_y = (region.min.y * pixels_per_point) as usize;
|
let min_y = (region.min.y * pixels_per_point) as usize;
|
||||||
let max_y = (region.max.y * pixels_per_point) as usize;
|
let max_y = (region.max.y * pixels_per_point) as usize;
|
||||||
assert!(min_x <= max_x);
|
assert!(
|
||||||
assert!(min_y <= max_y);
|
min_x <= max_x && min_y <= max_y,
|
||||||
|
"Screenshot region is invalid: {region:?}"
|
||||||
|
);
|
||||||
let width = max_x - min_x;
|
let width = max_x - min_x;
|
||||||
let height = max_y - min_y;
|
let height = max_y - min_y;
|
||||||
let mut output = Vec::with_capacity(width * height);
|
let mut output = Vec::with_capacity(width * height);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user