Compare commits
25 commits
master
...
emilk/zlay
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ce2d37d5d9 | ||
![]() |
cb051425ff | ||
![]() |
ac6344c104 | ||
![]() |
8fac6df249 | ||
![]() |
9ad5356cfa | ||
![]() |
f0d5b645f2 | ||
![]() |
95b318d3e0 | ||
![]() |
f87c789851 | ||
![]() |
494f5b3ef0 | ||
![]() |
9ca4d173c5 | ||
![]() |
9ee77aab84 | ||
![]() |
d2c7793370 | ||
![]() |
5473c0f8a0 | ||
![]() |
c29b80d41e | ||
![]() |
57f65b7be6 | ||
![]() |
45336eaf62 | ||
![]() |
3c2e669fe1 | ||
![]() |
191cc70362 | ||
![]() |
db46385073 | ||
![]() |
099e41bf3f | ||
![]() |
d0307417e1 | ||
![]() |
26c3fa22cb | ||
![]() |
7894cd4dfd | ||
![]() |
4560618d43 | ||
![]() |
1e770ae3bd |
137 changed files with 3386 additions and 3128 deletions
|
@ -1,6 +0,0 @@
|
|||
# clipboard api is still unstable, so web-sys requires the below flag to be passed for copy (ctrl + c) to work
|
||||
# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
|
||||
# check status at https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility
|
||||
# we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = ["--cfg=web_sys_unstable_apis"]
|
72
.github/workflows/rust.yml
vendored
72
.github/workflows/rust.yml
vendored
|
@ -16,93 +16,66 @@ jobs:
|
|||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: default
|
||||
toolchain: 1.65.0
|
||||
override: true
|
||||
|
||||
- name: Install packages (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
#uses: awalsh128/cache-apt-pkgs-action@v1.2.2
|
||||
#TODO(emilk) use upstream when https://github.com/awalsh128/cache-apt-pkgs-action/pull/90 is merged
|
||||
uses: rerun-io/cache-apt-pkgs-action@59534850182063abf1b2c11bb3686722a12a8397
|
||||
with:
|
||||
packages: libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev libgtk-3-dev # libgtk-3-dev is used by rfd
|
||||
version: 1.0
|
||||
execute_install_scripts: true
|
||||
|
||||
run: sudo apt-get update && sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev libgtk-3-dev # libgtk-3-dev is used by rfd
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Rustfmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Install cargo-cranky
|
||||
uses: baptiste0928/cargo-install@v1
|
||||
with:
|
||||
crate: cargo-cranky
|
||||
|
||||
- name: check --all-features
|
||||
- name: Check all features
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked --all-features --all-targets
|
||||
|
||||
- name: check default features
|
||||
args: --locked --all-features
|
||||
- name: Check default features
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked --all-targets
|
||||
|
||||
- name: check --no-default-features
|
||||
args: --locked
|
||||
- name: Check no default features
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked --no-default-features --lib --all-targets
|
||||
|
||||
- name: check eframe --no-default-features
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --locked --no-default-features --lib --all-targets -p eframe
|
||||
|
||||
args: --locked --no-default-features --lib
|
||||
- name: Test doc-tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --doc --all-features
|
||||
|
||||
- name: cargo doc --lib
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: --lib --no-deps --all-features
|
||||
|
||||
- name: cargo doc --document-private-items
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: --document-private-items --no-deps --all-features
|
||||
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all-features
|
||||
|
||||
- name: Cranky
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: cranky
|
||||
args: --all-targets --all-features -- -D warnings
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
check_wasm:
|
||||
name: Check wasm32 + wasm-bindgen
|
||||
runs-on: ubuntu-22.04
|
||||
|
@ -146,7 +119,7 @@ jobs:
|
|||
- name: wasm-bindgen
|
||||
uses: jetli/wasm-bindgen-action@v0.1.0
|
||||
with:
|
||||
version: "0.2.84"
|
||||
version: "0.2.83"
|
||||
|
||||
- run: ./sh/wasm_bindgen_check.sh --skip-setup
|
||||
|
||||
|
@ -156,8 +129,6 @@ jobs:
|
|||
command: cranky
|
||||
args: --target wasm32-unknown-unknown --all-features -p egui_demo_app --lib -- -D warnings
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
cargo-deny:
|
||||
name: cargo deny
|
||||
runs-on: ubuntu-22.04
|
||||
|
@ -167,45 +138,18 @@ jobs:
|
|||
with:
|
||||
rust-version: "1.65.0"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
android:
|
||||
name: android
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.65.0
|
||||
target: aarch64-linux-android
|
||||
override: true
|
||||
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- run: cargo check --features wgpu --target aarch64-linux-android
|
||||
working-directory: crates/eframe
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
windows:
|
||||
name: Check Windows
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: 1.65.0
|
||||
override: true
|
||||
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all-targets --all-features
|
||||
|
|
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -5,9 +5,6 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
|
||||
|
||||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08 - Deadlock fix and style customizability
|
||||
* ⚠️ BREAKING: `egui::Context` now use closures for locking ([#2625](https://github.com/emilk/egui/pull/2625)):
|
||||
* `ctx.input().key_pressed(Key::A)` -> `ctx.input(|i| i.key_pressed(Key::A))`
|
||||
* `ui.memory().toggle_popup(popup_id)` -> `ui.memory_mut(|mem| mem.toggle_popup(popup_id))`
|
||||
|
@ -26,27 +23,19 @@ NOTE: [`epaint`](crates/epaint/CHANGELOG.md), [`eframe`](crates/eframe/CHANGELOG
|
|||
* Add `Button::rounding` to enable round buttons ([#2616](https://github.com/emilk/egui/pull/2616)).
|
||||
* Add `WidgetVisuals::optional_bg_color` - set it to `Color32::TRANSPARENT` to hide button backgrounds ([#2621](https://github.com/emilk/egui/pull/2621)).
|
||||
* Add `Context::screen_rect` and `Context::set_cursor_icon` ([#2625](https://github.com/emilk/egui/pull/2625)).
|
||||
* You can turn off the vertical line left of indented regions with `Visuals::indent_has_left_vline` ([#2636](https://github.com/emilk/egui/pull/2636)).
|
||||
* Add `Response.highlight` to highlight a widget ([#2632](https://github.com/emilk/egui/pull/2632)).
|
||||
* Add `Separator::grow` and `Separator::shrink` ([#2665](https://github.com/emilk/egui/pull/2665)).
|
||||
* Add `Slider::trailing_fill` for trailing color behind the circle like a `ProgressBar` ([#2660](https://github.com/emilk/egui/pull/2660)).
|
||||
|
||||
### Changed 🔧
|
||||
* Improved plot grid appearance ([#2412](https://github.com/emilk/egui/pull/2412)).
|
||||
* Improved the algorithm for picking the number of decimals to show when hovering values in the `Plot`.
|
||||
* Default `ComboBox` is now controlled with `Spacing::combo_width` ([#2621](https://github.com/emilk/egui/pull/2621)).
|
||||
* `DragValue` and `Slider` now use the proportional font ([#2638](https://github.com/emilk/egui/pull/2638)).
|
||||
* `ScrollArea` is less aggressive about clipping its contents ([#2665](https://github.com/emilk/egui/pull/2665)).
|
||||
* Updated to be compatible with a major breaking change in AccessKit that drastically reduces memory usage when accessibility is enabled ([#2678](https://github.com/emilk/egui/pull/2678)).
|
||||
* Improve `DragValue` behavior ([#2649](https://github.com/emilk/egui/pull/2649), [#2650](https://github.com/emilk/egui/pull/2650), [#2688](https://github.com/emilk/egui/pull/2688), [#2638](https://github.com/emilk/egui/pull/2638)).
|
||||
|
||||
### Fixed 🐛
|
||||
* Trigger `PointerEvent::Released` for drags ([#2507](https://github.com/emilk/egui/pull/2507)).
|
||||
* Expose `TextEdit`'s multiline flag to AccessKit ([#2448](https://github.com/emilk/egui/pull/2448)).
|
||||
* Don't render `\r` (Carriage Return) ([#2452](https://github.com/emilk/egui/pull/2452)).
|
||||
* The `button_padding` style option works closer as expected with image+text buttons now ([#2510](https://github.com/emilk/egui/pull/2510)).
|
||||
* Fixed rendering of `…` (ellipsis).
|
||||
* Menus are now moved to fit on the screen.
|
||||
* Fix `Window::pivot` causing windows to move around ([#2694](https://github.com/emilk/egui/pull/2694)).
|
||||
|
||||
|
||||
## 0.20.1 - 2022-12-11 - Fix key-repeat
|
||||
|
|
1860
Cargo.lock
generated
1860
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -5,6 +5,7 @@ members = [
|
|||
"crates/egui_demo_app",
|
||||
"crates/egui_demo_lib",
|
||||
"crates/egui_extras",
|
||||
"crates/egui_glium",
|
||||
"crates/egui_glow",
|
||||
"crates/egui-wgpu",
|
||||
"crates/egui-winit",
|
||||
|
@ -26,8 +27,7 @@ opt-level = 2 # fast and small wasm, basically same as `opt-level = 's'`
|
|||
|
||||
|
||||
[profile.dev]
|
||||
# Can't leave this on by default, because it breaks the Windows build. Related: https://github.com/rust-lang/cargo/issues/4897
|
||||
# split-debuginfo = "unpacked" # faster debug builds on mac
|
||||
split-debuginfo = "unpacked" # faster debug builds on mac
|
||||
# opt-level = 1 # Make debug builds run faster
|
||||
|
||||
# Optimize all dependencies even in debug builds (does not affect workspace packages):
|
||||
|
|
13
README.md
13
README.md
|
@ -66,11 +66,11 @@ To test the demo app locally, run `cargo run --release -p egui_demo_app`.
|
|||
|
||||
The native backend is [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) (using [`glow`](https://crates.io/crates/glow)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run:
|
||||
|
||||
`sudo apt-get install -y libclang-dev libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev`
|
||||
`sudo apt-get install -y libclang-dev libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev`
|
||||
|
||||
On Fedora Rawhide you need to run:
|
||||
|
||||
`dnf install clang clang-devel clang-tools-extra libxkbcommon-devel pkg-config openssl-devel libxcb-devel`
|
||||
`dnf install clang clang-devel clang-tools-extra speech-dispatcher-devel libxkbcommon-devel pkg-config openssl-devel libxcb-devel`
|
||||
|
||||
**NOTE**: This is just for the demo app - egui itself is completely platform agnostic!
|
||||
|
||||
|
@ -158,17 +158,17 @@ An integration needs to do the following each frame:
|
|||
* **Input**: Gather input (mouse, touches, keyboard, screen size, etc) and give it to egui
|
||||
* Run the application code
|
||||
* **Output**: Handle egui output (cursor changes, paste, texture allocations, …)
|
||||
* **Painting**: Render the triangle mesh egui produces (see [OpenGL example](https://github.com/emilk/egui/blob/master/crates/egui_glow/src/painter.rs))
|
||||
* **Painting**: Render the triangle mesh egui produces (see [OpenGL example](https://github.com/emilk/egui/blob/master/crates/egui_glium/src/painter.rs))
|
||||
|
||||
### Official integrations
|
||||
|
||||
These are the official egui integrations:
|
||||
|
||||
* [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) for compiling the same app to web/wasm and desktop/native. Uses `egui-winit` and `egui_glow` or `egui-wgpu`.
|
||||
* [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) for compiling the same app to web/wasm and desktop/native. Uses `egui_glow` and `egui-winit`.
|
||||
* [`egui_glium`](https://github.com/emilk/egui/tree/master/crates/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium).
|
||||
* [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) for rendering egui with [glow](https://github.com/grovesNL/glow) on native and web, and for making native apps.
|
||||
* [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu) for [wgpu](https://crates.io/crates/wgpu) (WebGPU API).
|
||||
* [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit) for integrating with [winit](https://github.com/rust-windowing/winit).
|
||||
* [`egui_glium`](https://github.com/emilk/egui/tree/master/crates/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium) (DEPRECATED - looking for new maintainer).
|
||||
|
||||
### 3rd party integrations
|
||||
|
||||
|
@ -186,14 +186,13 @@ These are the official egui integrations:
|
|||
* [`egui-tetra`](https://crates.io/crates/egui-tetra) for [Tetra](https://crates.io/crates/tetra), a 2D game framework.
|
||||
* [`egui-winit-ash-integration`](https://github.com/MatchaChoco010/egui-winit-ash-integration) for [winit](https://github.com/rust-windowing/winit) and [ash](https://github.com/MaikKlein/ash).
|
||||
* [`fltk-egui`](https://crates.io/crates/fltk-egui) for [fltk-rs](https://github.com/fltk-rs/fltk-rs).
|
||||
* [`ggegui`](https://github.com/NemuiSen/ggegui) for the [ggez](https://ggez.rs/) game framework.
|
||||
* [`ggez-egui`](https://github.com/NemuiSen/ggez-egui) for the [ggez](https://ggez.rs/) game framework.
|
||||
* [`godot-egui`](https://github.com/setzer22/godot-egui) for [godot-rust](https://github.com/godot-rust/godot-rust).
|
||||
* [`nannou_egui`](https://github.com/nannou-org/nannou/tree/master/nannou_egui) for [nannou](https://nannou.cc).
|
||||
* [`notan_egui`](https://github.com/Nazariglez/notan/tree/main/crates/notan_egui) for [notan](https://github.com/Nazariglez/notan).
|
||||
* [`screen-13-egui`](https://github.com/attackgoat/screen-13/tree/master/contrib/screen-13-egui) for [Screen 13](https://github.com/attackgoat/screen-13).
|
||||
* [`egui_skia`](https://github.com/lucasmerlin/egui_skia) for [skia](https://github.com/rust-skia/rust-skia/tree/master/skia-safe).
|
||||
* [`smithay-egui`](https://github.com/Smithay/smithay-egui) for [smithay](https://github.com/Smithay/smithay/).
|
||||
* [`tauri-egui`](https://github.com/tauri-apps/tauri-egui) for [tauri](https://github.com/tauri-apps/tauri).
|
||||
|
||||
Missing an integration for the thing you're working on? Create one, it's easy!
|
||||
|
||||
|
|
|
@ -3,9 +3,6 @@ All notable changes to the `ecolor` crate will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08
|
||||
* Add `Color32::gamma_multiply` ([#2437](https://github.com/emilk/egui/pull/2437)).
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "ecolor"
|
||||
version = "0.21.0"
|
||||
version = "0.20.0"
|
||||
authors = [
|
||||
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
|
||||
"Andreas Reich <reichandreas@gmx.de>",
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
# ecolor - egui color library
|
||||
|
||||
[](https://crates.io/crates/ecolor)
|
||||
[](https://docs.rs/ecolor)
|
||||
[](https://github.com/rust-secure-code/safety-dance/)
|
||||

|
||||

|
||||
|
||||
A simple color storage and conversion library.
|
||||
|
||||
Made for [`egui`](https://github.com/emilk/egui/).
|
||||
|
|
|
@ -198,19 +198,4 @@ impl Color32 {
|
|||
// we need a somewhat expensive conversion to linear space and back.
|
||||
Rgba::from(self).multiply(factor).into()
|
||||
}
|
||||
|
||||
/// Converts to floating point values in the range 0-1 without any gamma space conversion.
|
||||
///
|
||||
/// Use this with great care! In almost all cases, you want to convert to [`crate::Rgba`] instead
|
||||
/// in order to obtain linear space color values.
|
||||
#[inline]
|
||||
pub fn to_normalized_gamma_f32(self) -> [f32; 4] {
|
||||
let Self([r, g, b, a]) = self;
|
||||
[
|
||||
r as f32 / 255.0,
|
||||
g as f32 / 255.0,
|
||||
b as f32 / 255.0,
|
||||
a as f32 / 255.0,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,37 +6,15 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C
|
|||
|
||||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.3 - 2023-02-15
|
||||
* Fix typing the letter 'P' on web ([#2740](https://github.com/emilk/egui/pull/2740)).
|
||||
|
||||
|
||||
## 0.21.2 - 2023-02-12
|
||||
* Allow compiling `eframe` with `--no-default-features` ([#2728](https://github.com/emilk/egui/pull/2728)).
|
||||
|
||||
|
||||
## 0.21.1 - 2023-02-12
|
||||
* Fixed crash when native window position is in an invalid state, which could happen e.g. due to changes in monitor size or DPI ([#2722](https://github.com/emilk/egui/issues/2722)).
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08 - Update to `winit` 0.28
|
||||
* ⚠️ BREAKING: `App::clear_color` now expects you to return a raw float array ([#2666](https://github.com/emilk/egui/pull/2666)).
|
||||
* The `screen_reader` feature has now been renamed `web_screen_reader` and only work on web. On other platforms, use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)).
|
||||
|
||||
#### Desktop/Native:
|
||||
* `eframe::run_native` now returns a `Result` ([#2433](https://github.com/emilk/egui/pull/2433)).
|
||||
* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)).
|
||||
* Fix bug where the cursor could get stuck using the wrong icon.
|
||||
* `NativeOptions::transparent` now works with the wgpu backend ([#2684](https://github.com/emilk/egui/pull/2684)).
|
||||
* Add `Frame::set_minimized` and `set_maximized` ([#2292](https://github.com/emilk/egui/pull/2292), [#2672](https://github.com/emilk/egui/pull/2672)).
|
||||
* Fixed persistence of native window position on Windows OS ([#2583](https://github.com/emilk/egui/issues/2583)).
|
||||
|
||||
#### Web:
|
||||
* Prevent ctrl-P/cmd-P from opening the print dialog ([#2598](https://github.com/emilk/egui/pull/2598)).
|
||||
|
||||
|
||||
## 0.20.1 - 2022-12-11
|
||||
* Fix [docs.rs](https://docs.rs/eframe) build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08 - AccessKit integration and `wgpu` web support
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "eframe"
|
||||
version = "0.21.3"
|
||||
version = "0.20.1"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "egui framework - write GUI apps that compiles to web and/or natively"
|
||||
edition = "2021"
|
||||
|
@ -14,7 +14,9 @@ keywords = ["egui", "gui", "gamedev"]
|
|||
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
# Avoid speech-dispatcher dependencies - see https://docs.rs/crate/eframe/0.20.0/builds/695200
|
||||
no-default-features = true
|
||||
features = ["document-features", "glow", "wgpu", "persistence", "wgpu"]
|
||||
targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
|
||||
|
||||
[lib]
|
||||
|
@ -36,7 +38,7 @@ dark-light = ["dep:dark-light"]
|
|||
default_fonts = ["egui/default_fonts"]
|
||||
|
||||
## Use [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow).
|
||||
glow = ["dep:glow", "dep:egui_glow", "dep:glutin", "dep:glutin-winit"]
|
||||
glow = ["dep:glow", "dep:egui_glow", "dep:glutin"]
|
||||
|
||||
## Enable saving app state to disk.
|
||||
persistence = [
|
||||
|
@ -53,10 +55,8 @@ persistence = [
|
|||
## `eframe` will call `puffin::GlobalProfiler::lock().new_frame()` for you
|
||||
puffin = ["dep:puffin", "egui_glow?/puffin", "egui-wgpu?/puffin"]
|
||||
|
||||
## Enable screen reader support (requires `ctx.options_mut(|o| o.screen_reader = true);`) on web.
|
||||
##
|
||||
## For other platforms, use the "accesskit" feature instead.
|
||||
web_screen_reader = ["tts"]
|
||||
## Enable screen reader support (requires `ctx.options_mut(|o| o.screen_reader = true);`)
|
||||
screen_reader = ["egui-winit/screen_reader", "tts"]
|
||||
|
||||
## If set, eframe will look for the env-var `EFRAME_SCREENSHOT_TO` and write a screenshot to that location, and then quit.
|
||||
## This is used to generate images for the examples.
|
||||
|
@ -68,7 +68,7 @@ wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"]
|
|||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
|
||||
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
|
||||
"bytemuck",
|
||||
"tracing",
|
||||
] }
|
||||
|
@ -79,38 +79,44 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
|
|||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
egui_glow = { version = "0.21.0", path = "../egui_glow", optional = true, default-features = false }
|
||||
glow = { version = "0.12", optional = true }
|
||||
egui_glow = { version = "0.20.0", path = "../egui_glow", optional = true, default-features = false }
|
||||
glow = { version = "0.11", optional = true }
|
||||
ron = { version = "0.8", optional = true, features = ["integer128"] }
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
|
||||
# -------------------------------------------
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
egui-winit = { version = "0.21.1", path = "../egui-winit", default-features = false, features = [
|
||||
egui-winit = { version = "0.20.0", path = "../egui-winit", default-features = false, features = [
|
||||
"clipboard",
|
||||
"links",
|
||||
] }
|
||||
raw-window-handle = { version = "0.5.0" }
|
||||
winit = "0.28.1"
|
||||
winit = "0.27.2"
|
||||
|
||||
# optional native:
|
||||
dark-light = { version = "1.0", optional = true }
|
||||
dark-light = { version = "0.2.1", optional = true }
|
||||
directories-next = { version = "2", optional = true }
|
||||
egui-wgpu = { version = "0.21.0", path = "../egui-wgpu", optional = true, features = [
|
||||
egui-wgpu = { version = "0.20.0", path = "../egui-wgpu", optional = true, features = [
|
||||
"winit",
|
||||
] } # if wgpu is used, use it with winit
|
||||
pollster = { version = "0.3", optional = true } # needed for wgpu
|
||||
pollster = { version = "0.2", optional = true } # needed for wgpu
|
||||
|
||||
# we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps.
|
||||
# this can be done at the same time we expose x11/wayland features of winit crate.
|
||||
glutin = { version = "0.30", optional = true }
|
||||
glutin-winit = { version = "0.3.0", optional = true }
|
||||
glutin = { version = "0.30.0", optional = true, es = [
|
||||
"egl",
|
||||
"glx",
|
||||
"x11",
|
||||
"wayland",
|
||||
"wgl",
|
||||
] }
|
||||
|
||||
image = { version = "0.24", optional = true, default-features = false, features = [
|
||||
"png",
|
||||
] }
|
||||
puffin = { version = "0.14", optional = true }
|
||||
wgpu = { version = "0.15.0", optional = true }
|
||||
wgpu = { version = "0.14", optional = true }
|
||||
|
||||
# -------------------------------------------
|
||||
# web:
|
||||
|
@ -118,7 +124,7 @@ wgpu = { version = "0.15.0", optional = true }
|
|||
bytemuck = "1.7"
|
||||
js-sys = "0.3"
|
||||
percent-encoding = "2.1"
|
||||
wasm-bindgen = "=0.2.84"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
web-sys = { version = "0.3.58", features = [
|
||||
"BinaryType",
|
||||
|
@ -164,6 +170,6 @@ web-sys = { version = "0.3.58", features = [
|
|||
] }
|
||||
|
||||
# optional web:
|
||||
egui-wgpu = { version = "0.21.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit
|
||||
tts = { version = "0.25", optional = true, default-features = false }
|
||||
wgpu = { version = "0.15.0", optional = true, features = ["webgl"] }
|
||||
egui-wgpu = { version = "0.20.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit
|
||||
tts = { version = "0.24", optional = true }
|
||||
wgpu = { version = "0.14", optional = true, features = ["webgl"] }
|
||||
|
|
|
@ -22,7 +22,7 @@ For how to use `egui`, see [the egui docs](https://docs.rs/egui).
|
|||
To use on Linux, first run:
|
||||
|
||||
```
|
||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
|
||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
|
||||
```
|
||||
|
||||
You need to either use `edition = "2021"`, or set `resolver = "2"` in the `[workspace]` section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info.
|
||||
|
|
|
@ -10,11 +10,9 @@
|
|||
use std::any::Any;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
pub use crate::native::run::UserEvent;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
pub use winit::event_loop::EventLoopBuilder;
|
||||
|
||||
/// Hook into the building of an event loop before it is run
|
||||
|
@ -22,7 +20,6 @@ pub use winit::event_loop::EventLoopBuilder;
|
|||
/// You can configure any platform specific details required on top of the default configuration
|
||||
/// done by `EFrame`.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
pub type EventLoopBuilderHook = Box<dyn FnOnce(&mut EventLoopBuilder<UserEvent>)>;
|
||||
|
||||
/// This is how your app is created.
|
||||
|
@ -152,21 +149,13 @@ pub trait App {
|
|||
egui::Vec2::INFINITY
|
||||
}
|
||||
|
||||
/// Background color values for the app, e.g. what is sent to `gl.clearColor`.
|
||||
///
|
||||
/// Background color for the app, e.g. what is sent to `gl.clearColor`.
|
||||
/// This is the background of your windows if you don't set a central panel.
|
||||
///
|
||||
/// ATTENTION:
|
||||
/// Since these float values go to the render as-is, any color space conversion as done
|
||||
/// e.g. by converting from [`egui::Color32`] to [`egui::Rgba`] may cause incorrect results.
|
||||
/// egui recommends that rendering backends use a normal "gamma-space" (non-sRGB-aware) blending,
|
||||
/// which means the values you return here should also be in `sRGB` gamma-space in the 0-1 range.
|
||||
/// You can use [`egui::Color32::to_normalized_gamma_f32`] for this.
|
||||
fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
|
||||
fn clear_color(&self, _visuals: &egui::Visuals) -> egui::Rgba {
|
||||
// NOTE: a bright gray makes the shadows of the windows look weird.
|
||||
// We use a bit of transparency so that if the user switches on the
|
||||
// `transparent()` option they get immediate results.
|
||||
egui::Color32::from_rgba_unmultiplied(12, 12, 12, 180).to_normalized_gamma_f32()
|
||||
egui::Color32::from_rgba_unmultiplied(12, 12, 12, 180).into()
|
||||
|
||||
// _visuals.window_fill() would also be a natural choice
|
||||
}
|
||||
|
@ -320,7 +309,6 @@ pub struct NativeOptions {
|
|||
pub hardware_acceleration: HardwareAcceleration,
|
||||
|
||||
/// What rendering backend to use.
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
pub renderer: Renderer,
|
||||
|
||||
/// Only used if the `dark-light` feature is enabled:
|
||||
|
@ -359,7 +347,6 @@ pub struct NativeOptions {
|
|||
/// event loop before it is run.
|
||||
///
|
||||
/// Note: A [`NativeOptions`] clone will not include any `event_loop_builder` hook.
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
pub event_loop_builder: Option<EventLoopBuilderHook>,
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
|
@ -386,13 +373,9 @@ impl Clone for NativeOptions {
|
|||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
icon_data: self.icon_data.clone(),
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
event_loop_builder: None, // Skip any builder callbacks if cloning
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
wgpu_options: self.wgpu_options.clone(),
|
||||
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
@ -406,10 +389,8 @@ impl Default for NativeOptions {
|
|||
maximized: false,
|
||||
decorated: true,
|
||||
fullscreen: false,
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fullsize_content: false,
|
||||
|
||||
drag_and_drop_support: true,
|
||||
icon_data: None,
|
||||
initial_window_pos: None,
|
||||
|
@ -424,22 +405,14 @@ impl Default for NativeOptions {
|
|||
depth_buffer: 0,
|
||||
stencil_buffer: 0,
|
||||
hardware_acceleration: HardwareAcceleration::Preferred,
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
renderer: Renderer::default(),
|
||||
|
||||
follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"),
|
||||
default_theme: Theme::Dark,
|
||||
run_and_return: true,
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
event_loop_builder: None,
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
shader_version: None,
|
||||
|
||||
centered: false,
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
|
||||
}
|
||||
|
@ -456,7 +429,6 @@ impl NativeOptions {
|
|||
match dark_light::detect() {
|
||||
dark_light::Mode::Dark => Some(Theme::Dark),
|
||||
dark_light::Mode::Light => Some(Theme::Light),
|
||||
dark_light::Mode::Default => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
@ -578,7 +550,6 @@ pub enum WebGlContextOption {
|
|||
/// What rendering backend to use.
|
||||
///
|
||||
/// You need to enable the "glow" and "wgpu" features to have a choice.
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
||||
|
@ -592,7 +563,6 @@ pub enum Renderer {
|
|||
Wgpu,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
impl Default for Renderer {
|
||||
fn default() -> Self {
|
||||
#[cfg(feature = "glow")]
|
||||
|
@ -608,7 +578,6 @@ impl Default for Renderer {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
impl std::fmt::Display for Renderer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
@ -621,7 +590,6 @@ impl std::fmt::Display for Renderer {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
impl std::str::FromStr for Renderer {
|
||||
type Err = String;
|
||||
|
||||
|
@ -741,18 +709,6 @@ impl Frame {
|
|||
self.output.close = true;
|
||||
}
|
||||
|
||||
/// Minimize or unminimize window. (native only)
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn set_minimized(&mut self, minimized: bool) {
|
||||
self.output.minimized = Some(minimized);
|
||||
}
|
||||
|
||||
/// Maximize or unmaximize window. (native only)
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn set_maximized(&mut self, maximized: bool) {
|
||||
self.output.maximized = Some(maximized);
|
||||
}
|
||||
|
||||
/// Tell `eframe` to close the desktop window.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[deprecated = "Renamed `close`"]
|
||||
|
@ -834,7 +790,6 @@ impl Frame {
|
|||
}
|
||||
|
||||
/// for integrations only: call once per frame
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
pub(crate) fn take_app_output(&mut self) -> backend::AppOutput {
|
||||
std::mem::take(&mut self.output)
|
||||
}
|
||||
|
@ -865,12 +820,6 @@ pub struct WindowInfo {
|
|||
/// Are we in fullscreen mode?
|
||||
pub fullscreen: bool,
|
||||
|
||||
/// Are we minimized?
|
||||
pub minimized: bool,
|
||||
|
||||
/// Are we maximized?
|
||||
pub maximized: bool,
|
||||
|
||||
/// Window inner size in egui points (logical pixels).
|
||||
pub size: egui::Vec2,
|
||||
|
||||
|
@ -1053,13 +1002,5 @@ pub(crate) mod backend {
|
|||
/// Set to some bool to tell the window always on top.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub always_on_top: Option<bool>,
|
||||
|
||||
/// Set to some bool to minimize or unminimize window.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub minimized: Option<bool>,
|
||||
|
||||
/// Set to some bool to maximize or unmaximize window.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub maximized: Option<bool>,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,7 +137,6 @@ pub async fn start_web(
|
|||
// When compiling natively
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
mod native;
|
||||
|
||||
/// This is how you start a native (desktop) app.
|
||||
|
@ -180,7 +179,6 @@ mod native;
|
|||
/// This function can fail if we fail to set up a graphics context.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
pub fn run_native(
|
||||
app_name: &str,
|
||||
native_options: NativeOptions,
|
||||
|
@ -223,40 +221,36 @@ pub enum Error {
|
|||
Glutin(#[from] glutin::error::Error),
|
||||
|
||||
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
|
||||
#[error("Found no glutin configs matching the template: {0:?}. error: {1:?}")]
|
||||
NoGlutinConfigs(glutin::config::ConfigTemplate, Box<dyn std::error::Error>),
|
||||
#[error("Found no glutin configs matching the template: {0:?}")]
|
||||
NoGlutinConfigs(glutin::config::ConfigTemplate),
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
#[error("WGPU error: {0}")]
|
||||
Wgpu(#[from] egui_wgpu::WgpuError),
|
||||
Wgpu(#[from] wgpu::RequestDeviceError),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
mod profiling_scopes {
|
||||
/// Profiling macro for feature "puffin"
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
macro_rules! profile_function {
|
||||
($($arg: tt)*) => {
|
||||
#[cfg(feature = "puffin")]
|
||||
puffin::profile_function!($($arg)*);
|
||||
};
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) use profile_function;
|
||||
|
||||
/// Profiling macro for feature "puffin"
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
macro_rules! profile_scope {
|
||||
($($arg: tt)*) => {
|
||||
#[cfg(feature = "puffin")]
|
||||
puffin::profile_scope!($($arg)*);
|
||||
};
|
||||
}
|
||||
pub(crate) use profile_scope;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
pub(crate) use profiling_scopes::*;
|
||||
pub(crate) use profile_scope;
|
||||
|
|
|
@ -12,14 +12,6 @@ use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings};
|
|||
|
||||
use crate::{epi, Theme, WindowInfo};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WindowState {
|
||||
// We cannot simply call `winit::Window::is_minimized/is_maximized`
|
||||
// because that deadlocks on mac.
|
||||
pub minimized: bool,
|
||||
pub maximized: bool,
|
||||
}
|
||||
|
||||
pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
|
||||
winit::dpi::LogicalSize {
|
||||
width: points.x as f64,
|
||||
|
@ -27,11 +19,7 @@ pub fn points_to_size(points: egui::Vec2) -> winit::dpi::LogicalSize<f64> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read_window_info(
|
||||
window: &winit::window::Window,
|
||||
pixels_per_point: f32,
|
||||
window_state: &WindowState,
|
||||
) -> WindowInfo {
|
||||
pub fn read_window_info(window: &winit::window::Window, pixels_per_point: f32) -> WindowInfo {
|
||||
let position = window
|
||||
.outer_position()
|
||||
.ok()
|
||||
|
@ -50,13 +38,9 @@ pub fn read_window_info(
|
|||
.inner_size()
|
||||
.to_logical::<f32>(pixels_per_point.into());
|
||||
|
||||
// NOTE: calling window.is_minimized() or window.is_maximized() deadlocks on Mac.
|
||||
|
||||
WindowInfo {
|
||||
position,
|
||||
fullscreen: window.fullscreen().is_some(),
|
||||
minimized: window_state.minimized,
|
||||
maximized: window_state.maximized,
|
||||
size: egui::Vec2 {
|
||||
x: size.width,
|
||||
y: size.height,
|
||||
|
@ -72,6 +56,7 @@ pub fn window_builder<E>(
|
|||
window_settings: Option<WindowSettings>,
|
||||
) -> winit::window::WindowBuilder {
|
||||
let epi::NativeOptions {
|
||||
always_on_top,
|
||||
maximized,
|
||||
decorated,
|
||||
fullscreen,
|
||||
|
@ -85,7 +70,6 @@ pub fn window_builder<E>(
|
|||
max_window_size,
|
||||
resizable,
|
||||
transparent,
|
||||
centered,
|
||||
..
|
||||
} = native_options;
|
||||
|
||||
|
@ -93,6 +77,7 @@ pub fn window_builder<E>(
|
|||
|
||||
let mut window_builder = winit::window::WindowBuilder::new()
|
||||
.with_title(title)
|
||||
.with_always_on_top(*always_on_top)
|
||||
.with_decorations(*decorated)
|
||||
.with_fullscreen(fullscreen.then(|| winit::window::Fullscreen::Borderless(None)))
|
||||
.with_maximized(*maximized)
|
||||
|
@ -120,16 +105,13 @@ pub fn window_builder<E>(
|
|||
|
||||
window_builder = window_builder_drag_and_drop(window_builder, *drag_and_drop_support);
|
||||
|
||||
let inner_size_points = if let Some(mut window_settings) = window_settings {
|
||||
if let Some(mut window_settings) = window_settings {
|
||||
// Restore pos/size from previous session
|
||||
window_settings.clamp_to_sane_values(largest_monitor_point_size(event_loop));
|
||||
#[cfg(windows)]
|
||||
window_settings.clamp_window_to_sane_position(&event_loop);
|
||||
window_builder = window_settings.initialize_window(window_builder);
|
||||
window_settings.inner_size_points()
|
||||
} else {
|
||||
if let Some(pos) = *initial_window_pos {
|
||||
window_builder = window_builder.with_position(winit::dpi::LogicalPosition {
|
||||
window_builder = window_builder.with_position(winit::dpi::PhysicalPosition {
|
||||
x: pos.x as f64,
|
||||
y: pos.y as f64,
|
||||
});
|
||||
|
@ -140,39 +122,11 @@ pub fn window_builder<E>(
|
|||
initial_window_size.at_most(largest_monitor_point_size(event_loop));
|
||||
window_builder = window_builder.with_inner_size(points_to_size(initial_window_size));
|
||||
}
|
||||
}
|
||||
|
||||
*initial_window_size
|
||||
};
|
||||
|
||||
if *centered {
|
||||
if let Some(monitor) = event_loop.available_monitors().next() {
|
||||
let monitor_size = monitor.size();
|
||||
let inner_size = inner_size_points.unwrap_or(egui::Vec2 { x: 800.0, y: 600.0 });
|
||||
if monitor_size.width > 0 && monitor_size.height > 0 {
|
||||
let x = (monitor_size.width - inner_size.x as u32) / 2;
|
||||
let y = (monitor_size.height - inner_size.y as u32) / 2;
|
||||
window_builder = window_builder.with_position(winit::dpi::LogicalPosition {
|
||||
x: x as f64,
|
||||
y: y as f64,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
window_builder
|
||||
}
|
||||
|
||||
pub fn apply_native_options_to_window(
|
||||
window: &winit::window::Window,
|
||||
native_options: &crate::NativeOptions,
|
||||
) {
|
||||
use winit::window::WindowLevel;
|
||||
window.set_window_level(if native_options.always_on_top {
|
||||
WindowLevel::AlwaysOnTop
|
||||
} else {
|
||||
WindowLevel::Normal
|
||||
});
|
||||
}
|
||||
|
||||
fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui::Vec2 {
|
||||
let mut max_size = egui::Vec2::ZERO;
|
||||
|
||||
|
@ -215,7 +169,6 @@ pub fn handle_app_output(
|
|||
window: &winit::window::Window,
|
||||
current_pixels_per_point: f32,
|
||||
app_output: epi::backend::AppOutput,
|
||||
window_state: &mut WindowState,
|
||||
) {
|
||||
let epi::backend::AppOutput {
|
||||
close: _,
|
||||
|
@ -227,8 +180,6 @@ pub fn handle_app_output(
|
|||
window_pos,
|
||||
visible: _, // handled in post_present
|
||||
always_on_top,
|
||||
minimized,
|
||||
maximized,
|
||||
} = app_output;
|
||||
|
||||
if let Some(decorated) = decorated {
|
||||
|
@ -265,22 +216,7 @@ pub fn handle_app_output(
|
|||
}
|
||||
|
||||
if let Some(always_on_top) = always_on_top {
|
||||
use winit::window::WindowLevel;
|
||||
window.set_window_level(if always_on_top {
|
||||
WindowLevel::AlwaysOnTop
|
||||
} else {
|
||||
WindowLevel::Normal
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(minimized) = minimized {
|
||||
window.set_minimized(minimized);
|
||||
window_state.minimized = minimized;
|
||||
}
|
||||
|
||||
if let Some(maximized) = maximized {
|
||||
window.set_maximized(maximized);
|
||||
window_state.maximized = maximized;
|
||||
window.set_always_on_top(always_on_top);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -307,7 +243,6 @@ pub struct EpiIntegration {
|
|||
/// When set, it is time to close the native window.
|
||||
close: bool,
|
||||
can_drag_window: bool,
|
||||
window_state: WindowState,
|
||||
}
|
||||
|
||||
impl EpiIntegration {
|
||||
|
@ -327,17 +262,12 @@ impl EpiIntegration {
|
|||
|
||||
let native_pixels_per_point = window.scale_factor() as f32;
|
||||
|
||||
let window_state = WindowState {
|
||||
minimized: window.is_minimized().unwrap_or(false),
|
||||
maximized: window.is_maximized(),
|
||||
};
|
||||
|
||||
let frame = epi::Frame {
|
||||
info: epi::IntegrationInfo {
|
||||
system_theme,
|
||||
cpu_usage: None,
|
||||
native_pixels_per_point: Some(native_pixels_per_point),
|
||||
window_info: read_window_info(window, egui_ctx.pixels_per_point(), &window_state),
|
||||
window_info: read_window_info(window, egui_ctx.pixels_per_point()),
|
||||
},
|
||||
output: epi::backend::AppOutput {
|
||||
visible: Some(true),
|
||||
|
@ -362,7 +292,6 @@ impl EpiIntegration {
|
|||
pending_full_output: Default::default(),
|
||||
close: false,
|
||||
can_drag_window: false,
|
||||
window_state,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -381,7 +310,7 @@ impl EpiIntegration {
|
|||
egui_ctx.enable_accesskit();
|
||||
// Enqueue a repaint so we'll receive a full tree update soon.
|
||||
egui_ctx.request_repaint();
|
||||
egui_ctx.accesskit_placeholder_tree_update()
|
||||
egui::accesskit_placeholder_tree_update()
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -444,16 +373,12 @@ impl EpiIntegration {
|
|||
) -> egui::FullOutput {
|
||||
let frame_start = std::time::Instant::now();
|
||||
|
||||
self.frame.info.window_info =
|
||||
read_window_info(window, self.egui_ctx.pixels_per_point(), &self.window_state);
|
||||
self.frame.info.window_info = read_window_info(window, self.egui_ctx.pixels_per_point());
|
||||
let raw_input = self.egui_winit.take_egui_input(window);
|
||||
|
||||
// Run user code:
|
||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
crate::profile_scope!("App::update");
|
||||
app.update(egui_ctx, &mut self.frame);
|
||||
});
|
||||
|
||||
self.pending_full_output.append(full_output);
|
||||
let full_output = std::mem::take(&mut self.pending_full_output);
|
||||
|
||||
|
@ -466,12 +391,7 @@ impl EpiIntegration {
|
|||
tracing::debug!("App::on_close_event returned {}", self.close);
|
||||
}
|
||||
self.frame.output.visible = app_output.visible; // this is handled by post_present
|
||||
handle_app_output(
|
||||
window,
|
||||
self.egui_ctx.pixels_per_point(),
|
||||
app_output,
|
||||
&mut self.window_state,
|
||||
);
|
||||
handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
|
||||
}
|
||||
|
||||
let frame_time = frame_start.elapsed().as_secs_f64() as f32;
|
||||
|
|
|
@ -38,7 +38,6 @@ pub use epi::NativeOptions;
|
|||
#[derive(Debug)]
|
||||
enum EventResult {
|
||||
Wait,
|
||||
|
||||
/// Causes a synchronous repaint inside the event handler. This should only
|
||||
/// be used in special situations if the window must be repainted while
|
||||
/// handling a specific event. This occurs on Windows when handling resizes.
|
||||
|
@ -46,28 +45,20 @@ enum EventResult {
|
|||
/// `RepaintNow` creates a new frame synchronously, and should therefore
|
||||
/// only be used for extremely urgent repaints.
|
||||
RepaintNow,
|
||||
|
||||
/// Queues a repaint for once the event loop handles its next redraw. Exists
|
||||
/// so that multiple input events can be handled in one frame. Does not
|
||||
/// cause any delay like `RepaintNow`.
|
||||
RepaintNext,
|
||||
|
||||
RepaintAt(Instant),
|
||||
|
||||
Exit,
|
||||
}
|
||||
|
||||
trait WinitApp {
|
||||
fn is_focused(&self) -> bool;
|
||||
|
||||
fn integration(&self) -> Option<&EpiIntegration>;
|
||||
|
||||
fn window(&self) -> Option<&winit::window::Window>;
|
||||
|
||||
fn save_and_destroy(&mut self);
|
||||
|
||||
fn paint(&mut self) -> EventResult;
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
|
@ -161,8 +152,8 @@ fn run_and_return(
|
|||
event => match winit_app.on_event(event_loop, event) {
|
||||
Ok(event_result) => event_result,
|
||||
Err(err) => {
|
||||
tracing::error!("Exiting because of error: {err:?} on event {event:?}");
|
||||
returned_result = Err(err);
|
||||
tracing::debug!("Exiting because of an error");
|
||||
EventResult::Exit
|
||||
}
|
||||
},
|
||||
|
@ -299,6 +290,27 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp +
|
|||
})
|
||||
}
|
||||
|
||||
fn center_window_pos(
|
||||
monitor: Option<winit::monitor::MonitorHandle>,
|
||||
native_options: &mut epi::NativeOptions,
|
||||
) {
|
||||
// Get the current_monitor.
|
||||
if let Some(monitor) = monitor {
|
||||
let monitor_size = monitor.size();
|
||||
let inner_size = native_options
|
||||
.initial_window_size
|
||||
.unwrap_or(egui::Vec2 { x: 800.0, y: 600.0 });
|
||||
if monitor_size.width > 0 && monitor_size.height > 0 {
|
||||
let x = (monitor_size.width - inner_size.x as u32) / 2;
|
||||
let y = (monitor_size.height - inner_size.y as u32) / 2;
|
||||
native_options.initial_window_pos = Some(egui::Pos2 {
|
||||
x: x as _,
|
||||
y: y as _,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/// Run an egui app
|
||||
#[cfg(feature = "glow")]
|
||||
|
@ -306,12 +318,6 @@ mod glow_integration {
|
|||
use std::sync::Arc;
|
||||
|
||||
use egui::NumExt as _;
|
||||
use glutin::{
|
||||
display::GetGlDisplay,
|
||||
prelude::{GlDisplay, NotCurrentGlContextSurfaceAccessor, PossiblyCurrentGlContext},
|
||||
surface::GlSurface,
|
||||
};
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -336,260 +342,132 @@ mod glow_integration {
|
|||
painter: egui_glow::Painter,
|
||||
integration: epi_integration::EpiIntegration,
|
||||
app: Box<dyn epi::App>,
|
||||
|
||||
// Conceptually this will be split out eventually so that the rest of the state
|
||||
// can be persistent.
|
||||
gl_window: GlutinWindowContext,
|
||||
}
|
||||
|
||||
/// This struct will contain both persistent and temporary glutin state.
|
||||
///
|
||||
/// Platform Quirks:
|
||||
/// * Microsoft Windows: requires that we create a window before opengl context.
|
||||
/// * Android: window and surface should be destroyed when we receive a suspend event. recreate on resume event.
|
||||
///
|
||||
/// winit guarantees that we will get a Resumed event on startup on all platforms.
|
||||
/// * Before Resumed event: `gl_config`, `gl_context` can be created at any time. on windows, a window must be created to get `gl_context`.
|
||||
/// * Resumed: `gl_surface` will be created here. `window` will be re-created here for android.
|
||||
/// * Suspended: on android, we drop window + surface. on other platforms, we don't get Suspended event.
|
||||
///
|
||||
/// The setup is divided between the `new` fn and `on_resume` fn. we can just assume that `on_resume` is a continuation of
|
||||
/// `new` fn on all platforms. only on android, do we get multiple resumed events because app can be suspended.
|
||||
struct GlutinWindowContext {
|
||||
builder: winit::window::WindowBuilder,
|
||||
swap_interval: glutin::surface::SwapInterval,
|
||||
gl_config: glutin::config::Config,
|
||||
current_gl_context: Option<glutin::context::PossiblyCurrentContext>,
|
||||
gl_surface: Option<glutin::surface::Surface<glutin::surface::WindowSurface>>,
|
||||
not_current_gl_context: Option<glutin::context::NotCurrentContext>,
|
||||
window: Option<winit::window::Window>,
|
||||
window: winit::window::Window,
|
||||
gl_context: glutin::context::PossiblyCurrentContext,
|
||||
gl_display: glutin::display::Display,
|
||||
gl_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
|
||||
}
|
||||
|
||||
impl GlutinWindowContext {
|
||||
/// There is a lot of complexity with opengl creation, so prefer extensivve logging to get all the help we can to debug issues.
|
||||
///
|
||||
// refactor this function to use `glutin-winit` crate eventually.
|
||||
// preferably add android support at the same time.
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn new(
|
||||
winit_window_builder: winit::window::WindowBuilder,
|
||||
winit_window: winit::window::Window,
|
||||
native_options: &epi::NativeOptions,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
) -> Result<Self> {
|
||||
use glutin::prelude::*;
|
||||
// convert native options to glutin options
|
||||
use raw_window_handle::*;
|
||||
let hardware_acceleration = match native_options.hardware_acceleration {
|
||||
crate::HardwareAcceleration::Required => Some(true),
|
||||
crate::HardwareAcceleration::Preferred => None,
|
||||
crate::HardwareAcceleration::Off => Some(false),
|
||||
};
|
||||
|
||||
let raw_display_handle = winit_window.raw_display_handle();
|
||||
let raw_window_handle = winit_window.raw_window_handle();
|
||||
|
||||
// EGL is crossplatform and the official khronos way
|
||||
// but sometimes platforms/drivers may not have it, so we use back up options where possible.
|
||||
// TODO: check whether we can expose these options as "features", so that users can select the relevant backend they want.
|
||||
|
||||
// try egl and fallback to windows wgl. Windows is the only platform that *requires* window handle to create display.
|
||||
#[cfg(target_os = "windows")]
|
||||
let preference =
|
||||
glutin::display::DisplayApiPreference::EglThenWgl(Some(raw_window_handle));
|
||||
// try egl and fallback to x11 glx
|
||||
#[cfg(target_os = "linux")]
|
||||
let preference = glutin::display::DisplayApiPreference::EglThenGlx(Box::new(
|
||||
winit::platform::unix::register_xlib_error_hook,
|
||||
));
|
||||
#[cfg(target_os = "macos")]
|
||||
let preference = glutin::display::DisplayApiPreference::Cgl;
|
||||
#[cfg(target_os = "android")]
|
||||
let preference = glutin::display::DisplayApiPreference::Egl;
|
||||
|
||||
let gl_display = glutin::display::Display::new(raw_display_handle, preference)?;
|
||||
let swap_interval = if native_options.vsync {
|
||||
glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap())
|
||||
} else {
|
||||
glutin::surface::SwapInterval::DontWait
|
||||
};
|
||||
/* opengl setup flow goes like this:
|
||||
1. we create a configuration for opengl "Display" / "Config" creation
|
||||
2. choose between special extensions like glx or egl or wgl and use them to create config/display
|
||||
3. opengl context configuration
|
||||
4. opengl context creation
|
||||
*/
|
||||
// start building config for gl display
|
||||
let config_template_builder = glutin::config::ConfigTemplateBuilder::new()
|
||||
|
||||
let config_template = glutin::config::ConfigTemplateBuilder::new()
|
||||
.prefer_hardware_accelerated(hardware_acceleration)
|
||||
.with_depth_size(native_options.depth_buffer)
|
||||
.with_stencil_size(native_options.stencil_buffer)
|
||||
.with_transparency(native_options.transparent);
|
||||
.with_depth_size(native_options.depth_buffer);
|
||||
// we don't know if multi sampling option is set. so, check if its more than 0.
|
||||
let config_template_builder = if native_options.multisampling > 0 {
|
||||
config_template_builder.with_multisampling(
|
||||
let config_template = if native_options.multisampling > 0 {
|
||||
config_template.with_multisampling(
|
||||
native_options
|
||||
.multisampling
|
||||
.try_into()
|
||||
.expect("failed to fit multisamples option of native_options into u8"),
|
||||
.expect("failed to fit multisamples into u8"),
|
||||
)
|
||||
} else {
|
||||
config_template_builder
|
||||
config_template
|
||||
};
|
||||
let config_template = config_template
|
||||
.with_stencil_size(native_options.stencil_buffer)
|
||||
.with_transparency(native_options.transparent)
|
||||
.compatible_with_native_window(raw_window_handle)
|
||||
.build();
|
||||
// finds all valid configurations supported by this display that match the config_template
|
||||
// this is where we will try to get a "fallback" config if we are okay with ignoring some native
|
||||
// options required by user like multi sampling, srgb, transparency etc..
|
||||
// TODO: need to figure out a good fallback config template
|
||||
let config = gl_display
|
||||
.find_configs(config_template.clone())?
|
||||
.next()
|
||||
.ok_or(crate::Error::NoGlutinConfigs(config_template))?;
|
||||
|
||||
tracing::debug!(
|
||||
"trying to create glutin Display with config: {:?}",
|
||||
&config_template_builder
|
||||
);
|
||||
// create gl display. this may probably create a window too on most platforms. definitely on `MS windows`. never on android.
|
||||
let (window, gl_config) = glutin_winit::DisplayBuilder::new()
|
||||
// we might want to expose this option to users in the future. maybe using an env var or using native_options.
|
||||
.with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
|
||||
.with_window_builder(Some(winit_window_builder.clone()))
|
||||
.build(
|
||||
event_loop,
|
||||
config_template_builder.clone(),
|
||||
|mut config_iterator| {
|
||||
let config = config_iterator.next().expect(
|
||||
"failed to find a matching configuration for creating glutin config",
|
||||
);
|
||||
tracing::debug!(
|
||||
"using the first config from config picker closure. config: {:?}",
|
||||
&config
|
||||
);
|
||||
config
|
||||
},
|
||||
)
|
||||
.map_err(|e| crate::Error::NoGlutinConfigs(config_template_builder.build(), e))?;
|
||||
|
||||
let gl_display = gl_config.display();
|
||||
tracing::debug!(
|
||||
"successfully created GL Display with version: {} and supported features: {:?}",
|
||||
gl_display.version_string(),
|
||||
gl_display.supported_features()
|
||||
);
|
||||
let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle());
|
||||
tracing::debug!(
|
||||
"creating gl context using raw window handle: {:?}",
|
||||
raw_window_handle
|
||||
);
|
||||
|
||||
// create gl context. if core context cannot be created, try gl es context as fallback.
|
||||
let context_attributes =
|
||||
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
|
||||
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
|
||||
.with_context_api(glutin::context::ContextApi::Gles(None))
|
||||
.build(raw_window_handle);
|
||||
let gl_context = match gl_config
|
||||
.display()
|
||||
.create_context(&gl_config, &context_attributes)
|
||||
{
|
||||
Ok(it) => it,
|
||||
Err(err) => {
|
||||
tracing::warn!("failed to create context using default context attributes {context_attributes:?} due to error: {err}");
|
||||
tracing::debug!("retrying with fallback context attributes: {fallback_context_attributes:?}");
|
||||
gl_config
|
||||
.display()
|
||||
.create_context(&gl_config, &fallback_context_attributes)?
|
||||
}
|
||||
};
|
||||
let not_current_gl_context = Some(gl_context);
|
||||
|
||||
// the fun part with opengl gl is that we never know whether there is an error. the context creation might have failed, but
|
||||
// it could keep working until we try to make surface current or swap buffers or something else. future glutin improvements might
|
||||
// help us start from scratch again if we fail context creation and go back to preferEgl or try with different config etc..
|
||||
// https://github.com/emilk/egui/pull/2541#issuecomment-1370767582
|
||||
Ok(GlutinWindowContext {
|
||||
builder: winit_window_builder,
|
||||
swap_interval,
|
||||
gl_config,
|
||||
current_gl_context: None,
|
||||
window,
|
||||
gl_surface: None,
|
||||
not_current_gl_context,
|
||||
})
|
||||
}
|
||||
|
||||
/// This will be run after `new`. on android, it might be called multiple times over the course of the app's lifetime.
|
||||
/// roughly,
|
||||
/// 1. check if window already exists. otherwise, create one now.
|
||||
/// 2. create attributes for surface creation.
|
||||
/// 3. create surface.
|
||||
/// 4. make surface and context current.
|
||||
///
|
||||
/// we presently assume that we will
|
||||
#[allow(unsafe_code)]
|
||||
fn on_resume(&mut self, event_loop: &EventLoopWindowTarget<UserEvent>) -> Result<()> {
|
||||
if self.gl_surface.is_some() {
|
||||
tracing::warn!(
|
||||
"on_resume called even thought we already have a surface. early return"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
tracing::debug!("running on_resume fn.");
|
||||
// make sure we have a window or create one.
|
||||
let window = self.window.take().unwrap_or_else(|| {
|
||||
tracing::debug!("window doesn't exist yet. creating one now with finalize_window");
|
||||
glutin_winit::finalize_window(event_loop, self.builder.clone(), &self.gl_config)
|
||||
.expect("failed to finalize glutin window")
|
||||
});
|
||||
// surface attributes
|
||||
let (width, height): (u32, u32) = window.inner_size().into();
|
||||
glutin::context::ContextAttributesBuilder::new().build(Some(raw_window_handle));
|
||||
// for surface creation.
|
||||
let (width, height): (u32, u32) = winit_window.inner_size().into();
|
||||
let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap();
|
||||
let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap();
|
||||
let surface_attributes =
|
||||
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
|
||||
.build(window.raw_window_handle(), width, height);
|
||||
tracing::debug!(
|
||||
"creating surface with attributes: {:?}",
|
||||
&surface_attributes
|
||||
);
|
||||
// create surface
|
||||
let gl_surface = unsafe {
|
||||
self.gl_config
|
||||
.display()
|
||||
.create_window_surface(&self.gl_config, &surface_attributes)?
|
||||
};
|
||||
tracing::debug!("surface created successfully: {gl_surface:?}.making context current");
|
||||
// make surface and context current.
|
||||
let not_current_gl_context = self
|
||||
.not_current_gl_context
|
||||
.take()
|
||||
.expect("failed to get not current context after resume event. impossible!");
|
||||
let current_gl_context = not_current_gl_context.make_current(&gl_surface)?;
|
||||
// try setting swap interval. but its not absolutely necessary, so don't panic on failure.
|
||||
tracing::debug!("made context current. setting swap interval for surface");
|
||||
if let Err(e) = gl_surface.set_swap_interval(¤t_gl_context, self.swap_interval) {
|
||||
tracing::error!("failed to set swap interval due to error: {e:?}");
|
||||
}
|
||||
// we will reach this point only once in most platforms except android.
|
||||
// create window/surface/make context current once and just use them forever.
|
||||
self.gl_surface = Some(gl_surface);
|
||||
self.current_gl_context = Some(current_gl_context);
|
||||
self.window = Some(window);
|
||||
Ok(())
|
||||
}
|
||||
.build(raw_window_handle, width, height);
|
||||
// start creating the gl objects
|
||||
let gl_context = gl_display.create_context(&config, &context_attributes)?;
|
||||
|
||||
/// only applies for android. but we basically drop surface + window and make context not current
|
||||
fn on_suspend(&mut self) -> Result<()> {
|
||||
tracing::debug!("received suspend event. dropping window and surface");
|
||||
self.gl_surface.take();
|
||||
self.window.take();
|
||||
if let Some(current) = self.current_gl_context.take() {
|
||||
tracing::debug!("context is current, so making it non-current");
|
||||
self.not_current_gl_context = Some(current.make_not_current()?);
|
||||
} else {
|
||||
tracing::debug!(
|
||||
"context is already not current??? could be duplicate suspend event"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
let gl_surface = gl_display.create_window_surface(&config, &surface_attributes)?;
|
||||
let gl_context = gl_context.make_current(&gl_surface)?;
|
||||
gl_surface.set_swap_interval(&gl_context, swap_interval)?;
|
||||
Ok(GlutinWindowContext {
|
||||
window: winit_window,
|
||||
gl_context,
|
||||
gl_display,
|
||||
gl_surface,
|
||||
})
|
||||
}
|
||||
|
||||
fn window(&self) -> &winit::window::Window {
|
||||
self.window.as_ref().expect("winit window doesn't exist")
|
||||
&self.window
|
||||
}
|
||||
|
||||
fn resize(&self, physical_size: winit::dpi::PhysicalSize<u32>) {
|
||||
use glutin::surface::GlSurface;
|
||||
let width = std::num::NonZeroU32::new(physical_size.width.at_least(1)).unwrap();
|
||||
let height = std::num::NonZeroU32::new(physical_size.height.at_least(1)).unwrap();
|
||||
self.gl_surface
|
||||
.as_ref()
|
||||
.expect("failed to get surface to resize")
|
||||
.resize(
|
||||
self.current_gl_context
|
||||
.as_ref()
|
||||
.expect("failed to get current context to resize surface"),
|
||||
width,
|
||||
height,
|
||||
);
|
||||
self.gl_surface.resize(&self.gl_context, width, height);
|
||||
}
|
||||
|
||||
fn swap_buffers(&self) -> glutin::error::Result<()> {
|
||||
self.gl_surface
|
||||
.as_ref()
|
||||
.expect("failed to get surface to swap buffers")
|
||||
.swap_buffers(
|
||||
self.current_gl_context
|
||||
.as_ref()
|
||||
.expect("failed to get current context to swap buffers"),
|
||||
)
|
||||
use glutin::surface::GlSurface;
|
||||
self.gl_surface.swap_buffers(&self.gl_context)
|
||||
}
|
||||
|
||||
fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void {
|
||||
self.gl_config.display().get_proc_address(addr)
|
||||
use glutin::display::GlDisplay;
|
||||
self.gl_display.get_proc_address(addr)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -637,17 +515,12 @@ mod glow_integration {
|
|||
|
||||
let window_settings = epi_integration::load_window_settings(storage);
|
||||
|
||||
let winit_window_builder =
|
||||
epi_integration::window_builder(event_loop, title, native_options, window_settings);
|
||||
let mut glutin_window_context = unsafe {
|
||||
GlutinWindowContext::new(winit_window_builder, native_options, event_loop)?
|
||||
};
|
||||
glutin_window_context.on_resume(event_loop)?;
|
||||
|
||||
if let Some(window) = &glutin_window_context.window {
|
||||
epi_integration::apply_native_options_to_window(window, native_options);
|
||||
}
|
||||
|
||||
let winit_window =
|
||||
epi_integration::window_builder(event_loop, title, native_options, window_settings)
|
||||
.build(event_loop)?;
|
||||
// a lot of the code below has been lifted from glutin example in their repo.
|
||||
let glutin_window_context =
|
||||
unsafe { GlutinWindowContext::new(winit_window, native_options)? };
|
||||
let gl = unsafe {
|
||||
glow::Context::from_loader_function(|s| {
|
||||
let s = std::ffi::CString::new(s)
|
||||
|
@ -876,24 +749,26 @@ mod glow_integration {
|
|||
) -> Result<EventResult> {
|
||||
Ok(match event {
|
||||
winit::event::Event::Resumed => {
|
||||
// first resume event.
|
||||
// we can actually move this outside of event loop.
|
||||
// and just run the on_resume fn of gl_window
|
||||
if self.running.is_none() {
|
||||
self.init_run_state(event_loop)?;
|
||||
} else {
|
||||
// not the first resume event. create whatever you need.
|
||||
self.running
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.gl_window
|
||||
.on_resume(event_loop)?;
|
||||
}
|
||||
EventResult::RepaintNow
|
||||
}
|
||||
winit::event::Event::Suspended => {
|
||||
self.running.as_mut().unwrap().gl_window.on_suspend()?;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
tracing::error!("Suspended app can't destroy Window surface state with current Egui Glow backend (undefined behaviour)");
|
||||
// Instead of destroying everything which we _know_ we can't re-create
|
||||
// we instead currently just try our luck with not destroying anything.
|
||||
//
|
||||
// When the application resumes then it will get a new `SurfaceView` but
|
||||
// we have no practical way currently of creating a new EGL surface
|
||||
// via the Glutin API while keeping the GL context and the rest of
|
||||
// our app state. This will likely result in a black screen or
|
||||
// frozen screen.
|
||||
//
|
||||
//self.running = None;
|
||||
}
|
||||
EventResult::Wait
|
||||
}
|
||||
|
||||
|
@ -988,13 +863,22 @@ mod glow_integration {
|
|||
app_creator: epi::AppCreator,
|
||||
) -> Result<()> {
|
||||
if native_options.run_and_return {
|
||||
with_event_loop(native_options, |event_loop, native_options| {
|
||||
with_event_loop(native_options, |event_loop, mut native_options| {
|
||||
if native_options.centered {
|
||||
center_window_pos(event_loop.available_monitors().next(), &mut native_options);
|
||||
}
|
||||
|
||||
let glow_eframe =
|
||||
GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
|
||||
run_and_return(event_loop, glow_eframe)
|
||||
})
|
||||
} else {
|
||||
let event_loop = create_event_loop_builder(&mut native_options).build();
|
||||
|
||||
if native_options.centered {
|
||||
center_window_pos(event_loop.available_monitors().next(), &mut native_options);
|
||||
}
|
||||
|
||||
let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
|
||||
run_and_exit(event_loop, glow_eframe);
|
||||
}
|
||||
|
@ -1062,20 +946,19 @@ mod wgpu_integration {
|
|||
storage: Option<&dyn epi::Storage>,
|
||||
title: &str,
|
||||
native_options: &NativeOptions,
|
||||
) -> std::result::Result<winit::window::Window, winit::error::OsError> {
|
||||
) -> Result<winit::window::Window> {
|
||||
let window_settings = epi_integration::load_window_settings(storage);
|
||||
let window_builder =
|
||||
epi_integration::window_builder(event_loop, title, native_options, window_settings);
|
||||
let window = window_builder.build(event_loop)?;
|
||||
epi_integration::apply_native_options_to_window(&window, native_options);
|
||||
Ok(window)
|
||||
Ok(
|
||||
epi_integration::window_builder(event_loop, title, native_options, window_settings)
|
||||
.build(event_loop)?,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
fn set_window(
|
||||
&mut self,
|
||||
window: winit::window::Window,
|
||||
) -> std::result::Result<(), egui_wgpu::WgpuError> {
|
||||
) -> std::result::Result<(), wgpu::RequestDeviceError> {
|
||||
self.window = Some(window);
|
||||
if let Some(running) = &mut self.running {
|
||||
unsafe {
|
||||
|
@ -1087,7 +970,7 @@ mod wgpu_integration {
|
|||
|
||||
#[allow(unsafe_code)]
|
||||
#[cfg(target_os = "android")]
|
||||
fn drop_window(&mut self) -> std::result::Result<(), egui_wgpu::WgpuError> {
|
||||
fn drop_window(&mut self) -> std::result::Result<(), wgpu::RequestDeviceError> {
|
||||
self.window = None;
|
||||
if let Some(running) = &mut self.running {
|
||||
unsafe {
|
||||
|
@ -1102,14 +985,13 @@ mod wgpu_integration {
|
|||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
storage: Option<Box<dyn epi::Storage>>,
|
||||
window: winit::window::Window,
|
||||
) -> std::result::Result<(), egui_wgpu::WgpuError> {
|
||||
) -> std::result::Result<(), wgpu::RequestDeviceError> {
|
||||
#[allow(unsafe_code, unused_mut, unused_unsafe)]
|
||||
let painter = unsafe {
|
||||
let mut painter = egui_wgpu::winit::Painter::new(
|
||||
self.native_options.wgpu_options.clone(),
|
||||
self.native_options.multisampling.max(1) as _,
|
||||
self.native_options.depth_buffer,
|
||||
self.native_options.transparent,
|
||||
);
|
||||
pollster::block_on(painter.set_window(Some(&window)))?;
|
||||
painter
|
||||
|
@ -1404,13 +1286,22 @@ mod wgpu_integration {
|
|||
app_creator: epi::AppCreator,
|
||||
) -> Result<()> {
|
||||
if native_options.run_and_return {
|
||||
with_event_loop(native_options, |event_loop, native_options| {
|
||||
with_event_loop(native_options, |event_loop, mut native_options| {
|
||||
if native_options.centered {
|
||||
center_window_pos(event_loop.available_monitors().next(), &mut native_options);
|
||||
}
|
||||
|
||||
let wgpu_eframe =
|
||||
WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
|
||||
run_and_return(event_loop, wgpu_eframe)
|
||||
})
|
||||
} else {
|
||||
let event_loop = create_event_loop_builder(&mut native_options).build();
|
||||
|
||||
if native_options.centered {
|
||||
center_window_pos(event_loop.available_monitors().next(), &mut native_options);
|
||||
}
|
||||
|
||||
let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
|
||||
run_and_exit(event_loop, wgpu_eframe);
|
||||
}
|
||||
|
|
|
@ -450,6 +450,8 @@ pub enum EventToUnsubscribe {
|
|||
|
||||
impl EventToUnsubscribe {
|
||||
pub fn unsubscribe(self) -> Result<(), JsValue> {
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
match self {
|
||||
EventToUnsubscribe::TargetEvent(handle) => {
|
||||
handle.target.remove_event_listener_with_callback(
|
||||
|
@ -466,7 +468,6 @@ impl EventToUnsubscribe {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppRunnerContainer {
|
||||
pub runner: AppRunnerRef,
|
||||
|
||||
|
@ -485,6 +486,8 @@ impl AppRunnerContainer {
|
|||
event_name: &'static str,
|
||||
mut closure: impl FnMut(E, MutexGuard<'_, AppRunner>) + 'static,
|
||||
) -> Result<(), JsValue> {
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
// Create a JS closure based on the FnMut provided
|
||||
let closure = Closure::wrap({
|
||||
// Clone atomics
|
||||
|
|
|
@ -31,6 +31,7 @@ pub fn paint_and_schedule(
|
|||
runner_ref: AppRunnerRef,
|
||||
panicked: Arc<AtomicBool>,
|
||||
) -> Result<(), JsValue> {
|
||||
use wasm_bindgen::JsCast;
|
||||
let window = web_sys::window().unwrap();
|
||||
let closure = Closure::once(move || paint_and_schedule(&runner_ref, panicked));
|
||||
window.request_animation_frame(closure.as_ref().unchecked_ref())?;
|
||||
|
@ -94,12 +95,7 @@ pub fn install_document_events(runner_container: &mut AppRunnerContainer) -> Res
|
|||
// egui wants to use tab to move to the next text field.
|
||||
true
|
||||
} else if egui_key == Some(Key::P) {
|
||||
#[allow(clippy::needless_bool)]
|
||||
if modifiers.ctrl || modifiers.command || modifiers.mac_cmd {
|
||||
true // Prevent ctrl-P opening the print dialog. Users may want to use it for a command palette.
|
||||
} else {
|
||||
false // let normal P:s through
|
||||
}
|
||||
} else if egui_wants_keyboard {
|
||||
matches!(
|
||||
event.key().as_str(),
|
||||
|
@ -408,7 +404,7 @@ pub fn install_canvas_events(runner_container: &mut AppRunnerContainer) -> Resul
|
|||
}
|
||||
web_sys::WheelEvent::DOM_DELTA_LINE => {
|
||||
#[allow(clippy::let_and_return)]
|
||||
let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in winit.
|
||||
let points_per_scroll_line = 8.0; // Note that this is intentionally different from what we use in egui_glium / winit.
|
||||
points_per_scroll_line
|
||||
}
|
||||
_ => 1.0, // DOM_DELTA_PIXEL
|
||||
|
|
|
@ -83,6 +83,7 @@ pub fn system_theme() -> Option<Theme> {
|
|||
}
|
||||
|
||||
pub fn canvas_element(canvas_id: &str) -> Option<web_sys::HtmlCanvasElement> {
|
||||
use wasm_bindgen::JsCast;
|
||||
let document = web_sys::window()?.document()?;
|
||||
let canvas = document.get_element_by_id(canvas_id)?;
|
||||
canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
|
||||
|
|
|
@ -10,6 +10,7 @@ use wasm_bindgen::prelude::*;
|
|||
static AGENT_ID: &str = "egui_text_agent";
|
||||
|
||||
pub fn text_agent() -> web_sys::HtmlInputElement {
|
||||
use wasm_bindgen::JsCast;
|
||||
web_sys::window()
|
||||
.unwrap()
|
||||
.document()
|
||||
|
@ -22,6 +23,7 @@ pub fn text_agent() -> web_sys::HtmlInputElement {
|
|||
|
||||
/// Text event handler,
|
||||
pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(), JsValue> {
|
||||
use wasm_bindgen::JsCast;
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let body = document.body().expect("document should have a body");
|
||||
|
@ -127,6 +129,7 @@ pub fn install_text_agent(runner_container: &mut AppRunnerContainer) -> Result<(
|
|||
|
||||
/// Focus or blur text agent to toggle mobile keyboard.
|
||||
pub fn update_text_agent(runner: MutexGuard<'_, AppRunner>) -> Option<()> {
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::HtmlInputElement;
|
||||
let window = web_sys::window()?;
|
||||
let document = window.document()?;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use egui::Rgba;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
/// Renderer for a browser canvas.
|
||||
|
@ -18,7 +19,7 @@ pub(crate) trait WebPainter {
|
|||
/// Update all internal textures and paint gui.
|
||||
fn paint_and_update_textures(
|
||||
&mut self,
|
||||
clear_color: [f32; 4],
|
||||
clear_color: Rgba,
|
||||
clipped_primitives: &[egui::ClippedPrimitive],
|
||||
pixels_per_point: f32,
|
||||
textures_delta: &egui::TexturesDelta,
|
||||
|
|
|
@ -2,6 +2,7 @@ use wasm_bindgen::JsCast;
|
|||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
use egui::Rgba;
|
||||
use egui_glow::glow;
|
||||
|
||||
use crate::{WebGlContextOption, WebOptions};
|
||||
|
@ -48,7 +49,7 @@ impl WebPainter for WebPainterGlow {
|
|||
|
||||
fn paint_and_update_textures(
|
||||
&mut self,
|
||||
clear_color: [f32; 4],
|
||||
clear_color: Rgba,
|
||||
clipped_primitives: &[egui::ClippedPrimitive],
|
||||
pixels_per_point: f32,
|
||||
textures_delta: &egui::TexturesDelta,
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
use egui::mutex::RwLock;
|
||||
use egui::{mutex::RwLock, Rgba};
|
||||
use egui_wgpu::{renderer::ScreenDescriptor, RenderState, SurfaceErrorAction};
|
||||
|
||||
use crate::WebOptions;
|
||||
|
@ -49,7 +49,6 @@ impl WebPainterWgpu {
|
|||
dimension: wgpu::TextureDimension::D2,
|
||||
format: depth_format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[depth_format],
|
||||
})
|
||||
.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
})
|
||||
|
@ -61,13 +60,8 @@ impl WebPainterWgpu {
|
|||
|
||||
let canvas = super::canvas_element_or_die(canvas_id);
|
||||
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: options.wgpu_options.backends,
|
||||
dx12_shader_compiler: Default::default(),
|
||||
});
|
||||
let surface = instance
|
||||
.create_surface_from_canvas(&canvas)
|
||||
.map_err(|err| format!("failed to create wgpu surface: {err}"))?;
|
||||
let instance = wgpu::Instance::new(options.wgpu_options.backends);
|
||||
let surface = instance.create_surface_from_canvas(&canvas);
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
|
@ -87,7 +81,7 @@ impl WebPainterWgpu {
|
|||
.map_err(|err| format!("Failed to find wgpu device: {}", err))?;
|
||||
|
||||
let target_format =
|
||||
egui_wgpu::preferred_framebuffer_format(&surface.get_capabilities(&adapter).formats);
|
||||
egui_wgpu::preferred_framebuffer_format(&surface.get_supported_formats(&adapter));
|
||||
|
||||
let depth_format = options.wgpu_options.depth_format;
|
||||
let renderer = egui_wgpu::Renderer::new(&device, target_format, depth_format, 1);
|
||||
|
@ -105,7 +99,6 @@ impl WebPainterWgpu {
|
|||
height: 0,
|
||||
present_mode: options.wgpu_options.present_mode,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
view_formats: vec![target_format],
|
||||
};
|
||||
|
||||
tracing::debug!("wgpu painter initialized.");
|
||||
|
@ -135,7 +128,7 @@ impl WebPainter for WebPainterWgpu {
|
|||
|
||||
fn paint_and_update_textures(
|
||||
&mut self,
|
||||
clear_color: [f32; 4],
|
||||
clear_color: Rgba,
|
||||
clipped_primitives: &[egui::ClippedPrimitive],
|
||||
pixels_per_point: f32,
|
||||
textures_delta: &egui::TexturesDelta,
|
||||
|
@ -228,10 +221,10 @@ impl WebPainter for WebPainterWgpu {
|
|||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: clear_color[0] as f64,
|
||||
g: clear_color[1] as f64,
|
||||
b: clear_color[2] as f64,
|
||||
a: clear_color[3] as f64,
|
||||
r: clear_color.r() as f64,
|
||||
g: clear_color.g() as f64,
|
||||
b: clear_color.b() as f64,
|
||||
a: clear_color.a() as f64,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
|
|
|
@ -3,14 +3,9 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08
|
||||
* Update to `wgpu` 0.15 ([#2629](https://github.com/emilk/egui/pull/2629))
|
||||
* Return `Err` instead of panic if we can't find a device ([#2428](https://github.com/emilk/egui/pull/2428)).
|
||||
* `winit::Painter::set_window` is now `async` ([#2434](https://github.com/emilk/egui/pull/2434)).
|
||||
* `egui-wgpu` now only depends on `epaint` instead of the entire `egui` ([#2438](https://github.com/emilk/egui/pull/2438)).
|
||||
* `winit::Painter` now supports transparent backbuffer ([#2684](https://github.com/emilk/egui/pull/2684)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08 - web support
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "egui-wgpu"
|
||||
version = "0.21.0"
|
||||
version = "0.20.0"
|
||||
description = "Bindings for using egui natively using the wgpu library"
|
||||
authors = [
|
||||
"Nils Hasenbanck <nils@hasenbanck.de>",
|
||||
|
@ -36,20 +36,20 @@ winit = ["dep:winit"]
|
|||
|
||||
|
||||
[dependencies]
|
||||
epaint = { version = "0.21.0", path = "../epaint", default-features = false, features = [
|
||||
epaint = { version = "0.20.0", path = "../epaint", default-features = false, features = [
|
||||
"bytemuck",
|
||||
] }
|
||||
|
||||
bytemuck = "1.7"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
type-map = "0.5.0"
|
||||
wgpu = "0.15.0"
|
||||
wgpu = "0.14"
|
||||
|
||||
#! ### Optional dependencies
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
winit = { version = "0.28", optional = true }
|
||||
winit = { version = "0.27.2", optional = true }
|
||||
|
||||
# Native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
|
|
@ -99,39 +99,7 @@ pub fn preferred_framebuffer_format(formats: &[wgpu::TextureFormat]) -> wgpu::Te
|
|||
}
|
||||
formats[0] // take the first
|
||||
}
|
||||
// maybe use this-error?
|
||||
#[derive(Debug)]
|
||||
pub enum WgpuError {
|
||||
DeviceError(wgpu::RequestDeviceError),
|
||||
SurfaceError(wgpu::CreateSurfaceError),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for WgpuError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Debug::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for WgpuError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
WgpuError::DeviceError(e) => e.source(),
|
||||
WgpuError::SurfaceError(e) => e.source(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wgpu::RequestDeviceError> for WgpuError {
|
||||
fn from(e: wgpu::RequestDeviceError) -> Self {
|
||||
Self::DeviceError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wgpu::CreateSurfaceError> for WgpuError {
|
||||
fn from(e: wgpu::CreateSurfaceError) -> Self {
|
||||
Self::SurfaceError(e)
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Profiling macro for feature "puffin"
|
||||
|
|
|
@ -554,7 +554,6 @@ impl Renderer {
|
|||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb, // Minspec for wgpu WebGL emulation is WebGL2, so this should always be supported.
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb],
|
||||
});
|
||||
let sampler = self
|
||||
.samplers
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use epaint::mutex::RwLock;
|
||||
|
||||
use tracing::error;
|
||||
use wgpu::{Adapter, Instance, Surface};
|
||||
|
||||
use epaint::mutex::RwLock;
|
||||
|
||||
use crate::{renderer, RenderState, Renderer, SurfaceErrorAction, WgpuConfiguration};
|
||||
|
||||
struct SurfaceState {
|
||||
surface: wgpu::Surface,
|
||||
alpha_mode: wgpu::CompositeAlphaMode,
|
||||
surface: Surface,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
@ -19,12 +19,11 @@ struct SurfaceState {
|
|||
pub struct Painter {
|
||||
configuration: WgpuConfiguration,
|
||||
msaa_samples: u32,
|
||||
support_transparent_backbuffer: bool,
|
||||
depth_format: Option<wgpu::TextureFormat>,
|
||||
depth_texture_view: Option<wgpu::TextureView>,
|
||||
|
||||
instance: wgpu::Instance,
|
||||
adapter: Option<wgpu::Adapter>,
|
||||
instance: Instance,
|
||||
adapter: Option<Adapter>,
|
||||
render_state: Option<RenderState>,
|
||||
surface_state: Option<SurfaceState>,
|
||||
}
|
||||
|
@ -42,21 +41,12 @@ impl Painter {
|
|||
/// [`set_window()`](Self::set_window) once you have
|
||||
/// a [`winit::window::Window`] with a valid `.raw_window_handle()`
|
||||
/// associated.
|
||||
pub fn new(
|
||||
configuration: WgpuConfiguration,
|
||||
msaa_samples: u32,
|
||||
depth_bits: u8,
|
||||
support_transparent_backbuffer: bool,
|
||||
) -> Self {
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends: configuration.backends,
|
||||
dx12_shader_compiler: Default::default(), //
|
||||
});
|
||||
pub fn new(configuration: WgpuConfiguration, msaa_samples: u32, depth_bits: u8) -> Self {
|
||||
let instance = wgpu::Instance::new(configuration.backends);
|
||||
|
||||
Self {
|
||||
configuration,
|
||||
msaa_samples,
|
||||
support_transparent_backbuffer,
|
||||
depth_format: (depth_bits > 0).then_some(wgpu::TextureFormat::Depth32Float),
|
||||
depth_texture_view: None,
|
||||
|
||||
|
@ -76,7 +66,7 @@ impl Painter {
|
|||
|
||||
async fn init_render_state(
|
||||
&self,
|
||||
adapter: &wgpu::Adapter,
|
||||
adapter: &Adapter,
|
||||
target_format: wgpu::TextureFormat,
|
||||
) -> Result<RenderState, wgpu::RequestDeviceError> {
|
||||
adapter
|
||||
|
@ -101,7 +91,7 @@ impl Painter {
|
|||
// will have the same format and so this render state will remain valid.
|
||||
async fn ensure_render_state_for_surface(
|
||||
&mut self,
|
||||
surface: &wgpu::Surface,
|
||||
surface: &Surface,
|
||||
) -> Result<(), wgpu::RequestDeviceError> {
|
||||
if self.adapter.is_none() {
|
||||
self.adapter = self
|
||||
|
@ -117,7 +107,7 @@ impl Painter {
|
|||
match &self.adapter {
|
||||
Some(adapter) => {
|
||||
let swapchain_format = crate::preferred_framebuffer_format(
|
||||
&surface.get_capabilities(adapter).formats,
|
||||
&surface.get_supported_formats(adapter),
|
||||
);
|
||||
let rs = self.init_render_state(adapter, swapchain_format).await?;
|
||||
self.render_state = Some(rs);
|
||||
|
@ -128,23 +118,33 @@ impl Painter {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_surface(
|
||||
surface_state: &SurfaceState,
|
||||
render_state: &RenderState,
|
||||
present_mode: wgpu::PresentMode,
|
||||
) {
|
||||
surface_state.surface.configure(
|
||||
&render_state.device,
|
||||
&wgpu::SurfaceConfiguration {
|
||||
fn configure_surface(&mut self, width_in_pixels: u32, height_in_pixels: u32) {
|
||||
crate::profile_function!();
|
||||
|
||||
let render_state = self
|
||||
.render_state
|
||||
.as_ref()
|
||||
.expect("Render state should exist before surface configuration");
|
||||
let format = render_state.target_format;
|
||||
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: render_state.target_format,
|
||||
width: surface_state.width,
|
||||
height: surface_state.height,
|
||||
present_mode,
|
||||
alpha_mode: surface_state.alpha_mode,
|
||||
view_formats: vec![render_state.target_format],
|
||||
},
|
||||
);
|
||||
format,
|
||||
width: width_in_pixels,
|
||||
height: height_in_pixels,
|
||||
present_mode: self.configuration.present_mode,
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
};
|
||||
|
||||
let surface_state = self
|
||||
.surface_state
|
||||
.as_mut()
|
||||
.expect("Surface state should exist before surface configuration");
|
||||
surface_state
|
||||
.surface
|
||||
.configure(&render_state.device, &config);
|
||||
surface_state.width = width_in_pixels;
|
||||
surface_state.height = height_in_pixels;
|
||||
}
|
||||
|
||||
/// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`]
|
||||
|
@ -177,41 +177,22 @@ impl Painter {
|
|||
pub async unsafe fn set_window(
|
||||
&mut self,
|
||||
window: Option<&winit::window::Window>,
|
||||
) -> Result<(), crate::WgpuError> {
|
||||
) -> Result<(), wgpu::RequestDeviceError> {
|
||||
match window {
|
||||
Some(window) => {
|
||||
let surface = self.instance.create_surface(&window)?;
|
||||
let surface = self.instance.create_surface(&window);
|
||||
|
||||
self.ensure_render_state_for_surface(&surface).await?;
|
||||
|
||||
let alpha_mode = if self.support_transparent_backbuffer {
|
||||
let supported_alpha_modes = surface
|
||||
.get_capabilities(self.adapter.as_ref().unwrap())
|
||||
.alpha_modes;
|
||||
|
||||
// Prefer pre multiplied over post multiplied!
|
||||
if supported_alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) {
|
||||
wgpu::CompositeAlphaMode::PreMultiplied
|
||||
} else if supported_alpha_modes
|
||||
.contains(&wgpu::CompositeAlphaMode::PostMultiplied)
|
||||
{
|
||||
wgpu::CompositeAlphaMode::PostMultiplied
|
||||
} else {
|
||||
tracing::warn!("Transparent window was requested, but the active wgpu surface does not support a `CompositeAlphaMode` with transparency.");
|
||||
wgpu::CompositeAlphaMode::Auto
|
||||
}
|
||||
} else {
|
||||
wgpu::CompositeAlphaMode::Auto
|
||||
};
|
||||
|
||||
let size = window.inner_size();
|
||||
let width = size.width;
|
||||
let height = size.height;
|
||||
self.surface_state = Some(SurfaceState {
|
||||
surface,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
alpha_mode,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
self.resize_and_generate_depth_texture_view(size.width, size.height);
|
||||
self.resize_and_generate_depth_texture_view(width, height);
|
||||
}
|
||||
None => {
|
||||
self.surface_state = None;
|
||||
|
@ -236,17 +217,10 @@ impl Painter {
|
|||
width_in_pixels: u32,
|
||||
height_in_pixels: u32,
|
||||
) {
|
||||
let render_state = self.render_state.as_ref().unwrap();
|
||||
let surface_state = self.surface_state.as_mut().unwrap();
|
||||
|
||||
surface_state.width = width_in_pixels;
|
||||
surface_state.height = height_in_pixels;
|
||||
|
||||
Self::configure_surface(surface_state, render_state, self.configuration.present_mode);
|
||||
|
||||
self.configure_surface(width_in_pixels, height_in_pixels);
|
||||
let device = &self.render_state.as_ref().unwrap().device;
|
||||
self.depth_texture_view = self.depth_format.map(|depth_format| {
|
||||
render_state
|
||||
.device
|
||||
device
|
||||
.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("egui_depth_texture"),
|
||||
size: wgpu::Extent3d {
|
||||
|
@ -260,7 +234,6 @@ impl Painter {
|
|||
format: depth_format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[depth_format],
|
||||
})
|
||||
.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
});
|
||||
|
@ -277,7 +250,7 @@ impl Painter {
|
|||
pub fn paint_and_update_textures(
|
||||
&mut self,
|
||||
pixels_per_point: f32,
|
||||
clear_color: [f32; 4],
|
||||
clear_color: epaint::Rgba,
|
||||
clipped_primitives: &[epaint::ClippedPrimitive],
|
||||
textures_delta: &epaint::textures::TexturesDelta,
|
||||
) {
|
||||
|
@ -291,6 +264,7 @@ impl Painter {
|
|||
Some(rs) => rs,
|
||||
None => return,
|
||||
};
|
||||
let (width, height) = (surface_state.width, surface_state.height);
|
||||
|
||||
let output_frame = {
|
||||
crate::profile_scope!("get_current_texture");
|
||||
|
@ -303,11 +277,7 @@ impl Painter {
|
|||
#[allow(clippy::single_match_else)]
|
||||
Err(e) => match (*self.configuration.on_surface_error)(e) {
|
||||
SurfaceErrorAction::RecreateSurface => {
|
||||
Self::configure_surface(
|
||||
surface_state,
|
||||
render_state,
|
||||
self.configuration.present_mode,
|
||||
);
|
||||
self.configure_surface(width, height);
|
||||
return;
|
||||
}
|
||||
SurfaceErrorAction::SkipFrame => {
|
||||
|
@ -325,7 +295,7 @@ impl Painter {
|
|||
|
||||
// Upload all resources for the GPU.
|
||||
let screen_descriptor = renderer::ScreenDescriptor {
|
||||
size_in_pixels: [surface_state.width, surface_state.height],
|
||||
size_in_pixels: [width, height],
|
||||
pixels_per_point,
|
||||
};
|
||||
|
||||
|
@ -360,10 +330,10 @@ impl Painter {
|
|||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: clear_color[0] as f64,
|
||||
g: clear_color[1] as f64,
|
||||
b: clear_color[2] as f64,
|
||||
a: clear_color[3] as f64,
|
||||
r: clear_color.r() as f64,
|
||||
g: clear_color.g() as f64,
|
||||
b: clear_color.b() as f64,
|
||||
a: clear_color.a() as f64,
|
||||
}),
|
||||
store: true,
|
||||
},
|
||||
|
|
|
@ -5,19 +5,8 @@ All notable changes to the `egui-winit` integration will be noted in this file.
|
|||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.1 - 2023-02-12
|
||||
* Fixed crash when window position is in an invalid state, which could happen e.g. due to changes in monitor size or DPI ([#2722](https://github.com/emilk/egui/issues/2722)).
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08
|
||||
* Fixed persistence of native window position on Windows OS ([#2583](https://github.com/emilk/egui/issues/2583)).
|
||||
* Update to `winit` 0.28, adding support for mac trackpad zoom ([#2654](https://github.com/emilk/egui/pull/2654)).
|
||||
* Remove the `screen_reader` feature. Use the `accesskit` feature flag instead ([#2669](https://github.com/emilk/egui/pull/2669)).
|
||||
* Fix bug where the cursor could get stuck using the wrong icon.
|
||||
|
||||
|
||||
## 0.20.1 - 2022-12-11
|
||||
* Fix [docs.rs](https://docs.rs/egui-winit) build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "egui-winit"
|
||||
version = "0.21.1"
|
||||
version = "0.20.1"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Bindings for using egui with winit"
|
||||
edition = "2021"
|
||||
|
@ -14,7 +14,8 @@ keywords = ["winit", "egui", "gui", "gamedev"]
|
|||
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
# Avoid speech-dispatcher dependencies - see https://docs.rs/crate/egui-winit/0.20.0/builds/695196
|
||||
features = ["document-features"]
|
||||
|
||||
|
||||
[features]
|
||||
|
@ -36,6 +37,9 @@ links = ["webbrowser"]
|
|||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
||||
puffin = ["dep:puffin"]
|
||||
|
||||
## Experimental support for a screen reader.
|
||||
screen_reader = ["tts"]
|
||||
|
||||
## Allow serialization of [`WindowSettings`] using [`serde`](https://docs.rs/serde).
|
||||
serde = ["egui/serde", "dep:serde"]
|
||||
|
||||
|
@ -43,26 +47,29 @@ serde = ["egui/serde", "dep:serde"]
|
|||
wayland = ["winit/wayland"]
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
|
||||
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
|
||||
"tracing",
|
||||
] }
|
||||
instant = { version = "0.1", features = [
|
||||
"wasm-bindgen",
|
||||
] } # We use instant so we can (maybe) compile for web
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
winit = { version = "0.28", default-features = false }
|
||||
winit = { version = "0.27.2", default-features = false }
|
||||
|
||||
#! ### Optional dependencies
|
||||
|
||||
# feature accesskit
|
||||
accesskit_winit = { version = "0.10.0", optional = true }
|
||||
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
# feature accesskit
|
||||
accesskit_winit = { version = "0.7.1", optional = true }
|
||||
|
||||
puffin = { version = "0.14", optional = true }
|
||||
serde = { version = "1.0", optional = true, features = ["derive"] }
|
||||
|
||||
# feature screen_reader
|
||||
tts = { version = "0.24", optional = true }
|
||||
|
||||
webbrowser = { version = "0.8.3", optional = true }
|
||||
|
||||
[target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies]
|
||||
|
@ -70,7 +77,3 @@ smithay-clipboard = { version = "0.6.3", optional = true }
|
|||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
arboard = { version = "3.2", optional = true, default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
# TODO(emilk): this is probably not the right place for specifying native-activity, but we need to do it somewhere for the CI
|
||||
android-activity = { version = "0.4", features = ["native-activity"] }
|
||||
|
|
|
@ -19,12 +19,23 @@ use egui::accesskit;
|
|||
pub use winit;
|
||||
|
||||
pub mod clipboard;
|
||||
pub mod screen_reader;
|
||||
mod window_settings;
|
||||
|
||||
pub use window_settings::WindowSettings;
|
||||
|
||||
use winit::event_loop::EventLoopWindowTarget;
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
use winit::platform::unix::EventLoopWindowTargetExtUnix;
|
||||
|
||||
pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 {
|
||||
window.scale_factor() as f32
|
||||
}
|
||||
|
@ -59,12 +70,12 @@ pub struct State {
|
|||
egui_input: egui::RawInput,
|
||||
pointer_pos_in_points: Option<egui::Pos2>,
|
||||
any_pointer_button_down: bool,
|
||||
current_cursor_icon: Option<egui::CursorIcon>,
|
||||
|
||||
current_cursor_icon: egui::CursorIcon,
|
||||
/// What egui uses.
|
||||
current_pixels_per_point: f32,
|
||||
|
||||
clipboard: clipboard::Clipboard,
|
||||
screen_reader: screen_reader::ScreenReader,
|
||||
|
||||
/// If `true`, mouse inputs will be treated as touches.
|
||||
/// Useful for debugging touch support in egui.
|
||||
|
@ -100,10 +111,11 @@ impl State {
|
|||
egui_input,
|
||||
pointer_pos_in_points: None,
|
||||
any_pointer_button_down: false,
|
||||
current_cursor_icon: None,
|
||||
current_cursor_icon: egui::CursorIcon::Default,
|
||||
current_pixels_per_point: 1.0,
|
||||
|
||||
clipboard: clipboard::Clipboard::new(wayland_display),
|
||||
screen_reader: screen_reader::ScreenReader::default(),
|
||||
|
||||
simulate_touch_screen: false,
|
||||
pointer_touch_id: None,
|
||||
|
@ -368,9 +380,8 @@ impl State {
|
|||
consumed: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Things that may require repaint:
|
||||
WindowEvent::CloseRequested
|
||||
WindowEvent::AxisMotion { .. }
|
||||
| WindowEvent::CloseRequested
|
||||
| WindowEvent::CursorEntered { .. }
|
||||
| WindowEvent::Destroyed
|
||||
| WindowEvent::Occluded(_)
|
||||
|
@ -380,26 +391,10 @@ impl State {
|
|||
repaint: true,
|
||||
consumed: false,
|
||||
},
|
||||
|
||||
// Things we completely ignore:
|
||||
WindowEvent::AxisMotion { .. }
|
||||
| WindowEvent::Moved(_)
|
||||
| WindowEvent::SmartMagnify { .. }
|
||||
| WindowEvent::TouchpadRotate { .. } => EventResponse {
|
||||
repaint: false,
|
||||
WindowEvent::Moved(_) => EventResponse {
|
||||
repaint: false, // moving a window doesn't warrant a repaint
|
||||
consumed: false,
|
||||
},
|
||||
|
||||
WindowEvent::TouchpadMagnify { delta, .. } => {
|
||||
// Positive delta values indicate magnification (zooming in).
|
||||
// Negative delta values indicate shrinking (zooming out).
|
||||
let zoom_factor = (*delta as f32).exp();
|
||||
self.egui_input.events.push(egui::Event::Zoom(zoom_factor));
|
||||
EventResponse {
|
||||
repaint: true,
|
||||
consumed: egui_ctx.wants_pointer_input(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -620,6 +615,11 @@ impl State {
|
|||
egui_ctx: &egui::Context,
|
||||
platform_output: egui::PlatformOutput,
|
||||
) {
|
||||
if egui_ctx.options(|o| o.screen_reader) {
|
||||
self.screen_reader
|
||||
.speak(&platform_output.events_description());
|
||||
}
|
||||
|
||||
let egui::PlatformOutput {
|
||||
cursor_icon,
|
||||
open_url,
|
||||
|
@ -655,25 +655,22 @@ impl State {
|
|||
}
|
||||
|
||||
fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) {
|
||||
if self.current_cursor_icon == Some(cursor_icon) {
|
||||
// Prevent flickering near frame boundary when Windows OS tries to control cursor icon for window resizing.
|
||||
// On other platforms: just early-out to save CPU.
|
||||
if self.current_cursor_icon == cursor_icon {
|
||||
return;
|
||||
}
|
||||
self.current_cursor_icon = cursor_icon;
|
||||
|
||||
if let Some(cursor_icon) = translate_cursor(cursor_icon) {
|
||||
window.set_cursor_visible(true);
|
||||
|
||||
let is_pointer_in_window = self.pointer_pos_in_points.is_some();
|
||||
if is_pointer_in_window {
|
||||
self.current_cursor_icon = Some(cursor_icon);
|
||||
|
||||
if let Some(winit_cursor_icon) = translate_cursor(cursor_icon) {
|
||||
window.set_cursor_visible(true);
|
||||
window.set_cursor_icon(winit_cursor_icon);
|
||||
} else {
|
||||
window.set_cursor_visible(false);
|
||||
window.set_cursor_icon(cursor_icon);
|
||||
}
|
||||
} else {
|
||||
// Remember to set the cursor again once the cursor returns to the screen:
|
||||
self.current_cursor_icon = None;
|
||||
window.set_cursor_visible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -882,7 +879,6 @@ fn wayland_display<T>(_event_loop: &EventLoopWindowTarget<T>) -> Option<*mut c_v
|
|||
target_os = "openbsd"
|
||||
))]
|
||||
{
|
||||
use winit::platform::wayland::EventLoopWindowTargetExtWayland as _;
|
||||
return _event_loop.wayland_display();
|
||||
}
|
||||
|
||||
|
|
49
crates/egui-winit/src/screen_reader.rs
Normal file
49
crates/egui-winit/src/screen_reader.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
pub struct ScreenReader {
|
||||
#[cfg(feature = "tts")]
|
||||
tts: Option<tts::Tts>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tts"))]
|
||||
#[allow(clippy::derivable_impls)] // False positive
|
||||
impl Default for ScreenReader {
|
||||
fn default() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tts")]
|
||||
impl Default for ScreenReader {
|
||||
fn default() -> Self {
|
||||
let tts = match tts::Tts::default() {
|
||||
Ok(screen_reader) => {
|
||||
tracing::debug!("Initialized screen reader.");
|
||||
Some(screen_reader)
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!("Failed to load screen reader: {}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
Self { tts }
|
||||
}
|
||||
}
|
||||
|
||||
impl ScreenReader {
|
||||
#[cfg(not(feature = "tts"))]
|
||||
#[allow(clippy::unused_self)]
|
||||
pub fn speak(&mut self, _text: &str) {}
|
||||
|
||||
#[cfg(feature = "tts")]
|
||||
pub fn speak(&mut self, text: &str) {
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
if let Some(tts) = &mut self.tts {
|
||||
tracing::debug!("Speaking: {:?}", text);
|
||||
let interrupt = true;
|
||||
if let Err(err) = tts.speak(text, interrupt) {
|
||||
tracing::warn!("Failed to read: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,10 +42,6 @@ impl WindowSettings {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn inner_size_points(&self) -> Option<egui::Vec2> {
|
||||
self.inner_size_points
|
||||
}
|
||||
|
||||
pub fn initialize_window(
|
||||
&self,
|
||||
mut window: winit::window::WindowBuilder,
|
||||
|
@ -53,14 +49,17 @@ impl WindowSettings {
|
|||
// If the app last ran on two monitors and only one is now connected, then
|
||||
// the given position is invalid.
|
||||
// If this happens on Mac, the window is clamped into valid area.
|
||||
// If this happens on Windows, the clamping behavior is managed by the function
|
||||
// clamp_window_to_sane_position.
|
||||
// If this happens on Windows, the window is hidden and very difficult to find.
|
||||
// So we don't restore window positions on Windows.
|
||||
let try_restore_position = !cfg!(target_os = "windows");
|
||||
if try_restore_position {
|
||||
if let Some(pos) = self.position {
|
||||
window = window.with_position(winit::dpi::PhysicalPosition {
|
||||
x: pos.x as f64,
|
||||
y: pos.y as f64,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(inner_size_points) = self.inner_size_points {
|
||||
window
|
||||
|
@ -87,58 +86,4 @@ impl WindowSettings {
|
|||
*size = size.at_most(max_size);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp_window_to_sane_position<E>(
|
||||
&mut self,
|
||||
event_loop: &winit::event_loop::EventLoopWindowTarget<E>,
|
||||
) {
|
||||
if let (Some(position), Some(inner_size_points)) =
|
||||
(&mut self.position, &self.inner_size_points)
|
||||
{
|
||||
let monitors = event_loop.available_monitors();
|
||||
// default to primary monitor, in case the correct monitor was disconnected.
|
||||
let mut active_monitor = if let Some(active_monitor) = event_loop
|
||||
.primary_monitor()
|
||||
.or_else(|| event_loop.available_monitors().next())
|
||||
{
|
||||
active_monitor
|
||||
} else {
|
||||
return; // no monitors 🤷
|
||||
};
|
||||
for monitor in monitors {
|
||||
let monitor_x_range = (monitor.position().x - inner_size_points.x as i32)
|
||||
..(monitor.position().x + monitor.size().width as i32);
|
||||
let monitor_y_range = (monitor.position().y - inner_size_points.y as i32)
|
||||
..(monitor.position().y + monitor.size().height as i32);
|
||||
|
||||
if monitor_x_range.contains(&(position.x as i32))
|
||||
&& monitor_y_range.contains(&(position.y as i32))
|
||||
{
|
||||
active_monitor = monitor;
|
||||
}
|
||||
}
|
||||
|
||||
let mut inner_size_pixels = *inner_size_points * (active_monitor.scale_factor() as f32);
|
||||
// Add size of title bar. This is 32 px by default in Win 10/11.
|
||||
if cfg!(target_os = "windows") {
|
||||
inner_size_pixels +=
|
||||
egui::Vec2::new(0.0, 32.0 * active_monitor.scale_factor() as f32);
|
||||
}
|
||||
let monitor_position = egui::Pos2::new(
|
||||
active_monitor.position().x as f32,
|
||||
active_monitor.position().y as f32,
|
||||
);
|
||||
let monitor_size = egui::Vec2::new(
|
||||
active_monitor.size().width as f32,
|
||||
active_monitor.size().height as f32,
|
||||
);
|
||||
|
||||
// Window size cannot be negative or the subsequent `clamp` will panic.
|
||||
let window_size = (monitor_size - inner_size_pixels).max(egui::Vec2::ZERO);
|
||||
// To get the maximum position, we get the rightmost corner of the display, then
|
||||
// subtract the size of the window to get the bottom right most value window.position
|
||||
// can have.
|
||||
*position = position.clamp(monitor_position, monitor_position + window_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "egui"
|
||||
version = "0.21.0"
|
||||
version = "0.20.1"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "An easy-to-use immediate mode GUI that runs on both web and native"
|
||||
edition = "2021"
|
||||
|
@ -54,12 +54,8 @@ persistence = ["serde", "epaint/serde", "ron"]
|
|||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||
serde = ["dep:serde", "epaint/serde", "accesskit?/serde"]
|
||||
|
||||
## Change Vertex layout to be compatible with unity
|
||||
unity = ["epaint/unity"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
epaint = { version = "0.21.0", path = "../epaint", default-features = false }
|
||||
epaint = { version = "0.20.0", path = "../epaint", default-features = false }
|
||||
|
||||
ahash = { version = "0.8.1", default-features = false, features = [
|
||||
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
|
||||
|
@ -70,7 +66,7 @@ nohash-hasher = "0.2"
|
|||
#! ### Optional dependencies
|
||||
## Exposes detailed accessibility implementation required by platform
|
||||
## accessibility APIs. Also requires support in the egui integration.
|
||||
accesskit = { version = "0.9.0", optional = true }
|
||||
accesskit = { version = "0.8.1", optional = true }
|
||||
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
|
|
@ -9,7 +9,6 @@ pub(crate) struct AnimationManager {
|
|||
#[derive(Clone, Debug)]
|
||||
struct BoolAnim {
|
||||
value: bool,
|
||||
|
||||
/// when did `value` last toggle?
|
||||
toggle_time: f64,
|
||||
}
|
||||
|
@ -17,9 +16,7 @@ struct BoolAnim {
|
|||
#[derive(Clone, Debug)]
|
||||
struct ValueAnim {
|
||||
from_value: f32,
|
||||
|
||||
to_value: f32,
|
||||
|
||||
/// when did `value` last toggle?
|
||||
toggle_time: f64,
|
||||
}
|
||||
|
|
|
@ -9,10 +9,8 @@ use crate::*;
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub(crate) struct State {
|
||||
/// Last known pos of the pivot
|
||||
pub pivot_pos: Pos2,
|
||||
|
||||
pub pivot: Align2,
|
||||
/// Last known pos
|
||||
pub pos: Pos2,
|
||||
|
||||
/// Last know size. Used for catching clicks.
|
||||
pub size: Vec2,
|
||||
|
@ -23,22 +21,8 @@ pub(crate) struct State {
|
|||
}
|
||||
|
||||
impl State {
|
||||
pub fn left_top_pos(&self) -> Pos2 {
|
||||
pos2(
|
||||
self.pivot_pos.x - self.pivot.x().to_factor() * self.size.x,
|
||||
self.pivot_pos.y - self.pivot.y().to_factor() * self.size.y,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_left_top_pos(&mut self, pos: Pos2) {
|
||||
self.pivot_pos = pos2(
|
||||
pos.x + self.pivot.x().to_factor() * self.size.x,
|
||||
pos.y + self.pivot.y().to_factor() * self.size.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn rect(&self) -> Rect {
|
||||
Rect::from_min_size(self.left_top_pos(), self.size)
|
||||
Rect::from_min_size(self.pos, self.size)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,8 +77,8 @@ impl Area {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn layer(&self) -> LayerId {
|
||||
LayerId::new(self.order, self.id)
|
||||
pub fn layer(&self) -> AreaLayerId {
|
||||
AreaLayerId::new(self.order, self.id)
|
||||
}
|
||||
|
||||
/// If false, no content responds to click
|
||||
|
@ -203,7 +187,7 @@ impl Area {
|
|||
}
|
||||
|
||||
pub(crate) struct Prepared {
|
||||
layer_id: LayerId,
|
||||
layer_id: AreaLayerId,
|
||||
state: State,
|
||||
move_response: Response,
|
||||
enabled: bool,
|
||||
|
@ -245,7 +229,7 @@ impl Area {
|
|||
constrain,
|
||||
} = self;
|
||||
|
||||
let layer_id = LayerId::new(order, id);
|
||||
let layer_id = AreaLayerId::new(order, id);
|
||||
|
||||
let state = ctx.memory(|mem| mem.areas.get(id).copied());
|
||||
let is_new = state.is_none();
|
||||
|
@ -253,19 +237,21 @@ impl Area {
|
|||
ctx.request_repaint(); // if we don't know the previous size we are likely drawing the area in the wrong place
|
||||
}
|
||||
let mut state = state.unwrap_or_else(|| State {
|
||||
pivot_pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)),
|
||||
pivot,
|
||||
pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)),
|
||||
size: Vec2::ZERO,
|
||||
interactable,
|
||||
});
|
||||
state.pivot_pos = new_pos.unwrap_or(state.pivot_pos);
|
||||
state.pos = new_pos.unwrap_or(state.pos);
|
||||
state.interactable = interactable;
|
||||
|
||||
if pivot != Align2::LEFT_TOP {
|
||||
state.pos.x -= pivot.x().to_factor() * state.size.x;
|
||||
state.pos.y -= pivot.y().to_factor() * state.size.y;
|
||||
}
|
||||
|
||||
if let Some((anchor, offset)) = anchor {
|
||||
let screen = ctx.available_rect();
|
||||
state.set_left_top_pos(
|
||||
anchor.align_size_within_rect(state.size, screen).left_top() + offset,
|
||||
);
|
||||
state.pos = anchor.align_size_within_rect(state.size, screen).min + offset;
|
||||
}
|
||||
|
||||
// interact right away to prevent frame-delay
|
||||
|
@ -282,7 +268,7 @@ impl Area {
|
|||
let move_response = ctx.interact(
|
||||
Rect::EVERYTHING,
|
||||
ctx.style().spacing.item_spacing,
|
||||
layer_id,
|
||||
layers::ZLayer::from_area_layer(layer_id),
|
||||
interact_id,
|
||||
state.rect(),
|
||||
sense,
|
||||
|
@ -292,13 +278,12 @@ impl Area {
|
|||
// Important check - don't try to move e.g. a combobox popup!
|
||||
if movable {
|
||||
if move_response.dragged() {
|
||||
state.pivot_pos += ctx.input(|i| i.pointer.delta());
|
||||
state.pos += ctx.input(|i| i.pointer.delta());
|
||||
}
|
||||
|
||||
state.set_left_top_pos(
|
||||
ctx.constrain_window_rect_to_area(state.rect(), drag_bounds)
|
||||
.min,
|
||||
);
|
||||
state.pos = ctx
|
||||
.constrain_window_rect_to_area(state.rect(), drag_bounds)
|
||||
.min;
|
||||
}
|
||||
|
||||
if (move_response.dragged() || move_response.clicked())
|
||||
|
@ -312,13 +297,12 @@ impl Area {
|
|||
move_response
|
||||
};
|
||||
|
||||
state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos()));
|
||||
state.pos = ctx.round_pos_to_pixels(state.pos);
|
||||
|
||||
if constrain {
|
||||
state.set_left_top_pos(
|
||||
ctx.constrain_window_rect_to_area(state.rect(), drag_bounds)
|
||||
.left_top(),
|
||||
);
|
||||
state.pos = ctx
|
||||
.constrain_window_rect_to_area(state.rect(), drag_bounds)
|
||||
.min;
|
||||
}
|
||||
|
||||
Prepared {
|
||||
|
@ -344,7 +328,7 @@ impl Area {
|
|||
return;
|
||||
}
|
||||
|
||||
let layer_id = LayerId::new(self.order, self.id);
|
||||
let layer_id = AreaLayerId::new(self.order, self.id);
|
||||
let area_rect = ctx.memory(|mem| mem.areas.get(self.id).map(|area| area.rect()));
|
||||
if let Some(area_rect) = area_rect {
|
||||
let clip_rect = ctx.available_rect();
|
||||
|
@ -390,16 +374,14 @@ impl Prepared {
|
|||
};
|
||||
|
||||
let max_rect = Rect::from_min_max(
|
||||
self.state.left_top_pos(),
|
||||
bounds
|
||||
.max
|
||||
.at_least(self.state.left_top_pos() + Vec2::splat(32.0)),
|
||||
self.state.pos,
|
||||
bounds.max.at_least(self.state.pos + Vec2::splat(32.0)),
|
||||
);
|
||||
|
||||
let shadow_radius = ctx.style().visuals.window_shadow.extrusion; // hacky
|
||||
let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius);
|
||||
|
||||
let clip_rect = Rect::from_min_max(self.state.left_top_pos(), bounds.max)
|
||||
let clip_rect = Rect::from_min_max(self.state.pos, bounds.max)
|
||||
.expand(clip_rect_margin)
|
||||
.intersect(bounds);
|
||||
|
||||
|
@ -434,7 +416,7 @@ impl Prepared {
|
|||
}
|
||||
}
|
||||
|
||||
fn pointer_pressed_on_area(ctx: &Context, layer_id: LayerId) -> bool {
|
||||
fn pointer_pressed_on_area(ctx: &Context, layer_id: AreaLayerId) -> bool {
|
||||
if let Some(pointer_pos) = ctx.pointer_interact_pos() {
|
||||
let any_pressed = ctx.input(|i| i.pointer.any_pressed());
|
||||
any_pressed && ctx.layer_id_at(pointer_pos) == Some(layer_id)
|
||||
|
|
|
@ -112,7 +112,10 @@ impl CollapsingState {
|
|||
response.rect.center().y,
|
||||
));
|
||||
let openness = self.openness(ui.ctx());
|
||||
let small_icon_response = response.clone().with_new_rect(icon_rect);
|
||||
let small_icon_response = Response {
|
||||
rect: icon_rect,
|
||||
..response.clone()
|
||||
};
|
||||
icon_fn(ui, openness, &small_icon_response);
|
||||
response
|
||||
}
|
||||
|
@ -141,10 +144,9 @@ impl CollapsingState {
|
|||
add_header: impl FnOnce(&mut Ui) -> HeaderRet,
|
||||
) -> HeaderResponse<'_, HeaderRet> {
|
||||
let header_response = ui.horizontal(|ui| {
|
||||
let prev_item_spacing = ui.spacing_mut().item_spacing;
|
||||
ui.spacing_mut().item_spacing.x = 0.0; // the toggler button uses the full indent width
|
||||
let collapser = self.show_default_button_indented(ui);
|
||||
ui.spacing_mut().item_spacing = prev_item_spacing;
|
||||
ui.spacing_mut().item_spacing.x = ui.spacing_mut().icon_spacing; // Restore spacing
|
||||
(collapser, add_header(ui))
|
||||
});
|
||||
HeaderResponse {
|
||||
|
@ -574,7 +576,10 @@ impl CollapsingHeader {
|
|||
header_response.rect.left() + ui.spacing().indent / 2.0,
|
||||
header_response.rect.center().y,
|
||||
));
|
||||
let icon_response = header_response.clone().with_new_rect(icon_rect);
|
||||
let icon_response = Response {
|
||||
rect: icon_rect,
|
||||
..header_response.clone()
|
||||
};
|
||||
if let Some(icon) = icon {
|
||||
icon(ui, openness, &icon_response);
|
||||
} else {
|
||||
|
|
|
@ -19,16 +19,11 @@ use epaint::*;
|
|||
pub struct Frame {
|
||||
/// Margin within the painted frame.
|
||||
pub inner_margin: Margin,
|
||||
|
||||
/// Margin outside the painted frame.
|
||||
pub outer_margin: Margin,
|
||||
|
||||
pub rounding: Rounding,
|
||||
|
||||
pub shadow: Shadow,
|
||||
|
||||
pub fill: Color32,
|
||||
|
||||
pub stroke: Stroke,
|
||||
}
|
||||
|
||||
|
|
|
@ -237,7 +237,7 @@ impl SidePanel {
|
|||
let we_are_on_top = ui
|
||||
.ctx()
|
||||
.layer_id_at(pointer)
|
||||
.map_or(true, |top_layer_id| top_layer_id == ui.layer_id());
|
||||
.map_or(true, |top_layer_id| top_layer_id == ui.area_layer_id());
|
||||
|
||||
let resize_x = side.opposite().side_x(panel_rect);
|
||||
let mouse_over_resize_line = we_are_on_top
|
||||
|
@ -297,21 +297,22 @@ impl SidePanel {
|
|||
|
||||
{
|
||||
let stroke = if is_resizing {
|
||||
ui.style().visuals.widgets.active.fg_stroke // highly visible
|
||||
ui.style().visuals.widgets.active.bg_stroke
|
||||
} else if resize_hover {
|
||||
ui.style().visuals.widgets.hovered.fg_stroke // highly visible
|
||||
ui.style().visuals.widgets.hovered.bg_stroke
|
||||
} else if show_separator_line {
|
||||
// TOOD(emilk): distinguish resizable from non-resizable
|
||||
ui.style().visuals.widgets.noninteractive.bg_stroke // dim
|
||||
ui.style().visuals.widgets.noninteractive.bg_stroke
|
||||
} else {
|
||||
Stroke::NONE
|
||||
};
|
||||
// TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done
|
||||
// In the meantime: nudge the line so its inside the panel, so it won't be covered by neighboring panel
|
||||
// (hence the shrink).
|
||||
let resize_x = side.opposite().side_x(rect.shrink(1.0));
|
||||
let resize_x = side.opposite().side_x(rect);
|
||||
let resize_x = ui.painter().round_to_pixel(resize_x);
|
||||
ui.painter().vline(resize_x, rect.y_range(), stroke);
|
||||
ui.painter().clone().with_z(layers::ZOrder::FRONT).vline(
|
||||
resize_x,
|
||||
rect.y_range(),
|
||||
stroke,
|
||||
);
|
||||
}
|
||||
|
||||
inner_response
|
||||
|
@ -332,7 +333,7 @@ impl SidePanel {
|
|||
ctx: &Context,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> InnerResponse<R> {
|
||||
let layer_id = LayerId::background();
|
||||
let layer_id = AreaLayerId::background();
|
||||
let side = self.side;
|
||||
let available_rect = ctx.available_rect();
|
||||
let clip_rect = ctx.screen_rect();
|
||||
|
@ -688,7 +689,7 @@ impl TopBottomPanel {
|
|||
let we_are_on_top = ui
|
||||
.ctx()
|
||||
.layer_id_at(pointer)
|
||||
.map_or(true, |top_layer_id| top_layer_id == ui.layer_id());
|
||||
.map_or(true, |top_layer_id| top_layer_id == ui.area_layer_id());
|
||||
|
||||
let resize_y = side.opposite().side_y(panel_rect);
|
||||
let mouse_over_resize_line = we_are_on_top
|
||||
|
@ -748,21 +749,22 @@ impl TopBottomPanel {
|
|||
|
||||
{
|
||||
let stroke = if is_resizing {
|
||||
ui.style().visuals.widgets.active.fg_stroke // highly visible
|
||||
ui.style().visuals.widgets.active.bg_stroke
|
||||
} else if resize_hover {
|
||||
ui.style().visuals.widgets.hovered.fg_stroke // highly visible
|
||||
ui.style().visuals.widgets.hovered.bg_stroke
|
||||
} else if show_separator_line {
|
||||
// TOOD(emilk): distinguish resizable from non-resizable
|
||||
ui.style().visuals.widgets.noninteractive.bg_stroke // dim
|
||||
ui.style().visuals.widgets.noninteractive.bg_stroke
|
||||
} else {
|
||||
Stroke::NONE
|
||||
};
|
||||
// TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done
|
||||
// In the meantime: nudge the line so its inside the panel, so it won't be covered by neighboring panel
|
||||
// (hence the shrink).
|
||||
let resize_y = side.opposite().side_y(rect.shrink(1.0));
|
||||
let resize_y = side.opposite().side_y(rect);
|
||||
let resize_y = ui.painter().round_to_pixel(resize_y);
|
||||
ui.painter().hline(rect.x_range(), resize_y, stroke);
|
||||
ui.painter().clone().with_z(layers::ZOrder::FRONT).hline(
|
||||
rect.x_range(),
|
||||
resize_y,
|
||||
stroke,
|
||||
);
|
||||
}
|
||||
|
||||
inner_response
|
||||
|
@ -783,7 +785,7 @@ impl TopBottomPanel {
|
|||
ctx: &Context,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> InnerResponse<R> {
|
||||
let layer_id = LayerId::background();
|
||||
let layer_id = AreaLayerId::background();
|
||||
let available_rect = ctx.available_rect();
|
||||
let side = self.side;
|
||||
|
||||
|
@ -1041,7 +1043,7 @@ impl CentralPanel {
|
|||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> InnerResponse<R> {
|
||||
let available_rect = ctx.available_rect();
|
||||
let layer_id = LayerId::background();
|
||||
let layer_id = AreaLayerId::background();
|
||||
let id = Id::new("central_panel");
|
||||
|
||||
let clip_rect = ctx.screen_rect();
|
||||
|
|
|
@ -260,9 +260,8 @@ fn show_tooltip_area_dyn<'c, R>(
|
|||
Area::new(area_id)
|
||||
.order(Order::Tooltip)
|
||||
.fixed_pos(window_pos)
|
||||
.constrain(true)
|
||||
.interactable(false)
|
||||
.drag_bounds(ctx.screen_rect())
|
||||
.drag_bounds(Rect::EVERYTHING) // disable clip rect
|
||||
.show(ctx, |ui| {
|
||||
Frame::popup(&ctx.style())
|
||||
.show(ui, |ui| {
|
||||
|
@ -280,7 +279,7 @@ pub fn was_tooltip_open_last_frame(ctx: &Context, tooltip_id: Id) -> bool {
|
|||
for (count, (individual_id, _size)) in &state.individual_ids_and_sizes {
|
||||
if *individual_id == tooltip_id {
|
||||
let area_id = common_id.with(count);
|
||||
let layer_id = LayerId::new(Order::Tooltip, area_id);
|
||||
let layer_id = AreaLayerId::new(Order::Tooltip, area_id);
|
||||
if ctx.memory(|mem| mem.areas.visible_last_frame(&layer_id)) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -426,34 +426,15 @@ impl ScrollArea {
|
|||
|
||||
let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size);
|
||||
let mut content_ui = ui.child_ui(content_max_rect, *ui.layout());
|
||||
|
||||
{
|
||||
// Clip the content, but only when we really need to:
|
||||
let clip_rect_margin = ui.visuals().clip_rect_margin;
|
||||
let scroll_bar_inner_margin = ui.spacing().scroll_bar_inner_margin;
|
||||
let mut content_clip_rect = ui.clip_rect();
|
||||
for d in 0..2 {
|
||||
if has_bar[d] {
|
||||
if state.content_is_too_large[d] {
|
||||
content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin;
|
||||
content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin;
|
||||
}
|
||||
|
||||
if state.show_scroll[d] {
|
||||
// Make sure content doesn't cover scroll bars
|
||||
let tiny_gap = 1.0;
|
||||
content_clip_rect.max[1 - d] =
|
||||
inner_rect.max[1 - d] + scroll_bar_inner_margin - tiny_gap;
|
||||
}
|
||||
} else {
|
||||
let mut content_clip_rect = inner_rect.expand(ui.visuals().clip_rect_margin);
|
||||
content_clip_rect = content_clip_rect.intersect(ui.clip_rect());
|
||||
// Nice handling of forced resizing beyond the possible:
|
||||
for d in 0..2 {
|
||||
if !has_bar[d] {
|
||||
content_clip_rect.max[d] = ui.clip_rect().max[d] - current_bar_use[d];
|
||||
}
|
||||
}
|
||||
// Make sure we din't accidentally expand the clip rect
|
||||
content_clip_rect = content_clip_rect.intersect(ui.clip_rect());
|
||||
content_ui.set_clip_rect(content_clip_rect);
|
||||
}
|
||||
|
||||
let viewport = Rect::from_min_size(Pos2::ZERO + state.offset, inner_size);
|
||||
|
||||
|
@ -841,7 +822,7 @@ impl Prepared {
|
|||
),
|
||||
)
|
||||
};
|
||||
let min_handle_size = ui.spacing().scroll_handle_min_length;
|
||||
let min_handle_size = ui.spacing().scroll_bar_width;
|
||||
if handle_rect.size()[d] < min_handle_size {
|
||||
handle_rect = Rect::from_center_size(
|
||||
handle_rect.center(),
|
||||
|
|
|
@ -438,12 +438,9 @@ impl<'open> Window<'open> {
|
|||
content_inner
|
||||
};
|
||||
|
||||
{
|
||||
let pos = ctx
|
||||
area.state_mut().pos = ctx
|
||||
.constrain_window_rect_to_area(area.state().rect(), area.drag_bounds())
|
||||
.left_top();
|
||||
area.state_mut().set_left_top_pos(pos);
|
||||
}
|
||||
.min;
|
||||
|
||||
let full_response = area.end(ctx, area_content_ui);
|
||||
|
||||
|
@ -513,7 +510,7 @@ impl PossibleInteractions {
|
|||
/// Either a move or resize
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct WindowInteraction {
|
||||
pub(crate) area_layer_id: LayerId,
|
||||
pub(crate) area_layer_id: AreaLayerId,
|
||||
pub(crate) start_rect: Rect,
|
||||
pub(crate) left: bool,
|
||||
pub(crate) right: bool,
|
||||
|
@ -543,7 +540,7 @@ fn interact(
|
|||
window_interaction: WindowInteraction,
|
||||
ctx: &Context,
|
||||
margins: Vec2,
|
||||
area_layer_id: LayerId,
|
||||
area_layer_id: AreaLayerId,
|
||||
area: &mut area::Prepared,
|
||||
resize_id: Id,
|
||||
) -> Option<WindowInteraction> {
|
||||
|
@ -553,7 +550,7 @@ fn interact(
|
|||
let new_rect = ctx.constrain_window_rect_to_area(new_rect, area.drag_bounds());
|
||||
|
||||
// TODO(emilk): add this to a Window state instead as a command "move here next frame"
|
||||
area.state_mut().set_left_top_pos(new_rect.left_top());
|
||||
area.state_mut().pos = new_rect.min;
|
||||
|
||||
if window_interaction.is_resize() {
|
||||
if let Some(mut state) = resize::State::load(ctx, resize_id) {
|
||||
|
@ -610,7 +607,7 @@ fn move_and_resize_window(ctx: &Context, window_interaction: &WindowInteraction)
|
|||
fn window_interaction(
|
||||
ctx: &Context,
|
||||
possible: PossibleInteractions,
|
||||
area_layer_id: LayerId,
|
||||
area_layer_id: AreaLayerId,
|
||||
id: Id,
|
||||
rect: Rect,
|
||||
) -> Option<WindowInteraction> {
|
||||
|
@ -652,7 +649,7 @@ fn window_interaction(
|
|||
fn resize_hover(
|
||||
ctx: &Context,
|
||||
possible: PossibleInteractions,
|
||||
area_layer_id: LayerId,
|
||||
area_layer_id: AreaLayerId,
|
||||
rect: Rect,
|
||||
) -> Option<WindowInteraction> {
|
||||
let pointer = ctx.input(|i| i.pointer.interact_pos())?;
|
||||
|
|
|
@ -64,15 +64,12 @@ struct ContextImpl {
|
|||
requested_repaint_last_frame: bool,
|
||||
|
||||
/// Written to during the frame.
|
||||
layer_rects_this_frame: ahash::HashMap<LayerId, Vec<(Id, Rect)>>,
|
||||
|
||||
layer_rects_this_frame: ahash::HashMap<AreaLayerId, Vec<(Id, layers::ZOrder, Rect)>>,
|
||||
/// Read
|
||||
layer_rects_prev_frame: ahash::HashMap<LayerId, Vec<(Id, Rect)>>,
|
||||
layer_rects_prev_frame: ahash::HashMap<AreaLayerId, Vec<(Id, layers::ZOrder, Rect)>>,
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
is_accesskit_enabled: bool,
|
||||
#[cfg(feature = "accesskit")]
|
||||
accesskit_node_classes: accesskit::NodeClassSet,
|
||||
}
|
||||
|
||||
impl ContextImpl {
|
||||
|
@ -104,10 +101,9 @@ impl ContextImpl {
|
|||
// Ensure we register the background area so panels and background ui can catch clicks:
|
||||
let screen_rect = self.input.screen_rect();
|
||||
self.memory.areas.set_state(
|
||||
LayerId::background(),
|
||||
AreaLayerId::background(),
|
||||
containers::area::State {
|
||||
pivot_pos: screen_rect.left_top(),
|
||||
pivot: Align2::LEFT_TOP,
|
||||
pos: screen_rect.min,
|
||||
size: screen_rect.size(),
|
||||
interactable: true,
|
||||
},
|
||||
|
@ -117,14 +113,17 @@ impl ContextImpl {
|
|||
if self.is_accesskit_enabled {
|
||||
use crate::frame_state::AccessKitFrameState;
|
||||
let id = crate::accesskit_root_id();
|
||||
let mut builder = accesskit::NodeBuilder::new(accesskit::Role::Window);
|
||||
builder.set_transform(accesskit::Affine::scale(
|
||||
self.input.pixels_per_point().into(),
|
||||
));
|
||||
let mut node_builders = IdMap::default();
|
||||
node_builders.insert(id, builder);
|
||||
let node = Box::new(accesskit::Node {
|
||||
role: accesskit::Role::Window,
|
||||
transform: Some(
|
||||
accesskit::kurbo::Affine::scale(self.input.pixels_per_point().into()).into(),
|
||||
),
|
||||
..Default::default()
|
||||
});
|
||||
let mut nodes = IdMap::default();
|
||||
nodes.insert(id, node);
|
||||
self.frame_state.accesskit_state = Some(AccessKitFrameState {
|
||||
node_builders,
|
||||
nodes,
|
||||
parent_stack: vec![id],
|
||||
});
|
||||
}
|
||||
|
@ -157,16 +156,16 @@ impl ContextImpl {
|
|||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::NodeBuilder {
|
||||
fn accesskit_node(&mut self, id: Id) -> &mut accesskit::Node {
|
||||
let state = self.frame_state.accesskit_state.as_mut().unwrap();
|
||||
let builders = &mut state.node_builders;
|
||||
if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) {
|
||||
let nodes = &mut state.nodes;
|
||||
if let std::collections::hash_map::Entry::Vacant(entry) = nodes.entry(id) {
|
||||
entry.insert(Default::default());
|
||||
let parent_id = state.parent_stack.last().unwrap();
|
||||
let parent_builder = builders.get_mut(parent_id).unwrap();
|
||||
parent_builder.push_child(id.accesskit_id());
|
||||
let parent = nodes.get_mut(parent_id).unwrap();
|
||||
parent.children.push(id.accesskit_id());
|
||||
}
|
||||
builders.get_mut(&id).unwrap()
|
||||
nodes.get_mut(&id).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -535,7 +534,7 @@ impl Context {
|
|||
&self,
|
||||
clip_rect: Rect,
|
||||
item_spacing: Vec2,
|
||||
layer_id: LayerId,
|
||||
layer: layers::ZLayer,
|
||||
id: Id,
|
||||
rect: Rect,
|
||||
sense: Sense,
|
||||
|
@ -552,13 +551,13 @@ impl Context {
|
|||
|
||||
// Respect clip rectangle when interacting
|
||||
let interact_rect = clip_rect.intersect(interact_rect);
|
||||
let mut hovered = self.rect_contains_pointer(layer_id, interact_rect);
|
||||
let mut hovered = self.rect_contains_pointer(layer.area_layer, interact_rect);
|
||||
|
||||
// This solves the problem of overlapping widgets.
|
||||
// Whichever widget is added LAST (=on top) gets the input:
|
||||
if interact_rect.is_positive() && sense.interactive() {
|
||||
if self.style().debug.show_interactive_widgets {
|
||||
Self::layer_painter(self, LayerId::debug()).rect(
|
||||
Self::layer_painter(self, AreaLayerId::debug()).rect(
|
||||
interact_rect,
|
||||
0.0,
|
||||
Color32::YELLOW.additive().linear_multiply(0.005),
|
||||
|
@ -568,16 +567,18 @@ impl Context {
|
|||
|
||||
self.write(|ctx| {
|
||||
ctx.layer_rects_this_frame
|
||||
.entry(layer_id)
|
||||
.entry(layer.area_layer)
|
||||
.or_default()
|
||||
.push((id, interact_rect));
|
||||
.push((id, layer.z, interact_rect));
|
||||
|
||||
if hovered {
|
||||
let pointer_pos = ctx.input.pointer.interact_pos();
|
||||
if let Some(pointer_pos) = pointer_pos {
|
||||
if let Some(rects) = ctx.layer_rects_prev_frame.get(&layer_id) {
|
||||
for &(prev_id, prev_rect) in rects.iter().rev() {
|
||||
if prev_id == id {
|
||||
if let Some(rects) = ctx.layer_rects_prev_frame.get_mut(&layer.area_layer) {
|
||||
rects.sort_by_key(|(_id, z, ..)| *z);
|
||||
|
||||
for &(prev_id, prev_z, prev_rect) in rects.iter().rev() {
|
||||
if prev_id == id && prev_z <= layer.z {
|
||||
break; // there is no other interactive widget covering us at the pointer position.
|
||||
}
|
||||
if prev_rect.contains(pointer_pos) {
|
||||
|
@ -585,12 +586,12 @@ impl Context {
|
|||
// so we aren't hovered.
|
||||
|
||||
if ctx.memory.options.style.debug.show_blocking_widget {
|
||||
Self::layer_painter(self, LayerId::debug()).debug_rect(
|
||||
Self::layer_painter(self, AreaLayerId::debug()).debug_rect(
|
||||
interact_rect,
|
||||
Color32::GREEN,
|
||||
"Covered",
|
||||
);
|
||||
Self::layer_painter(self, LayerId::debug()).debug_rect(
|
||||
Self::layer_painter(self, AreaLayerId::debug()).debug_rect(
|
||||
prev_rect,
|
||||
Color32::LIGHT_BLUE,
|
||||
"On top",
|
||||
|
@ -607,13 +608,13 @@ impl Context {
|
|||
});
|
||||
}
|
||||
|
||||
self.interact_with_hovered(layer_id, id, rect, sense, enabled, hovered)
|
||||
self.interact_with_hovered(layer.area_layer, id, rect, sense, enabled, hovered)
|
||||
}
|
||||
|
||||
/// You specify if a thing is hovered, and the function gives a [`Response`].
|
||||
pub(crate) fn interact_with_hovered(
|
||||
&self,
|
||||
layer_id: LayerId,
|
||||
layer_id: AreaLayerId,
|
||||
id: Id,
|
||||
rect: Rect,
|
||||
sense: Sense,
|
||||
|
@ -622,8 +623,6 @@ impl Context {
|
|||
) -> Response {
|
||||
let hovered = hovered && enabled; // can't even hover disabled widgets
|
||||
|
||||
let highlighted = self.frame_state(|fs| fs.highlight_this_frame.contains(&id));
|
||||
|
||||
let mut response = Response {
|
||||
ctx: self.clone(),
|
||||
layer_id,
|
||||
|
@ -632,7 +631,6 @@ impl Context {
|
|||
sense,
|
||||
enabled,
|
||||
hovered,
|
||||
highlighted,
|
||||
clicked: Default::default(),
|
||||
double_clicked: Default::default(),
|
||||
triple_clicked: Default::default(),
|
||||
|
@ -656,7 +654,7 @@ impl Context {
|
|||
// Make sure anything that can receive focus has an AccessKit node.
|
||||
// TODO(mwcampbell): For nodes that are filled from widget info,
|
||||
// some information is written to the node twice.
|
||||
self.accesskit_node_builder(id, |builder| response.fill_accesskit_node_common(builder));
|
||||
self.accesskit_node(id, |node| response.fill_accesskit_node_common(node));
|
||||
}
|
||||
|
||||
let clicked_elsewhere = response.clicked_elsewhere();
|
||||
|
@ -763,14 +761,14 @@ impl Context {
|
|||
}
|
||||
|
||||
/// Get a full-screen painter for a new or existing layer
|
||||
pub fn layer_painter(&self, layer_id: LayerId) -> Painter {
|
||||
pub fn layer_painter(&self, layer_id: AreaLayerId) -> Painter {
|
||||
let screen_rect = self.screen_rect();
|
||||
Painter::new(self.clone(), layer_id, screen_rect)
|
||||
}
|
||||
|
||||
/// Paint on top of everything else
|
||||
pub fn debug_painter(&self) -> Painter {
|
||||
Self::layer_painter(self, LayerId::debug())
|
||||
Self::layer_painter(self, AreaLayerId::debug())
|
||||
}
|
||||
|
||||
/// What operating system are we running on?
|
||||
|
@ -1129,20 +1127,12 @@ impl Context {
|
|||
if let Some(state) = state {
|
||||
let has_focus = self.input(|i| i.raw.has_focus);
|
||||
let root_id = crate::accesskit_root_id().accesskit_id();
|
||||
let nodes = self.write(|ctx| {
|
||||
state
|
||||
.node_builders
|
||||
.into_iter()
|
||||
.map(|(id, builder)| {
|
||||
(
|
||||
id.accesskit_id(),
|
||||
builder.build(&mut ctx.accesskit_node_classes),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
platform_output.accesskit_update = Some(accesskit::TreeUpdate {
|
||||
nodes,
|
||||
nodes: state
|
||||
.nodes
|
||||
.into_iter()
|
||||
.map(|(id, node)| (id.accesskit_id(), Arc::from(node)))
|
||||
.collect(),
|
||||
tree: Some(accesskit::Tree::new(root_id)),
|
||||
focus: has_focus.then(|| {
|
||||
let focus_id = self.memory(|mem| mem.interaction.focus.id);
|
||||
|
@ -1296,15 +1286,6 @@ impl Context {
|
|||
pub fn wants_keyboard_input(&self) -> bool {
|
||||
self.memory(|m| m.interaction.focus.focused().is_some())
|
||||
}
|
||||
|
||||
/// Highlight this widget, to make it look like it is hovered, even if it isn't.
|
||||
///
|
||||
/// The highlight takes on frame to take effect if you call this after the widget has been fully rendered.
|
||||
///
|
||||
/// See also [`Response::highlight`].
|
||||
pub fn highlight_widget(&self, id: Id) {
|
||||
self.frame_state_mut(|fs| fs.highlight_next_frame.insert(id));
|
||||
}
|
||||
}
|
||||
|
||||
// Ergonomic methods to forward some calls often used in 'if let' without holding the borrow
|
||||
|
@ -1343,14 +1324,14 @@ impl Context {
|
|||
/// Move all the graphics at the given layer.
|
||||
///
|
||||
/// Can be used to implement drag-and-drop (see relevant demo).
|
||||
pub fn translate_layer(&self, layer_id: LayerId, delta: Vec2) {
|
||||
pub fn translate_layer(&self, layer_id: AreaLayerId, delta: Vec2) {
|
||||
if delta != Vec2::ZERO {
|
||||
self.graphics_mut(|g| g.list(layer_id).translate(delta));
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<AreaLayerId> {
|
||||
self.memory(|mem| {
|
||||
mem.layer_id_at(pos, mem.options.style.interaction.resize_grab_radius_side)
|
||||
})
|
||||
|
@ -1359,11 +1340,11 @@ impl Context {
|
|||
/// Moves the given area to the top in its [`Order`].
|
||||
///
|
||||
/// [`Area`]:s and [`Window`]:s also do this automatically when being clicked on or interacted with.
|
||||
pub fn move_to_top(&self, layer_id: LayerId) {
|
||||
pub fn move_to_top(&self, layer_id: AreaLayerId) {
|
||||
self.memory_mut(|mem| mem.areas.move_to_top(layer_id));
|
||||
}
|
||||
|
||||
pub(crate) fn rect_contains_pointer(&self, layer_id: LayerId, rect: Rect) -> bool {
|
||||
pub(crate) fn rect_contains_pointer(&self, layer_id: AreaLayerId, rect: Rect) -> bool {
|
||||
rect.is_positive() && {
|
||||
let pointer_pos = self.input(|i| i.pointer.interact_pos());
|
||||
if let Some(pointer_pos) = pointer_pos {
|
||||
|
@ -1620,7 +1601,7 @@ impl Context {
|
|||
ui.indent("areas", |ui| {
|
||||
ui.label("Visible areas, 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<AreaLayerId> = self.memory(|mem| mem.areas.order().to_vec());
|
||||
for layer_id in layers_ids {
|
||||
let area = self.memory(|mem| mem.areas.get(layer_id.id).copied());
|
||||
if let Some(area) = area {
|
||||
|
@ -1729,8 +1710,8 @@ impl Context {
|
|||
}
|
||||
|
||||
/// If AccessKit support is active for the current frame, get or create
|
||||
/// a node builder with the specified ID and return a mutable reference to it.
|
||||
/// For newly created nodes, the parent is the node with the ID at the top
|
||||
/// a node with the specified ID and return a mutable reference to it.
|
||||
/// For newly crated nodes, the parent is the node with the ID at the top
|
||||
/// of the stack managed by [`Context::with_accessibility_parent`].
|
||||
///
|
||||
/// The `Context` lock is held while the given closure is called!
|
||||
|
@ -1738,16 +1719,16 @@ impl Context {
|
|||
/// Returns `None` if acesskit is off.
|
||||
// TODO: consider making both RO and RW versions
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub fn accesskit_node_builder<R>(
|
||||
pub fn accesskit_node<R>(
|
||||
&self,
|
||||
id: Id,
|
||||
writer: impl FnOnce(&mut accesskit::NodeBuilder) -> R,
|
||||
writer: impl FnOnce(&mut accesskit::Node) -> R,
|
||||
) -> Option<R> {
|
||||
self.write(|ctx| {
|
||||
ctx.frame_state
|
||||
.accesskit_state
|
||||
.is_some()
|
||||
.then(|| ctx.accesskit_node_builder(id))
|
||||
.then(|| ctx.accesskit_node(id))
|
||||
.map(writer)
|
||||
})
|
||||
}
|
||||
|
@ -1759,30 +1740,12 @@ impl Context {
|
|||
/// being called by the AccessKit adapter to provide the initial tree update,
|
||||
/// then it should do so, to provide a complete AccessKit tree to the adapter
|
||||
/// immediately. Otherwise, it should enqueue a repaint and use the
|
||||
/// placeholder tree update from [`Context::accesskit_placeholder_tree_update`]
|
||||
/// placeholder tree update from [`crate::accesskit_placeholder_tree_update`]
|
||||
/// in the meantime.
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub fn enable_accesskit(&self) {
|
||||
self.write(|ctx| ctx.is_accesskit_enabled = true);
|
||||
}
|
||||
|
||||
/// Return a tree update that the egui integration should provide to the
|
||||
/// AccessKit adapter if it cannot immediately run the egui application
|
||||
/// to get a full tree update after running [`Context::enable_accesskit`].
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub fn accesskit_placeholder_tree_update(&self) -> accesskit::TreeUpdate {
|
||||
use accesskit::{NodeBuilder, Role, Tree, TreeUpdate};
|
||||
|
||||
let root_id = crate::accesskit_root_id().accesskit_id();
|
||||
self.write(|ctx| TreeUpdate {
|
||||
nodes: vec![(
|
||||
root_id,
|
||||
NodeBuilder::new(Role::Window).build(&mut ctx.accesskit_node_classes),
|
||||
)],
|
||||
tree: Some(Tree::new(root_id)),
|
||||
focus: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::ops::RangeInclusive;
|
||||
|
||||
use crate::{id::IdSet, *};
|
||||
use crate::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) struct TooltipFrameState {
|
||||
|
@ -12,7 +12,7 @@ pub(crate) struct TooltipFrameState {
|
|||
#[cfg(feature = "accesskit")]
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct AccessKitFrameState {
|
||||
pub(crate) node_builders: IdMap<accesskit::NodeBuilder>,
|
||||
pub(crate) nodes: IdMap<Box<accesskit::Node>>,
|
||||
pub(crate) parent_stack: Vec<Id>,
|
||||
}
|
||||
|
||||
|
@ -51,12 +51,6 @@ pub(crate) struct FrameState {
|
|||
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub(crate) accesskit_state: Option<AccessKitFrameState>,
|
||||
|
||||
/// Highlight these widgets this next frame. Read from this.
|
||||
pub(crate) highlight_this_frame: IdSet,
|
||||
|
||||
/// Highlight these widgets the next frame. Write to this.
|
||||
pub(crate) highlight_next_frame: IdSet,
|
||||
}
|
||||
|
||||
impl Default for FrameState {
|
||||
|
@ -71,8 +65,6 @@ impl Default for FrameState {
|
|||
scroll_target: [None, None],
|
||||
#[cfg(feature = "accesskit")]
|
||||
accesskit_state: None,
|
||||
highlight_this_frame: Default::default(),
|
||||
highlight_next_frame: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,8 +81,6 @@ impl FrameState {
|
|||
scroll_target,
|
||||
#[cfg(feature = "accesskit")]
|
||||
accesskit_state,
|
||||
highlight_this_frame,
|
||||
highlight_next_frame,
|
||||
} = self;
|
||||
|
||||
used_ids.clear();
|
||||
|
@ -100,13 +90,10 @@ impl FrameState {
|
|||
*tooltip_state = None;
|
||||
*scroll_delta = input.scroll_delta;
|
||||
*scroll_target = [None, None];
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
{
|
||||
*accesskit_state = None;
|
||||
}
|
||||
|
||||
*highlight_this_frame = std::mem::take(highlight_next_frame);
|
||||
}
|
||||
|
||||
/// How much space is still available after panels has been added.
|
||||
|
|
|
@ -168,8 +168,5 @@ impl std::hash::BuildHasher for BuilIdHasher {
|
|||
}
|
||||
}
|
||||
|
||||
/// `IdSet` is a `HashSet<Id>` optimized by knowing that [`Id`] has good entropy, and doesn't need more hashing.
|
||||
pub type IdSet = std::collections::HashSet<Id, BuilIdHasher>;
|
||||
|
||||
/// `IdMap<V>` is a `HashMap<Id, V>` optimized by knowing that [`Id`] has good entropy, and doesn't need more hashing.
|
||||
pub type IdMap<V> = std::collections::HashMap<Id, V, BuilIdHasher>;
|
||||
|
|
|
@ -447,10 +447,8 @@ impl InputState {
|
|||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub(crate) struct Click {
|
||||
pub pos: Pos2,
|
||||
|
||||
/// 1 or 2 (double-click) or 3 (triple-click)
|
||||
pub count: u32,
|
||||
|
||||
/// Allows you to check for e.g. shift-click
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
|
|
@ -96,14 +96,10 @@ struct GestureState {
|
|||
struct DynGestureState {
|
||||
/// used for proportional zooming
|
||||
avg_distance: f32,
|
||||
|
||||
/// used for non-proportional zooming
|
||||
avg_abs_distance2: Vec2,
|
||||
|
||||
avg_pos: Pos2,
|
||||
|
||||
avg_force: f32,
|
||||
|
||||
heading: f32,
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,6 @@ impl Order {
|
|||
Self::Tooltip,
|
||||
Self::Debug,
|
||||
];
|
||||
pub const TOP: Self = Self::Debug;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn allow_interaction(&self) -> bool {
|
||||
|
@ -70,12 +69,16 @@ impl Order {
|
|||
/// Also acts as an identifier for [`Area`]:s.
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct LayerId {
|
||||
pub struct AreaLayerId {
|
||||
pub order: Order,
|
||||
pub id: Id,
|
||||
}
|
||||
|
||||
impl LayerId {
|
||||
/// For backwards-compatibility with `AreaLayerId`
|
||||
#[deprecated(note = "Use `AreaLayerId` instead")]
|
||||
pub type LayerId = AreaLayerId;
|
||||
|
||||
impl AreaLayerId {
|
||||
pub fn new(order: Order, id: Id) -> Self {
|
||||
Self { order, id }
|
||||
}
|
||||
|
@ -99,6 +102,11 @@ impl LayerId {
|
|||
self.order.allow_interaction()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_z(self, z: ZOrder) -> ZLayer {
|
||||
ZLayer::from_area_layer_z(self, z)
|
||||
}
|
||||
|
||||
/// Short and readable summary
|
||||
pub fn short_debug_format(&self) -> String {
|
||||
format!(
|
||||
|
@ -109,13 +117,163 @@ impl LayerId {
|
|||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Represents the relative order an element should be displayed.
|
||||
///
|
||||
/// Lower values render first, and therefore appear below higher values.
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct ZOrder(pub i32);
|
||||
|
||||
impl ZOrder {
|
||||
/// The default layer 0.
|
||||
pub const BASE: ZOrder = ZOrder(0);
|
||||
|
||||
/// In front of everything else.
|
||||
pub const FRONT: ZOrder = ZOrder(i32::MAX);
|
||||
|
||||
/// Behind everything else.
|
||||
pub const BACK: ZOrder = ZOrder(i32::MIN);
|
||||
|
||||
/// Directly above
|
||||
pub fn in_front(self) -> Self {
|
||||
self.in_front_by(1)
|
||||
}
|
||||
|
||||
/// Directly behind
|
||||
pub fn behind(self) -> Self {
|
||||
self.behind_by(1)
|
||||
}
|
||||
|
||||
/// In front of by the number of levels given
|
||||
pub fn in_front_by(self, levels: i32) -> Self {
|
||||
Self(self.0.saturating_add(levels))
|
||||
}
|
||||
|
||||
/// Behind by the number of levels given
|
||||
pub fn behind_by(self, levels: i32) -> Self {
|
||||
Self(self.0.saturating_sub(levels))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ZOrder {
|
||||
fn default() -> Self {
|
||||
Self::BASE
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<ZOffset> for ZOrder {
|
||||
type Output = ZOrder;
|
||||
|
||||
fn add(self, offset: ZOffset) -> Self::Output {
|
||||
Self(self.0.saturating_add(offset))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<ZOffset> for ZOrder {
|
||||
fn add_assign(&mut self, offset: ZOffset) {
|
||||
self.0 = self.0.saturating_add(offset);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Offset within a [`ZOrder`].
|
||||
///
|
||||
/// * Positive: more in front of.
|
||||
/// * Negative: more behind.
|
||||
pub type ZOffset = i32;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// An identifier for a paint layer which supports Z-indexing
|
||||
///
|
||||
/// This says: draw on [`AreaLayerId`] with index z. This only affects the display
|
||||
/// order of elements on the same area layer. Order of area layers still takes
|
||||
/// precedence over z-index.
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct ZLayer {
|
||||
pub area_layer: AreaLayerId,
|
||||
pub z: ZOrder,
|
||||
}
|
||||
|
||||
impl ZLayer {
|
||||
pub fn new(order: Order, id: Id, z: ZOrder) -> Self {
|
||||
Self {
|
||||
area_layer: AreaLayerId { order, id },
|
||||
z,
|
||||
}
|
||||
}
|
||||
|
||||
/// Use specified Z-level
|
||||
pub fn from_area_layer_z(area_layer: AreaLayerId, z: ZOrder) -> Self {
|
||||
Self { area_layer, z }
|
||||
}
|
||||
|
||||
/// Use base Z-level
|
||||
pub fn from_area_layer(area_layer: AreaLayerId) -> Self {
|
||||
Self::from_area_layer_z(area_layer, ZOrder::default())
|
||||
}
|
||||
|
||||
pub fn debug() -> Self {
|
||||
Self::from_area_layer(AreaLayerId::debug())
|
||||
}
|
||||
|
||||
pub fn background() -> Self {
|
||||
Self::from_area_layer(AreaLayerId::background())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_z(self, z: ZOrder) -> Self {
|
||||
Self::from_area_layer_z(self.area_layer, z)
|
||||
}
|
||||
|
||||
/// Get the `ZLayer` directly in front of this one.
|
||||
#[must_use]
|
||||
pub fn in_front(self) -> Self {
|
||||
self.with_z(self.z.in_front())
|
||||
}
|
||||
|
||||
/// Get the `ZLayer` in front of this one by `levels` levels.
|
||||
#[must_use]
|
||||
pub fn in_front_by(self, levels: i32) -> Self {
|
||||
self.with_z(self.z.in_front_by(levels))
|
||||
}
|
||||
|
||||
/// Get the `ZLayer` directly behind this one.
|
||||
#[must_use]
|
||||
pub fn behind(self) -> Self {
|
||||
self.with_z(self.z.behind())
|
||||
}
|
||||
|
||||
/// Get the `ZLayer` behind this one by `levels` levels.
|
||||
#[must_use]
|
||||
pub fn behind_by(self, levels: i32) -> Self {
|
||||
self.with_z(self.z.behind_by(levels))
|
||||
}
|
||||
|
||||
/// `Id` of underlying area layer
|
||||
#[inline(always)]
|
||||
pub fn id(&self) -> Id {
|
||||
self.area_layer.id
|
||||
}
|
||||
|
||||
/// `Order` of underlying area layer
|
||||
#[inline(always)]
|
||||
pub fn order(&self) -> Order {
|
||||
self.area_layer.order
|
||||
}
|
||||
}
|
||||
|
||||
/// A unique identifier of a specific [`Shape`] in a [`PaintList`].
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ShapeIdx(usize);
|
||||
|
||||
/// A list of [`Shape`]s paired with a clip rectangle.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PaintList(Vec<ClippedShape>);
|
||||
pub struct PaintList(Vec<(ZOrder, ClippedShape)>);
|
||||
|
||||
impl PaintList {
|
||||
#[inline(always)]
|
||||
|
@ -123,22 +281,37 @@ impl PaintList {
|
|||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the index of the new [`Shape`] that can be used with `PaintList::set`.
|
||||
/// Returns the index of the new [`Shape`] at index `z` that can be used with `PaintList::set`.
|
||||
#[inline(always)]
|
||||
pub fn add(&mut self, clip_rect: Rect, shape: Shape) -> ShapeIdx {
|
||||
pub fn add_at_z(&mut self, clip_rect: Rect, shape: Shape, z: ZOrder) -> ShapeIdx {
|
||||
let idx = ShapeIdx(self.0.len());
|
||||
self.0.push(ClippedShape(clip_rect, shape));
|
||||
self.0.push((z, ClippedShape(clip_rect, shape)));
|
||||
idx
|
||||
}
|
||||
|
||||
pub fn extend<I: IntoIterator<Item = Shape>>(&mut self, clip_rect: Rect, shapes: I) {
|
||||
/// Returns the index of the new [`Shape`] at base z-index that can be used with `PaintList::set`.
|
||||
#[inline(always)]
|
||||
pub fn add(&mut self, clip_rect: Rect, shape: Shape) -> ShapeIdx {
|
||||
self.add_at_z(clip_rect, shape, ZOrder::BASE)
|
||||
}
|
||||
|
||||
pub fn extend_at_z<I: IntoIterator<Item = Shape>>(
|
||||
&mut self,
|
||||
clip_rect: Rect,
|
||||
shapes: I,
|
||||
z: ZOrder,
|
||||
) {
|
||||
self.0.extend(
|
||||
shapes
|
||||
.into_iter()
|
||||
.map(|shape| ClippedShape(clip_rect, shape)),
|
||||
.map(|shape| (z, ClippedShape(clip_rect, shape))),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn extend<I: IntoIterator<Item = Shape>>(&mut self, clip_rect: Rect, shapes: I) {
|
||||
self.extend_at_z(clip_rect, shapes, ZOrder::BASE);
|
||||
}
|
||||
|
||||
/// Modify an existing [`Shape`].
|
||||
///
|
||||
/// Sometimes you want to paint a frame behind some contents, but don't know how large the frame needs to be
|
||||
|
@ -148,12 +321,12 @@ impl PaintList {
|
|||
/// and then later setting it using `paint_list.set(idx, cr, frame);`.
|
||||
#[inline(always)]
|
||||
pub fn set(&mut self, idx: ShapeIdx, clip_rect: Rect, shape: Shape) {
|
||||
self.0[idx.0] = ClippedShape(clip_rect, shape);
|
||||
self.0[idx.0].1 = ClippedShape(clip_rect, shape);
|
||||
}
|
||||
|
||||
/// Translate each [`Shape`] and clip rectangle by this much, in-place
|
||||
pub fn translate(&mut self, delta: Vec2) {
|
||||
for ClippedShape(clip_rect, shape) in &mut self.0 {
|
||||
for (.., ClippedShape(clip_rect, shape)) in &mut self.0 {
|
||||
*clip_rect = clip_rect.translate(delta);
|
||||
shape.translate(delta);
|
||||
}
|
||||
|
@ -164,18 +337,26 @@ impl PaintList {
|
|||
pub(crate) struct GraphicLayers([IdMap<PaintList>; Order::COUNT]);
|
||||
|
||||
impl GraphicLayers {
|
||||
pub fn list(&mut self, layer_id: LayerId) -> &mut PaintList {
|
||||
pub fn list(&mut self, layer_id: AreaLayerId) -> &mut PaintList {
|
||||
self.0[layer_id.order as usize]
|
||||
.entry(layer_id.id)
|
||||
.or_default()
|
||||
}
|
||||
|
||||
pub fn drain(&mut self, area_order: &[LayerId]) -> impl ExactSizeIterator<Item = ClippedShape> {
|
||||
pub fn drain(
|
||||
&mut self,
|
||||
area_order: &[AreaLayerId],
|
||||
) -> impl ExactSizeIterator<Item = ClippedShape> {
|
||||
let mut all_shapes: Vec<_> = Default::default();
|
||||
|
||||
for &order in &Order::ALL {
|
||||
let order_map = &mut self.0[order as usize];
|
||||
|
||||
// Sort by z-order
|
||||
for list in order_map.values_mut() {
|
||||
list.0.sort_by_key(|(z, ..)| *z);
|
||||
}
|
||||
|
||||
// If a layer is empty at the start of the frame
|
||||
// then nobody has added to it, and it is old and defunct.
|
||||
// Free it to save memory:
|
||||
|
@ -196,6 +377,6 @@ impl GraphicLayers {
|
|||
}
|
||||
}
|
||||
|
||||
all_shapes.into_iter()
|
||||
all_shapes.into_iter().map(|(.., shape)| shape)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -361,19 +361,22 @@ pub use {
|
|||
grid::Grid,
|
||||
id::{Id, IdMap},
|
||||
input_state::{InputState, MultiTouchInfo, PointerState},
|
||||
layers::{LayerId, Order},
|
||||
layers::{AreaLayerId, Order, ZOffset, ZOrder},
|
||||
layout::*,
|
||||
memory::{Memory, Options},
|
||||
painter::Painter,
|
||||
response::{InnerResponse, Response},
|
||||
sense::Sense,
|
||||
style::{FontSelection, Margin, Style, TextStyle, Visuals},
|
||||
style::{FontSelection, Style, TextStyle, Visuals},
|
||||
text::{Galley, TextFormat},
|
||||
ui::Ui,
|
||||
widget_text::{RichText, WidgetText},
|
||||
widgets::*,
|
||||
};
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub use layers::LayerId;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Helper function that adds a label when compiling with debug assertions enabled.
|
||||
|
@ -513,30 +516,18 @@ pub mod special_emojis {
|
|||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum WidgetType {
|
||||
Label, // TODO(emilk): emit Label events
|
||||
|
||||
/// e.g. a hyperlink
|
||||
Link,
|
||||
|
||||
TextEdit,
|
||||
|
||||
Button,
|
||||
|
||||
Checkbox,
|
||||
|
||||
RadioButton,
|
||||
|
||||
SelectableLabel,
|
||||
|
||||
ComboBox,
|
||||
|
||||
Slider,
|
||||
|
||||
DragValue,
|
||||
|
||||
ColorButton,
|
||||
|
||||
ImageButton,
|
||||
|
||||
CollapsingHeader,
|
||||
|
||||
/// If you cannot fit any of the above slots.
|
||||
|
@ -571,3 +562,25 @@ pub fn __run_test_ui(mut add_contents: impl FnMut(&mut Ui)) {
|
|||
pub fn accesskit_root_id() -> Id {
|
||||
Id::new("accesskit_root")
|
||||
}
|
||||
|
||||
/// Return a tree update that the egui integration should provide to the
|
||||
/// AccessKit adapter if it cannot immediately run the egui application
|
||||
/// to get a full tree update after running [`Context::enable_accesskit`].
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub fn accesskit_placeholder_tree_update() -> accesskit::TreeUpdate {
|
||||
use accesskit::{Node, Role, Tree, TreeUpdate};
|
||||
use std::sync::Arc;
|
||||
|
||||
let root_id = accesskit_root_id().accesskit_id();
|
||||
TreeUpdate {
|
||||
nodes: vec![(
|
||||
root_id,
|
||||
Arc::new(Node {
|
||||
role: Role::Window,
|
||||
..Default::default()
|
||||
}),
|
||||
)],
|
||||
tree: Some(Tree::new(root_id)),
|
||||
focus: None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{area, window, Id, IdMap, InputState, LayerId, Pos2, Rect, Style};
|
||||
use crate::{area, window, AreaLayerId, Id, IdMap, InputState, Pos2, Rect, Style};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
@ -103,15 +103,9 @@ pub struct Options {
|
|||
/// Controls the tessellator.
|
||||
pub tessellation_options: epaint::TessellationOptions,
|
||||
|
||||
/// This is a signal to any backend that we want the [`crate::PlatformOutput::events`] read out loud.
|
||||
///
|
||||
/// The only change to egui is that labels can be focused by pressing tab.
|
||||
///
|
||||
/// This does not at all change the behavior of egui,
|
||||
/// but is a signal to any backend that we want the [`crate::PlatformOutput::events`] read out loud.
|
||||
/// Screen readers is an experimental feature of egui, and not supported on all platforms.
|
||||
///
|
||||
/// `eframe` supports it only on web, using the `web_screen_reader` feature flag,
|
||||
/// but you should consider using [AccessKit](https://github.com/AccessKit/accesskit) instead,
|
||||
/// which `eframe` supports.
|
||||
pub screen_reader: bool,
|
||||
|
||||
/// If true, the most common glyphs (ASCII) are pre-rendered to the texture atlas.
|
||||
|
@ -364,12 +358,12 @@ impl Memory {
|
|||
}
|
||||
|
||||
/// Top-most layer at the given position.
|
||||
pub fn layer_id_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option<LayerId> {
|
||||
pub fn layer_id_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option<AreaLayerId> {
|
||||
self.areas.layer_id_at(pos, resize_interact_radius_side)
|
||||
}
|
||||
|
||||
/// 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 = AreaLayerId> + '_ {
|
||||
self.areas.order().iter().copied()
|
||||
}
|
||||
|
||||
|
@ -536,16 +530,16 @@ impl Memory {
|
|||
pub struct Areas {
|
||||
areas: IdMap<area::State>,
|
||||
/// Back-to-front. Top is last.
|
||||
order: Vec<LayerId>,
|
||||
visible_last_frame: ahash::HashSet<LayerId>,
|
||||
visible_current_frame: ahash::HashSet<LayerId>,
|
||||
order: Vec<AreaLayerId>,
|
||||
visible_last_frame: ahash::HashSet<AreaLayerId>,
|
||||
visible_current_frame: ahash::HashSet<AreaLayerId>,
|
||||
|
||||
/// When an area want to be on top, it is put in here.
|
||||
/// At the end of the frame, this is used to reorder the layers.
|
||||
/// This means if several layers want to be on top, they will keep their relative order.
|
||||
/// So if you close three windows and then reopen them all in one frame,
|
||||
/// they will all be sent to the top, but keep their previous internal order.
|
||||
wants_to_be_on_top: ahash::HashSet<LayerId>,
|
||||
wants_to_be_on_top: ahash::HashSet<AreaLayerId>,
|
||||
}
|
||||
|
||||
impl Areas {
|
||||
|
@ -558,11 +552,11 @@ impl Areas {
|
|||
}
|
||||
|
||||
/// Back-to-front. Top is last.
|
||||
pub(crate) fn order(&self) -> &[LayerId] {
|
||||
pub(crate) fn order(&self) -> &[AreaLayerId] {
|
||||
&self.order
|
||||
}
|
||||
|
||||
pub(crate) fn set_state(&mut self, layer_id: LayerId, state: area::State) {
|
||||
pub(crate) fn set_state(&mut self, layer_id: AreaLayerId, state: area::State) {
|
||||
self.visible_current_frame.insert(layer_id);
|
||||
self.areas.insert(layer_id.id, state);
|
||||
if !self.order.iter().any(|x| *x == layer_id) {
|
||||
|
@ -571,7 +565,7 @@ impl Areas {
|
|||
}
|
||||
|
||||
/// Top-most layer at the given position.
|
||||
pub fn layer_id_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option<LayerId> {
|
||||
pub fn layer_id_at(&self, pos: Pos2, resize_interact_radius_side: f32) -> Option<AreaLayerId> {
|
||||
for layer in self.order.iter().rev() {
|
||||
if self.is_visible(layer) {
|
||||
if let Some(state) = self.areas.get(&layer.id) {
|
||||
|
@ -589,15 +583,15 @@ impl Areas {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn visible_last_frame(&self, layer_id: &LayerId) -> bool {
|
||||
pub fn visible_last_frame(&self, layer_id: &AreaLayerId) -> bool {
|
||||
self.visible_last_frame.contains(layer_id)
|
||||
}
|
||||
|
||||
pub fn is_visible(&self, layer_id: &LayerId) -> bool {
|
||||
pub fn is_visible(&self, layer_id: &AreaLayerId) -> bool {
|
||||
self.visible_last_frame.contains(layer_id) || self.visible_current_frame.contains(layer_id)
|
||||
}
|
||||
|
||||
pub fn visible_layer_ids(&self) -> ahash::HashSet<LayerId> {
|
||||
pub fn visible_layer_ids(&self) -> ahash::HashSet<AreaLayerId> {
|
||||
self.visible_last_frame
|
||||
.iter()
|
||||
.copied()
|
||||
|
@ -613,7 +607,7 @@ impl Areas {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn move_to_top(&mut self, layer_id: LayerId) {
|
||||
pub fn move_to_top(&mut self, layer_id: AreaLayerId) {
|
||||
self.visible_current_frame.insert(layer_id);
|
||||
self.wants_to_be_on_top.insert(layer_id);
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ use std::sync::Arc;
|
|||
|
||||
use crate::{
|
||||
emath::{Align2, Pos2, Rect, Vec2},
|
||||
layers::{LayerId, PaintList, ShapeIdx},
|
||||
Color32, Context, FontId,
|
||||
layers::{PaintList, ShapeIdx, ZLayer, ZOffset, ZOrder},
|
||||
AreaLayerId, Color32, Context, FontId,
|
||||
};
|
||||
use epaint::{
|
||||
text::{Fonts, Galley},
|
||||
|
@ -20,7 +20,7 @@ pub struct Painter {
|
|||
ctx: Context,
|
||||
|
||||
/// Where we paint
|
||||
layer_id: LayerId,
|
||||
z_layer: ZLayer,
|
||||
|
||||
/// Everything painted in this [`Painter`] will be clipped against this.
|
||||
/// This means nothing outside of this rectangle will be visible on screen.
|
||||
|
@ -33,24 +33,34 @@ pub struct Painter {
|
|||
|
||||
impl Painter {
|
||||
/// Create a painter to a specific layer within a certain clip rectangle.
|
||||
pub fn new(ctx: Context, layer_id: LayerId, clip_rect: Rect) -> Self {
|
||||
pub fn new(ctx: Context, area_layer: AreaLayerId, clip_rect: Rect) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
layer_id,
|
||||
z_layer: ZLayer::from_area_layer(area_layer),
|
||||
clip_rect,
|
||||
fade_to_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Redirect where you are painting.
|
||||
/// Redirect where you are painting with default z-index
|
||||
#[must_use]
|
||||
pub fn with_layer_id(self, layer_id: LayerId) -> Self {
|
||||
Self {
|
||||
ctx: self.ctx,
|
||||
layer_id,
|
||||
clip_rect: self.clip_rect,
|
||||
fade_to_color: None,
|
||||
pub fn with_layer_id(mut self, layer: AreaLayerId) -> Self {
|
||||
self.z_layer = ZLayer::from_area_layer(layer);
|
||||
self
|
||||
}
|
||||
|
||||
/// Redirect z-index
|
||||
#[must_use]
|
||||
pub fn with_z(mut self, z: ZOrder) -> Self {
|
||||
self.z_layer.z = z;
|
||||
self
|
||||
}
|
||||
|
||||
/// Modify z-index
|
||||
#[must_use]
|
||||
pub fn with_z_offset(mut self, z_offset: ZOffset) -> Self {
|
||||
self.z_layer.z += z_offset;
|
||||
self
|
||||
}
|
||||
|
||||
/// Create a painter for a sub-region of this [`Painter`].
|
||||
|
@ -60,15 +70,25 @@ impl Painter {
|
|||
pub fn with_clip_rect(&self, rect: Rect) -> Self {
|
||||
Self {
|
||||
ctx: self.ctx.clone(),
|
||||
layer_id: self.layer_id,
|
||||
z_layer: self.z_layer,
|
||||
clip_rect: rect.intersect(self.clip_rect),
|
||||
fade_to_color: self.fade_to_color,
|
||||
}
|
||||
}
|
||||
|
||||
/// Redirect where you are painting.
|
||||
pub fn set_layer_id(&mut self, layer_id: LayerId) {
|
||||
self.layer_id = layer_id;
|
||||
/// Redirect what area layer you are painting.
|
||||
pub fn set_layer_id(&mut self, area_layer: AreaLayerId) {
|
||||
self.z_layer.area_layer = area_layer;
|
||||
}
|
||||
|
||||
/// Redirect at what z order you are drawing
|
||||
pub fn set_z(&mut self, z: ZOrder) {
|
||||
self.z_layer.z = z;
|
||||
}
|
||||
|
||||
/// Redirect where you are drawing
|
||||
pub fn set_layer(&mut self, layer: ZLayer) {
|
||||
self.z_layer = layer;
|
||||
}
|
||||
|
||||
/// If set, colors will be modified to look like this
|
||||
|
@ -89,7 +109,7 @@ impl Painter {
|
|||
pub fn sub_region(&self, rect: Rect) -> Self {
|
||||
Self {
|
||||
ctx: self.ctx.clone(),
|
||||
layer_id: self.layer_id,
|
||||
z_layer: self.z_layer,
|
||||
clip_rect: rect.intersect(self.clip_rect),
|
||||
fade_to_color: self.fade_to_color,
|
||||
}
|
||||
|
@ -114,8 +134,19 @@ impl Painter {
|
|||
|
||||
/// Where we paint
|
||||
#[inline(always)]
|
||||
pub fn layer_id(&self) -> LayerId {
|
||||
self.layer_id
|
||||
pub fn area_layer_id(&self) -> AreaLayerId {
|
||||
self.z_layer.area_layer
|
||||
}
|
||||
|
||||
/// Where we paint, and on what Z-level
|
||||
#[inline(always)]
|
||||
pub fn z_layer(&self) -> ZLayer {
|
||||
self.z_layer
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn z(&self) -> ZOrder {
|
||||
self.z_layer.z
|
||||
}
|
||||
|
||||
/// Everything painted in this [`Painter`] will be clipped against this.
|
||||
|
@ -153,9 +184,9 @@ impl Painter {
|
|||
|
||||
/// ## Low level
|
||||
impl Painter {
|
||||
#[inline]
|
||||
fn paint_list<R>(&self, writer: impl FnOnce(&mut PaintList) -> R) -> R {
|
||||
self.ctx.graphics_mut(|g| writer(g.list(self.layer_id)))
|
||||
self.ctx
|
||||
.graphics_mut(|g| writer(g.list(self.z_layer.area_layer)))
|
||||
}
|
||||
|
||||
fn transform_shape(&self, shape: &mut Shape) {
|
||||
|
@ -164,16 +195,28 @@ impl Painter {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_to_paint_list(&self, shape: Shape) -> ShapeIdx {
|
||||
self.paint_list(|l| l.add_at_z(self.clip_rect, shape, self.z_layer.z))
|
||||
}
|
||||
|
||||
fn extend_paint_list(&self, shapes: impl IntoIterator<Item = Shape>) {
|
||||
self.paint_list(|l| l.extend_at_z(self.clip_rect, shapes, self.z_layer.z));
|
||||
}
|
||||
|
||||
fn set_shape_in_paint_list(&self, idx: ShapeIdx, shape: Shape) {
|
||||
self.paint_list(|l| l.set(idx, self.clip_rect, shape));
|
||||
}
|
||||
|
||||
/// It is up to the caller to make sure there is room for this.
|
||||
/// Can be used for free painting.
|
||||
/// NOTE: all coordinates are screen coordinates!
|
||||
pub fn add(&self, shape: impl Into<Shape>) -> ShapeIdx {
|
||||
if self.fade_to_color == Some(Color32::TRANSPARENT) {
|
||||
self.paint_list(|l| l.add(self.clip_rect, Shape::Noop))
|
||||
self.add_to_paint_list(Shape::Noop)
|
||||
} else {
|
||||
let mut shape = shape.into();
|
||||
self.transform_shape(&mut shape);
|
||||
self.paint_list(|l| l.add(self.clip_rect, shape))
|
||||
self.add_to_paint_list(shape)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,9 +232,9 @@ impl Painter {
|
|||
self.transform_shape(&mut shape);
|
||||
shape
|
||||
});
|
||||
self.paint_list(|l| l.extend(self.clip_rect, shapes));
|
||||
self.extend_paint_list(shapes);
|
||||
} else {
|
||||
self.paint_list(|l| l.extend(self.clip_rect, shapes));
|
||||
self.extend_paint_list(shapes);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -202,7 +245,7 @@ impl Painter {
|
|||
}
|
||||
let mut shape = shape.into();
|
||||
self.transform_shape(&mut shape);
|
||||
self.paint_list(|l| l.set(idx, self.clip_rect, shape));
|
||||
self.set_shape_in_paint_list(idx, shape);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
emath::{Align, Pos2, Rect, Vec2},
|
||||
menu, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetText,
|
||||
menu, AreaLayerId, Context, CursorIcon, Id, PointerButton, Sense, Ui, WidgetText,
|
||||
NUM_POINTER_BUTTONS,
|
||||
};
|
||||
|
||||
|
@ -13,7 +13,6 @@ use crate::{
|
|||
///
|
||||
/// Whenever something gets added to a [`Ui`], a [`Response`] object is returned.
|
||||
/// [`ui.add`] returns a [`Response`], as does [`ui.button`], and all similar shortcuts.
|
||||
// TODO(emilk): we should be using bit sets instead of so many bools
|
||||
#[derive(Clone)]
|
||||
pub struct Response {
|
||||
// CONTEXT:
|
||||
|
@ -22,7 +21,7 @@ pub struct Response {
|
|||
|
||||
// IN:
|
||||
/// Which layer the widget is part of.
|
||||
pub layer_id: LayerId,
|
||||
pub layer_id: AreaLayerId,
|
||||
|
||||
/// The [`Id`] of the widget/area this response pertains.
|
||||
pub id: Id,
|
||||
|
@ -43,10 +42,6 @@ pub struct Response {
|
|||
#[doc(hidden)]
|
||||
pub hovered: bool,
|
||||
|
||||
/// The widget is highlighted via a call to [`Self::highlight`] or [`Context::highlight_widget`].
|
||||
#[doc(hidden)]
|
||||
pub highlighted: bool,
|
||||
|
||||
/// The pointer clicked this thing this frame.
|
||||
#[doc(hidden)]
|
||||
pub clicked: [bool; NUM_POINTER_BUTTONS],
|
||||
|
@ -57,7 +52,7 @@ pub struct Response {
|
|||
pub double_clicked: [bool; NUM_POINTER_BUTTONS],
|
||||
|
||||
/// The thing was triple-clicked.
|
||||
pub triple_clicked: [bool; NUM_POINTER_BUTTONS],
|
||||
pub(crate) triple_clicked: [bool; NUM_POINTER_BUTTONS],
|
||||
|
||||
/// The widgets is being dragged
|
||||
#[doc(hidden)]
|
||||
|
@ -95,7 +90,6 @@ impl std::fmt::Debug for Response {
|
|||
sense,
|
||||
enabled,
|
||||
hovered,
|
||||
highlighted,
|
||||
clicked,
|
||||
double_clicked,
|
||||
triple_clicked,
|
||||
|
@ -112,7 +106,6 @@ impl std::fmt::Debug for Response {
|
|||
.field("sense", sense)
|
||||
.field("enabled", enabled)
|
||||
.field("hovered", hovered)
|
||||
.field("highlighted", highlighted)
|
||||
.field("clicked", clicked)
|
||||
.field("double_clicked", double_clicked)
|
||||
.field("triple_clicked", triple_clicked)
|
||||
|
@ -220,12 +213,6 @@ impl Response {
|
|||
self.hovered
|
||||
}
|
||||
|
||||
/// The widget is highlighted via a call to [`Self::highlight`] or [`Context::highlight_widget`].
|
||||
#[doc(hidden)]
|
||||
pub fn highlighted(&self) -> bool {
|
||||
self.highlighted
|
||||
}
|
||||
|
||||
/// This widget has the keyboard focus (i.e. is receiving key presses).
|
||||
///
|
||||
/// This function only returns true if the UI as a whole (e.g. window)
|
||||
|
@ -467,17 +454,6 @@ impl Response {
|
|||
})
|
||||
}
|
||||
|
||||
/// Highlight this widget, to make it look like it is hovered, even if it isn't.
|
||||
///
|
||||
/// The highlight takes on frame to take effect if you call this after the widget has been fully rendered.
|
||||
///
|
||||
/// See also [`Context::highlight_widget`].
|
||||
pub fn highlight(mut self) -> Self {
|
||||
self.ctx.highlight_widget(self.id);
|
||||
self.highlighted = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Show this text when hovering if the widget is disabled.
|
||||
pub fn on_disabled_hover_text(self, text: impl Into<WidgetText>) -> Self {
|
||||
self.on_disabled_hover_ui(|ui| {
|
||||
|
@ -573,47 +549,47 @@ impl Response {
|
|||
self.output_event(event);
|
||||
} else {
|
||||
#[cfg(feature = "accesskit")]
|
||||
self.ctx.accesskit_node_builder(self.id, |builder| {
|
||||
self.fill_accesskit_node_from_widget_info(builder, make_info());
|
||||
self.ctx.accesskit_node(self.id, |node| {
|
||||
self.fill_accesskit_node_from_widget_info(node, make_info());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output_event(&self, event: crate::output::OutputEvent) {
|
||||
#[cfg(feature = "accesskit")]
|
||||
self.ctx.accesskit_node_builder(self.id, |builder| {
|
||||
self.fill_accesskit_node_from_widget_info(builder, event.widget_info().clone());
|
||||
self.ctx.accesskit_node(self.id, |node| {
|
||||
self.fill_accesskit_node_from_widget_info(node, event.widget_info().clone());
|
||||
});
|
||||
self.ctx.output_mut(|o| o.events.push(event));
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub(crate) fn fill_accesskit_node_common(&self, builder: &mut accesskit::NodeBuilder) {
|
||||
builder.set_bounds(accesskit::Rect {
|
||||
pub(crate) fn fill_accesskit_node_common(&self, node: &mut accesskit::Node) {
|
||||
node.bounds = Some(accesskit::kurbo::Rect {
|
||||
x0: self.rect.min.x.into(),
|
||||
y0: self.rect.min.y.into(),
|
||||
x1: self.rect.max.x.into(),
|
||||
y1: self.rect.max.y.into(),
|
||||
});
|
||||
if self.sense.focusable {
|
||||
builder.add_action(accesskit::Action::Focus);
|
||||
node.focusable = true;
|
||||
}
|
||||
if self.sense.click && builder.default_action_verb().is_none() {
|
||||
builder.set_default_action_verb(accesskit::DefaultActionVerb::Click);
|
||||
if self.sense.click && node.default_action_verb.is_none() {
|
||||
node.default_action_verb = Some(accesskit::DefaultActionVerb::Click);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
fn fill_accesskit_node_from_widget_info(
|
||||
&self,
|
||||
builder: &mut accesskit::NodeBuilder,
|
||||
node: &mut accesskit::Node,
|
||||
info: crate::WidgetInfo,
|
||||
) {
|
||||
use crate::WidgetType;
|
||||
use accesskit::{CheckedState, Role};
|
||||
|
||||
self.fill_accesskit_node_common(builder);
|
||||
builder.set_role(match info.typ {
|
||||
self.fill_accesskit_node_common(node);
|
||||
node.role = match info.typ {
|
||||
WidgetType::Label => Role::StaticText,
|
||||
WidgetType::Link => Role::Link,
|
||||
WidgetType::TextEdit => Role::TextField,
|
||||
|
@ -628,18 +604,18 @@ impl Response {
|
|||
WidgetType::DragValue => Role::SpinButton,
|
||||
WidgetType::ColorButton => Role::ColorWell,
|
||||
WidgetType::Other => Role::Unknown,
|
||||
});
|
||||
};
|
||||
if let Some(label) = info.label {
|
||||
builder.set_name(label);
|
||||
node.name = Some(label.into());
|
||||
}
|
||||
if let Some(value) = info.current_text_value {
|
||||
builder.set_value(value);
|
||||
node.value = Some(value.into());
|
||||
}
|
||||
if let Some(value) = info.value {
|
||||
builder.set_numeric_value(value);
|
||||
node.numeric_value = Some(value);
|
||||
}
|
||||
if let Some(selected) = info.selected {
|
||||
builder.set_checked_state(if selected {
|
||||
node.checked_state = Some(if selected {
|
||||
CheckedState::True
|
||||
} else {
|
||||
CheckedState::False
|
||||
|
@ -662,9 +638,8 @@ impl Response {
|
|||
/// ```
|
||||
pub fn labelled_by(self, id: Id) -> Self {
|
||||
#[cfg(feature = "accesskit")]
|
||||
self.ctx.accesskit_node_builder(self.id, |builder| {
|
||||
builder.push_labelled_by(id.accesskit_id());
|
||||
});
|
||||
self.ctx
|
||||
.accesskit_node(self.id, |node| node.labelled_by.push(id.accesskit_id()));
|
||||
#[cfg(not(feature = "accesskit"))]
|
||||
{
|
||||
let _ = id;
|
||||
|
@ -713,7 +688,6 @@ impl Response {
|
|||
sense: self.sense.union(other.sense),
|
||||
enabled: self.enabled || other.enabled,
|
||||
hovered: self.hovered || other.hovered,
|
||||
highlighted: self.highlighted || other.highlighted,
|
||||
clicked: [
|
||||
self.clicked[0] || other.clicked[0],
|
||||
self.clicked[1] || other.clicked[1],
|
||||
|
@ -745,13 +719,6 @@ impl Response {
|
|||
}
|
||||
}
|
||||
|
||||
impl Response {
|
||||
/// Returns a response with a modified [`Self::rect`].
|
||||
pub fn with_new_rect(self, rect: Rect) -> Self {
|
||||
Self { rect, ..self }
|
||||
}
|
||||
}
|
||||
|
||||
/// To summarize the response from many widgets you can use this pattern:
|
||||
///
|
||||
/// ```
|
||||
|
|
|
@ -174,9 +174,6 @@ pub struct Style {
|
|||
/// ```
|
||||
pub text_styles: BTreeMap<TextStyle, FontId>,
|
||||
|
||||
/// The style to use for [`DragValue`] text.
|
||||
pub drag_value_text_style: TextStyle,
|
||||
|
||||
/// If set, labels buttons wtc will use this to determine whether or not
|
||||
/// to wrap the text at the right edge of the [`Ui`] they are in.
|
||||
/// By default this is `None`.
|
||||
|
@ -300,12 +297,8 @@ pub struct Spacing {
|
|||
|
||||
pub scroll_bar_width: f32,
|
||||
|
||||
/// Make sure the scroll handle is at least this big
|
||||
pub scroll_handle_min_length: f32,
|
||||
|
||||
/// Margin between contents and scroll bar.
|
||||
pub scroll_bar_inner_margin: f32,
|
||||
|
||||
/// Margin between scroll bar and the outer container (e.g. right of a vertical scroll bar).
|
||||
pub scroll_bar_outer_margin: f32,
|
||||
}
|
||||
|
@ -492,7 +485,6 @@ pub struct Visuals {
|
|||
pub resize_corner_size: f32,
|
||||
|
||||
pub text_cursor_width: f32,
|
||||
|
||||
/// show where the text cursor would be if you clicked
|
||||
pub text_cursor_preview: bool,
|
||||
|
||||
|
@ -505,17 +497,9 @@ pub struct Visuals {
|
|||
/// Show a background behind collapsing headers.
|
||||
pub collapsing_header_frame: bool,
|
||||
|
||||
/// Draw a vertical lien left of indented region, in e.g. [`crate::CollapsingHeader`].
|
||||
pub indent_has_left_vline: bool,
|
||||
|
||||
/// Wether or not Grids and Tables should be striped by default
|
||||
/// (have alternating rows differently colored).
|
||||
pub striped: bool,
|
||||
|
||||
/// Show trailing color behind the circle of a [`Slider`]. Default is OFF.
|
||||
///
|
||||
/// Enabling this will affect ALL sliders, and can be enabled/disabled per slider with [`Slider::trailing_fill`].
|
||||
pub slider_trailing_fill: bool,
|
||||
}
|
||||
|
||||
impl Visuals {
|
||||
|
@ -586,9 +570,7 @@ pub struct Widgets {
|
|||
/// The style of an interactive widget, such as a button, at rest.
|
||||
pub inactive: WidgetVisuals,
|
||||
|
||||
/// The style of an interactive widget while you hover it, or when it is highlighted.
|
||||
///
|
||||
/// See [`Response::hovered`], [`Response::highlighted`] and [`Response::highlight`].
|
||||
/// The style of an interactive widget while you hover it.
|
||||
pub hovered: WidgetVisuals,
|
||||
|
||||
/// The style of an interactive widget as you are clicking or dragging it.
|
||||
|
@ -604,7 +586,7 @@ impl Widgets {
|
|||
&self.noninteractive
|
||||
} else if response.is_pointer_button_down_on() || response.has_focus() {
|
||||
&self.active
|
||||
} else if response.hovered() || response.highlighted() {
|
||||
} else if response.hovered() {
|
||||
&self.hovered
|
||||
} else {
|
||||
&self.inactive
|
||||
|
@ -693,7 +675,6 @@ impl Default for Style {
|
|||
override_font_id: None,
|
||||
override_text_style: None,
|
||||
text_styles: default_text_styles(),
|
||||
drag_value_text_style: TextStyle::Button,
|
||||
wrap: None,
|
||||
spacing: Spacing::default(),
|
||||
interaction: Interaction::default(),
|
||||
|
@ -723,7 +704,6 @@ impl Default for Spacing {
|
|||
tooltip_width: 600.0,
|
||||
combo_height: 200.0,
|
||||
scroll_bar_width: 8.0,
|
||||
scroll_handle_min_length: 12.0,
|
||||
scroll_bar_inner_margin: 4.0,
|
||||
scroll_bar_outer_margin: 0.0,
|
||||
indent_ends_with_horizontal_line: false,
|
||||
|
@ -772,11 +752,8 @@ impl Visuals {
|
|||
clip_rect_margin: 3.0, // should be at least half the size of the widest frame stroke + max WidgetVisuals::expansion
|
||||
button_frame: true,
|
||||
collapsing_header_frame: false,
|
||||
indent_has_left_vline: true,
|
||||
|
||||
striped: false,
|
||||
|
||||
slider_trailing_fill: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -941,7 +918,6 @@ impl Style {
|
|||
override_font_id,
|
||||
override_text_style,
|
||||
text_styles,
|
||||
drag_value_text_style,
|
||||
wrap: _,
|
||||
spacing,
|
||||
interaction,
|
||||
|
@ -983,19 +959,6 @@ impl Style {
|
|||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Text style of DragValue:");
|
||||
crate::ComboBox::from_id_source("drag_value_text_style")
|
||||
.selected_text(drag_value_text_style.to_string())
|
||||
.show_ui(ui, |ui| {
|
||||
let all_text_styles = ui.style().text_styles();
|
||||
for style in all_text_styles {
|
||||
let text =
|
||||
crate::RichText::new(style.to_string()).text_style(style.clone());
|
||||
ui.selectable_value(drag_value_text_style, style, text);
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Animation duration:");
|
||||
ui.add(
|
||||
Slider::new(animation_time, 0.0..=1.0)
|
||||
|
@ -1053,7 +1016,6 @@ impl Spacing {
|
|||
indent_ends_with_horizontal_line,
|
||||
combo_height,
|
||||
scroll_bar_width,
|
||||
scroll_handle_min_length,
|
||||
scroll_bar_inner_margin,
|
||||
scroll_bar_outer_margin,
|
||||
} = self;
|
||||
|
@ -1086,10 +1048,6 @@ impl Spacing {
|
|||
ui.add(DragValue::new(scroll_bar_width).clamp_range(0.0..=32.0));
|
||||
ui.label("Scroll-bar width");
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(scroll_handle_min_length).clamp_range(0.0..=32.0));
|
||||
ui.label("Scroll-bar handle min length");
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(scroll_bar_inner_margin).clamp_range(0.0..=32.0));
|
||||
ui.label("Scroll-bar inner margin");
|
||||
|
@ -1339,11 +1297,8 @@ impl Visuals {
|
|||
clip_rect_margin,
|
||||
button_frame,
|
||||
collapsing_header_frame,
|
||||
indent_has_left_vline,
|
||||
|
||||
striped,
|
||||
|
||||
slider_trailing_fill,
|
||||
} = self;
|
||||
|
||||
ui.collapsing("Background Colors", |ui| {
|
||||
|
@ -1399,15 +1354,9 @@ impl Visuals {
|
|||
|
||||
ui.checkbox(button_frame, "Button has a frame");
|
||||
ui.checkbox(collapsing_header_frame, "Collapsing header has a frame");
|
||||
ui.checkbox(
|
||||
indent_has_left_vline,
|
||||
"Paint a vertical line to the left of indented regions",
|
||||
);
|
||||
|
||||
ui.checkbox(striped, "By default, add stripes to grids and tables?");
|
||||
|
||||
ui.checkbox(slider_trailing_fill, "Add trailing color to sliders");
|
||||
|
||||
ui.vertical_centered(|ui| reset_button(ui, self));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ use std::sync::Arc;
|
|||
use epaint::mutex::RwLock;
|
||||
|
||||
use crate::{
|
||||
containers::*, ecolor::*, epaint::text::Fonts, layout::*, menu::MenuState, placer::Placer,
|
||||
util::IdTypeMap, widgets::*, *,
|
||||
containers::*, ecolor::*, epaint::text::Fonts, layers::ZLayer, layout::*, menu::MenuState,
|
||||
placer::Placer, util::IdTypeMap, widgets::*, *,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -71,7 +71,13 @@ impl Ui {
|
|||
///
|
||||
/// Normally you would not use this directly, but instead use
|
||||
/// [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`].
|
||||
pub fn new(ctx: Context, layer_id: LayerId, id: Id, max_rect: Rect, clip_rect: Rect) -> Self {
|
||||
pub fn new(
|
||||
ctx: Context,
|
||||
layer_id: AreaLayerId,
|
||||
id: Id,
|
||||
max_rect: Rect,
|
||||
clip_rect: Rect,
|
||||
) -> Self {
|
||||
let style = ctx.style();
|
||||
Ui {
|
||||
id,
|
||||
|
@ -310,8 +316,19 @@ impl Ui {
|
|||
|
||||
/// Use this to paint stuff within this [`Ui`].
|
||||
#[inline]
|
||||
pub fn layer_id(&self) -> LayerId {
|
||||
self.painter().layer_id()
|
||||
pub fn area_layer_id(&self) -> AreaLayerId {
|
||||
self.painter().area_layer_id()
|
||||
}
|
||||
|
||||
#[deprecated = "Rename area_layer_id"]
|
||||
#[inline]
|
||||
pub fn layer_id(&self) -> AreaLayerId {
|
||||
self.area_layer_id()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn z_layer(&self) -> ZLayer {
|
||||
self.painter().z_layer()
|
||||
}
|
||||
|
||||
/// The height of text of this text style
|
||||
|
@ -616,7 +633,7 @@ impl Ui {
|
|||
self.ctx().interact(
|
||||
self.clip_rect(),
|
||||
self.spacing().item_spacing,
|
||||
self.layer_id(),
|
||||
self.z_layer(),
|
||||
id,
|
||||
rect,
|
||||
sense,
|
||||
|
@ -636,8 +653,14 @@ impl Ui {
|
|||
id: Id,
|
||||
sense: Sense,
|
||||
) -> Response {
|
||||
self.ctx()
|
||||
.interact_with_hovered(self.layer_id(), id, rect, sense, self.enabled, hovered)
|
||||
self.ctx().interact_with_hovered(
|
||||
self.area_layer_id(),
|
||||
id,
|
||||
rect,
|
||||
sense,
|
||||
self.enabled,
|
||||
hovered,
|
||||
)
|
||||
}
|
||||
|
||||
/// Is the pointer (mouse/touch) above this rectangle in this [`Ui`]?
|
||||
|
@ -646,7 +669,7 @@ impl Ui {
|
|||
/// if this [`Ui`] is behind some other window, this will always return `false`.
|
||||
pub fn rect_contains_pointer(&self, rect: Rect) -> bool {
|
||||
self.ctx()
|
||||
.rect_contains_pointer(self.layer_id(), self.clip_rect().intersect(rect))
|
||||
.rect_contains_pointer(self.area_layer_id(), self.clip_rect().intersect(rect))
|
||||
}
|
||||
|
||||
/// Is the pointer (mouse/touch) above this [`Ui`]?
|
||||
|
@ -942,7 +965,7 @@ impl Ui {
|
|||
pub fn allocate_painter(&mut self, desired_size: Vec2, sense: Sense) -> (Response, Painter) {
|
||||
let response = self.allocate_response(desired_size, sense);
|
||||
let clip_rect = self.clip_rect().intersect(response.rect); // Make sure we don't paint out of bounds
|
||||
let painter = Painter::new(self.ctx().clone(), self.layer_id(), clip_rect);
|
||||
let painter = Painter::new(self.ctx().clone(), self.area_layer_id(), clip_rect);
|
||||
(response, painter)
|
||||
}
|
||||
|
||||
|
@ -1572,7 +1595,7 @@ impl Ui {
|
|||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// See also [`crate::Image`] and [`crate::ImageButton`].
|
||||
/// Se also [`crate::Image`] and [`crate::ImageButton`].
|
||||
#[inline]
|
||||
pub fn image(&mut self, texture_id: impl Into<TextureId>, size: impl Into<Vec2>) -> Response {
|
||||
Image::new(texture_id, size).ui(self)
|
||||
|
@ -1734,10 +1757,10 @@ impl Ui {
|
|||
InnerResponse::new(ret, response)
|
||||
}
|
||||
|
||||
/// Redirect shapes to another paint layer.
|
||||
/// Redirect shapes to another area layer.
|
||||
pub fn with_layer_id<R>(
|
||||
&mut self,
|
||||
layer_id: LayerId,
|
||||
layer_id: AreaLayerId,
|
||||
add_contents: impl FnOnce(&mut Self) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
self.scope(|ui| {
|
||||
|
@ -1746,6 +1769,33 @@ impl Ui {
|
|||
})
|
||||
}
|
||||
|
||||
/// Set z-index and layer id at the same time
|
||||
pub fn with_z_layer<R>(
|
||||
&mut self,
|
||||
layer: ZLayer,
|
||||
add_contents: impl FnOnce(&mut Self) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
self.scope(|ui| {
|
||||
ui.painter.set_layer(layer);
|
||||
add_contents(ui)
|
||||
})
|
||||
}
|
||||
|
||||
/// Set z-index for all shapes drawn on the current layer
|
||||
///
|
||||
/// Note that this z-index is for this layer only. The draw order of area
|
||||
/// layers takes precedence over this z-index.
|
||||
pub fn with_z<R>(
|
||||
&mut self,
|
||||
z: layers::ZOrder,
|
||||
add_contents: impl FnOnce(&mut Self) -> R,
|
||||
) -> InnerResponse<R> {
|
||||
self.scope(|ui| {
|
||||
ui.painter.set_z(z);
|
||||
add_contents(ui)
|
||||
})
|
||||
}
|
||||
|
||||
/// A [`CollapsingHeader`] that starts out collapsed.
|
||||
pub fn collapsing<R>(
|
||||
&mut self,
|
||||
|
@ -1789,32 +1839,25 @@ impl Ui {
|
|||
};
|
||||
let ret = add_contents(&mut child_ui);
|
||||
|
||||
let left_vline = self.visuals().indent_has_left_vline;
|
||||
let end_with_horizontal_line = self.spacing().indent_ends_with_horizontal_line;
|
||||
|
||||
if left_vline || end_with_horizontal_line {
|
||||
if end_with_horizontal_line {
|
||||
child_ui.add_space(4.0);
|
||||
}
|
||||
|
||||
// draw a faint line on the left to mark the indented section
|
||||
let stroke = self.visuals().widgets.noninteractive.bg_stroke;
|
||||
let left_top = child_rect.min - 0.5 * indent * Vec2::X;
|
||||
let left_top = self.painter().round_pos_to_pixels(left_top);
|
||||
let left_bottom = pos2(left_top.x, child_ui.min_rect().bottom() - 2.0);
|
||||
let left_bottom = self.painter().round_pos_to_pixels(left_bottom);
|
||||
|
||||
if left_vline {
|
||||
// draw a faint line on the left to mark the indented section
|
||||
self.painter.line_segment([left_top, left_bottom], stroke);
|
||||
}
|
||||
|
||||
if end_with_horizontal_line {
|
||||
let fudge = 2.0; // looks nicer with button rounding in collapsing headers
|
||||
let right_bottom = pos2(child_ui.min_rect().right() - fudge, left_bottom.y);
|
||||
self.painter
|
||||
.line_segment([left_bottom, right_bottom], stroke);
|
||||
}
|
||||
}
|
||||
|
||||
let response = self.allocate_rect(child_ui.min_rect(), Sense::hover());
|
||||
InnerResponse::new(ret, response)
|
||||
|
@ -2174,6 +2217,7 @@ impl Ui {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Create a menu button with an image that when clicked will show the given menu.
|
||||
///
|
||||
/// If called from within a menu this will instead create a button for a sub-menu.
|
||||
|
@ -2197,7 +2241,6 @@ impl Ui {
|
|||
/// ```
|
||||
///
|
||||
/// See also: [`Self::close_menu`] and [`Response::context_menu`].
|
||||
#[inline]
|
||||
pub fn menu_image_button<R>(
|
||||
&mut self,
|
||||
texture_id: TextureId,
|
||||
|
|
|
@ -269,10 +269,6 @@ impl<'a> Checkbox<'a> {
|
|||
text: text.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn without_text(checked: &'a mut bool) -> Self {
|
||||
Self::new(checked, WidgetText::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for Checkbox<'a> {
|
||||
|
|
|
@ -456,28 +456,19 @@ impl<'a> Widget for DragValue<'a> {
|
|||
}
|
||||
};
|
||||
|
||||
let text_style = ui.style().drag_value_text_style.clone();
|
||||
|
||||
// some clones below are redundant if AccessKit is disabled
|
||||
#[allow(clippy::redundant_clone)]
|
||||
let mut response = if is_kb_editing {
|
||||
let button_width = ui.spacing().interact_size.x;
|
||||
let mut value_text = ui
|
||||
.memory_mut(|mem| mem.drag_value.edit_string.take())
|
||||
.unwrap_or_else(|| value_text.clone());
|
||||
let response = ui.add(
|
||||
TextEdit::singleline(&mut value_text)
|
||||
.clip_text(false)
|
||||
.horizontal_align(ui.layout().horizontal_align())
|
||||
.vertical_align(ui.layout().vertical_align())
|
||||
.margin(ui.spacing().button_padding)
|
||||
.min_size(ui.spacing().interact_size)
|
||||
.id(id)
|
||||
.desired_width(ui.spacing().interact_size.x)
|
||||
.font(text_style),
|
||||
.desired_width(button_width)
|
||||
.font(TextStyle::Monospace),
|
||||
);
|
||||
// Only update the value when the user presses enter, or clicks elsewhere. NOT every frame.
|
||||
// See https://github.com/emilk/egui/issues/2687
|
||||
if response.lost_focus() {
|
||||
let parsed_value = match custom_parser {
|
||||
Some(parser) => parser(&value_text),
|
||||
None => value_text.parse().ok(),
|
||||
|
@ -486,13 +477,11 @@ impl<'a> Widget for DragValue<'a> {
|
|||
let parsed_value = clamp_to_range(parsed_value, clamp_range.clone());
|
||||
set(&mut get_set_value, parsed_value);
|
||||
}
|
||||
}
|
||||
ui.memory_mut(|mem| mem.drag_value.edit_string = Some(value_text));
|
||||
response
|
||||
} else {
|
||||
let button = Button::new(
|
||||
RichText::new(format!("{}{}{}", prefix, value_text.clone(), suffix))
|
||||
.text_style(text_style),
|
||||
RichText::new(format!("{}{}{}", prefix, value_text.clone(), suffix)).monospace(),
|
||||
)
|
||||
.wrap(false)
|
||||
.sense(Sense::click_and_drag())
|
||||
|
@ -515,12 +504,6 @@ impl<'a> Widget for DragValue<'a> {
|
|||
mem.drag_value.edit_string = None;
|
||||
mem.request_focus(id);
|
||||
});
|
||||
let mut state = TextEdit::load_state(ui.ctx(), id).unwrap_or_default();
|
||||
state.set_ccursor_range(Some(text::CCursorRange::two(
|
||||
epaint::text::cursor::CCursor::default(),
|
||||
epaint::text::cursor::CCursor::new(value_text.chars().count()),
|
||||
)));
|
||||
state.store(ui.ctx(), response.id);
|
||||
} else if response.dragged() {
|
||||
ui.ctx().set_cursor_icon(CursorIcon::ResizeHorizontal);
|
||||
|
||||
|
@ -565,28 +548,28 @@ impl<'a> Widget for DragValue<'a> {
|
|||
response.widget_info(|| WidgetInfo::drag_value(value));
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
ui.ctx().accesskit_node_builder(response.id, |builder| {
|
||||
ui.ctx().accesskit_node(response.id, |node| {
|
||||
use accesskit::Action;
|
||||
// If either end of the range is unbounded, it's better
|
||||
// to leave the corresponding AccessKit field set to None,
|
||||
// to allow for platform-specific default behavior.
|
||||
if clamp_range.start().is_finite() {
|
||||
builder.set_min_numeric_value(*clamp_range.start());
|
||||
node.min_numeric_value = Some(*clamp_range.start());
|
||||
}
|
||||
if clamp_range.end().is_finite() {
|
||||
builder.set_max_numeric_value(*clamp_range.end());
|
||||
node.max_numeric_value = Some(*clamp_range.end());
|
||||
}
|
||||
builder.set_numeric_value_step(speed);
|
||||
builder.add_action(Action::SetValue);
|
||||
node.numeric_value_step = Some(speed);
|
||||
node.actions |= Action::SetValue;
|
||||
if value < *clamp_range.end() {
|
||||
builder.add_action(Action::Increment);
|
||||
node.actions |= Action::Increment;
|
||||
}
|
||||
if value > *clamp_range.start() {
|
||||
builder.add_action(Action::Decrement);
|
||||
node.actions |= Action::Decrement;
|
||||
}
|
||||
// The name field is set to the current value by the button,
|
||||
// but we don't want it set that way on this widget type.
|
||||
builder.clear_name();
|
||||
node.name = None;
|
||||
// Always expose the value as a string. This makes the widget
|
||||
// more stable to accessibility users as it switches
|
||||
// between edit and button modes. This is particularly important
|
||||
|
@ -607,7 +590,7 @@ impl<'a> Widget for DragValue<'a> {
|
|||
// when in edit mode.
|
||||
if !is_kb_editing {
|
||||
let value_text = format!("{}{}{}", prefix, value_text, suffix);
|
||||
builder.set_value(value_text);
|
||||
node.value = Some(value_text.into());
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ impl Widget for Label {
|
|||
if ui.is_rect_visible(response.rect) {
|
||||
let response_color = ui.style().interact(&response).text_color();
|
||||
|
||||
let underline = if response.has_focus() || response.highlighted() {
|
||||
let underline = if response.has_focus() {
|
||||
Stroke::new(1.0, response_color)
|
||||
} else {
|
||||
Stroke::NONE
|
||||
|
|
|
@ -742,7 +742,7 @@ impl Plot {
|
|||
});
|
||||
|
||||
let PlotMemory {
|
||||
bounds_modified,
|
||||
mut bounds_modified,
|
||||
mut hovered_entry,
|
||||
mut hidden_items,
|
||||
last_screen_transform,
|
||||
|
@ -754,7 +754,6 @@ impl Plot {
|
|||
items: Vec::new(),
|
||||
next_auto_color_idx: 0,
|
||||
last_screen_transform,
|
||||
bounds_modified,
|
||||
response,
|
||||
ctx: ui.ctx().clone(),
|
||||
};
|
||||
|
@ -763,7 +762,6 @@ impl Plot {
|
|||
mut items,
|
||||
mut response,
|
||||
last_screen_transform,
|
||||
mut bounds_modified,
|
||||
..
|
||||
} = plot_ui;
|
||||
|
||||
|
@ -1044,7 +1042,6 @@ pub struct PlotUi {
|
|||
items: Vec<Box<dyn PlotItem>>,
|
||||
next_auto_color_idx: usize,
|
||||
last_screen_transform: ScreenTransform,
|
||||
bounds_modified: AxisBools,
|
||||
response: Response,
|
||||
ctx: Context,
|
||||
}
|
||||
|
@ -1072,13 +1069,11 @@ impl PlotUi {
|
|||
/// Set the plot bounds. Can be useful for implementing alternative plot navigation methods.
|
||||
pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) {
|
||||
self.last_screen_transform.set_bounds(plot_bounds);
|
||||
self.bounds_modified = true.into();
|
||||
}
|
||||
|
||||
/// Move the plot bounds. Can be useful for implementing alternative plot navigation methods.
|
||||
pub fn translate_bounds(&mut self, delta_pos: Vec2) {
|
||||
self.last_screen_transform.translate_bounds(delta_pos);
|
||||
self.bounds_modified = true.into();
|
||||
}
|
||||
|
||||
/// Returns `true` if the plot area is currently hovered.
|
||||
|
|
|
@ -61,7 +61,7 @@ impl Widget for SelectableLabel {
|
|||
|
||||
let visuals = ui.style().interact_selectable(&response, selected);
|
||||
|
||||
if selected || response.hovered() || response.highlighted() || response.has_focus() {
|
||||
if selected || response.hovered() || response.has_focus() {
|
||||
let rect = rect.expand(visuals.expansion);
|
||||
|
||||
ui.painter().rect(
|
||||
|
|
|
@ -14,7 +14,6 @@ use crate::*;
|
|||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||
pub struct Separator {
|
||||
spacing: f32,
|
||||
grow: f32,
|
||||
is_horizontal_line: Option<bool>,
|
||||
}
|
||||
|
||||
|
@ -22,7 +21,6 @@ impl Default for Separator {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
spacing: 6.0,
|
||||
grow: 0.0,
|
||||
is_horizontal_line: None,
|
||||
}
|
||||
}
|
||||
|
@ -30,19 +28,12 @@ impl Default for Separator {
|
|||
|
||||
impl Separator {
|
||||
/// How much space we take up. The line is painted in the middle of this.
|
||||
///
|
||||
/// In a vertical layout, with a horizontal Separator,
|
||||
/// this is the height of the separator widget.
|
||||
///
|
||||
/// In a horizontal layout, with a vertical Separator,
|
||||
/// this is the width of the separator widget.
|
||||
pub fn spacing(mut self, spacing: f32) -> Self {
|
||||
self.spacing = spacing;
|
||||
self
|
||||
}
|
||||
|
||||
/// Explicitly ask for a horizontal line.
|
||||
///
|
||||
/// By default you will get a horizontal line in vertical layouts,
|
||||
/// and a vertical line in horizontal layouts.
|
||||
pub fn horizontal(mut self) -> Self {
|
||||
|
@ -51,40 +42,18 @@ impl Separator {
|
|||
}
|
||||
|
||||
/// Explicitly ask for a vertical line.
|
||||
///
|
||||
/// By default you will get a horizontal line in vertical layouts,
|
||||
/// and a vertical line in horizontal layouts.
|
||||
pub fn vertical(mut self) -> Self {
|
||||
self.is_horizontal_line = Some(false);
|
||||
self
|
||||
}
|
||||
|
||||
/// Extend each end of the separator line by this much.
|
||||
///
|
||||
/// The default is to take up the available width/height of the parent.
|
||||
///
|
||||
/// This will make the line extend outside the parent ui.
|
||||
pub fn grow(mut self, extra: f32) -> Self {
|
||||
self.grow += extra;
|
||||
self
|
||||
}
|
||||
|
||||
/// Contract each end of the separator line by this much.
|
||||
///
|
||||
/// The default is to take up the available width/height of the parent.
|
||||
///
|
||||
/// This effectively adds margins to the line.
|
||||
pub fn shrink(mut self, shrink: f32) -> Self {
|
||||
self.grow -= shrink;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Separator {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let Separator {
|
||||
spacing,
|
||||
grow,
|
||||
is_horizontal_line,
|
||||
} = self;
|
||||
|
||||
|
@ -106,14 +75,14 @@ impl Widget for Separator {
|
|||
let painter = ui.painter();
|
||||
if is_horizontal_line {
|
||||
painter.hline(
|
||||
(rect.left() - grow)..=(rect.right() + grow),
|
||||
rect.x_range(),
|
||||
painter.round_to_pixel(rect.center().y),
|
||||
stroke,
|
||||
);
|
||||
} else {
|
||||
painter.vline(
|
||||
painter.round_to_pixel(rect.center().x),
|
||||
(rect.top() - grow)..=(rect.bottom() + grow),
|
||||
rect.y_range(),
|
||||
stroke,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -84,7 +84,6 @@ pub struct Slider<'a> {
|
|||
max_decimals: Option<usize>,
|
||||
custom_formatter: Option<NumFormatter<'a>>,
|
||||
custom_parser: Option<NumParser<'a>>,
|
||||
trailing_fill: Option<bool>,
|
||||
}
|
||||
|
||||
impl<'a> Slider<'a> {
|
||||
|
@ -130,7 +129,6 @@ impl<'a> Slider<'a> {
|
|||
max_decimals: None,
|
||||
custom_formatter: None,
|
||||
custom_parser: None,
|
||||
trailing_fill: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -271,17 +269,6 @@ impl<'a> Slider<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Display trailing color behind the slider's circle. Default is OFF.
|
||||
///
|
||||
/// This setting can be enabled globally for all sliders with [`Visuals::slider_trailing_fill`].
|
||||
/// Toggling it here will override the above setting ONLY for this individual slider.
|
||||
///
|
||||
/// The fill color will be taken from `selection.bg_fill` in your [`Visuals`], the same as a [`ProgressBar`].
|
||||
pub fn trailing_fill(mut self, trailing_fill: bool) -> Self {
|
||||
self.trailing_fill = Some(trailing_fill);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set custom formatter defining how numbers are converted into text.
|
||||
///
|
||||
/// A custom formatter takes a `f64` for the numeric value and a `RangeInclusive<usize>` representing
|
||||
|
@ -629,40 +616,18 @@ impl<'a> Slider<'a> {
|
|||
let rail_radius = ui.painter().round_to_pixel(self.rail_radius_limit(rect));
|
||||
let rail_rect = self.rail_rect(rect, rail_radius);
|
||||
|
||||
let visuals = ui.style().interact(response);
|
||||
let widget_visuals = &ui.visuals().widgets;
|
||||
|
||||
ui.painter().rect_filled(
|
||||
rail_rect,
|
||||
widget_visuals.inactive.rounding,
|
||||
widget_visuals.inactive.bg_fill,
|
||||
);
|
||||
|
||||
let position_1d = self.position_from_value(value, position_range);
|
||||
|
||||
let visuals = ui.style().interact(response);
|
||||
ui.painter().add(epaint::RectShape {
|
||||
rect: rail_rect,
|
||||
rounding: ui.visuals().widgets.inactive.rounding,
|
||||
fill: ui.visuals().widgets.inactive.bg_fill,
|
||||
stroke: Default::default(),
|
||||
});
|
||||
|
||||
let center = self.marker_center(position_1d, &rail_rect);
|
||||
|
||||
// Decide if we should add trailing fill.
|
||||
let trailing_fill = self
|
||||
.trailing_fill
|
||||
.unwrap_or_else(|| ui.visuals().slider_trailing_fill);
|
||||
|
||||
// Paint trailing fill.
|
||||
if trailing_fill {
|
||||
let mut trailing_rail_rect = rail_rect;
|
||||
|
||||
// The trailing rect has to be drawn differently depending on the orientation.
|
||||
match self.orientation {
|
||||
SliderOrientation::Vertical => trailing_rail_rect.min.y = center.y,
|
||||
SliderOrientation::Horizontal => trailing_rail_rect.max.x = center.x,
|
||||
};
|
||||
|
||||
ui.painter().rect_filled(
|
||||
trailing_rail_rect,
|
||||
widget_visuals.inactive.rounding,
|
||||
ui.visuals().selection.bg_fill,
|
||||
);
|
||||
}
|
||||
|
||||
ui.painter().add(epaint::CircleShape {
|
||||
center,
|
||||
radius: self.handle_radius(rect) + visuals.expansion,
|
||||
|
@ -792,20 +757,18 @@ impl<'a> Slider<'a> {
|
|||
response.widget_info(|| WidgetInfo::slider(value, self.text.text()));
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
ui.ctx().accesskit_node_builder(response.id, |builder| {
|
||||
ui.ctx().accesskit_node(response.id, |node| {
|
||||
use accesskit::Action;
|
||||
builder.set_min_numeric_value(*self.range.start());
|
||||
builder.set_max_numeric_value(*self.range.end());
|
||||
if let Some(step) = self.step {
|
||||
builder.set_numeric_value_step(step);
|
||||
}
|
||||
builder.add_action(Action::SetValue);
|
||||
node.min_numeric_value = Some(*self.range.start());
|
||||
node.max_numeric_value = Some(*self.range.end());
|
||||
node.numeric_value_step = self.step;
|
||||
node.actions |= Action::SetValue;
|
||||
let clamp_range = self.clamp_range();
|
||||
if value < *clamp_range.end() {
|
||||
builder.add_action(Action::Increment);
|
||||
node.actions |= Action::Increment;
|
||||
}
|
||||
if value > *clamp_range.start() {
|
||||
builder.add_action(Action::Decrement);
|
||||
node.actions |= Action::Decrement;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -67,9 +67,6 @@ pub struct TextEdit<'t> {
|
|||
desired_height_rows: usize,
|
||||
lock_focus: bool,
|
||||
cursor_at_end: bool,
|
||||
min_size: Vec2,
|
||||
align: Align2,
|
||||
clip_text: bool,
|
||||
}
|
||||
|
||||
impl<'t> WidgetWithState for TextEdit<'t> {
|
||||
|
@ -92,7 +89,6 @@ impl<'t> TextEdit<'t> {
|
|||
Self {
|
||||
desired_height_rows: 1,
|
||||
multiline: false,
|
||||
clip_text: true,
|
||||
..Self::multiline(text)
|
||||
}
|
||||
}
|
||||
|
@ -116,9 +112,6 @@ impl<'t> TextEdit<'t> {
|
|||
desired_height_rows: 4,
|
||||
lock_focus: false,
|
||||
cursor_at_end: true,
|
||||
min_size: Vec2::ZERO,
|
||||
align: Align2::LEFT_TOP,
|
||||
clip_text: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,37 +269,6 @@ impl<'t> TextEdit<'t> {
|
|||
self.cursor_at_end = b;
|
||||
self
|
||||
}
|
||||
|
||||
/// When `true` (default), overflowing text will be clipped.
|
||||
///
|
||||
/// When `false`, widget width will expand to make all text visible.
|
||||
///
|
||||
/// This only works for singleline [`TextEdit`].
|
||||
pub fn clip_text(mut self, b: bool) -> Self {
|
||||
// always show everything in multiline
|
||||
if !self.multiline {
|
||||
self.clip_text = b;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the horizontal align of the inner text.
|
||||
pub fn horizontal_align(mut self, align: Align) -> Self {
|
||||
self.align.0[0] = align;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the vertical align of the inner text.
|
||||
pub fn vertical_align(mut self, align: Align) -> Self {
|
||||
self.align.0[1] = align;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the minimum size of the [`TextEdit`].
|
||||
pub fn min_size(mut self, min_size: Vec2) -> Self {
|
||||
self.min_size = min_size;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -402,16 +364,13 @@ impl<'t> TextEdit<'t> {
|
|||
layouter,
|
||||
password,
|
||||
frame: _,
|
||||
margin,
|
||||
margin: _,
|
||||
multiline,
|
||||
interactive,
|
||||
desired_width,
|
||||
desired_height_rows,
|
||||
lock_focus,
|
||||
cursor_at_end,
|
||||
min_size,
|
||||
align,
|
||||
clip_text,
|
||||
} = self;
|
||||
|
||||
let text_color = text_color
|
||||
|
@ -430,7 +389,7 @@ impl<'t> TextEdit<'t> {
|
|||
available_width
|
||||
} else {
|
||||
desired_width.min(available_width)
|
||||
} - margin.x * 2.0;
|
||||
};
|
||||
|
||||
let font_id_clone = font_id.clone();
|
||||
let mut default_layouter = move |ui: &Ui, text: &str, wrap_width: f32| {
|
||||
|
@ -447,14 +406,13 @@ impl<'t> TextEdit<'t> {
|
|||
|
||||
let mut galley = layouter(ui, text.as_str(), wrap_width);
|
||||
|
||||
let desired_width = if clip_text {
|
||||
wrap_width // visual clipping with scroll in singleline input.
|
||||
let desired_width = if multiline {
|
||||
galley.size().x.max(wrap_width) // always show everything in multiline
|
||||
} else {
|
||||
galley.size().x.max(wrap_width)
|
||||
wrap_width // visual clipping with scroll in singleline input. TODO(emilk): opt-in/out?
|
||||
};
|
||||
let desired_height = (desired_height_rows.at_least(1) as f32) * row_height;
|
||||
let desired_size = vec2(desired_width, galley.size().y.max(desired_height))
|
||||
.at_least(min_size - margin * 2.0);
|
||||
let desired_size = vec2(desired_width, galley.size().y.max(desired_height));
|
||||
|
||||
let (auto_id, rect) = ui.allocate_space(desired_size);
|
||||
|
||||
|
@ -589,14 +547,10 @@ impl<'t> TextEdit<'t> {
|
|||
cursor_range = Some(new_cursor_range);
|
||||
}
|
||||
|
||||
let mut text_draw_pos = align
|
||||
.align_size_within_rect(galley.size(), response.rect)
|
||||
.intersect(response.rect) // limit pos to the response rect area
|
||||
.min;
|
||||
let align_offset = response.rect.left() - text_draw_pos.x;
|
||||
let mut text_draw_pos = response.rect.min;
|
||||
|
||||
// Visual clipping for singleline text editor with text larger than width
|
||||
if clip_text && align_offset == 0.0 {
|
||||
if !multiline {
|
||||
let cursor_pos = match (cursor_range, ui.memory(|mem| mem.has_focus(id))) {
|
||||
(Some(cursor_range), true) => galley.pos_from_cursor(&cursor_range.primary).min.x,
|
||||
_ => 0.0,
|
||||
|
@ -619,8 +573,6 @@ impl<'t> TextEdit<'t> {
|
|||
|
||||
state.singleline_offset = offset_x;
|
||||
text_draw_pos -= vec2(offset_x, 0.0);
|
||||
} else {
|
||||
state.singleline_offset = align_offset;
|
||||
}
|
||||
|
||||
let selection_changed = if let (Some(cursor_range), Some(prev_cursor_range)) =
|
||||
|
@ -714,7 +666,7 @@ impl<'t> TextEdit<'t> {
|
|||
|
||||
#[cfg(feature = "accesskit")]
|
||||
{
|
||||
let parent_id = ui.ctx().accesskit_node_builder(response.id, |builder| {
|
||||
let parent_id = ui.ctx().accesskit_node(response.id, |node| {
|
||||
use accesskit::{TextPosition, TextSelection};
|
||||
|
||||
let parent_id = response.id;
|
||||
|
@ -722,7 +674,7 @@ impl<'t> TextEdit<'t> {
|
|||
if let Some(cursor_range) = &cursor_range {
|
||||
let anchor = &cursor_range.secondary.rcursor;
|
||||
let focus = &cursor_range.primary.rcursor;
|
||||
builder.set_text_selection(TextSelection {
|
||||
node.text_selection = Some(TextSelection {
|
||||
anchor: TextPosition {
|
||||
node: parent_id.with(anchor.row).accesskit_id(),
|
||||
character_index: anchor.column,
|
||||
|
@ -734,10 +686,8 @@ impl<'t> TextEdit<'t> {
|
|||
});
|
||||
}
|
||||
|
||||
builder.set_default_action_verb(accesskit::DefaultActionVerb::Focus);
|
||||
if self.multiline {
|
||||
builder.set_multiline();
|
||||
}
|
||||
node.default_action_verb = Some(accesskit::DefaultActionVerb::Focus);
|
||||
node.multiline = self.multiline;
|
||||
|
||||
parent_id
|
||||
});
|
||||
|
@ -749,16 +699,16 @@ impl<'t> TextEdit<'t> {
|
|||
ui.ctx().with_accessibility_parent(parent_id, || {
|
||||
for (i, row) in galley.rows.iter().enumerate() {
|
||||
let id = parent_id.with(i);
|
||||
ui.ctx().accesskit_node_builder(id, |builder| {
|
||||
builder.set_role(Role::InlineTextBox);
|
||||
ui.ctx().accesskit_node(id, |node| {
|
||||
node.role = Role::InlineTextBox;
|
||||
let rect = row.rect.translate(text_draw_pos.to_vec2());
|
||||
builder.set_bounds(accesskit::Rect {
|
||||
node.bounds = Some(accesskit::kurbo::Rect {
|
||||
x0: rect.min.x.into(),
|
||||
y0: rect.min.y.into(),
|
||||
x1: rect.max.x.into(),
|
||||
y1: rect.max.y.into(),
|
||||
});
|
||||
builder.set_text_direction(TextDirection::LeftToRight);
|
||||
node.text_direction = Some(TextDirection::LeftToRight);
|
||||
// TODO(mwcampbell): Set more node fields for the row
|
||||
// once AccessKit adapters expose text formatting info.
|
||||
|
||||
|
@ -798,11 +748,11 @@ impl<'t> TextEdit<'t> {
|
|||
}
|
||||
word_lengths.push((character_lengths.len() - last_word_start) as _);
|
||||
|
||||
builder.set_value(value);
|
||||
builder.set_character_lengths(character_lengths);
|
||||
builder.set_character_positions(character_positions);
|
||||
builder.set_character_widths(character_widths);
|
||||
builder.set_word_lengths(word_lengths);
|
||||
node.value = Some(value.into());
|
||||
node.character_lengths = character_lengths.into();
|
||||
node.character_positions = Some(character_positions.into());
|
||||
node.character_widths = Some(character_widths.into());
|
||||
node.word_lengths = word_lengths.into();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "egui_demo_app"
|
||||
version = "0.21.0"
|
||||
version = "0.20.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
|
@ -20,7 +20,7 @@ default = ["glow", "persistence"]
|
|||
|
||||
http = ["ehttp", "image", "poll-promise", "egui_extras/image"]
|
||||
persistence = ["eframe/persistence", "egui/persistence", "serde"]
|
||||
web_screen_reader = ["eframe/web_screen_reader"] # experimental
|
||||
screen_reader = ["eframe/screen_reader"] # experimental
|
||||
serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"]
|
||||
syntax_highlighting = ["egui_demo_lib/syntax_highlighting"]
|
||||
|
||||
|
@ -30,11 +30,11 @@ wgpu = ["eframe/wgpu", "bytemuck"]
|
|||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4", features = ["js-sys", "wasmbind"] }
|
||||
eframe = { version = "0.21.0", path = "../eframe", default-features = false }
|
||||
egui = { version = "0.21.0", path = "../egui", features = [
|
||||
eframe = { version = "0.20.0", path = "../eframe", default-features = false }
|
||||
egui = { version = "0.20.0", path = "../egui", features = [
|
||||
"extra_debug_asserts",
|
||||
] }
|
||||
egui_demo_lib = { version = "0.21.0", path = "../egui_demo_lib", features = [
|
||||
egui_demo_lib = { version = "0.20.0", path = "../egui_demo_lib", features = [
|
||||
"chrono",
|
||||
] }
|
||||
tracing = "0.1"
|
||||
|
@ -42,7 +42,7 @@ tracing = "0.1"
|
|||
# Optional dependencies:
|
||||
|
||||
bytemuck = { version = "1.7.1", optional = true }
|
||||
egui_extras = { version = "0.21.0", optional = true, path = "../egui_extras" }
|
||||
egui_extras = { version = "0.20.0", optional = true, path = "../egui_extras" }
|
||||
|
||||
# feature "http":
|
||||
ehttp = { version = "0.2.0", optional = true }
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use std::{num::NonZeroU64, sync::Arc};
|
||||
|
||||
use eframe::{
|
||||
egui_wgpu::wgpu::util::DeviceExt,
|
||||
egui_wgpu::{self, wgpu},
|
||||
wgpu::util::DeviceExt,
|
||||
};
|
||||
|
||||
pub struct Custom3d {
|
||||
|
|
|
@ -41,7 +41,7 @@ impl FractalClock {
|
|||
|
||||
let painter = Painter::new(
|
||||
ui.ctx().clone(),
|
||||
ui.layer_id(),
|
||||
ui.area_layer_id(),
|
||||
ui.available_rect_before_wrap(),
|
||||
);
|
||||
self.paint(&painter);
|
||||
|
|
|
@ -130,8 +130,6 @@ impl BackendPanel {
|
|||
|
||||
ui.separator();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(feature = "web_screen-reader")]
|
||||
{
|
||||
let mut screen_reader = ui.ctx().options(|o| o.screen_reader);
|
||||
ui.checkbox(&mut screen_reader, "🔈 Screen reader").on_hover_text("Experimental feature: checking this will turn on the screen reader on supported platforms");
|
||||
|
|
|
@ -175,8 +175,8 @@ impl eframe::App for WrapApp {
|
|||
eframe::set_value(storage, eframe::APP_KEY, &self.state);
|
||||
}
|
||||
|
||||
fn clear_color(&self, visuals: &egui::Visuals) -> [f32; 4] {
|
||||
visuals.panel_fill.to_normalized_gamma_f32()
|
||||
fn clear_color(&self, visuals: &egui::Visuals) -> egui::Rgba {
|
||||
visuals.panel_fill.into()
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
|
@ -355,8 +355,10 @@ impl WrapApp {
|
|||
text
|
||||
});
|
||||
|
||||
let painter =
|
||||
ctx.layer_painter(LayerId::new(Order::Foreground, Id::new("file_drop_target")));
|
||||
let painter = ctx.layer_painter(AreaLayerId::new(
|
||||
Order::Foreground,
|
||||
Id::new("file_drop_target"),
|
||||
));
|
||||
|
||||
let screen_rect = ctx.screen_rect();
|
||||
painter.rect_filled(screen_rect, 0.0, Color32::from_black_alpha(192));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "egui_demo_lib"
|
||||
version = "0.21.0"
|
||||
version = "0.20.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Example library for egui"
|
||||
edition = "2021"
|
||||
|
@ -30,8 +30,8 @@ syntax_highlighting = ["syntect"]
|
|||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.21.0", path = "../egui", default-features = false }
|
||||
egui_extras = { version = "0.21.0", path = "../egui_extras" }
|
||||
egui = { version = "0.20.0", path = "../egui", default-features = false }
|
||||
egui_extras = { version = "0.20.0", path = "../egui_extras" }
|
||||
enum-map = { version = "2", features = ["serde"] }
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
unicode_names2 = { version = "0.6.0", default-features = false }
|
||||
|
|
|
@ -90,7 +90,6 @@ impl Default for Tests {
|
|||
fn default() -> Self {
|
||||
Self::from_demos(vec![
|
||||
Box::new(super::tests::CursorTest::default()),
|
||||
Box::new(super::highlighting::Highlighting::default()),
|
||||
Box::new(super::tests::IdTest::default()),
|
||||
Box::new(super::tests::InputTest::default()),
|
||||
Box::new(super::layout_test::LayoutTest::default()),
|
||||
|
|
|
@ -15,7 +15,7 @@ pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) {
|
|||
ui.ctx().set_cursor_icon(CursorIcon::Grabbing);
|
||||
|
||||
// Paint the body to a new layer:
|
||||
let layer_id = LayerId::new(Order::Tooltip, id);
|
||||
let layer_id = AreaLayerId::new(Order::Tooltip, id);
|
||||
let response = ui.with_layer_id(layer_id, body).response;
|
||||
|
||||
// Now we move the visuals of the body to where the mouse is.
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct Highlighting {}
|
||||
|
||||
impl super::Demo for Highlighting {
|
||||
fn name(&self) -> &'static str {
|
||||
"Highlighting"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.default_width(320.0)
|
||||
.open(open)
|
||||
.show(ctx, |ui| {
|
||||
use super::View as _;
|
||||
self.ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl super::View for Highlighting {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label("This demo demonstrates highlighting a widget.");
|
||||
ui.add_space(4.0);
|
||||
let label_response = ui.label("Hover me to highlight the button!");
|
||||
ui.add_space(4.0);
|
||||
let mut button_response = ui.button("Hover the button to highlight the label!");
|
||||
|
||||
if label_response.hovered() {
|
||||
button_response = button_response.highlight();
|
||||
}
|
||||
if button_response.hovered() {
|
||||
label_response.highlight();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ pub mod dancing_strings;
|
|||
pub mod demo_app_windows;
|
||||
pub mod drag_and_drop;
|
||||
pub mod font_book;
|
||||
pub mod highlighting;
|
||||
pub mod layout_test;
|
||||
pub mod misc_demo_window;
|
||||
pub mod multi_touch;
|
||||
|
|
|
@ -16,7 +16,6 @@ pub struct Sliders {
|
|||
pub integer: bool,
|
||||
pub vertical: bool,
|
||||
pub value: f64,
|
||||
pub trailing_fill: bool,
|
||||
}
|
||||
|
||||
impl Default for Sliders {
|
||||
|
@ -32,7 +31,6 @@ impl Default for Sliders {
|
|||
integer: false,
|
||||
vertical: false,
|
||||
value: 10.0,
|
||||
trailing_fill: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +64,6 @@ impl super::View for Sliders {
|
|||
integer,
|
||||
vertical,
|
||||
value,
|
||||
trailing_fill,
|
||||
} = self;
|
||||
|
||||
ui.label("You can click a slider value to edit it with the keyboard.");
|
||||
|
@ -98,8 +95,7 @@ impl super::View for Sliders {
|
|||
.smart_aim(*smart_aim)
|
||||
.orientation(orientation)
|
||||
.text("i32 demo slider")
|
||||
.step_by(istep)
|
||||
.trailing_fill(*trailing_fill),
|
||||
.step_by(istep),
|
||||
);
|
||||
*value = value_i32 as f64;
|
||||
} else {
|
||||
|
@ -110,8 +106,7 @@ impl super::View for Sliders {
|
|||
.smart_aim(*smart_aim)
|
||||
.orientation(orientation)
|
||||
.text("f64 demo slider")
|
||||
.step_by(istep)
|
||||
.trailing_fill(*trailing_fill),
|
||||
.step_by(istep),
|
||||
);
|
||||
|
||||
ui.label(
|
||||
|
@ -131,24 +126,17 @@ impl super::View for Sliders {
|
|||
Slider::new(min, type_min..=type_max)
|
||||
.logarithmic(true)
|
||||
.smart_aim(*smart_aim)
|
||||
.text("left")
|
||||
.trailing_fill(*trailing_fill),
|
||||
.text("left"),
|
||||
);
|
||||
ui.add(
|
||||
Slider::new(max, type_min..=type_max)
|
||||
.logarithmic(true)
|
||||
.smart_aim(*smart_aim)
|
||||
.text("right")
|
||||
.trailing_fill(*trailing_fill),
|
||||
.text("right"),
|
||||
);
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.checkbox(trailing_fill, "Toggle trailing color");
|
||||
ui.label("When enabled, trailing color will be painted up until the circle.");
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.checkbox(use_steps, "Use steps");
|
||||
ui.label("When enabled, the minimal value change would be restricted to a given step.");
|
||||
if *use_steps {
|
||||
|
|
|
@ -85,8 +85,6 @@ fn lorem_ipsum(ui: &mut egui::Ui) {
|
|||
egui::Layout::top_down(egui::Align::LEFT).with_cross_justify(true),
|
||||
|ui| {
|
||||
ui.label(egui::RichText::new(crate::LOREM_IPSUM_LONG).small().weak());
|
||||
ui.add(egui::Separator::default().grow(8.0));
|
||||
ui.label(egui::RichText::new(crate::LOREM_IPSUM_LONG).small().weak());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -92,8 +92,7 @@ fn test_egui_zero_window_size() {
|
|||
let clipped_primitives = ctx.tessellate(full_output.shapes);
|
||||
assert!(
|
||||
clipped_primitives.is_empty(),
|
||||
"There should be nothing to show, has at least one primitive with clip_rect: {:?}",
|
||||
clipped_primitives[0].clip_rect
|
||||
"There should be nothing to show"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,6 @@ All notable changes to the `egui_extras` integration will be noted in this file.
|
|||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08
|
||||
* Update to egui 0.21
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08
|
||||
* Added `RetainedImage::from_svg_bytes_with_size` to be able to specify a size for SVGs to be rasterized at.
|
||||
* Lots of `Table` improvements ([#2369](https://github.com/emilk/egui/pull/2369)):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "egui_extras"
|
||||
version = "0.21.0"
|
||||
version = "0.20.0"
|
||||
authors = [
|
||||
"Dominik Rössler <dominik@freshx.de>",
|
||||
"Emil Ernerfeldt <emil.ernerfeldt@gmail.com>",
|
||||
|
@ -37,7 +37,7 @@ tracing = ["dep:tracing", "egui/tracing"]
|
|||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.21.0", path = "../egui", default-features = false }
|
||||
egui = { version = "0.20.0", path = "../egui", default-features = false }
|
||||
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
|
@ -58,9 +58,9 @@ document-features = { version = "0.2", optional = true }
|
|||
image = { version = "0.24", optional = true, default-features = false }
|
||||
|
||||
# svg feature
|
||||
resvg = { version = "0.28", optional = true, default-features = false }
|
||||
tiny-skia = { version = "0.8", optional = true, default-features = false } # must be updated in lock-step with resvg
|
||||
usvg = { version = "0.28", optional = true, default-features = false }
|
||||
resvg = { version = "0.23", optional = true }
|
||||
tiny-skia = { version = "0.6", optional = true } # must be updated in lock-step with resvg
|
||||
usvg = { version = "0.23", optional = true }
|
||||
|
||||
# feature "tracing"
|
||||
tracing = { version = "0.1", optional = true, default-features = false, features = [
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
[](https://crates.io/crates/egui_extras)
|
||||
[](https://docs.rs/egui_extras)
|
||||
[](https://github.com/rust-secure-code/safety-dance/)
|
||||

|
||||

|
||||
|
||||
|
|
|
@ -231,11 +231,12 @@ pub fn load_svg_bytes_with_size(
|
|||
svg_bytes: &[u8],
|
||||
fit_to: FitTo,
|
||||
) -> Result<egui::ColorImage, String> {
|
||||
let opt = usvg::Options::default();
|
||||
let mut opt = usvg::Options::default();
|
||||
opt.fontdb.load_system_fonts();
|
||||
|
||||
let rtree = usvg::Tree::from_data(svg_bytes, &opt).map_err(|err| err.to_string())?;
|
||||
let rtree = usvg::Tree::from_data(svg_bytes, &opt.to_ref()).map_err(|err| err.to_string())?;
|
||||
|
||||
let pixmap_size = rtree.size.to_screen_size();
|
||||
let pixmap_size = rtree.svg_node().size.to_screen_size();
|
||||
let [w, h] = match fit_to {
|
||||
FitTo::Original => [pixmap_size.width(), pixmap_size.height()],
|
||||
FitTo::Size(w, h) => [w, h],
|
||||
|
|
|
@ -3,11 +3,10 @@ All notable changes to the `egui_glium` integration will be noted in this file.
|
|||
|
||||
|
||||
## Unreleased
|
||||
* Remove the `screen_reader` feature ([#2669](https://github.com/emilk/egui/pull/2669)).
|
||||
|
||||
|
||||
## 0.20.1 - 2022-12-11
|
||||
* Fix [docs.rs](https://docs.rs/egui_glium) build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "egui_glium"
|
||||
version = "0.21.0"
|
||||
version = "0.20.1"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Bindings for using egui natively using the glium library"
|
||||
edition = "2021"
|
||||
|
@ -20,7 +20,8 @@ include = [
|
|||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
# Avoid speech-dispatcher dependencies - see https://docs.rs/crate/egui_glium/0.20.0/builds/695197
|
||||
features = ["document-features"]
|
||||
|
||||
|
||||
[features]
|
||||
|
@ -34,12 +35,15 @@ clipboard = ["egui-winit/clipboard"]
|
|||
## Enable opening links in a browser when an egui hyperlink is clicked.
|
||||
links = ["egui-winit/links"]
|
||||
|
||||
## Experimental support for a screen reader.
|
||||
screen_reader = ["egui-winit/screen_reader"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
|
||||
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
|
||||
"bytemuck",
|
||||
] }
|
||||
egui-winit = { version = "0.21.1", path = "../egui-winit", default-features = false }
|
||||
egui-winit = { version = "0.20.0", path = "../egui-winit", default-features = false }
|
||||
|
||||
ahash = { version = "0.8.1", default-features = false, features = [
|
||||
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
|
||||
|
@ -54,5 +58,5 @@ document-features = { version = "0.2", optional = true }
|
|||
|
||||
|
||||
[dev-dependencies]
|
||||
egui_demo_lib = { version = "0.21.0", path = "../egui_demo_lib", default-features = false }
|
||||
egui_demo_lib = { version = "0.20.0", path = "../egui_demo_lib", default-features = false }
|
||||
image = { version = "0.24", default-features = false, features = ["png"] }
|
||||
|
|
|
@ -11,11 +11,7 @@ This crates provides bindings between [`egui`](https://github.com/emilk/egui) an
|
|||
To use on Linux, first run:
|
||||
|
||||
```
|
||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
|
||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
|
||||
```
|
||||
|
||||
This crate depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit).
|
||||
|
||||
|
||||
## DEPRECATED - Looking for new maintainer
|
||||
This crate is no longer being updated. If you are interested in keeping `egui_glium` updated, then fork it to its own repository, make a PR to the egui repo removing it, and then I will give you access to it on crates.io so you can publish new `egui_glium` crates.
|
||||
|
|
|
@ -5,13 +5,8 @@ All notable changes to the `egui_glow` integration will be noted in this file.
|
|||
## Unreleased
|
||||
|
||||
|
||||
## 0.21.0 - 2023-02-08
|
||||
* Update to `glow` 0.12 ([#2695](https://github.com/emilk/egui/pull/2695)).
|
||||
* Remove the `screen_reader` feature ([#2669](https://github.com/emilk/egui/pull/2669)).
|
||||
|
||||
|
||||
## 0.20.1 - 2022-12-11
|
||||
* Fix [docs.rs](https://docs.rs/egui_glow) build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
* Fix docs.rs build ([#2420](https://github.com/emilk/egui/pull/2420)).
|
||||
|
||||
|
||||
## 0.20.0 - 2022-12-08
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "egui_glow"
|
||||
version = "0.21.0"
|
||||
version = "0.20.1"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Bindings for using egui natively using the glow library"
|
||||
edition = "2021"
|
||||
|
@ -20,7 +20,8 @@ include = [
|
|||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
# Avoid speech-dispatcher dependencies - see https://docs.rs/crate/egui_glow/0.20.0/builds/695194
|
||||
features = ["document-features"]
|
||||
|
||||
|
||||
[features]
|
||||
|
@ -36,6 +37,9 @@ clipboard = ["egui-winit?/clipboard"]
|
|||
## enable opening links in a browser when an egui hyperlink is clicked.
|
||||
links = ["egui-winit?/links"]
|
||||
|
||||
## Experimental support for a screen reader.
|
||||
screen_reader = ["egui-winit?/screen_reader"]
|
||||
|
||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
||||
puffin = ["dep:puffin", "egui-winit?/puffin"]
|
||||
|
||||
|
@ -44,12 +48,12 @@ winit = ["egui-winit"]
|
|||
|
||||
|
||||
[dependencies]
|
||||
egui = { version = "0.21.0", path = "../egui", default-features = false, features = [
|
||||
egui = { version = "0.20.0", path = "../egui", default-features = false, features = [
|
||||
"bytemuck",
|
||||
] }
|
||||
|
||||
bytemuck = "1.7"
|
||||
glow = "0.12"
|
||||
glow = "0.11"
|
||||
memoffset = "0.6"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
|
@ -59,7 +63,7 @@ document-features = { version = "0.2", optional = true }
|
|||
|
||||
# Native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
egui-winit = { version = "0.21.1", path = "../egui-winit", optional = true, default-features = false }
|
||||
egui-winit = { version = "0.20.0", path = "../egui-winit", optional = true, default-features = false }
|
||||
puffin = { version = "0.14", optional = true }
|
||||
|
||||
# Web:
|
||||
|
@ -69,9 +73,8 @@ wasm-bindgen = { version = "0.2" }
|
|||
|
||||
|
||||
[dev-dependencies]
|
||||
glutin = "0.30" # examples/pure_glow
|
||||
glutin = "0.30.2" # examples/pure_glow
|
||||
raw-window-handle = "0.5.0"
|
||||
glutin-winit = "0.3.0"
|
||||
|
||||
|
||||
[[example]]
|
||||
|
|
|
@ -14,13 +14,7 @@ To write web apps using `glow` you can use [`eframe`](https://github.com/emilk/e
|
|||
To use on Linux, first run:
|
||||
|
||||
```
|
||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
|
||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev libxkbcommon-dev libssl-dev
|
||||
```
|
||||
|
||||
This crate optionally depends on [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit).
|
||||
|
||||
Text the example with:
|
||||
|
||||
``` sh
|
||||
cargo run -p egui_glow --example pure_glow --features=winit,egui/default_fonts
|
||||
```
|
||||
|
|
|
@ -17,91 +17,66 @@ impl GlutinWindowContext {
|
|||
// refactor this function to use `glutin-winit` crate eventually.
|
||||
// preferably add android support at the same time.
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn new(event_loop: &winit::event_loop::EventLoopWindowTarget<()>) -> Self {
|
||||
use egui::NumExt;
|
||||
use glutin::context::NotCurrentGlContextSurfaceAccessor;
|
||||
use glutin::display::GetGlDisplay;
|
||||
use glutin::display::GlDisplay;
|
||||
use glutin::prelude::GlSurface;
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
let winit_window_builder = winit::window::WindowBuilder::new()
|
||||
.with_resizable(true)
|
||||
.with_inner_size(winit::dpi::LogicalSize {
|
||||
width: 800.0,
|
||||
height: 600.0,
|
||||
})
|
||||
.with_title("egui_glow example") // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
.with_visible(false);
|
||||
unsafe fn new(winit_window: winit::window::Window) -> Self {
|
||||
use glutin::prelude::*;
|
||||
use raw_window_handle::*;
|
||||
|
||||
let config_template_builder = glutin::config::ConfigTemplateBuilder::new()
|
||||
let raw_display_handle = winit_window.raw_display_handle();
|
||||
let raw_window_handle = winit_window.raw_window_handle();
|
||||
|
||||
// EGL is crossplatform and the official khronos way
|
||||
// but sometimes platforms/drivers may not have it, so we use back up options where possible.
|
||||
|
||||
// try egl and fallback to windows wgl. Windows is the only platform that *requires* window handle to create display.
|
||||
#[cfg(target_os = "windows")]
|
||||
let preference = glutin::display::DisplayApiPreference::EglThenWgl(Some(window_handle));
|
||||
// try egl and fallback to x11 glx
|
||||
#[cfg(target_os = "linux")]
|
||||
let preference = glutin::display::DisplayApiPreference::EglThenGlx(Box::new(
|
||||
winit::platform::unix::register_xlib_error_hook,
|
||||
));
|
||||
#[cfg(target_os = "macos")]
|
||||
let preference = glutin::display::DisplayApiPreference::Cgl;
|
||||
#[cfg(target_os = "android")]
|
||||
let preference = glutin::display::DisplayApiPreference::Egl;
|
||||
|
||||
let gl_display = glutin::display::Display::new(raw_display_handle, preference).unwrap();
|
||||
|
||||
let config_template = glutin::config::ConfigTemplateBuilder::new()
|
||||
.prefer_hardware_accelerated(None)
|
||||
.with_depth_size(0)
|
||||
.with_stencil_size(0)
|
||||
.with_transparency(false);
|
||||
.with_transparency(false)
|
||||
.compatible_with_native_window(raw_window_handle)
|
||||
.build();
|
||||
|
||||
tracing::debug!("trying to get gl_config");
|
||||
let (mut window, gl_config) =
|
||||
glutin_winit::DisplayBuilder::new() // let glutin-winit helper crate handle the complex parts of opengl context creation
|
||||
.with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
|
||||
.with_window_builder(Some(winit_window_builder.clone()))
|
||||
.build(
|
||||
event_loop,
|
||||
config_template_builder,
|
||||
|mut config_iterator| {
|
||||
config_iterator.next().expect(
|
||||
"failed to find a matching configuration for creating glutin config",
|
||||
)
|
||||
},
|
||||
)
|
||||
.expect("failed to create gl_config");
|
||||
let gl_display = gl_config.display();
|
||||
tracing::debug!("found gl_config: {:?}", &gl_config);
|
||||
let config = gl_display
|
||||
.find_configs(config_template)
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle());
|
||||
tracing::debug!("raw window handle: {:?}", raw_window_handle);
|
||||
let context_attributes =
|
||||
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
|
||||
// by default, glutin will try to create a core opengl context. but, if it is not available, try to create a gl-es context using this fallback attributes
|
||||
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
|
||||
.with_context_api(glutin::context::ContextApi::Gles(None))
|
||||
.build(raw_window_handle);
|
||||
let not_current_gl_context = unsafe {
|
||||
gl_display
|
||||
.create_context(&gl_config, &context_attributes)
|
||||
.unwrap_or_else(|_| {
|
||||
tracing::debug!("failed to create gl_context with attributes: {:?}. retrying with fallback context attributes: {:?}",
|
||||
&context_attributes,
|
||||
&fallback_context_attributes);
|
||||
gl_config
|
||||
.display()
|
||||
.create_context(&gl_config, &fallback_context_attributes)
|
||||
.expect("failed to create context even with fallback attributes")
|
||||
})
|
||||
};
|
||||
|
||||
// this is where the window is created, if it has not been created while searching for suitable gl_config
|
||||
let window = window.take().unwrap_or_else(|| {
|
||||
tracing::debug!("window doesn't exist yet. creating one now with finalize_window");
|
||||
glutin_winit::finalize_window(event_loop, winit_window_builder.clone(), &gl_config)
|
||||
.expect("failed to finalize glutin window")
|
||||
});
|
||||
let (width, height): (u32, u32) = window.inner_size().into();
|
||||
let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap();
|
||||
let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap();
|
||||
glutin::context::ContextAttributesBuilder::new().build(Some(raw_window_handle));
|
||||
// for surface creation.
|
||||
let (width, height): (u32, u32) = winit_window.inner_size().into();
|
||||
let surface_attributes =
|
||||
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
|
||||
.build(window.raw_window_handle(), width, height);
|
||||
tracing::debug!(
|
||||
"creating surface with attributes: {:?}",
|
||||
&surface_attributes
|
||||
.build(
|
||||
raw_window_handle,
|
||||
std::num::NonZeroU32::new(width).unwrap(),
|
||||
std::num::NonZeroU32::new(height).unwrap(),
|
||||
);
|
||||
let gl_surface = unsafe {
|
||||
gl_display
|
||||
.create_window_surface(&gl_config, &surface_attributes)
|
||||
.unwrap()
|
||||
};
|
||||
tracing::debug!("surface created successfully: {gl_surface:?}.making context current");
|
||||
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
|
||||
// start creating the gl objects
|
||||
let gl_context = gl_display
|
||||
.create_context(&config, &context_attributes)
|
||||
.unwrap();
|
||||
|
||||
let gl_surface = gl_display
|
||||
.create_window_surface(&config, &surface_attributes)
|
||||
.unwrap();
|
||||
|
||||
let gl_context = gl_context.make_current(&gl_surface).unwrap();
|
||||
|
||||
gl_surface
|
||||
.set_swap_interval(
|
||||
|
@ -111,7 +86,7 @@ impl GlutinWindowContext {
|
|||
.unwrap();
|
||||
|
||||
GlutinWindowContext {
|
||||
window,
|
||||
window: winit_window,
|
||||
gl_context,
|
||||
gl_display,
|
||||
gl_surface,
|
||||
|
@ -241,7 +216,19 @@ fn main() {
|
|||
fn create_display(
|
||||
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
|
||||
) -> (GlutinWindowContext, glow::Context) {
|
||||
let glutin_window_context = unsafe { GlutinWindowContext::new(event_loop) };
|
||||
let winit_window = winit::window::WindowBuilder::new()
|
||||
.with_resizable(true)
|
||||
.with_inner_size(winit::dpi::LogicalSize {
|
||||
width: 800.0,
|
||||
height: 600.0,
|
||||
})
|
||||
.with_title("egui_glow example")
|
||||
.with_visible(false) // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
.build(event_loop)
|
||||
.unwrap();
|
||||
|
||||
// a lot of the code below has been lifted from glutin example in their repo.
|
||||
let glutin_window_context = unsafe { GlutinWindowContext::new(winit_window) };
|
||||
let gl = unsafe {
|
||||
glow::Context::from_loader_function(|s| {
|
||||
let s = std::ffi::CString::new(s)
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::{collections::HashMap, sync::Arc};
|
|||
|
||||
use egui::{
|
||||
emath::Rect,
|
||||
epaint::{Mesh, PaintCallbackInfo, Primitive, Vertex},
|
||||
epaint::{Color32, Mesh, PaintCallbackInfo, Primitive, Vertex},
|
||||
};
|
||||
use glow::HasContext as _;
|
||||
use memoffset::offset_of;
|
||||
|
@ -106,23 +106,6 @@ impl Painter {
|
|||
crate::profile_function!();
|
||||
crate::check_for_gl_error_even_in_release!(&gl, "before Painter::new");
|
||||
|
||||
// some useful debug info. all three of them are present in gl 1.1.
|
||||
unsafe {
|
||||
let version = gl.get_parameter_string(glow::VERSION);
|
||||
let renderer = gl.get_parameter_string(glow::RENDERER);
|
||||
let vendor = gl.get_parameter_string(glow::VENDOR);
|
||||
tracing::debug!(
|
||||
"\nopengl version: {version}\nopengl renderer: {renderer}\nopengl vendor: {vendor}"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if gl.version().major < 2 {
|
||||
// this checks on desktop that we are not using opengl 1.1 microsoft sw rendering context.
|
||||
// ShaderVersion::get fn will segfault due to SHADING_LANGUAGE_VERSION (added in gl2.0)
|
||||
return Err("egui_glow requires opengl 2.0+. ".to_owned());
|
||||
}
|
||||
|
||||
let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize;
|
||||
let shader_version = shader_version.unwrap_or_else(|| ShaderVersion::get(&gl));
|
||||
let is_webgl_1 = shader_version == ShaderVersion::Es100;
|
||||
|
@ -682,7 +665,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: egui::Rgba) {
|
||||
crate::profile_function!();
|
||||
unsafe {
|
||||
gl.disable(glow::SCISSOR_TEST);
|
||||
|
@ -693,12 +676,24 @@ pub fn clear(gl: &glow::Context, screen_size_in_pixels: [u32; 2], clear_color: [
|
|||
screen_size_in_pixels[0] as i32,
|
||||
screen_size_in_pixels[1] as i32,
|
||||
);
|
||||
|
||||
if true {
|
||||
// verified to be correct on eframe native (on Mac).
|
||||
gl.clear_color(
|
||||
clear_color[0],
|
||||
clear_color[1],
|
||||
clear_color[2],
|
||||
clear_color[3],
|
||||
);
|
||||
} else {
|
||||
let clear_color: Color32 = clear_color.into();
|
||||
gl.clear_color(
|
||||
clear_color[0] as f32 / 255.0,
|
||||
clear_color[1] as f32 / 255.0,
|
||||
clear_color[2] as f32 / 255.0,
|
||||
clear_color[3] as f32 / 255.0,
|
||||
);
|
||||
}
|
||||
gl.clear(glow::COLOR_BUFFER_BIT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "emath"
|
||||
version = "0.21.0"
|
||||
version = "0.20.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Minimal 2D math library for GUI work"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
# emath - egui math library
|
||||
|
||||
[](https://crates.io/crates/emath)
|
||||
[](https://docs.rs/emath)
|
||||
[](https://github.com/rust-secure-code/safety-dance/)
|
||||

|
||||

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

|
||||

|
||||
|
||||
A bare-bones 2D graphics library for turning simple 2D shapes and text into textured triangles.
|
||||
|
||||
Made for [`egui`](https://github.com/emilk/egui/).
|
||||
|
|
|
@ -6,7 +6,6 @@ use emath::*;
|
|||
/// Should be friendly to send to GPU as is.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[cfg(not(feature = "unity"))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
pub struct Vertex {
|
||||
|
@ -23,25 +22,6 @@ pub struct Vertex {
|
|||
pub color: Color32, // 32 bit
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[cfg(feature = "unity")]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
pub struct Vertex {
|
||||
/// Logical pixel coordinates (points).
|
||||
/// (0,0) is the top left corner of the screen.
|
||||
pub pos: Pos2, // 64 bit
|
||||
|
||||
/// sRGBA with premultiplied alpha
|
||||
pub color: Color32, // 32 bit
|
||||
|
||||
/// Normalized texture coordinates.
|
||||
/// (0, 0) is the top left corner of the texture.
|
||||
/// (1, 1) is the bottom right corner of the texture.
|
||||
pub uv: Pos2, // 64 bit
|
||||
}
|
||||
|
||||
/// Textured triangles in two dimensions.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
|
|
|
@ -973,16 +973,12 @@ pub struct Tessellator {
|
|||
pixels_per_point: f32,
|
||||
options: TessellationOptions,
|
||||
font_tex_size: [usize; 2],
|
||||
|
||||
/// See [`TextureAtlas::prepared_discs`].
|
||||
prepared_discs: Vec<PreparedDisc>,
|
||||
|
||||
/// size of feathering in points. normally the size of a physical pixel. 0.0 if disabled
|
||||
feathering: f32,
|
||||
|
||||
/// Only used for culling
|
||||
clip_rect: Rect,
|
||||
|
||||
scratchpad_points: Vec<Pos2>,
|
||||
scratchpad_path: Path,
|
||||
}
|
||||
|
|
|
@ -33,10 +33,6 @@ impl UvRect {
|
|||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct GlyphInfo {
|
||||
/// Used for pair-kerning.
|
||||
///
|
||||
/// Doesn't need to be unique.
|
||||
/// Use `ab_glyph::GlyphId(0)` if you just want to have an id, and don't care.
|
||||
pub(crate) id: ab_glyph::GlyphId,
|
||||
|
||||
/// Unit: points.
|
||||
|
|
14
deny.toml
14
deny.toml
|
@ -24,29 +24,23 @@ ignore = [
|
|||
multiple-versions = "deny"
|
||||
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
||||
deny = [
|
||||
{ name = "cmake" }, # Lord no
|
||||
{ name = "openssl-sys" }, # prefer rustls
|
||||
{ name = "openssl" }, # prefer rustls
|
||||
{ name = "openssl-sys" }, # prefer rustls
|
||||
]
|
||||
|
||||
skip = [
|
||||
{ name = "ahash" }, # old version via dark-light
|
||||
{ name = "arrayvec" }, # old version via tiny-skiaz
|
||||
{ name = "hashbrown" }, # old version via dark-light
|
||||
{ name = "nix" }, # old version via winit
|
||||
{ name = "time" }, # old version pulled in by unmaintianed crate 'chrono'
|
||||
{ name = "tiny-skia" }, # winit uses a different version from egui_extras (TODO(emilk): update egui_extras!)
|
||||
{ name = "ttf-parser" }, # different versions pulled in by ab_glyph and usvg
|
||||
{ name = "wayland-sys" }, # old version via winit
|
||||
{ name = "windows_x86_64_msvc" }, # old version via glutin
|
||||
{ name = "windows-sys" }, # old version via glutin
|
||||
{ name = "windows" }, # old version via accesskit
|
||||
]
|
||||
skip-tree = [
|
||||
{ name = "criterion" }, # dev-dependency
|
||||
{ name = "darling" }, # old version via tts
|
||||
{ name = "foreign-types" }, # old version from wgpu
|
||||
{ name = "glium" }, # legacy crate, lots of old dependencies
|
||||
{ name = "rfd" }, # example dependency
|
||||
{ name = "three-d" }, # example dependency
|
||||
{ name = "tts" }, # we are migrating away from tts to accesskit
|
||||
]
|
||||
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue