Compare commits

..

25 commits

Author SHA1 Message Date
Emil Ernerfeldt
ce2d37d5d9 Use "in front" and "behind" instead of "below" and "above" 2023-01-26 16:05:52 +01:00
Emil Ernerfeldt
cb051425ff Paint panel separators ontop of everything 2023-01-26 16:00:03 +01:00
Emil Ernerfeldt
ac6344c104 Add ZOffset 2023-01-26 15:59:20 +01:00
Emil Ernerfeldt
8fac6df249 Make consts pub 2023-01-26 15:00:40 +01:00
Emil Ernerfeldt
9ad5356cfa naming 2023-01-26 14:59:48 +01:00
Emil Ernerfeldt
f0d5b645f2 zlayer -> z_layer 2023-01-26 14:59:15 +01:00
Emil Ernerfeldt
95b318d3e0 Better naming 2023-01-26 14:58:38 +01:00
Emil Ernerfeldt
f87c789851 fn layer() -> fn zlayer 2023-01-26 14:56:11 +01:00
Emil Ernerfeldt
494f5b3ef0 fix doclink 2023-01-26 14:56:03 +01:00
Emil Ernerfeldt
9ca4d173c5 Merge branch 'master' into zlayer
# Conflicts:
#	crates/egui/src/containers/area.rs
#	crates/egui/src/containers/popup.rs
#	crates/egui/src/context.rs
#	crates/egui/src/painter.rs
#	crates/egui/src/ui.rs
2023-01-26 14:52:56 +01:00
Emil Ernerfeldt
9ee77aab84 Fix warning 2023-01-26 14:46:23 +01:00
charburgx
d2c7793370 export LayerId 2022-12-19 21:52:19 -06:00
charburgx
5473c0f8a0 oops i broke the public api 2022-12-19 04:56:28 -06:00
charburgx
c29b80d41e docs 2022-12-19 04:46:44 -06:00
charburgx
57f65b7be6 LayerId compat 2022-12-19 04:44:24 -06:00
charburgx
45336eaf62 remove unneeded use statements 2022-12-19 04:31:30 -06:00
charburgx
3c2e669fe1 docs 2022-12-19 04:27:52 -06:00
charburgx
191cc70362 syntax 2022-12-19 04:12:06 -06:00
charburgx
db46385073 Merge branch 'master' into zlayer 2022-12-19 04:07:51 -06:00
charburgx
099e41bf3f add example 2022-12-19 03:58:38 -06:00
charburgx
d0307417e1 implement for interact 2022-12-19 03:57:03 -06:00
charburgx
26c3fa22cb switch to tuple in PaintList 2022-12-19 00:46:36 -06:00
charburgx
7894cd4dfd zorder/zlayer utilities 2022-12-17 20:18:03 -06:00
charburgx
4560618d43 implement for painter 2022-12-17 18:27:10 -06:00
charburgx
1e770ae3bd rename LayerId to AreaLayerId 2022-12-17 12:20:18 -06:00
137 changed files with 3386 additions and 3128 deletions

View file

@ -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"]

View file

@ -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

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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):

View file

@ -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!

View file

@ -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)).

View file

@ -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>",

View file

@ -1,11 +1,5 @@
# ecolor - egui color library
[![Latest version](https://img.shields.io/crates/v/ecolor.svg)](https://crates.io/crates/ecolor)
[![Documentation](https://docs.rs/ecolor/badge.svg)](https://docs.rs/ecolor)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
A simple color storage and conversion library.
Made for [`egui`](https://github.com/emilk/egui/).

View file

@ -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,
]
}
}

View file

@ -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

View file

@ -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"] }

View file

@ -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.

View file

@ -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>,
}
}

View file

@ -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>;
// ---------------------------------------------------------------------------
/// Profiling macro for feature "puffin"
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
mod profiling_scopes {
/// Profiling macro for feature "puffin"
macro_rules! profile_function {
macro_rules! profile_function {
($($arg: tt)*) => {
#[cfg(feature = "puffin")]
puffin::profile_function!($($arg)*);
};
}
pub(crate) use profile_function;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use profile_function;
/// Profiling macro for feature "puffin"
macro_rules! profile_scope {
/// 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;

View file

@ -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;

View file

@ -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(&current_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);
}

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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()?;

View file

@ -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,

View file

@ -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,

View file

@ -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,
},

View file

@ -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

View file

@ -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]

View file

@ -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"

View file

@ -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

View file

@ -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,
},

View file

@ -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

View file

@ -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"] }

View file

@ -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();
}

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

View file

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

View file

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

View file

@ -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,
}

View file

@ -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)

View file

@ -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 {

View file

@ -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,
}

View file

@ -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();

View file

@ -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;
}

View file

@ -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(),

View file

@ -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())?;

View file

@ -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]

View file

@ -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.

View file

@ -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>;

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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)
}
}

View file

@ -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,
}
}

View file

@ -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);

View file

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

View file

@ -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:
///
/// ```

View file

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

View file

@ -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,

View file

@ -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> {

View file

@ -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());
}
});

View file

@ -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

View file

@ -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.

View file

@ -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(

View file

@ -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,
);
}

View file

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

View file

@ -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();
});
}
});

View file

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

View file

@ -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 {

View file

@ -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);

View file

@ -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");

View file

@ -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));

View file

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

View file

@ -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()),

View file

@ -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.

View file

@ -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();
}
}
}

View file

@ -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;

View file

@ -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 {

View file

@ -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());
},
);
}

View file

@ -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"
);
}
}

View file

@ -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)):

View file

@ -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 = [

View file

@ -2,7 +2,6 @@
[![Latest version](https://img.shields.io/crates/v/egui_extras.svg)](https://crates.io/crates/egui_extras)
[![Documentation](https://docs.rs/egui_extras/badge.svg)](https://docs.rs/egui_extras)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)

View file

@ -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],

View file

@ -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

View file

@ -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"] }

View file

@ -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.

View file

@ -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

View file

@ -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]]

View file

@ -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
```

View file

@ -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)

View file

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

View file

@ -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"

View file

@ -1,11 +1,5 @@
# emath - egui math library
[![Latest version](https://img.shields.io/crates/v/emath.svg)](https://crates.io/crates/emath)
[![Documentation](https://docs.rs/emath/badge.svg)](https://docs.rs/emath)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
A bare-bones 2D math library with types and functions useful for GUI building.
Made for [`egui`](https://github.com/emilk/egui/).

View file

@ -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

View file

@ -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)).

View file

@ -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 = [

View file

@ -1,11 +1,5 @@
# epaint - egui paint library
[![Latest version](https://img.shields.io/crates/v/epaint.svg)](https://crates.io/crates/epaint)
[![Documentation](https://docs.rs/epaint/badge.svg)](https://docs.rs/epaint)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
A bare-bones 2D graphics library for turning simple 2D shapes and text into textured triangles.
Made for [`egui`](https://github.com/emilk/egui/).

View file

@ -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))]

View file

@ -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,
}

View file

@ -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.

View file

@ -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