1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 22:53:14 -04:00

Merge branch 'emilk:main' into main

This commit is contained in:
AdrienZ.
2025-12-17 11:33:35 +01:00
committed by GitHub
346 changed files with 4160 additions and 2852 deletions

View File

@@ -1,21 +1,8 @@
name: Check spelling and links
on: [pull_request]
name: Link checker
# on: [pull_request] # Disabled because it is so broken
on: workflow_dispatch
jobs:
typos:
# https://github.com/crate-ci/typos
# Add exceptions to .typos.toml
# install and run locally: cargo install typos-cli && typos
name: typos
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4
- name: Check spelling of entire workspace
uses: crate-ci/typos@v1.38.0
lychee:
name: lychee
runs-on: ubuntu-latest
@@ -33,4 +20,3 @@ jobs:
uses: lycheeverse/lychee-action@v2
with:
args: "'**/*.md' '**/*.toml' --exclude localhost --exclude reddit.com" # I guess reddit doesn't like github action IPs

View File

@@ -46,7 +46,9 @@ jobs:
- run: cargo clippy --locked --no-default-features --lib --all-targets
- run: cargo clippy --locked --no-default-features --features x11 --lib -p eframe
- run: cargo clippy --locked --no-default-features --lib -p eframe --features x11
- run: cargo clippy --locked --no-default-features --lib -p eframe --features x11,wgpu_no_default_features
- run: cargo clippy --locked --no-default-features --lib -p egui_extras
@@ -87,7 +89,7 @@ jobs:
run: cargo clippy -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features
- name: clippy wasm32 eframe
run: cargo clippy -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown
run: cargo clippy -p eframe --lib --no-default-features --features wgpu,persistence --target wasm32-unknown-unknown
- name: wasm-bindgen
uses: jetli/wasm-bindgen-action@v0.1.0

17
.github/workflows/typos.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Typos
on: [pull_request]
jobs:
typos:
# https://github.com/crate-ci/typos
# Add exceptions to .typos.toml
# install and run locally: cargo install typos-cli && typos
name: typos
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4
- name: Check spelling of entire workspace
uses: crate-ci/typos@v1.38.0

View File

@@ -5,7 +5,7 @@ Also see [`CONTRIBUTING.md`](CONTRIBUTING.md) for what to do before opening a PR
## Crate overview
The crates in this repository are: `egui, emath, epaint, epaint_default_fonts, egui_extras, egui-winit, egui_glow, egui_demo_lib, egui_demo_app`.
The crates in this repository are: `egui, emath, epaint, epaint_default_fonts, egui_extras, egui-winit, egui_glow, egui-wgpu, egui_demo_lib, egui_demo_app`.
### `egui`: The main GUI library.
Example code: `if ui.button("Click me").clicked() { … }`
@@ -37,6 +37,9 @@ The library translates winit events to egui, handled copy/paste, updates the cur
### `egui_glow`
Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glow](https://github.com/grovesNL/glow).
### `egui-wgpu`
Paints the triangles that egui outputs using [wgpu](https://github.com/grovesNL/wgpu).
### `eframe`
`eframe` is the official `egui` framework, built so you can compile the same app for either web or native.

View File

@@ -14,6 +14,27 @@ This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
## 0.33.3 - 2025-12-11
* Treat `.` as a word-splitter in text navigation [#7741](https://github.com/emilk/egui/pull/7741) by [@emilk](https://github.com/emilk)
* Change text color of selected text [#7691](https://github.com/emilk/egui/pull/7691) by [@emilk](https://github.com/emilk)
## 0.33.2 - 2025-11-13
### ⭐ Added
* Add `Plugin::on_widget_under_pointer` to support widget inspector [#7652](https://github.com/emilk/egui/pull/7652) by [@juancampa](https://github.com/juancampa)
* Add `Response::total_drag_delta` and `PointerState::total_drag_delta` [#7708](https://github.com/emilk/egui/pull/7708) by [@emilk](https://github.com/emilk)
### 🔧 Changed
* Improve accessibility and testability of `ComboBox` [#7658](https://github.com/emilk/egui/pull/7658) by [@lucasmerlin](https://github.com/lucasmerlin)
### 🐛 Fixed
* Fix `profiling::scope` compile error when profiling using `tracing` backend [#7646](https://github.com/emilk/egui/pull/7646) by [@PPakalns](https://github.com/PPakalns)
* Fix edge cases in "smart aiming" in sliders [#7680](https://github.com/emilk/egui/pull/7680) by [@emilk](https://github.com/emilk)
* Hide scroll bars when dragging other things [#7689](https://github.com/emilk/egui/pull/7689) by [@emilk](https://github.com/emilk)
* Prevent widgets sometimes appearing to move relative to each other [#7710](https://github.com/emilk/egui/pull/7710) by [@emilk](https://github.com/emilk)
* Fix `ui.response().interact(Sense::click())` being flakey [#7713](https://github.com/emilk/egui/pull/7713) by [@lucasmerlin](https://github.com/lucasmerlin)
## 0.33.0 - 2025-10-09 - `egui::Plugin`, better kerning, kitdiff viewer
Highlights from this release:
- `egui::Plugin` a improved way to create and access egui plugins

View File

@@ -863,6 +863,15 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "color"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18ef4657441fb193b65f34dc39b3781f0dfec23d3bd94d0eeb4e88cde421edb"
dependencies = [
"bytemuck",
]
[[package]]
name = "color-hex"
version = "0.2.0"
@@ -1248,7 +1257,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
[[package]]
name = "ecolor"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"bytemuck",
"cint",
@@ -1260,7 +1269,7 @@ dependencies = [
[[package]]
name = "eframe"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"ahash",
"bytemuck",
@@ -1299,7 +1308,7 @@ dependencies = [
[[package]]
name = "egui"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"accesskit",
"ahash",
@@ -1319,7 +1328,7 @@ dependencies = [
[[package]]
name = "egui-wgpu"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"ahash",
"bytemuck",
@@ -1337,7 +1346,7 @@ dependencies = [
[[package]]
name = "egui-winit"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"accesskit_winit",
"arboard",
@@ -1360,7 +1369,7 @@ dependencies = [
[[package]]
name = "egui_demo_app"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"accesskit",
"accesskit_consumer",
@@ -1390,7 +1399,7 @@ dependencies = [
[[package]]
name = "egui_demo_lib"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"chrono",
"criterion",
@@ -1407,7 +1416,7 @@ dependencies = [
[[package]]
name = "egui_extras"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"ahash",
"chrono",
@@ -1426,7 +1435,7 @@ dependencies = [
[[package]]
name = "egui_glow"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"bytemuck",
"document-features",
@@ -1445,7 +1454,7 @@ dependencies = [
[[package]]
name = "egui_kittest"
version = "0.33.1"
version = "0.33.3"
dependencies = [
"dify",
"document-features",
@@ -1457,13 +1466,15 @@ dependencies = [
"kittest",
"open",
"pollster",
"serde",
"tempfile",
"toml",
"wgpu",
]
[[package]]
name = "egui_tests"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"egui",
"egui_extras",
@@ -1473,9 +1484,9 @@ dependencies = [
[[package]]
name = "ehttp"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59a81c221a1e4dad06cb9c9deb19aea1193a5eea084e8cd42d869068132bf876"
checksum = "04499d3c719edecfad5c9b46031726c8540905d73be6d7e4f9788c4a298da908"
dependencies = [
"document-features",
"js-sys",
@@ -1493,7 +1504,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "emath"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"bytemuck",
"document-features",
@@ -1591,9 +1602,8 @@ dependencies = [
[[package]]
name = "epaint"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"ab_glyph",
"ahash",
"bytemuck",
"criterion",
@@ -1607,13 +1617,16 @@ dependencies = [
"parking_lot",
"profiling",
"rayon",
"self_cell",
"serde",
"similar-asserts",
"skrifa",
"vello_cpu",
]
[[package]]
name = "epaint_default_fonts"
version = "0.33.0"
version = "0.33.3"
[[package]]
name = "equivalent"
@@ -1637,6 +1650,15 @@ version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
[[package]]
name = "euclid"
version = "0.22.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48"
dependencies = [
"num-traits",
]
[[package]]
name = "event-listener"
version = "5.3.1"
@@ -1704,6 +1726,15 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "fearless_simd"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fb2907d1f08b2b316b9223ced5b0e89d87028ba8deae9764741dba8ff7f3903"
dependencies = [
"bytemuck",
]
[[package]]
name = "file_dialog"
version = "0.1.0"
@@ -1747,6 +1778,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "font-types"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5"
dependencies = [
"bytemuck",
]
[[package]]
name = "fontconfig-parser"
version = "0.5.7"
@@ -2526,6 +2566,17 @@ dependencies = [
"smallvec",
]
[[package]]
name = "kurbo"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce9729cc38c18d86123ab736fd2e7151763ba226ac2490ec092d1dd148825e32"
dependencies = [
"arrayvec",
"euclid",
"smallvec",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -2575,6 +2626,12 @@ dependencies = [
"redox_syscall 0.5.7",
]
[[package]]
name = "linebender_resource_handle"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
@@ -3251,6 +3308,19 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "peniko"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3c76095c9a636173600478e0373218c7b955335048c2bcd12dc6a79657649d8"
dependencies = [
"bytemuck",
"color",
"kurbo 0.12.0",
"linebender_resource_handle",
"smallvec",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -3381,9 +3451,9 @@ dependencies = [
[[package]]
name = "png"
version = "0.17.14"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
@@ -3425,7 +3495,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
[[package]]
name = "popups"
version = "0.33.0"
version = "0.33.3"
dependencies = [
"eframe",
"env_logger",
@@ -3690,6 +3760,16 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "read-fonts"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358"
dependencies = [
"bytemuck",
"font-types",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
@@ -3983,6 +4063,12 @@ dependencies = [
"tiny-skia",
]
[[package]]
name = "self_cell"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33"
[[package]]
name = "serde"
version = "1.0.228"
@@ -4036,6 +4122,15 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]]
name = "serial_windows"
version = "0.1.0"
@@ -4101,6 +4196,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "skrifa"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841"
dependencies = [
"bytemuck",
"read-fonts",
]
[[package]]
name = "slab"
version = "0.4.9"
@@ -4222,7 +4327,7 @@ version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
dependencies = [
"kurbo",
"kurbo 0.11.1",
"siphasher",
]
@@ -4491,11 +4596,26 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "toml"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
@@ -4504,6 +4624,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
@@ -4707,7 +4829,7 @@ dependencies = [
"flate2",
"fontdb",
"imagesize",
"kurbo",
"kurbo 0.11.1",
"log",
"pico-args",
"roxmltree",
@@ -4735,6 +4857,30 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vello_common"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a235ba928b3109ad9e7696270edb09445a52ae1c7c08e6d31a19b1cdd6cbc24a"
dependencies = [
"bytemuck",
"fearless_simd",
"log",
"peniko",
"skrifa",
"smallvec",
]
[[package]]
name = "vello_cpu"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0bd1fcf9c1814f17a491e07113623d44e3ec1125a9f3401f5e047d6d326da21"
dependencies = [
"bytemuck",
"vello_common",
]
[[package]]
name = "version_check"
version = "0.9.5"
@@ -5645,9 +5791,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "0.7.3"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [
"memchr",
]
@@ -5748,7 +5894,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "xtask"
version = "0.33.0"
version = "0.33.3"
[[package]]
name = "yaml-rust"

View File

@@ -24,7 +24,7 @@ members = [
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.88"
version = "0.33.0"
version = "0.33.3"
[profile.release]
@@ -55,23 +55,22 @@ opt-level = 2
[workspace.dependencies]
emath = { version = "0.33.0", path = "crates/emath", default-features = false }
ecolor = { version = "0.33.0", path = "crates/ecolor", default-features = false }
epaint = { version = "0.33.0", path = "crates/epaint", default-features = false }
epaint_default_fonts = { version = "0.33.0", path = "crates/epaint_default_fonts" }
egui = { version = "0.33.0", path = "crates/egui", default-features = false }
egui-winit = { version = "0.33.0", path = "crates/egui-winit", default-features = false }
egui_extras = { version = "0.33.0", path = "crates/egui_extras", default-features = false }
egui-wgpu = { version = "0.33.0", path = "crates/egui-wgpu", default-features = false }
egui_demo_lib = { version = "0.33.0", path = "crates/egui_demo_lib", default-features = false }
egui_glow = { version = "0.33.0", path = "crates/egui_glow", default-features = false }
egui_kittest = { version = "0.33.1", path = "crates/egui_kittest", default-features = false }
eframe = { version = "0.33.0", path = "crates/eframe", default-features = false }
emath = { version = "0.33.3", path = "crates/emath", default-features = false }
ecolor = { version = "0.33.3", path = "crates/ecolor", default-features = false }
epaint = { version = "0.33.3", path = "crates/epaint", default-features = false }
epaint_default_fonts = { version = "0.33.3", path = "crates/epaint_default_fonts" }
egui = { version = "0.33.3", path = "crates/egui", default-features = false }
egui-winit = { version = "0.33.3", path = "crates/egui-winit", default-features = false }
egui_extras = { version = "0.33.3", path = "crates/egui_extras", default-features = false }
egui-wgpu = { version = "0.33.3", path = "crates/egui-wgpu", default-features = false }
egui_demo_lib = { version = "0.33.3", path = "crates/egui_demo_lib", default-features = false }
egui_glow = { version = "0.33.3", path = "crates/egui_glow", default-features = false }
egui_kittest = { version = "0.33.3", path = "crates/egui_kittest", default-features = false }
eframe = { version = "0.33.3", path = "crates/eframe", default-features = false }
accesskit = "0.21.1"
accesskit_consumer = "0.30.1"
accesskit_winit = "0.29.1"
ab_glyph = "0.2.32"
ahash = { version = "0.8.12", default-features = false, features = [
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
"std",
@@ -88,7 +87,7 @@ criterion = { version = "0.7.0", default-features = false }
dify = { version = "0.7.4", default-features = false }
directories = "6.0.0"
document-features = "0.2.11"
ehttp = { version = "0.5.0", default-features = false }
ehttp = { version = "0.6.0", default-features = false }
enum-map = "2.7.3"
env_logger = { version = "0.11.8", default-features = false }
glow = "0.16.0"
@@ -122,8 +121,10 @@ rayon = "1.11.0"
resvg = { version = "0.45.1", default-features = false }
rfd = "0.15.4"
ron = "0.11.0"
self_cell = "1.2.1"
serde = { version = "1.0.228", features = ["derive"] }
similar-asserts = "1.7.0"
skrifa = { version = "0.37.0", default-features = false, features = ["std", "autohint_shaping"] }
smallvec = "1.15.1"
smithay-clipboard = "0.7.2"
static_assertions = "1.1.0"
@@ -131,9 +132,11 @@ syntect = { version = "5.3.0", default-features = false }
tempfile = "3.23.0"
thiserror = "2.0.17"
tokio = "1.47.1"
toml = "0.8"
type-map = "0.5.1"
unicode_names2 = { version = "2.0.0", default-features = false }
unicode-segmentation = "1.12.0"
vello_cpu = { version = "0.0.4", default-features = false, features = ["std"] }
wasm-bindgen = "0.2.100" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml
wasm-bindgen-futures = "0.4.0"
wayland-cursor = { version = "0.31.11", default-features = false }
@@ -232,6 +235,7 @@ iter_on_single_items = "warn"
iter_over_hash_type = "warn"
iter_without_into_iter = "warn"
large_digit_groups = "warn"
large_futures = "warn"
large_include_file = "warn"
large_stack_arrays = "warn"
large_stack_frames = "warn"
@@ -276,6 +280,7 @@ non_zero_suggestions = "warn"
nonstandard_macro_braces = "warn"
option_as_ref_cloned = "warn"
option_option = "warn"
or_fun_call = "warn"
path_buf_push_overwrite = "warn"
pathbuf_init_then_push = "warn"
precedence_bits = "warn"
@@ -329,6 +334,7 @@ unnecessary_semicolon = "warn"
unnecessary_struct_initialization = "warn"
unnecessary_wraps = "warn"
unnested_or_patterns = "warn"
unused_async = "warn"
unused_peekable = "warn"
unused_rounding = "warn"
unused_self = "warn"

View File

@@ -80,7 +80,7 @@ If you have questions, use [GitHub Discussions](https://github.com/emilk/egui/di
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/main/crates/egui_glow) (using [`glow`](https://crates.io/crates/glow)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run:
The native backend is [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu) (using [`wgpu`](https://crates.io/crates/wgpu)) 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`

View File

@@ -6,6 +6,14 @@ This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
## 0.33.3 - 2025-12-11
Nothing new
## 0.33.2 - 2025-11-13
Nothing new
## 0.33.0 - 2025-10-09
* Align `Color32` to 4 bytes [#7318](https://github.com/emilk/egui/pull/7318) by [@anti-social](https://github.com/anti-social)
* Make the `hex_color` macro `const` [#7444](https://github.com/emilk/egui/pull/7444) by [@YgorSouza](https://github.com/YgorSouza)

View File

@@ -7,6 +7,15 @@ This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
## 0.33.3 - 2025-12-11
Nothing new
## 0.33.2 - 2025-11-13
* Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman)
* Make sure `native_pixels_per_point` is set during app creation [#7683](https://github.com/emilk/egui/pull/7683) by [@emilk](https://github.com/emilk)
## 0.33.0 - 2025-10-09
### ⭐ Added
* Add an option to limit the repaint rate in the web runner [#7482](https://github.com/emilk/egui/pull/7482) by [@s-nie](https://github.com/s-nie)

View File

@@ -28,19 +28,15 @@ workspace = true
default = [
"accesskit",
"default_fonts",
"glow",
"wayland", # Required for Linux support (including CI!)
"web_screen_reader",
"wgpu",
"winit/default",
"x11",
"egui-wgpu?/fragile-send-sync-non-atomic-wasm",
# Let's enable some backends so that users can use `eframe` out-of-the-box
# without having to explicitly opt-in to backends
"egui-wgpu?/default",
]
## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/).
accesskit = ["egui/accesskit", "egui-winit/accesskit"]
accesskit = ["egui-winit/accesskit"]
# Allow crates to choose an android-activity backend via Winit
# - It's important that most applications should not have to depend on android-activity directly, and can
@@ -56,7 +52,11 @@ android-native-activity = ["egui-winit/android-native-activity"]
## If you plan on specifying your own fonts you may disable this feature.
default_fonts = ["egui/default_fonts"]
## Use [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow).
## Enable [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow).
##
## There is generally no need to enable both the `wgpu` and `glow` features,
## but if you do you can pick the renderer to use with [`NativeOptions::renderer`]
## and `WebOptions::renderer`.
glow = ["dep:egui_glow", "dep:glow", "dep:glutin-winit", "dep:glutin"]
## Enable saving app state to disk.
@@ -78,20 +78,28 @@ wayland = [
## For other platforms, use the `accesskit` feature instead.
web_screen_reader = ["web-sys/SpeechSynthesis", "web-sys/SpeechSynthesisUtterance"]
## Use [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu)).
## Enable [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu)).
##
## This overrides the `glow` feature.
## There is generally no need to enable both the `wgpu` and `glow` features,
## but if you do you can pick the renderer to use with [`NativeOptions::renderer`]
## and `WebOptions::renderer`.
##
## By default, only WebGPU is enabled on web.
## If you want to enable WebGL, you need to turn on the `webgl` feature of crate `wgpu`:
##
## ```toml
## wgpu = { version = "*", features = ["webgpu", "webgl"] }
## ```
## Switching from `wgpu (the default)` to `glow` can significantly reduce your binary size
## (including the .wasm of a web app).
## See <https://github.com/emilk/egui/issues/5889> for more details.
##
## By default, eframe will prefer WebGPU over WebGL, but
## you can configure this at run-time with [`NativeOptions::wgpu_options`].
wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"]
wgpu = ["wgpu_no_default_features", "egui-wgpu/default"]
## This is exactly like the `wgpu` feature, but does NOT enable the default features of `wgpu` and `egui-wgpu`.
##
## This means that no `wgpu` backends are enabled. You will need to enable them yourself, e.g. like this:
##
## ```toml
## wgpu = { version = "*", features = ["dx12", "metal", "webgl"] }
## ```
wgpu_no_default_features = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"]
## Enables compiling for x11.
x11 = [
@@ -137,6 +145,7 @@ winit = { workspace = true, default-features = false, features = ["rwh_06"] }
# optional native:
egui-wgpu = { workspace = true, optional = true, features = [
"winit",
"capture",
] } # if wgpu is used, use it with winit
pollster = { workspace = true, optional = true } # needed for wgpu
@@ -242,7 +251,7 @@ web-sys = { workspace = true, features = [
] }
# optional web:
egui-wgpu = { workspace = true, optional = true } # if wgpu is used, use it without (!) winit
egui-wgpu = { workspace = true, optional = true, features = ["capture"] } # if wgpu is used, use it without (!) winit
wgpu = { workspace = true, optional = true }
# Native dev dependencies for testing

View File

@@ -16,7 +16,7 @@ For how to use `egui`, see [the egui docs](https://docs.rs/egui).
---
`eframe` uses [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow) for rendering, and on native it uses [`egui-winit`](https://github.com/emilk/egui/tree/main/crates/egui-winit).
`eframe` defaults to using [wgpu](https://crates.io/crates/wgpu) for rendering (with an option to change to [glow](https://crates.io/crates/glow)), and on native it uses [`egui-winit`](https://github.com/emilk/egui/tree/main/crates/egui-winit).
To use on Linux, first run:
@@ -26,7 +26,7 @@ sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev lib
You need to either use `edition = "2024"`, or set `resolver = "2"` in the `[workspace]` section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info.
You can opt-in to the using [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu) for rendering by enabling the `wgpu` feature and setting `NativeOptions::renderer` to `Renderer::Wgpu`.
You can opt-in to the using [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow) for rendering by enabling the `glow` feature and setting `NativeOptions::renderer` to `Renderer::Glow`.
## Alternatives
`eframe` is not the only way to write an app using `egui`! You can also try [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad), [`bevy_egui`](https://github.com/mvlabat/bevy_egui), [`egui_sdl2_gl`](https://github.com/ArjunNair/egui_sdl2_gl), and others.
@@ -35,7 +35,7 @@ You can also use `egui_glow` and [`winit`](https://github.com/rust-windowing/win
## Limitations when running egui on the web
`eframe` uses WebGL (via [`glow`](https://crates.io/crates/glow)) and Wasm, and almost nothing else from the web tech stack. This has some benefits, but also produces some challenges and serious downsides.
`eframe` and egui compiles to Wasm using either WebGPU (when available) or WebGL2 for rendering, and almost nothing else from the web tech stack. This has some benefits, but also produces some challenges and serious downsides.
* Rendering: Getting pixel-perfect rendering right on the web is very difficult.
* Search: you cannot search an egui web page like you would a normal web page.

View File

@@ -10,7 +10,7 @@
use std::any::Any;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub use crate::native::winit_integration::UserEvent;
#[cfg(not(target_arch = "wasm32"))]
@@ -22,7 +22,7 @@ use raw_window_handle::{
use static_assertions::assert_not_impl_any;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub use winit::{event_loop::EventLoopBuilder, window::WindowAttributes};
/// Hook into the building of an event loop before it is run
@@ -30,7 +30,7 @@ pub use winit::{event_loop::EventLoopBuilder, window::WindowAttributes};
/// 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"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub type EventLoopBuilderHook = Box<dyn FnOnce(&mut EventLoopBuilder<UserEvent>)>;
/// Hook into the building of a the native window.
@@ -38,7 +38,7 @@ pub type EventLoopBuilderHook = Box<dyn FnOnce(&mut EventLoopBuilder<UserEvent>)
/// 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"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub type WindowBuilderHook = Box<dyn FnOnce(egui::ViewportBuilder) -> egui::ViewportBuilder>;
type DynError = Box<dyn std::error::Error + Send + Sync>;
@@ -79,7 +79,7 @@ pub struct CreationContext<'s> {
/// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`].
///
/// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s.
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
pub wgpu_render_state: Option<egui_wgpu::RenderState>,
/// Raw platform window handle
@@ -121,7 +121,7 @@ impl CreationContext<'_> {
gl: None,
#[cfg(feature = "glow")]
get_proc_address: None,
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
wgpu_render_state: None,
#[cfg(not(target_arch = "wasm32"))]
raw_window_handle: Err(HandleError::NotSupported),
@@ -135,9 +135,34 @@ impl CreationContext<'_> {
/// Implement this trait to write apps that can be compiled for both web/wasm and desktop/native using [`eframe`](https://github.com/emilk/egui/tree/main/crates/eframe).
pub trait App {
/// Called once before each call to [`Self::ui`],
/// and additionally also called when the UI is hidden, but [`egui::Context::request_repaint`] was called.
///
/// You may NOT show any ui or do any painting during the call to [`Self::logic`].
///
/// The [`egui::Context`] can be cloned and saved if you like.
///
/// To force another call to [`Self::logic`], call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
fn logic(&mut self, ctx: &egui::Context, frame: &mut Frame) {
_ = (ctx, frame);
}
/// Called each time the UI needs repainting, which may be many times per second.
///
/// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
/// The given [`egui::Ui`] has no margin or background color.
/// You can wrap your UI code in [`egui::CentralPanel`] or a [`egui::Frame::central_panel`] to remedy this.
///
/// The [`egui::Ui::ctx`] can be cloned and saved if you like.
/// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
///
/// This is called for the root viewport ([`egui::ViewportId::ROOT`]).
/// Use [`egui::Context::show_viewport_deferred`] to spawn additional viewports (windows).
/// (A "viewport" in egui means an native OS window).
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame);
/// Called each time the UI needs repainting, which may be many times per second.
///
/// Put your widgets into a [`egui::Panel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
///
/// The [`egui::Context`] can be cloned and saved if you like.
///
@@ -146,7 +171,10 @@ pub trait App {
/// This is called for the root viewport ([`egui::ViewportId::ROOT`]).
/// Use [`egui::Context::show_viewport_deferred`] to spawn additional viewports (windows).
/// (A "viewport" in egui means an native OS window).
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame);
#[deprecated = "Use Self::ui instead"]
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
_ = (ctx, frame);
}
/// Get a handle to the app.
///
@@ -317,7 +345,7 @@ pub struct NativeOptions {
pub hardware_acceleration: HardwareAcceleration,
/// What rendering backend to use.
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub renderer: Renderer,
/// This controls what happens when you close the main eframe window.
@@ -340,7 +368,7 @@ 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"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub event_loop_builder: Option<EventLoopBuilderHook>,
/// Hook into the building of a window.
@@ -349,7 +377,7 @@ pub struct NativeOptions {
/// window appearance.
///
/// Note: A [`NativeOptions`] clone will not include any `window_builder` hook.
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub window_builder: Option<WindowBuilderHook>,
#[cfg(feature = "glow")]
@@ -367,7 +395,7 @@ pub struct NativeOptions {
pub centered: bool,
/// Configures wgpu instance/device/adapter/surface creation and renderloop.
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
pub wgpu_options: egui_wgpu::WgpuConfiguration,
/// Controls whether or not the native window position and size will be
@@ -404,13 +432,13 @@ impl Clone for NativeOptions {
Self {
viewport: self.viewport.clone(),
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
event_loop_builder: None, // Skip any builder callbacks if cloning
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
window_builder: None, // Skip any builder callbacks if cloning
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
wgpu_options: self.wgpu_options.clone(),
persistence_path: self.persistence_path.clone(),
@@ -435,15 +463,15 @@ impl Default for NativeOptions {
stencil_buffer: 0,
hardware_acceleration: HardwareAcceleration::Preferred,
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
renderer: Renderer::default(),
run_and_return: true,
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
event_loop_builder: None,
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
window_builder: None,
#[cfg(feature = "glow")]
@@ -451,7 +479,7 @@ impl Default for NativeOptions {
centered: false,
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
persist_window: true,
@@ -471,6 +499,10 @@ impl Default for NativeOptions {
/// Options when using `eframe` in a web page.
#[cfg(target_arch = "wasm32")]
pub struct WebOptions {
/// What rendering backend to use.
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub renderer: Renderer,
/// Sets the number of bits in the depth buffer.
///
/// `egui` doesn't need the depth buffer, so the default value is 0.
@@ -484,7 +516,7 @@ pub struct WebOptions {
pub webgl_context_option: WebGlContextOption,
/// Configures wgpu instance/device/adapter/surface creation and renderloop.
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
pub wgpu_options: egui_wgpu::WgpuConfiguration,
/// Controls whether to apply dithering to minimize banding artifacts.
@@ -519,12 +551,15 @@ pub struct WebOptions {
impl Default for WebOptions {
fn default() -> Self {
Self {
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
renderer: Renderer::default(),
depth_buffer: 0,
#[cfg(feature = "glow")]
webgl_context_option: WebGlContextOption::BestFirst,
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
dithering: true,
@@ -561,7 +596,7 @@ 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"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
@@ -571,49 +606,49 @@ pub enum Renderer {
Glow,
/// Use [`egui_wgpu`] renderer for [`wgpu`](https://github.com/gfx-rs/wgpu).
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
Wgpu,
}
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
impl Default for Renderer {
fn default() -> Self {
#[cfg(not(feature = "glow"))]
#[cfg(not(feature = "wgpu"))]
#[cfg(not(feature = "wgpu_no_default_features"))]
compile_error!(
"eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'"
);
#[cfg(feature = "glow")]
#[cfg(not(feature = "wgpu"))]
#[cfg(not(feature = "wgpu_no_default_features"))]
return Self::Glow;
#[cfg(not(feature = "glow"))]
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
return Self::Wgpu;
// By default, only the `glow` feature is enabled, so if the user added `wgpu` to the feature list
// they probably wanted to use wgpu:
// It's weird that the user has enabled both glow and wgpu,
// but let's pick the better of the two (wgpu):
#[cfg(feature = "glow")]
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
return Self::Wgpu;
}
}
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
impl std::fmt::Display for Renderer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(feature = "glow")]
Self::Glow => "glow".fmt(f),
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
Self::Wgpu => "wgpu".fmt(f),
}
}
}
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
impl std::str::FromStr for Renderer {
type Err = String;
@@ -622,7 +657,7 @@ impl std::str::FromStr for Renderer {
#[cfg(feature = "glow")]
"glow" => Ok(Self::Glow),
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
"wgpu" => Ok(Self::Wgpu),
_ => Err(format!(
@@ -655,7 +690,7 @@ pub struct Frame {
Option<Box<dyn FnMut(glow::Texture) -> egui::TextureId>>,
/// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s.
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
#[doc(hidden)]
pub wgpu_render_state: Option<egui_wgpu::RenderState>,
@@ -705,7 +740,7 @@ impl Frame {
#[cfg(not(target_arch = "wasm32"))]
raw_window_handle: Err(HandleError::NotSupported),
storage: None,
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
wgpu_render_state: None,
}
}
@@ -764,7 +799,7 @@ impl Frame {
/// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`].
///
/// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s.
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> {
self.wgpu_render_state.as_ref()
}

View File

@@ -35,7 +35,7 @@
//!
//! impl MyEguiApp {
//! fn new(cc: &eframe::CreationContext<'_>) -> Self {
//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_global_style.
//! // Restore app state using cc.storage (requires the "persistence" feature).
//! // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
//! // for e.g. egui::PaintCallback.
@@ -44,8 +44,8 @@
//! }
//!
//! impl eframe::App for MyEguiApp {
//! fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
//! egui::CentralPanel::default().show(ctx, |ui| {
//! fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
//! egui::CentralPanel::default().show_inside(ui, |ui| {
//! ui.heading("Hello World!");
//! });
//! }
@@ -159,7 +159,7 @@ pub use {egui, egui::emath, egui::epaint};
#[cfg(feature = "glow")]
pub use {egui_glow, glow};
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
pub use {egui_wgpu, wgpu};
mod epi;
@@ -188,19 +188,19 @@ pub use web::{WebLogger, WebRunner};
// When compiling natively
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
mod native;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub use native::run::EframeWinitApplication;
#[cfg(not(any(target_arch = "wasm32", target_os = "ios")))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub use native::run::EframePumpStatus;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
#[cfg(feature = "persistence")]
pub use native::file_storage::storage_dir;
@@ -232,7 +232,7 @@ pub mod icon_data;
///
/// impl MyEguiApp {
/// fn new(cc: &eframe::CreationContext<'_>) -> Self {
/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_global_style.
/// // Restore app state using cc.storage (requires the "persistence" feature).
/// // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
/// // for e.g. egui::PaintCallback.
@@ -241,8 +241,8 @@ pub mod icon_data;
/// }
///
/// impl eframe::App for MyEguiApp {
/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
/// egui::CentralPanel::default().show(ctx, |ui| {
/// fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
/// egui::CentralPanel::default().show_inside(ui, |ui| {
/// ui.heading("Hello World!");
/// });
/// }
@@ -252,7 +252,7 @@ pub mod icon_data;
/// # Errors
/// This function can fail if we fail to set up a graphics context.
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
#[allow(clippy::needless_pass_by_value, clippy::allow_attributes)]
pub fn run_native(
app_name: &str,
@@ -268,7 +268,7 @@ pub fn run_native(
native::run::run_glow(app_name, native_options, app_creator)
}
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
Renderer::Wgpu => {
log::debug!("Using the wgpu renderer");
native::run::run_wgpu(app_name, native_options, app_creator)
@@ -312,8 +312,8 @@ pub fn run_native(
/// }
///
/// impl eframe::App for MyEguiApp {
/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
/// egui::CentralPanel::default().show(ctx, |ui| {
/// fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
/// egui::CentralPanel::default().show_inside(ui, |ui| {
/// ui.heading("Hello World!");
/// });
/// }
@@ -322,7 +322,7 @@ pub fn run_native(
///
/// See the `external_eventloop` example for a more complete example.
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub fn create_native<'a>(
app_name: &str,
mut native_options: NativeOptions,
@@ -343,7 +343,7 @@ pub fn create_native<'a>(
))
}
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
Renderer::Wgpu => {
log::debug!("Using the wgpu renderer");
EframeWinitApplication::new(native::run::create_wgpu(
@@ -357,7 +357,7 @@ pub fn create_native<'a>(
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer {
#[cfg(not(feature = "__screenshot"))]
assert!(
@@ -371,7 +371,7 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer {
let renderer = native_options.renderer;
#[cfg(all(feature = "glow", feature = "wgpu"))]
#[cfg(all(feature = "glow", feature = "wgpu_no_default_features"))]
{
match native_options.renderer {
Renderer::Glow => "glow",
@@ -385,6 +385,65 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer {
// ----------------------------------------------------------------------------
/// The simplest way to get started when writing a native app.
///
/// This does NOT support persistence of custom user data. For that you need to use [`run_native`].
/// However, it DOES support persistence of egui data (window positions and sizes, how far the user has scrolled in a
/// [`ScrollArea`](egui::ScrollArea), etc.) if the persistence feature is enabled.
///
/// # Example
/// ``` no_run
/// fn main() -> eframe::Result {
/// // Our application state:
/// let mut name = "Arthur".to_owned();
/// let mut age = 42;
///
/// let options = eframe::NativeOptions::default();
/// eframe::run_ui_native("My egui App", options, move |ui, _frame| {
/// // Wrap everything in a CentralPanel so we get some margins and a background color:
/// egui::CentralPanel::default().show_inside(ui, |ui| {
/// ui.heading("My egui Application");
/// ui.horizontal(|ui| {
/// let name_label = ui.label("Your name: ");
/// ui.text_edit_singleline(&mut name)
/// .labelled_by(name_label.id);
/// });
/// ui.add(egui::Slider::new(&mut age, 0..=120).text("age"));
/// if ui.button("Increment").clicked() {
/// age += 1;
/// }
/// ui.label(format!("Hello '{name}', age {age}"));
/// });
/// })
/// }
/// ```
///
/// # Errors
/// This function can fail if we fail to set up a graphics context.
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub fn run_ui_native(
app_name: &str,
native_options: NativeOptions,
ui_fun: impl FnMut(&mut egui::Ui, &mut Frame) + 'static,
) -> Result {
struct SimpleApp<U> {
ui_fun: U,
}
impl<U: FnMut(&mut egui::Ui, &mut Frame) + 'static> App for SimpleApp<U> {
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame) {
(self.ui_fun)(ui, frame);
}
}
run_native(
app_name,
native_options,
Box::new(|_cc| Ok(Box::new(SimpleApp { ui_fun }))),
)
}
/// The simplest way to get started when writing a native app.
///
/// This does NOT support persistence of custom user data. For that you need to use [`run_native`].
@@ -419,8 +478,9 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer {
///
/// # Errors
/// This function can fail if we fail to set up a graphics context.
#[deprecated = "Use run_ui_native instead"]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub fn run_simple_native(
app_name: &str,
native_options: NativeOptions,
@@ -431,6 +491,8 @@ pub fn run_simple_native(
}
impl<U: FnMut(&egui::Context, &mut Frame) + 'static> App for SimpleApp<U> {
fn ui(&mut self, _ui: &mut egui::Ui, _frame: &mut Frame) {}
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
(self.update_fun)(ctx, frame);
}
@@ -472,7 +534,7 @@ pub enum Error {
OpenGL(egui_glow::PainterError),
/// An error from [`wgpu`].
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
Wgpu(egui_wgpu::WgpuError),
}
@@ -510,7 +572,7 @@ impl From<egui_glow::PainterError> for Error {
}
}
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
impl From<egui_wgpu::WgpuError> for Error {
#[inline]
fn from(err: egui_wgpu::WgpuError) -> Self {
@@ -551,7 +613,7 @@ impl std::fmt::Display for Error {
write!(f, "egui_glow: {err}")
}
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
Self::Wgpu(err) => {
write!(f, "WGPU error: {err}")
}

View File

@@ -179,7 +179,9 @@ impl EpiIntegration {
#[cfg(feature = "glow")] glow_register_native_texture: Option<
Box<dyn FnMut(glow::Texture) -> egui::TextureId>,
>,
#[cfg(feature = "wgpu")] wgpu_render_state: Option<egui_wgpu::RenderState>,
#[cfg(feature = "wgpu_no_default_features")] wgpu_render_state: Option<
egui_wgpu::RenderState,
>,
) -> Self {
let frame = epi::Frame {
info: epi::IntegrationInfo { cpu_usage: None },
@@ -188,7 +190,7 @@ impl EpiIntegration {
gl,
#[cfg(feature = "glow")]
glow_register_native_texture,
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
wgpu_render_state,
raw_display_handle: window.display_handle().map(|h| h.as_raw()),
raw_window_handle: window.window_handle().map(|h| h.as_raw()),
@@ -270,14 +272,27 @@ impl EpiIntegration {
app.raw_input_hook(&self.egui_ctx, &mut raw_input);
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
let full_output = self.egui_ctx.run_ui(raw_input, |ui| {
if let Some(viewport_ui_cb) = viewport_ui_cb {
// Child viewport
profiling::scope!("viewport_callback");
viewport_ui_cb(egui_ctx);
viewport_ui_cb(ui);
} else {
profiling::scope!("App::update");
app.update(egui_ctx, &mut self.frame);
{
profiling::scope!("App::logic");
app.logic(ui.ctx(), &mut self.frame);
}
{
profiling::scope!("App::update");
#[expect(deprecated)]
app.update(ui.ctx(), &mut self.frame);
}
{
profiling::scope!("App::ui");
app.ui(ui, &mut self.frame);
}
}
});

View File

@@ -239,7 +239,7 @@ impl<'app> GlowWinitApp<'app> {
let painter = painter.clone();
move |native| painter.borrow_mut().register_native_texture(native)
})),
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
None,
);
@@ -301,7 +301,7 @@ impl<'app> GlowWinitApp<'app> {
storage: integration.frame.storage(),
gl: Some(gl),
get_proc_address: Some(&get_proc_address),
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
wgpu_render_state: None,
raw_display_handle: window.display_handle().map(|h| h.as_raw()),
raw_window_handle: window.window_handle().map(|h| h.as_raw()),
@@ -571,7 +571,7 @@ impl GlowWinitRunning<'_> {
.options_mut(|opt| opt.begin_pass(&raw_input));
let clear_color = self
.app
.clear_color(&self.integration.egui_ctx.style().visuals);
.clear_color(&self.integration.egui_ctx.global_style().visuals);
let has_many_viewports = self.glutin.borrow().viewports.len() > 1;
let clear_before_update = !has_many_viewports; // HACK: for some reason, an early clear doesn't "take" on Mac with multiple viewports.
@@ -719,11 +719,11 @@ impl GlowWinitRunning<'_> {
// vsync - don't count as frame-time:
frame_timer.pause();
profiling::scope!("swap_buffers");
let context = current_gl_context
.as_ref()
.ok_or(egui_glow::PainterError::from(
let context = current_gl_context.as_ref().ok_or_else(|| {
egui_glow::PainterError::from(
"failed to get current context to swap buffers".to_owned(),
))?;
)
})?;
gl_surface.swap_buffers(context)?;
frame_timer.resume();
@@ -1362,7 +1362,7 @@ fn initialize_or_update_viewport(
ids: ViewportIdPair,
class: ViewportClass,
mut builder: ViewportBuilder,
viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
viewport_ui_cb: Option<Arc<dyn Fn(&mut egui::Ui) + Send + Sync>>,
) -> &mut Viewport {
profiling::function_scope!();
@@ -1494,8 +1494,8 @@ fn render_immediate_viewport(
shapes,
pixels_per_point,
viewport_output,
} = egui_ctx.run(input, |ctx| {
viewport_ui_cb(ctx);
} = egui_ctx.run_ui(input, |ui| {
viewport_ui_cb(ui);
});
// ---------------------------------------------------

View File

@@ -12,5 +12,5 @@ pub(crate) mod winit_integration;
#[cfg(feature = "glow")]
mod glow_integration;
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
mod wgpu_integration;

View File

@@ -381,7 +381,7 @@ pub fn create_glow<'a>(
// ----------------------------------------------------------------------------
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
pub fn run_wgpu(
app_name: &str,
mut native_options: epi::NativeOptions,
@@ -404,7 +404,7 @@ pub fn run_wgpu(
run_and_exit(event_loop, wgpu_eframe)
}
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
pub fn create_wgpu<'a>(
app_name: &str,
native_options: epi::NativeOptions,

View File

@@ -690,7 +690,7 @@ impl WgpuWinitRunning<'_> {
let vsync_secs = painter.paint_and_update_textures(
viewport_id,
pixels_per_point,
app.clear_color(&egui_ctx.style().visuals),
app.clear_color(&egui_ctx.global_style().visuals),
&clipped_primitives,
&textures_delta,
screenshot_commands,
@@ -1027,8 +1027,8 @@ fn render_immediate_viewport(
shapes,
pixels_per_point,
viewport_output,
} = egui_ctx.run(input, |ctx| {
viewport_ui_cb(ctx);
} = egui_ctx.run_ui(input, |ui| {
viewport_ui_cb(ui);
});
// ------------------------------------------
@@ -1155,7 +1155,7 @@ fn initialize_or_update_viewport<'a>(
ids: ViewportIdPair,
class: ViewportClass,
mut builder: ViewportBuilder,
viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
viewport_ui_cb: Option<Arc<dyn Fn(&mut egui::Ui) + Send + Sync>>,
painter: &mut egui_wgpu::winit::Painter,
) -> &'a mut Viewport {
use std::collections::btree_map::Entry;

View File

@@ -1,15 +1,15 @@
use egui::{TexturesDelta, UserData, ViewportCommand};
use crate::{App, epi};
use crate::{App, epi, web::web_painter::WebPainter};
use super::{NeedRepaint, now_sec, text_agent::TextAgent, web_painter::WebPainter as _};
use super::{NeedRepaint, now_sec, text_agent::TextAgent};
pub struct AppRunner {
#[allow(dead_code, clippy::allow_attributes)]
pub(crate) web_options: crate::WebOptions,
pub(crate) frame: epi::Frame,
egui_ctx: egui::Context,
painter: super::ActiveWebPainter,
painter: Box<dyn WebPainter>,
pub(crate) input: super::WebInput,
app: Box<dyn epi::App>,
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
@@ -34,6 +34,10 @@ impl Drop for AppRunner {
impl AppRunner {
/// # Errors
/// Failure to initialize WebGL renderer, or failure to create app.
#[cfg_attr(
not(feature = "wgpu_no_default_features"),
expect(clippy::unused_async)
)]
pub async fn new(
canvas: web_sys::HtmlCanvasElement,
web_options: crate::WebOptions,
@@ -41,7 +45,41 @@ impl AppRunner {
text_agent: TextAgent,
) -> Result<Self, String> {
let egui_ctx = egui::Context::default();
let painter = super::ActiveWebPainter::new(egui_ctx.clone(), canvas, &web_options).await?;
#[allow(clippy::allow_attributes, unused_assignments)]
#[cfg(feature = "glow")]
let mut gl = None;
#[allow(clippy::allow_attributes, unused_assignments)]
#[cfg(feature = "wgpu_no_default_features")]
let mut wgpu_render_state = None;
let painter = match web_options.renderer {
#[cfg(feature = "glow")]
epi::Renderer::Glow => {
log::debug!("Using the glow renderer");
let painter = super::web_painter_glow::WebPainterGlow::new(
egui_ctx.clone(),
canvas,
&web_options,
)?;
gl = Some(painter.gl().clone());
Box::new(painter) as Box<dyn WebPainter>
}
#[cfg(feature = "wgpu_no_default_features")]
epi::Renderer::Wgpu => {
log::debug!("Using the wgpu renderer");
let painter = super::web_painter_wgpu::WebPainterWgpu::new(
egui_ctx.clone(),
canvas,
&web_options,
)
.await?;
wgpu_render_state = painter.render_state();
Box::new(painter) as Box<dyn WebPainter>
}
};
let info = epi::IntegrationInfo {
web_info: epi::WebInfo {
@@ -79,15 +117,13 @@ impl AppRunner {
storage: Some(&storage),
#[cfg(feature = "glow")]
gl: Some(painter.gl().clone()),
gl: gl.clone(),
#[cfg(feature = "glow")]
get_proc_address: None,
#[cfg(all(feature = "wgpu", not(feature = "glow")))]
wgpu_render_state: painter.render_state(),
#[cfg(all(feature = "wgpu", feature = "glow"))]
wgpu_render_state: None,
#[cfg(feature = "wgpu_no_default_features")]
wgpu_render_state: wgpu_render_state.clone(),
};
let app = app_creator(&cc).map_err(|err| err.to_string())?;
@@ -96,12 +132,10 @@ impl AppRunner {
storage: Some(Box::new(storage)),
#[cfg(feature = "glow")]
gl: Some(painter.gl().clone()),
gl,
#[cfg(all(feature = "wgpu", not(feature = "glow")))]
wgpu_render_state: painter.render_state(),
#[cfg(all(feature = "wgpu", feature = "glow"))]
wgpu_render_state: None,
#[cfg(feature = "wgpu_no_default_features")]
wgpu_render_state,
};
let needs_repaint: std::sync::Arc<NeedRepaint> =
@@ -239,8 +273,13 @@ impl AppRunner {
self.app.raw_input_hook(&self.egui_ctx, &mut raw_input);
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
self.app.update(egui_ctx, &mut self.frame);
let full_output = self.egui_ctx.run_ui(raw_input, |ui| {
self.app.logic(ui.ctx(), &mut self.frame);
#[expect(deprecated)]
self.app.update(ui.ctx(), &mut self.frame);
self.app.ui(ui, &mut self.frame);
});
let egui::FullOutput {
platform_output,
@@ -297,7 +336,7 @@ impl AppRunner {
}
if let Err(err) = self.painter.paint_and_update_textures(
self.app.clear_color(&self.egui_ctx.style().visuals),
self.app.clear_color(&self.egui_ctx.global_style().visuals),
&clipped_primitives,
self.egui_ctx.pixels_per_point(),
&textures_delta,
@@ -324,8 +363,7 @@ impl AppRunner {
events: _, // already handled
mutable_text_under_cursor: _, // TODO(#4569): https://github.com/emilk/egui/issues/4569
ime,
#[cfg(feature = "accesskit")]
accesskit_update: _, // not currently implemented
accesskit_update: _, // not currently implemented
num_completed_passes: _, // handled by `Context::run`
request_discard_reasons: _, // handled by `Context::run`
} = platform_output;

View File

@@ -196,11 +196,13 @@ pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner)
let prevent_default = should_prevent_default_for_key(runner, &modifiers, egui_key);
// log::debug!(
// "On keydown {:?} {egui_key:?}, has_focus: {has_focus}, egui_wants_keyboard: {}, prevent_default: {prevent_default}",
// event.key().as_str(),
// runner.egui_ctx().wants_keyboard_input()
// );
if false {
log::debug!(
"On keydown {:?} {egui_key:?}, has_focus: {has_focus}, egui_wants_keyboard: {}, prevent_default: {prevent_default}",
event.key().as_str(),
runner.egui_ctx().egui_wants_keyboard_input()
);
}
if prevent_default {
event.prevent_default();

View File

@@ -23,20 +23,16 @@ pub use panic_handler::{PanicHandler, PanicSummary};
pub use web_logger::WebLogger;
pub use web_runner::WebRunner;
#[cfg(not(any(feature = "glow", feature = "wgpu")))]
#[cfg(not(any(feature = "glow", feature = "wgpu_no_default_features")))]
compile_error!("You must enable either the 'glow' or 'wgpu' feature");
mod web_painter;
#[cfg(feature = "glow")]
mod web_painter_glow;
#[cfg(feature = "glow")]
pub(crate) type ActiveWebPainter = web_painter_glow::WebPainterGlow;
#[cfg(feature = "wgpu")]
#[cfg(feature = "wgpu_no_default_features")]
mod web_painter_wgpu;
#[cfg(all(feature = "wgpu", not(feature = "glow")))]
pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu;
pub use backend::*;

View File

@@ -20,7 +20,7 @@ impl WebPainterGlow {
self.painter.gl()
}
pub async fn new(
pub fn new(
_ctx: egui::Context,
canvas: HtmlCanvasElement,
options: &WebOptions,

View File

@@ -1,13 +1,15 @@
use std::sync::Arc;
use super::web_painter::WebPainter;
use crate::WebOptions;
use egui::{Event, UserData, ViewportId};
use egui_wgpu::capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel};
use egui_wgpu::{RenderState, SurfaceErrorAction};
use egui_wgpu::{
RenderState, SurfaceErrorAction,
capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel},
};
use wasm_bindgen::JsValue;
use web_sys::HtmlCanvasElement;
use super::web_painter::WebPainter;
pub(crate) struct WebPainterWgpu {
canvas: HtmlCanvasElement,
surface: wgpu::Surface<'static>,
@@ -23,7 +25,6 @@ pub(crate) struct WebPainterWgpu {
}
impl WebPainterWgpu {
#[expect(unused)] // only used if `wgpu` is the only active feature.
pub fn render_state(&self) -> Option<RenderState> {
self.render_state.clone()
}
@@ -55,11 +56,10 @@ impl WebPainterWgpu {
})
}
#[expect(unused)] // only used if `wgpu` is the only active feature.
pub async fn new(
ctx: egui::Context,
canvas: web_sys::HtmlCanvasElement,
options: &WebOptions,
options: &crate::WebOptions,
) -> Result<Self, String> {
log::debug!("Creating wgpu painter");
@@ -243,13 +243,26 @@ impl WebPainter for WebPainterWgpu {
depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| {
wgpu::RenderPassDepthStencilAttachment {
view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
// It is very unlikely that the depth buffer is needed after egui finished rendering
// so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon)
store: wgpu::StoreOp::Discard,
}),
stencil_ops: None,
depth_ops: self
.depth_stencil_format
.is_some_and(|depth_stencil_format| {
depth_stencil_format.has_depth_aspect()
})
.then_some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
// It is very unlikely that the depth buffer is needed after egui finished rendering
// so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon)
store: wgpu::StoreOp::Discard,
}),
stencil_ops: self
.depth_stencil_format
.is_some_and(|depth_stencil_format| {
depth_stencil_format.has_stencil_aspect()
})
.then_some(wgpu::Operations {
load: wgpu::LoadOp::Clear(0),
store: wgpu::StoreOp::Discard,
}),
}
}),
label: Some("egui_render"),

View File

@@ -6,6 +6,14 @@ This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
## 0.33.3 - 2025-12-11
Nothing new
## 0.33.2 - 2025-11-13
* Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman)
## 0.33.0 - 2025-10-09
### 🔧 Changed
* Update wgpu to 26 and wasm-bindgen to 0.2.100 [#7540](https://github.com/emilk/egui/pull/7540) by [@Kumpelinus](https://github.com/Kumpelinus)

View File

@@ -27,8 +27,11 @@ rustdoc-args = ["--generate-link-to-definition"]
[features]
default = ["fragile-send-sync-non-atomic-wasm", "macos-window-resize-jitter-fix", "wgpu/default"]
## Enables the `capture` module for capturing screenshots.
capture = ["dep:egui"]
## Enable [`winit`](https://docs.rs/winit) integration. On Linux, requires either `wayland` or `x11`
winit = ["dep:winit", "winit/rwh_06"]
winit = ["dep:winit", "winit/rwh_06", "dep:egui", "capture"]
## Enables Wayland support for winit.
wayland = ["winit?/wayland"]
@@ -47,7 +50,6 @@ fragile-send-sync-non-atomic-wasm = ["wgpu/fragile-send-sync-non-atomic-wasm"]
macos-window-resize-jitter-fix = ["wgpu/metal"]
[dependencies]
egui = { workspace = true, default-features = false }
epaint = { workspace = true, default-features = false, features = ["bytemuck"] }
ahash.workspace = true
@@ -62,4 +64,5 @@ wgpu = { workspace = true, features = ["wgsl"] }
# Optional dependencies:
egui = { workspace = true, optional = true, default-features = false }
winit = { workspace = true, optional = true, default-features = false }

View File

@@ -29,6 +29,7 @@ pub use renderer::*;
pub use setup::{NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew, WgpuSetupExisting};
/// Helpers for capturing screenshots of the UI.
#[cfg(feature = "capture")]
pub mod capture;
/// Module for painting [`egui`](https://github.com/emilk/egui) with [`wgpu`] on [`winit`].

View File

@@ -526,13 +526,28 @@ impl Painter {
depth_stencil_attachment: self.depth_texture_view.get(&viewport_id).map(|view| {
wgpu::RenderPassDepthStencilAttachment {
view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
// It is very unlikely that the depth buffer is needed after egui finished rendering
// so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon)
store: wgpu::StoreOp::Discard,
}),
stencil_ops: None,
depth_ops: self
.options
.depth_stencil_format
.is_some_and(|depth_stencil_format| {
depth_stencil_format.has_depth_aspect()
})
.then_some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
// It is very unlikely that the depth buffer is needed after egui finished rendering
// so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon)
store: wgpu::StoreOp::Discard,
}),
stencil_ops: self
.options
.depth_stencil_format
.is_some_and(|depth_stencil_format| {
depth_stencil_format.has_stencil_aspect()
})
.then_some(wgpu::Operations {
load: wgpu::LoadOp::Clear(0),
store: wgpu::StoreOp::Discard,
}),
}
}),
timestamp_writes: None,

View File

@@ -5,6 +5,14 @@ This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
## 0.33.3 - 2025-12-11
Nothing new
## 0.33.2 - 2025-11-13
* Don't enable `arboard` on iOS [#7663](https://github.com/emilk/egui/pull/7663) by [@irh](https://github.com/irh)
## 0.33.0 - 2025-10-09
### ⭐ Added
* Add rotation gesture support for trackpad sources [#7453](https://github.com/emilk/egui/pull/7453) by [@thatcomputerguy0101](https://github.com/thatcomputerguy0101)

View File

@@ -24,7 +24,7 @@ rustdoc-args = ["--generate-link-to-definition"]
default = ["clipboard", "links", "wayland", "winit/default", "x11"]
## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/).
accesskit = ["dep:accesskit_winit", "egui/accesskit"]
accesskit = ["dep:accesskit_winit"]
# Allow crates to choose an android-activity backend via Winit
# - It's important that most applications should not have to depend on android-activity directly, and can

View File

@@ -309,21 +309,21 @@ impl State {
self.on_mouse_button_input(*state, *button);
EventResponse {
repaint: true,
consumed: self.egui_ctx.wants_pointer_input(),
consumed: self.egui_ctx.egui_wants_pointer_input(),
}
}
WindowEvent::MouseWheel { delta, phase, .. } => {
self.on_mouse_wheel(window, *delta, *phase);
EventResponse {
repaint: true,
consumed: self.egui_ctx.wants_pointer_input(),
consumed: self.egui_ctx.egui_wants_pointer_input(),
}
}
WindowEvent::CursorMoved { position, .. } => {
self.on_cursor_moved(window, *position);
EventResponse {
repaint: true,
consumed: self.egui_ctx.is_using_pointer(),
consumed: self.egui_ctx.egui_is_using_pointer(),
}
}
WindowEvent::CursorLeft { .. } => {
@@ -340,8 +340,10 @@ impl State {
let consumed = match touch.phase {
winit::event::TouchPhase::Started
| winit::event::TouchPhase::Ended
| winit::event::TouchPhase::Cancelled => self.egui_ctx.wants_pointer_input(),
winit::event::TouchPhase::Moved => self.egui_ctx.is_using_pointer(),
| winit::event::TouchPhase::Cancelled => {
self.egui_ctx.egui_wants_pointer_input()
}
winit::event::TouchPhase::Moved => self.egui_ctx.egui_is_using_pointer(),
};
EventResponse {
repaint: true,
@@ -392,7 +394,7 @@ impl State {
EventResponse {
repaint: true,
consumed: self.egui_ctx.wants_keyboard_input(),
consumed: self.egui_ctx.egui_wants_keyboard_input(),
}
}
WindowEvent::KeyboardInput {
@@ -413,7 +415,7 @@ impl State {
self.on_keyboard_input(event);
// When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes.
let consumed = self.egui_ctx.wants_keyboard_input()
let consumed = self.egui_ctx.egui_wants_keyboard_input()
|| event.logical_key
== winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
EventResponse {
@@ -528,7 +530,7 @@ impl State {
self.egui_input.events.push(egui::Event::Zoom(zoom_factor));
EventResponse {
repaint: true,
consumed: self.egui_ctx.wants_pointer_input(),
consumed: self.egui_ctx.egui_wants_pointer_input(),
}
}
@@ -541,7 +543,7 @@ impl State {
.push(egui::Event::Rotate(-delta.to_radians()));
EventResponse {
repaint: true,
consumed: self.egui_ctx.wants_pointer_input(),
consumed: self.egui_ctx.egui_wants_pointer_input(),
}
}
@@ -556,7 +558,7 @@ impl State {
});
EventResponse {
repaint: true,
consumed: self.egui_ctx.wants_pointer_input(),
consumed: self.egui_ctx.egui_wants_pointer_input(),
}
}
}
@@ -888,7 +890,6 @@ impl State {
events: _, // handled elsewhere
mutable_text_under_cursor: _, // only used in eframe web
ime,
#[cfg(feature = "accesskit")]
accesskit_update,
num_completed_passes: _, // `egui::Context::run` handles this
request_discard_reasons: _, // `egui::Context::run` handles this
@@ -947,6 +948,9 @@ impl State {
profiling::scope!("accesskit");
accesskit.update_if_active(|| update);
}
#[cfg(not(feature = "accesskit"))]
let _ = accesskit_update;
}
fn set_cursor_icon(&mut self, window: &Window, cursor_icon: egui::CursorIcon) {

View File

@@ -26,10 +26,6 @@ rustdoc-args = ["--generate-link-to-definition"]
[features]
default = ["default_fonts"]
## Exposes detailed accessibility implementation required by platform
## accessibility APIs. Also requires support in the egui integration.
accesskit = ["dep:accesskit"]
## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`epaint::Vertex`], [`emath::Vec2`] etc to `&[u8]`.
bytemuck = ["epaint/bytemuck"]
@@ -61,7 +57,7 @@ persistence = ["serde", "epaint/serde", "ron"]
rayon = ["epaint/rayon"]
## Allow serialization using [`serde`](https://docs.rs/serde).
serde = ["dep:serde", "epaint/serde", "accesskit?/serde"]
serde = ["dep:serde", "epaint/serde", "accesskit/serde"]
## Change Vertex layout to be compatible with unity
unity = ["epaint/unity"]
@@ -75,6 +71,7 @@ _override_unity = ["epaint/_override_unity"]
emath = { workspace = true, default-features = false }
epaint = { workspace = true, default-features = false }
accesskit.workspace = true
ahash.workspace = true
bitflags.workspace = true
log.workspace = true
@@ -84,7 +81,6 @@ smallvec.workspace = true
unicode-segmentation.workspace = true
#! ### Optional dependencies
accesskit = { workspace = true, optional = true }
backtrace = { workspace = true, optional = true }

View File

@@ -82,7 +82,7 @@ impl<'a> AtomKind<'a> {
) -> (Vec2, SizedAtomKind<'a>) {
match self {
AtomKind::Text(text) => {
let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode());
let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
let galley = text.into_galley(ui, Some(wrap_mode), available_size.x, fallback_font);
(galley.intrinsic_size(), SizedAtomKind::Text(galley))
}

View File

@@ -168,7 +168,7 @@ impl<'a> AtomLayout<'a> {
let fallback_font = fallback_font.unwrap_or_default();
let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode());
let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
// If the TextWrapMode is not Extend, ensure there is some item marked as `shrink`.
// If none is found, mark the first text item as `shrink`.
@@ -188,7 +188,7 @@ impl<'a> AtomLayout<'a> {
let fallback_text_color =
fallback_text_color.unwrap_or_else(|| ui.style().visuals.text_color());
let gap = gap.unwrap_or(ui.spacing().icon_spacing);
let gap = gap.unwrap_or_else(|| ui.spacing().icon_spacing);
// The size available for the content
let available_inner_size = ui.available_size() - frame.total_margin().sum();

View File

@@ -469,7 +469,7 @@ impl Area {
// during the sizing pass we will use this as the max size
let mut size = default_size;
let default_area_size = ctx.style().spacing.default_area_size;
let default_area_size = ctx.global_style().spacing.default_area_size;
if size.x.is_nan() {
size.x = default_area_size.x;
}
@@ -525,11 +525,21 @@ impl Area {
true,
);
// Used to prevent drift
let pivot_at_start_of_drag_id = id.with("pivot_at_drag_start");
if movable
&& move_response.dragged()
&& let Some(pivot_pos) = &mut state.pivot_pos
{
*pivot_pos += move_response.drag_delta();
let pivot_at_start_of_drag = ctx.data_mut(|data| {
*data.get_temp_mut_or::<Pos2>(pivot_at_start_of_drag_id, *pivot_pos)
});
*pivot_pos =
pivot_at_start_of_drag + move_response.total_drag_delta().unwrap_or_default();
} else {
ctx.data_mut(|data| data.remove::<Pos2>(pivot_at_start_of_drag_id));
}
if (move_response.dragged() || move_response.clicked())
@@ -543,13 +553,14 @@ impl Area {
move_response
};
if constrain {
state.set_left_top_pos(
Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min,
);
}
state.set_left_top_pos(state.left_top_pos());
state.set_left_top_pos(round_area_position(
ctx,
if constrain {
Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min
} else {
state.left_top_pos()
},
));
// Update response with possibly moved/constrained rect:
move_response.rect = state.rect();
@@ -570,6 +581,16 @@ impl Area {
}
}
fn round_area_position(ctx: &Context, pos: Pos2) -> Pos2 {
// We round a lot of rendering to pixels, so we round the whole
// area positions to pixels too, so avoid widgets appearing to float
// around independently of each other when the area is dragged.
// But just in case pixels_per_point is irrational,
// we then also round to ui coordinates:
pos.round_to_pixels(ctx.pixels_per_point()).round_ui()
}
impl Prepared {
pub(crate) fn state(&self) -> &AreaState {
&self.state
@@ -613,7 +634,8 @@ impl Prepared {
{
let age =
ctx.input(|i| (i.time - last_became_visible_at) as f32 + i.predicted_dt / 2.0);
let opacity = crate::remap_clamp(age, 0.0..=ctx.style().animation_time, 0.0..=1.0);
let opacity =
crate::remap_clamp(age, 0.0..=ctx.global_style().animation_time, 0.0..=1.0);
let opacity = emath::easing::quadratic_out(opacity); // slow fade-out = quick fade-in
ui.multiply_opacity(opacity);
if opacity < 1.0 {
@@ -690,7 +712,7 @@ fn automatic_area_position(ctx: &Context, layer_id: LayerId) -> Pos2 {
// NOTE: for the benefit of the egui demo, we position the windows so they don't
// cover the side panels, which means we use `available_rect` here instead of `constrain_rect` or `screen_rect`.
let available_rect = ctx.available_rect();
let available_rect = ctx.globally_available_rect();
let spacing = 16.0;
let left = available_rect.left() + spacing;

View File

@@ -69,7 +69,7 @@ impl CollapsingState {
pub fn toggle(&mut self, ui: &Ui) {
self.state.open = !self.state.open;
ui.ctx().request_repaint();
ui.request_repaint();
}
/// 0 for closed, 1 for open, with tweening

View File

@@ -161,14 +161,13 @@ impl MenuState {
if state.last_visible_pass + 1 < pass_nr {
state.open_item = None;
}
if let Some(item) = state.open_item {
if data
if let Some(item) = state.open_item
&& data
.get_temp(item.with(Self::ID))
.is_none_or(|item: Self| item.last_visible_pass + 1 < pass_nr)
{
// If the open item wasn't shown for at least a frame, reset the open item
state.open_item = None;
}
{
// If the open item wasn't shown for at least a frame, reset the open item
state.open_item = None;
}
let r = f(&mut state);
data.insert_temp(state_id, state);
@@ -208,7 +207,7 @@ impl MenuState {
/// egui::MenuBar::new().ui(ui, |ui| {
/// ui.menu_button("File", |ui| {
/// if ui.button("Quit").clicked() {
/// ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
/// ui.send_viewport_cmd(egui::ViewportCommand::Close);
/// }
/// });
/// });
@@ -557,7 +556,7 @@ impl SubMenu {
if is_moving_towards_rect {
// We need to repaint while this is true, so we can detect when
// the pointer is no longer moving towards the rect
ui.ctx().request_repaint();
ui.request_repaint();
}
let hovering_other_menu_entry = is_open
&& !is_hovered

View File

@@ -1,4 +1,4 @@
//! Containers are pieces of the UI which wraps other pieces of UI. Examples: [`Window`], [`ScrollArea`], [`Resize`], [`SidePanel`], etc.
//! Containers are pieces of the UI which wraps other pieces of UI. Examples: [`Window`], [`ScrollArea`], [`Resize`], [`Panel`], etc.
//!
//! For instance, a [`Frame`] adds a frame and background to some contained UI.
@@ -27,7 +27,7 @@ pub use {
frame::Frame,
modal::{Modal, ModalResponse},
old_popup::*,
panel::{CentralPanel, SidePanel, TopBottomPanel},
panel::*,
popup::*,
resize::Resize,
scene::{DragPanButtons, Scene},

View File

@@ -11,7 +11,7 @@ use crate::{
///
/// You can show multiple modals on top of each other. The topmost modal will always be
/// the most recently shown one.
/// If multiple modals are newly shown in the same frame, the order of the modals not undefined
/// If multiple modals are newly shown in the same frame, the order of the modals is undefined
/// (either first or second could be top).
pub struct Modal {
pub area: Area,

File diff suppressed because it is too large Load Diff

View File

@@ -465,7 +465,7 @@ impl<'a> Popup<'a> {
pub fn get_best_align(&self) -> RectAlign {
let expected_popup_size = self
.get_expected_size()
.unwrap_or(vec2(self.width.unwrap_or(0.0), 0.0));
.unwrap_or_else(|| vec2(self.width.unwrap_or(0.0), 0.0));
let Some(anchor_rect) = self.anchor.rect(self.id, &self.ctx) else {
return self.rect_align;
@@ -473,6 +473,7 @@ impl<'a> Popup<'a> {
RectAlign::find_best_align(
#[expect(clippy::iter_on_empty_collections)]
#[expect(clippy::or_fun_call)]
once(self.rect_align).chain(
self.alternative_aligns
// Need the empty slice so the iters have the same type so we can unwrap_or

View File

@@ -213,7 +213,7 @@ impl Resize {
});
let mut state = State::load(ui.ctx(), id).unwrap_or_else(|| {
ui.ctx().request_repaint(); // counter frame delay
ui.request_repaint(); // counter frame delay
let default_size = self
.default_size
@@ -362,20 +362,20 @@ impl Resize {
paint_resize_corner(ui, &corner_response);
if corner_response.hovered() || corner_response.dragged() {
ui.ctx().set_cursor_icon(CursorIcon::ResizeNwSe);
ui.set_cursor_icon(CursorIcon::ResizeNwSe);
}
}
state.store(ui.ctx(), id);
#[cfg(debug_assertions)]
if ui.ctx().style().debug.show_resize {
ui.ctx().debug_painter().debug_rect(
if ui.global_style().debug.show_resize {
ui.debug_painter().debug_rect(
Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size),
Color32::GREEN,
"desired_size",
);
ui.ctx().debug_painter().debug_rect(
ui.debug_painter().debug_rect(
Rect::from_min_size(content_ui.min_rect().left_top(), state.last_content_size),
Color32::LIGHT_BLUE,
"last_content_size",

View File

@@ -244,8 +244,8 @@ impl Scene {
&& resp.contains_pointer()
{
let pointer_in_scene = to_global.inverse() * mouse_pos;
let zoom_delta = ui.ctx().input(|i| i.zoom_delta());
let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta());
let zoom_delta = ui.input(|i| i.zoom_delta());
let pan_delta = ui.input(|i| i.smooth_scroll_delta());
// Most of the time we can return early. This is also important to
// avoid `ui_from_scene` to change slightly due to floating point errors.

View File

@@ -1,7 +1,12 @@
//! See [`ScrollArea`] for docs.
#![allow(clippy::needless_range_loop)]
use std::ops::{Add, AddAssign, BitOr, BitOrAssign};
use emath::GuiRounding as _;
use epaint::Margin;
use crate::{
Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder,
UiKind, UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp,
@@ -256,7 +261,7 @@ impl AddAssign for ScrollSource {
/// ### Coordinate system
/// * content: size of contents (generally large; that's why we want scroll bars)
/// * outer: size of scroll area including scroll bar(s)
/// * inner: excluding scroll bar(s). The area we clip the contents to.
/// * inner: excluding scroll bar(s). The area we clip the contents to. Includes `content_margin`.
///
/// If the floating scroll bars settings is turned on then `inner == outer`.
///
@@ -292,6 +297,8 @@ pub struct ScrollArea {
scroll_source: ScrollSource,
wheel_scroll_multiplier: Vec2,
content_margin: Option<Margin>,
/// If true for vertical or horizontal the scroll wheel will stick to the
/// end position until user manually changes position. It will become true
/// again once scroll handle makes contact with end.
@@ -344,6 +351,7 @@ impl ScrollArea {
on_drag_cursor: None,
scroll_source: ScrollSource::default(),
wheel_scroll_multiplier: Vec2::splat(1.0),
content_margin: None,
stick_to_end: Vec2b::FALSE,
animated: true,
}
@@ -591,6 +599,18 @@ impl ScrollArea {
self.direction_enabled[0] || self.direction_enabled[1]
}
/// Extra margin added around the contents.
///
/// The scroll bars will be either on top of this margin, or outside of it,
/// depending on the value of [`crate::style::ScrollStyle::floating`].
///
/// Default: [`crate::style::ScrollStyle::content_margin`].
#[inline]
pub fn content_margin(mut self, margin: impl Into<Margin>) -> Self {
self.content_margin = Some(margin.into());
self
}
/// The scroll handle will stick to the rightmost position even while the content size
/// changes dynamically. This can be useful to simulate text scrollers coming in from right
/// hand side. The scroll handle remains stuck until user manually changes position. Once "unstuck"
@@ -642,7 +662,7 @@ struct Prepared {
scroll_bar_visibility: ScrollBarVisibility,
scroll_bar_rect: Option<Rect>,
/// Where on the screen the content is (excludes scroll bars).
/// Where on the screen the content is (excludes scroll bars; includes `content_margin`).
inner_rect: Rect,
content_ui: Ui,
@@ -681,6 +701,7 @@ impl ScrollArea {
on_drag_cursor,
scroll_source,
wheel_scroll_multiplier,
content_margin: _, // Used elsewhere
stick_to_end,
animated,
} = self;
@@ -748,6 +769,12 @@ impl ScrollArea {
}
let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size);
// Round to pixels to avoid widgets appearing to "float" when scrolling fractional amounts:
let content_max_rect = content_max_rect
.round_to_pixels(ui.pixels_per_point())
.round_ui();
let mut content_ui = ui.new_child(
UiBuilder::new()
.ui_stack_info(UiStackInfo::new(UiKind::ScrollArea))
@@ -829,11 +856,11 @@ impl ScrollArea {
if response.dragged()
&& let Some(cursor) = on_drag_cursor
{
ui.ctx().set_cursor_icon(cursor);
ui.set_cursor_icon(cursor);
} else if response.hovered()
&& let Some(cursor) = on_hover_cursor
{
ui.ctx().set_cursor_icon(cursor);
ui.set_cursor_icon(cursor);
}
}
@@ -975,10 +1002,21 @@ impl ScrollArea {
ui: &mut Ui,
add_contents: Box<dyn FnOnce(&mut Ui, Rect) -> R + 'c>,
) -> ScrollAreaOutput<R> {
let margin = self
.content_margin
.unwrap_or_else(|| ui.spacing().scroll.content_margin);
let mut prepared = self.begin(ui);
let id = prepared.id;
let inner_rect = prepared.inner_rect;
let inner = add_contents(&mut prepared.content_ui, prepared.viewport);
let inner = crate::Frame::NONE
.inner_margin(margin)
.show(&mut prepared.content_ui, |ui| {
add_contents(ui, prepared.viewport)
})
.inner;
let (content_size, state) = prepared.end(ui);
ScrollAreaOutput {
inner,
@@ -1087,7 +1125,7 @@ impl Prepared {
target_offset,
});
}
ui.ctx().request_repaint();
ui.request_repaint();
}
}
}
@@ -1140,7 +1178,7 @@ impl Prepared {
&& direction_enabled[0] != direction_enabled[1];
for d in 0..2 {
if direction_enabled[d] {
let scroll_delta = ui.ctx().input(|input| {
let scroll_delta = ui.input(|input| {
if always_scroll_enabled_direction {
// no bidirectional scrolling; allow horizontal scrolling without pressing shift
input.smooth_scroll_delta()[0] + input.smooth_scroll_delta()[1]
@@ -1157,7 +1195,7 @@ impl Prepared {
state.offset[d] -= scroll_delta;
// Clear scroll delta so no parent scroll will use it:
ui.ctx().input_mut(|input| {
ui.input_mut(|input| {
if always_scroll_enabled_direction {
input.smooth_scroll_delta()[0] = 0.0;
input.smooth_scroll_delta()[1] = 0.0;
@@ -1437,7 +1475,7 @@ impl Prepared {
ui.advance_cursor_after_rect(outer_rect);
if show_scroll_this_frame != state.show_scroll {
ui.ctx().request_repaint();
ui.request_repaint();
}
let available_offset = content_size - inner_rect.size();

View File

@@ -41,7 +41,7 @@ impl Tooltip<'_> {
parent_widget: Id,
anchor: impl Into<PopupAnchor>,
) -> Self {
let width = ctx.style().spacing.tooltip_width;
let width = ctx.global_style().spacing.tooltip_width;
Self {
popup: Popup::new(parent_widget, ctx, anchor.into(), parent_layer)
.kind(PopupKind::Tooltip)
@@ -58,7 +58,7 @@ impl Tooltip<'_> {
let popup = Popup::from_response(response)
.kind(PopupKind::Tooltip)
.gap(4.0)
.width(response.ctx.style().spacing.tooltip_width)
.width(response.ctx.global_style().spacing.tooltip_width)
.sense(Sense::hover());
Self {
popup,
@@ -229,7 +229,7 @@ impl Tooltip<'_> {
return false;
}
let style = response.ctx.style();
let style = response.ctx.global_style();
let tooltip_delay = style.interaction.tooltip_delay;
let tooltip_grace_time = style.interaction.tooltip_grace_time;

View File

@@ -70,6 +70,41 @@ impl<'open> Window<'open> {
}
}
/// Construct a [`Window`] that follows the given viewport.
pub fn from_viewport(id: ViewportId, viewport: ViewportBuilder) -> Self {
let ViewportBuilder {
title,
app_id,
inner_size,
min_inner_size,
max_inner_size,
resizable,
decorations,
title_shown,
minimize_button,
.. // A lot of things not implemented yet
} = viewport;
let mut window = Self::new(title.or(app_id).unwrap_or_else(String::new)).id(Id::new(id));
if let Some(inner_size) = inner_size {
window = window.default_size(inner_size);
}
if let Some(min_inner_size) = min_inner_size {
window = window.min_size(min_inner_size);
}
if let Some(max_inner_size) = max_inner_size {
window = window.max_size(max_inner_size);
}
if let Some(resizable) = resizable {
window = window.resizable(resizable);
}
window = window.title_bar(decorations.unwrap_or(true) && title_shown.unwrap_or(true));
window = window.collapsible(minimize_button.unwrap_or(true));
window
}
/// Assign a unique id to the Window. Required if the title changes, or is shared with another window.
#[inline]
pub fn id(mut self, id: Id) -> Self {
@@ -440,9 +475,11 @@ impl Window<'_> {
fade_out,
} = self;
let header_color =
frame.map_or_else(|| ctx.style().visuals.widgets.open.weak_bg_fill, |f| f.fill);
let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));
let header_color = frame.map_or_else(
|| ctx.global_style().visuals.widgets.open.weak_bg_fill,
|f| f.fill,
);
let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.global_style()));
let is_explicitly_closed = matches!(open, Some(false));
let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible());
@@ -474,7 +511,7 @@ impl Window<'_> {
// Calculate roughly how much larger the full window inner size is compared to the content rect
let (title_bar_height_with_margin, title_content_spacing) = if with_title_bar {
let style = ctx.style();
let style = ctx.global_style();
let title_bar_inner_height = ctx
.fonts_mut(|fonts| title.font_height(fonts, &style))
.at_least(style.spacing.interact_size.y);
@@ -825,7 +862,7 @@ fn resize_response(
area: &mut area::Prepared,
resize_id: Id,
) {
let Some(mut new_rect) = move_and_resize_window(ctx, &resize_interaction) else {
let Some(mut new_rect) = move_and_resize_window(ctx, resize_id, &resize_interaction) else {
return;
};
@@ -847,27 +884,38 @@ fn resize_response(
}
/// Acts on outer rect (outside the stroke)
fn move_and_resize_window(ctx: &Context, interaction: &ResizeInteraction) -> Option<Rect> {
fn move_and_resize_window(ctx: &Context, id: Id, interaction: &ResizeInteraction) -> Option<Rect> {
// Used to prevent drift
let rect_at_start_of_drag_id = id.with("window_rect_at_drag_start");
if !interaction.any_dragged() {
ctx.data_mut(|data| {
data.remove::<Rect>(rect_at_start_of_drag_id);
});
return None;
}
let pointer_pos = ctx.input(|i| i.pointer.interact_pos())?;
let mut rect = interaction.outer_rect; // prevent drift
let total_drag_delta = ctx.input(|i| i.pointer.total_drag_delta())?;
let rect_at_start_of_drag = ctx.data_mut(|data| {
*data.get_temp_mut_or::<Rect>(rect_at_start_of_drag_id, interaction.outer_rect)
});
let mut rect = rect_at_start_of_drag; // prevent drift
// Put the rect in the center of the stroke:
rect = rect.shrink(interaction.window_frame.stroke.width / 2.0);
if interaction.left.drag {
rect.min.x = pointer_pos.x;
rect.min.x += total_drag_delta.x;
} else if interaction.right.drag {
rect.max.x = pointer_pos.x;
rect.max.x += total_drag_delta.x;
}
if interaction.top.drag {
rect.min.y = pointer_pos.y;
rect.min.y += total_drag_delta.y;
} else if interaction.bottom.drag {
rect.max.y = pointer_pos.y;
rect.max.y += total_drag_delta.y;
}
// Return to having the rect outside the stroke:
@@ -899,7 +947,6 @@ fn resize_interaction(
let rect = outer_rect.shrink(window_frame.stroke.width / 2.0);
let side_response = |rect, id| {
#[cfg(feature = "accesskit")]
ctx.register_accesskit_parent(id, _accessibility_parent);
let response = ctx.create_widget(
WidgetRect {
@@ -920,8 +967,8 @@ fn resize_interaction(
let id = Id::new(layer_id).with("edge_drag");
let side_grab_radius = ctx.style().interaction.resize_grab_radius_side;
let corner_grab_radius = ctx.style().interaction.resize_grab_radius_corner;
let side_grab_radius = ctx.global_style().interaction.resize_grab_radius_side;
let corner_grab_radius = ctx.global_style().interaction.resize_grab_radius_corner;
let vetrtical_rect = |a: Pos2, b: Pos2| {
Rect::from_min_max(a, b).expand2(vec2(side_grab_radius, -corner_grab_radius))
@@ -1131,8 +1178,7 @@ impl TitleBar {
title_bar_height_with_margin: f32,
) -> Self {
if false {
ui.ctx()
.debug_painter()
ui.debug_painter()
.debug_rect(ui.min_rect(), Color32::GREEN, "outer_min_rect");
}
@@ -1160,8 +1206,7 @@ impl TitleBar {
let min_rect = Rect::from_min_size(ui.min_rect().min, min_inner_size);
if false {
ui.ctx()
.debug_painter()
ui.debug_painter()
.debug_rect(min_rect, Color32::LIGHT_BLUE, "min_rect");
}
@@ -1198,8 +1243,7 @@ impl TitleBar {
let title_inner_rect = self.inner_rect;
if false {
ui.ctx()
.debug_painter()
ui.debug_painter()
.debug_rect(self.inner_rect, Color32::RED, "TitleBar");
}
@@ -1238,8 +1282,7 @@ impl TitleBar {
// Paint separator between title and content:
let content_rect = content_response.rect;
if false {
ui.ctx()
.debug_painter()
ui.debug_painter()
.debug_rect(content_rect, Color32::RED, "content_rect");
}
let y = title_inner_rect.bottom() + window_frame.stroke.width / 2.0;
@@ -1253,11 +1296,8 @@ impl TitleBar {
let double_click_rect = title_inner_rect.shrink2(vec2(32.0, 0.0));
if false {
ui.ctx().debug_painter().debug_rect(
double_click_rect,
Color32::GREEN,
"double_click_rect",
);
ui.debug_painter()
.debug_rect(double_click_rect, Color32::GREEN, "double_click_rect");
}
let id = ui.unique_id().with("__window_title_bar");

View File

@@ -20,7 +20,7 @@ use crate::{
ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText,
SafeAreaInsets, ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui,
ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet,
ViewportOutput, Widget as _, WidgetRect, WidgetText,
ViewportOutput, Visuals, Widget as _, WidgetRect, WidgetText,
animation_manager::AnimationManager,
containers::{self, area::AreaState},
data::output::PlatformOutput,
@@ -34,14 +34,12 @@ use crate::{
os::OperatingSystem,
output::FullOutput,
pass_state::PassState,
plugin,
plugin::TypedPluginHandle,
plugin::{self, TypedPluginHandle},
resize, response, scroll_area,
util::IdTypeMap,
viewport::ViewportClass,
};
#[cfg(feature = "accesskit")]
use crate::IdMap;
/// Information given to the backend about when it is time to repaint the ui.
@@ -195,7 +193,7 @@ impl ContextImpl {
pub struct ViewportState {
/// The type of viewport.
///
/// This will never be [`ViewportClass::Embedded`],
/// This will never be [`ViewportClass::EmbeddedWindow`],
/// since those don't result in real viewports.
pub class: ViewportClass,
@@ -404,7 +402,6 @@ struct ContextImpl {
embed_viewports: bool,
#[cfg(feature = "accesskit")]
is_accesskit_enabled: bool,
loaders: Arc<Loaders>,
@@ -507,7 +504,6 @@ impl ContextImpl {
},
);
#[cfg(feature = "accesskit")]
if self.is_accesskit_enabled {
profiling::scope!("accesskit");
use crate::pass_state::AccessKitPassState;
@@ -567,7 +563,10 @@ impl ContextImpl {
log::trace!("Adding new fonts");
}
let text_alpha_from_coverage = self.memory.options.style().visuals.text_alpha_from_coverage;
let Visuals {
mut text_options, ..
} = self.memory.options.style().visuals;
text_options.max_texture_side = max_texture_side;
let mut is_new = false;
@@ -576,23 +575,19 @@ impl ContextImpl {
is_new = true;
profiling::scope!("Fonts::new");
Fonts::new(
max_texture_side,
text_alpha_from_coverage,
self.font_definitions.clone(),
)
Fonts::new(text_options, self.font_definitions.clone())
});
{
profiling::scope!("Fonts::begin_pass");
fonts.begin_pass(max_texture_side, text_alpha_from_coverage);
fonts.begin_pass(text_options);
}
}
#[cfg(feature = "accesskit")]
fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::Node {
let state = self.viewport().this_pass.accesskit_state.as_mut().unwrap();
let builders = &mut state.nodes;
if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) {
entry.insert(Default::default());
@@ -614,11 +609,12 @@ impl ContextImpl {
}
let parent_id = find_accesskit_parent(&state.parent_map, builders, id)
.unwrap_or(crate::accesskit_root_id());
.unwrap_or_else(crate::accesskit_root_id);
let parent_builder = builders.get_mut(&parent_id).unwrap();
parent_builder.push_child(id.accesskit_id());
}
builders.get_mut(&id).unwrap()
}
@@ -766,7 +762,52 @@ impl Context {
/// and only on the rare occasion that [`Context::request_discard`] is called.
/// Usually, it `run_ui` will only be called once.
///
/// Put your widgets into a [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`].
/// The [`Ui`] given to the callback will cover the entire [`Self::content_rect`],
/// with no margin or background color. Use [`crate::Frame`] to add that.
///
/// You can organize your GUI using [`crate::Panel`].
///
/// Instead of calling `run_ui`, you can alternatively use [`Self::begin_pass`] and [`Context::end_pass`].
///
/// ```
/// // One egui context that you keep reusing:
/// let mut ctx = egui::Context::default();
///
/// // Each frame:
/// let input = egui::RawInput::default();
/// let full_output = ctx.run_ui(input, |ui| {
/// ui.label("Hello egui!");
/// });
/// // handle full_output
/// ```
///
/// ## See also
/// * [`Self::run`]
#[must_use]
pub fn run_ui(&self, new_input: RawInput, mut run_ui: impl FnMut(&mut Ui)) -> FullOutput {
self.run_ui_dyn(new_input, &mut run_ui)
}
#[must_use]
fn run_ui_dyn(&self, new_input: RawInput, run_ui: &mut dyn FnMut(&mut Ui)) -> FullOutput {
let plugins = self.read(|ctx| ctx.plugins.ordered_plugins());
#[expect(deprecated)]
self.run(new_input, |ctx| {
crate::CentralPanel::no_frame().show(ctx, |ui| {
plugins.on_begin_pass(ui);
run_ui(ui);
plugins.on_end_pass(ui);
});
})
}
/// Run the ui code for one frame.
///
/// At most [`Options::max_passes`] calls will be issued to `run_ui`,
/// and only on the rare occasion that [`Context::request_discard`] is called.
/// Usually, it `run_ui` will only be called once.
///
/// Put your widgets into a [`crate::Panel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`].
///
/// Instead of calling `run`, you can alternatively use [`Self::begin_pass`] and [`Context::end_pass`].
///
@@ -783,8 +824,17 @@ impl Context {
/// });
/// // handle full_output
/// ```
///
/// ## See also
/// * [`Self::run_ui`]
#[must_use]
pub fn run(&self, mut new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput {
#[deprecated = "Call run_ui instead"]
pub fn run(&self, new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput {
self.run_dyn(new_input, &mut run_ui)
}
#[must_use]
fn run_dyn(&self, mut new_input: RawInput, run_ui: &mut dyn FnMut(&Self)) -> FullOutput {
profiling::function_scope!();
let viewport_id = new_input.viewport_id;
let max_passes = self.write(|ctx| ctx.memory.options.max_passes.get());
@@ -879,9 +929,6 @@ impl Context {
plugins.on_input(&mut new_input);
self.write(|ctx| ctx.begin_pass(new_input));
// Plugins run just after the pass starts:
plugins.on_begin_pass(self);
}
/// See [`Self::begin_pass`].
@@ -1103,7 +1150,7 @@ impl Context {
let content_rect = self.content_rect();
let text = format!("🔥 {text}");
let color = self.style().visuals.error_fg_color;
let color = self.global_style().visuals.error_fg_color;
let painter = self.debug_painter();
painter.rect_stroke(widget_rect, 0.0, (1.0, color), StrokeKind::Outside);
@@ -1204,7 +1251,6 @@ impl Context {
plugins.on_widget_under_pointer(self, &w);
}
#[cfg(feature = "accesskit")]
if allow_focus && w.sense.is_focusable() {
// Make sure anything that can receive focus has an AccessKit node.
// TODO(mwcampbell): For nodes that are filled from widget info,
@@ -1212,7 +1258,6 @@ impl Context {
self.accesskit_node_builder(w.id, |builder| res.fill_accesskit_node_common(builder));
}
#[cfg(feature = "accesskit")]
self.write(|ctx| {
use crate::{Align, pass_state::ScrollTarget, style::ScrollAnimation};
let viewport = ctx.viewport_for(ctx.viewport_id());
@@ -1220,12 +1265,14 @@ impl Context {
viewport
.input
.consume_accesskit_action_requests(res.id, |request| {
use accesskit::Action;
// TODO(lucasmerlin): Correctly handle the scroll unit:
// https://github.com/AccessKit/accesskit/blob/e639c0e0d8ccbfd9dff302d972fa06f9766d608e/common/src/lib.rs#L2621
const DISTANCE: f32 = 100.0;
match &request.action {
accesskit::Action::ScrollIntoView => {
Action::ScrollIntoView => {
viewport.this_pass.scroll_target = [
Some(ScrollTarget::new(
res.rect.x_range(),
@@ -1239,16 +1286,16 @@ impl Context {
)),
];
}
accesskit::Action::ScrollDown => {
Action::ScrollDown => {
viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::UP;
}
accesskit::Action::ScrollUp => {
Action::ScrollUp => {
viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::DOWN;
}
accesskit::Action::ScrollLeft => {
Action::ScrollLeft => {
viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::LEFT;
}
accesskit::Action::ScrollRight => {
Action::ScrollRight => {
viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::RIGHT;
}
_ => return false,
@@ -1341,7 +1388,6 @@ impl Context {
res.flags.set(Flags::FAKE_PRIMARY_CLICKED, true);
}
#[cfg(feature = "accesskit")]
if enabled
&& sense.senses_click()
&& input.has_accesskit_action_request(id, accesskit::Action::Click)
@@ -1461,7 +1507,7 @@ impl Context {
Painter::new(self.clone(), layer_id, content_rect)
}
/// Paint on top of everything else
/// Paint on top of _everything_ else (even on top of tooltips and popups).
pub fn debug_painter(&self) -> Painter {
Self::layer_painter(self, LayerId::debug())
}
@@ -1559,7 +1605,7 @@ impl Context {
..
} = ModifierNames::SYMBOLS;
let font_id = TextStyle::Body.resolve(&self.style());
let font_id = TextStyle::Body.resolve(&self.global_style());
self.fonts_mut(|f| {
let mut font = f.fonts.font(&font_id.family);
font.has_glyphs(alt)
@@ -1960,15 +2006,12 @@ impl Context {
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
profiling::function_scope!();
let mut update_fonts = true;
self.read(|ctx| {
if let Some(current_fonts) = ctx.fonts.as_ref() {
// NOTE: this comparison is expensive since it checks TTF data for equality
if current_fonts.definitions() == &font_definitions {
update_fonts = false; // no need to update
}
}
let update_fonts = self.read(|ctx| {
// NOTE: this comparison is expensive since it checks TTF data for equality
// TODO(valadaptive): add_font only checks the *names* for equality. Change this?
ctx.fonts
.as_ref()
.is_none_or(|fonts| fonts.definitions() != &font_definitions)
});
if update_fonts {
@@ -2011,13 +2054,13 @@ impl Context {
}
/// The [`Theme`] used to select the appropriate [`Style`] (dark or light)
/// used by all subsequent windows, panels etc.
/// used by all subsequent popups, menus, etc.
pub fn theme(&self) -> Theme {
self.options(|opt| opt.theme())
}
/// The [`Theme`] used to select between dark and light [`Self::style`]
/// as the active style used by all subsequent windows, panels etc.
/// as the active style used by all subsequent popups, menus, etc.
///
/// Example:
/// ```
@@ -2028,37 +2071,70 @@ impl Context {
self.options_mut(|opt| opt.theme_preference = theme_preference.into());
}
/// The currently active [`Style`] used by all subsequent windows, panels etc.
/// The currently active [`Style`] used by all subsequent popups, menus, etc.
pub fn global_style(&self) -> Arc<Style> {
self.options(|opt| opt.style().clone())
}
/// The currently active [`Style`] used by all subsequent popups, menus, etc.
#[deprecated = "Renamed to `global_style` to avoid confusion with `ui.style()`"]
pub fn style(&self) -> Arc<Style> {
self.options(|opt| opt.style().clone())
}
/// Mutate the currently active [`Style`] used by all subsequent windows, panels etc.
/// Mutate the currently active [`Style`] used by all subsequent popups, menus, etc.
/// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
///
/// Example:
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.style_mut(|style| {
/// ctx.global_style_mut(|style| {
/// style.spacing.item_spacing = egui::vec2(10.0, 20.0);
/// });
/// ```
pub fn global_style_mut(&self, mutate_style: impl FnOnce(&mut Style)) {
self.options_mut(|opt| mutate_style(Arc::make_mut(opt.style_mut())));
}
/// Mutate the currently active [`Style`] used by all subsequent popups, menus, etc.
/// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
///
/// Example:
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.global_style_mut(|style| {
/// style.spacing.item_spacing = egui::vec2(10.0, 20.0);
/// });
/// ```
#[deprecated = "Renamed to `global_style_mut` to avoid confusion with `ui.style_mut()`"]
pub fn style_mut(&self, mutate_style: impl FnOnce(&mut Style)) {
self.options_mut(|opt| mutate_style(Arc::make_mut(opt.style_mut())));
}
/// The currently active [`Style`] used by all new windows, panels etc.
/// The currently active [`Style`] used by all new popups, menus, etc.
///
/// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
///
/// You can also change this using [`Self::global_style_mut`].
///
/// You can use [`Ui::style_mut`] to change the style of a single [`Ui`].
pub fn set_global_style(&self, style: impl Into<Arc<Style>>) {
self.options_mut(|opt| *opt.style_mut() = style.into());
}
/// The currently active [`Style`] used by all new popups, menus, etc.
///
/// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
///
/// You can also change this using [`Self::style_mut`].
///
/// You can use [`Ui::style_mut`] to change the style of a single [`Ui`].
#[deprecated = "Renamed to `set_global_style` to avoid confusion with `ui.set_style()`"]
pub fn set_style(&self, style: impl Into<Arc<Style>>) {
self.options_mut(|opt| *opt.style_mut() = style.into());
}
/// Mutate the [`Style`]s used by all subsequent windows, panels etc. in both dark and light mode.
/// Mutate the [`Style`]s used by all subsequent popups, menus, etc. in both dark and light mode.
///
/// Example:
/// ```
@@ -2074,7 +2150,7 @@ impl Context {
});
}
/// The [`Style`] used by all subsequent windows, panels etc.
/// The [`Style`] used by all subsequent popups, menus, etc.
pub fn style_of(&self, theme: Theme) -> Arc<Style> {
self.options(|opt| match theme {
Theme::Dark => opt.dark_style.clone(),
@@ -2082,7 +2158,7 @@ impl Context {
})
}
/// Mutate the [`Style`] used by all subsequent windows, panels etc.
/// Mutate the [`Style`] used by all subsequent popups, menus, etc.
///
/// Example:
/// ```
@@ -2098,7 +2174,7 @@ impl Context {
});
}
/// The [`Style`] used by all new windows, panels etc.
/// The [`Style`] used by all new popups, menus, etc.
/// Use [`Self::set_theme`] to choose between dark and light mode.
///
/// You can also change this using [`Self::style_mut_of`].
@@ -2112,7 +2188,7 @@ impl Context {
});
}
/// The [`crate::Visuals`] used by all subsequent windows, panels etc.
/// The [`crate::Visuals`] used by all subsequent popups, menus, etc.
///
/// You can also use [`Ui::visuals_mut`] to change the visuals of a single [`Ui`].
///
@@ -2125,7 +2201,7 @@ impl Context {
self.style_mut_of(theme, |style| style.visuals = visuals);
}
/// The [`crate::Visuals`] used by all subsequent windows, panels etc.
/// The [`crate::Visuals`] used by all subsequent popups, menus, etc.
///
/// You can also use [`Ui::visuals_mut`] to change the visuals of a single [`Ui`].
///
@@ -2304,15 +2380,14 @@ impl Context {
crate::gui_zoom::zoom_with_keyboard(self);
}
// Plugins run just before the pass ends.
let plugins = self.read(|ctx| ctx.plugins.ordered_plugins());
plugins.on_end_pass(self);
#[cfg(debug_assertions)]
self.debug_painting();
let mut output = self.write(|ctx| ctx.end_pass());
let plugins = self.read(|ctx| ctx.plugins.ordered_plugins());
plugins.on_output(&mut output);
output
}
@@ -2344,7 +2419,7 @@ impl Context {
}
};
if self.style().debug.show_interactive_widgets {
if self.global_style().debug.show_interactive_widgets {
// Show all interactive widgets:
let rects = self.write(|ctx| ctx.viewport().this_pass.widgets.clone());
for (layer_id, rects) in rects.layers() {
@@ -2422,7 +2497,7 @@ impl Context {
}
}
if self.style().debug.show_widget_hits {
if self.global_style().debug.show_widget_hits {
let hits = self.write(|ctx| ctx.viewport().hits.clone());
let WidgetHits {
close,
@@ -2498,7 +2573,6 @@ impl ContextImpl {
let mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output);
#[cfg(feature = "accesskit")]
{
profiling::scope!("accesskit");
let state = viewport.this_pass.accesskit_state.take();
@@ -2725,12 +2799,18 @@ impl Context {
}
/// How much space is still available after panels have been added.
pub fn available_rect(&self) -> Rect {
pub fn globally_available_rect(&self) -> Rect {
self.pass_state(|s| s.available_rect()).round_ui()
}
/// How much space is still available after panels have been added.
#[deprecated = "Renamed to globally_available_rect"]
pub fn available_rect(&self) -> Rect {
self.globally_available_rect()
}
/// How much space is used by panels and windows.
pub fn used_rect(&self) -> Rect {
pub fn globally_used_rect(&self) -> Rect {
self.write(|ctx| {
let mut used = ctx.viewport().this_pass.used_by_panels;
for (_id, window) in ctx.memory.areas().visible_windows() {
@@ -2740,17 +2820,31 @@ impl Context {
})
}
/// How much space is used by panels and windows.
#[deprecated = "Renamed to globally_used_rect"]
pub fn used_rect(&self) -> Rect {
self.globally_used_rect()
}
/// How much space is used by panels and windows.
///
/// You can shrink your egui area to this size and still fit all egui components.
pub fn globally_used_size(&self) -> Vec2 {
(self.globally_used_rect().max - Pos2::ZERO).round_ui()
}
/// How much space is used by panels and windows.
///
/// You can shrink your egui area to this size and still fit all egui components.
#[deprecated = "Renamed to globally_used_size"]
pub fn used_size(&self) -> Vec2 {
(self.used_rect().max - Pos2::ZERO).round_ui()
(self.globally_used_rect().max - Pos2::ZERO).round_ui()
}
// ---------------------------------------------------------------------
/// Is the pointer (mouse/touch) over any egui area?
pub fn is_pointer_over_area(&self) -> bool {
pub fn is_pointer_over_egui(&self) -> bool {
let pointer_pos = self.input(|i| i.pointer.interact_pos());
if let Some(pointer_pos) = pointer_pos {
if let Some(layer) = self.layer_id_at(pointer_pos) {
@@ -2767,29 +2861,60 @@ impl Context {
}
}
/// Is the pointer (mouse/touch) over any egui area?
#[deprecated = "Renamed to is_pointer_over_egui"]
pub fn is_pointer_over_area(&self) -> bool {
self.is_pointer_over_egui()
}
/// True if egui is currently interested in the pointer (mouse or touch).
///
/// Could be the pointer is hovering over a [`crate::Window`] or the user is dragging a widget.
/// If `false`, the pointer is outside of any egui area and so
/// you may be interested in what it is doing (e.g. controlling your game).
/// Returns `false` if a drag started outside of egui and then moved over an egui area.
pub fn egui_wants_pointer_input(&self) -> bool {
self.egui_is_using_pointer()
|| (self.is_pointer_over_egui() && !self.input(|i| i.pointer.any_down()))
}
/// True if egui is currently interested in the pointer (mouse or touch).
///
/// Could be the pointer is hovering over a [`crate::Window`] or the user is dragging a widget.
/// If `false`, the pointer is outside of any egui area and so
/// you may be interested in what it is doing (e.g. controlling your game).
/// Returns `false` if a drag started outside of egui and then moved over an egui area.
#[deprecated = "Renamed to egui_wants_pointer_input"]
pub fn wants_pointer_input(&self) -> bool {
self.is_using_pointer()
|| (self.is_pointer_over_area() && !self.input(|i| i.pointer.any_down()))
self.egui_wants_pointer_input()
}
/// Is egui currently using the pointer position (e.g. dragging a slider)?
///
/// NOTE: this will return `false` if the pointer is just hovering over an egui area.
pub fn is_using_pointer(&self) -> bool {
pub fn egui_is_using_pointer(&self) -> bool {
self.memory(|m| m.interaction().is_using_pointer())
}
/// Is egui currently using the pointer position (e.g. dragging a slider)?
///
/// NOTE: this will return `false` if the pointer is just hovering over an egui area.
#[deprecated = "Renamed to egui_is_using_pointer"]
pub fn is_using_pointer(&self) -> bool {
self.egui_is_using_pointer()
}
/// If `true`, egui is currently listening on text input (e.g. typing text in a [`crate::TextEdit`]).
pub fn wants_keyboard_input(&self) -> bool {
pub fn egui_wants_keyboard_input(&self) -> bool {
self.memory(|m| m.focused().is_some())
}
/// If `true`, egui is currently listening on text input (e.g. typing text in a [`crate::TextEdit`]).
#[deprecated = "Renamed to egui_wants_keyboard_input"]
pub fn wants_keyboard_input(&self) -> bool {
self.egui_wants_keyboard_input()
}
/// Highlight this widget, to make it look like it is hovered, even if it isn't.
///
/// If you call this after the widget has been fully rendered,
@@ -2804,7 +2929,7 @@ impl Context {
///
/// This only works with the old, deprecated [`crate::menu`] API.
#[expect(deprecated)]
#[deprecated = "Use `is_popup_open` instead"]
#[deprecated = "Use `any_popup_open` instead"]
pub fn is_context_menu_open(&self) -> bool {
self.data(|d| {
d.get_temp::<crate::menu::BarState>(crate::menu::CONTEXT_MENU_ID_STR.into())
@@ -2815,6 +2940,18 @@ impl Context {
/// Is a popup or (context) menu open?
///
/// Will return false for [`crate::Tooltip`]s (which are technically popups as well).
pub fn any_popup_open(&self) -> bool {
self.pass_state_mut(|fs| {
fs.layers
.values()
.any(|layer| !layer.open_popups.is_empty())
})
}
/// Is a popup or (context) menu open?
///
/// Will return false for [`crate::Tooltip`]s (which are technically popups as well).
#[deprecated = "Renamed to any_popup_open"]
pub fn is_popup_open(&self) -> bool {
self.pass_state_mut(|fs| {
fs.layers
@@ -2840,7 +2977,7 @@ impl Context {
self.input(|i| i.pointer.hover_pos())
}
/// If you detect a click or drag and wants to know where it happened, use this.
/// If you detect a click or drag and want to know where it happened, use this.
///
/// Latest position of the mouse, but ignoring any [`crate::Event::PointerGone`]
/// if there were interactions this pass.
@@ -2913,7 +3050,7 @@ impl Context {
/// Moves the given area to the top in its [`Order`].
///
/// [`crate::Area`]:s and [`crate::Window`]:s also do this automatically when being clicked on or interacted with.
/// [`crate::Area`]s and [`crate::Window`]s also do this automatically when being clicked on or interacted with.
pub fn move_to_top(&self, layer_id: LayerId) {
self.memory_mut(|mem| mem.areas_mut().move_to_top(layer_id));
}
@@ -2995,7 +3132,7 @@ impl Context {
/// The animation time is taken from [`Style::animation_time`].
#[track_caller] // To track repaint cause
pub fn animate_bool(&self, id: Id, value: bool) -> f32 {
let animation_time = self.style().animation_time;
let animation_time = self.global_style().animation_time;
self.animate_bool_with_time_and_easing(id, value, animation_time, emath::easing::linear)
}
@@ -3011,7 +3148,7 @@ impl Context {
/// Like [`Self::animate_bool`] but allows you to control the easing function.
#[track_caller] // To track repaint cause
pub fn animate_bool_with_easing(&self, id: Id, value: bool, easing: fn(f32) -> f32) -> f32 {
let animation_time = self.style().animation_time;
let animation_time = self.global_style().animation_time;
self.animate_bool_with_time_and_easing(id, value, animation_time, easing)
}
@@ -3032,7 +3169,7 @@ impl Context {
/// for a responsive start and a slow end.
///
/// The easing function flips when `target_value` is `false`,
/// so that when going back towards 0.0, we get
/// so that when going back towards 0.0, we get the reverse behavior.
#[track_caller] // To track repaint cause
pub fn animate_bool_with_time_and_easing(
&self,
@@ -3144,16 +3281,16 @@ impl Context {
ui.label("Is using pointer")
.on_hover_text("Is egui currently using the pointer actively (e.g. dragging a slider)?");
ui.monospace(self.is_using_pointer().to_string());
ui.monospace(self.egui_is_using_pointer().to_string());
ui.end_row();
ui.label("Wants pointer input")
.on_hover_text("Is egui currently interested in the location of the pointer (either because it is in use, or because it is hovering over a window).");
ui.monospace(self.wants_pointer_input().to_string());
ui.monospace(self.egui_wants_pointer_input().to_string());
ui.end_row();
ui.label("Wants keyboard input").on_hover_text("Is egui currently listening for text input?");
ui.monospace(self.wants_keyboard_input().to_string());
ui.monospace(self.egui_wants_keyboard_input().to_string());
ui.end_row();
ui.label("Keyboard focus widget").on_hover_text("Is egui currently listening for text input?");
@@ -3418,9 +3555,7 @@ impl Context {
let response =
ui.add(Label::new(RichText::new(text).monospace()).sense(Sense::click()));
if response.hovered() && is_visible {
ui.ctx()
.debug_painter()
.debug_rect(area.rect(), Color32::RED, "");
ui.debug_painter().debug_rect(area.rect(), Color32::RED, "");
}
} else {
ui.monospace(layer_id.short_debug_format());
@@ -3497,9 +3632,8 @@ impl Context {
///
/// The `Context` lock is held while the given closure is called!
///
/// Returns `None` if acesskit is off.
/// Returns `None` if accesskit is off.
// TODO(emilk): consider making both read-only and read-write versions
#[cfg(feature = "accesskit")]
pub fn accesskit_node_builder<R>(
&self,
id: Id,
@@ -3515,7 +3649,6 @@ impl Context {
})
}
#[cfg(feature = "accesskit")]
pub(crate) fn register_accesskit_parent(&self, id: Id, parent_id: Id) {
self.write(|ctx| {
if let Some(state) = ctx.viewport().this_pass.accesskit_state.as_mut() {
@@ -3525,13 +3658,11 @@ impl Context {
}
/// Enable generation of AccessKit tree updates in all future frames.
#[cfg(feature = "accesskit")]
pub fn enable_accesskit(&self) {
self.write(|ctx| ctx.is_accesskit_enabled = true);
}
/// Disable generation of AccessKit tree updates in all future frames.
#[cfg(feature = "accesskit")]
pub fn disable_accesskit(&self) {
self.write(|ctx| ctx.is_accesskit_enabled = false);
}
@@ -3883,21 +4014,23 @@ impl Context {
///
/// If [`Context::embed_viewports`] is `true` (e.g. if the current egui
/// backend does not support multiple viewports), the given callback
/// will be called immediately, embedding the new viewport in the current one.
/// You can check this with the [`ViewportClass`] given in the callback.
/// If you find [`ViewportClass::Embedded`], you need to create a new [`crate::Window`] for you content.
/// will be called immediately, embedding the new viewport in the current one,
/// inside of a [`crate::Window`].
/// You can know by checking for [`ViewportClass::EmbeddedWindow`].
///
/// See [`crate::viewport`] for more information about viewports.
pub fn show_viewport_deferred(
&self,
new_viewport_id: ViewportId,
viewport_builder: ViewportBuilder,
viewport_ui_cb: impl Fn(&Self, ViewportClass) + Send + Sync + 'static,
viewport_ui_cb: impl Fn(&mut Ui, ViewportClass) + Send + Sync + 'static,
) {
profiling::function_scope!();
if self.embed_viewports() {
viewport_ui_cb(self, ViewportClass::Embedded);
crate::Window::from_viewport(new_viewport_id, viewport_builder).show(self, |ui| {
viewport_ui_cb(ui, ViewportClass::EmbeddedWindow);
});
} else {
self.write(|ctx| {
ctx.viewport_parents
@@ -3907,8 +4040,8 @@ impl Context {
viewport.class = ViewportClass::Deferred;
viewport.builder = viewport_builder;
viewport.used = true;
viewport.viewport_ui_cb = Some(Arc::new(move |ctx| {
(viewport_ui_cb)(ctx, ViewportClass::Deferred);
viewport.viewport_ui_cb = Some(Arc::new(move |ui| {
(viewport_ui_cb)(ui, ViewportClass::Deferred);
}));
});
}
@@ -3917,7 +4050,7 @@ impl Context {
/// Show an immediate viewport, creating a new native window, if possible.
///
/// This is the easier type of viewport to use, but it is less performant
/// at it requires both parent and child to repaint if any one of them needs repainting,
/// as it requires both parent and child to repaint if any one of them needs repainting,
/// which effectively produce double work for two viewports, and triple work for three viewports, etc.
/// To avoid this, use [`Self::show_viewport_deferred`] instead.
///
@@ -3935,28 +4068,32 @@ impl Context {
///
/// If [`Context::embed_viewports`] is `true` (e.g. if the current egui
/// backend does not support multiple viewports), the given callback
/// will be called immediately, embedding the new viewport in the current one.
/// You can check this with the [`ViewportClass`] given in the callback.
/// If you find [`ViewportClass::Embedded`], you need to create a new [`crate::Window`] for you content.
/// will be called immediately, embedding the new viewport in the current one,
/// inside of a [`crate::Window`].
/// You can know by checking for [`ViewportClass::EmbeddedWindow`].
///
/// See [`crate::viewport`] for more information about viewports.
pub fn show_viewport_immediate<T>(
&self,
new_viewport_id: ViewportId,
builder: ViewportBuilder,
mut viewport_ui_cb: impl FnMut(&Self, ViewportClass) -> T,
mut viewport_ui_cb: impl FnMut(&mut Ui, ViewportClass) -> T,
) -> T {
profiling::function_scope!();
if self.embed_viewports() {
return viewport_ui_cb(self, ViewportClass::Embedded);
return self.show_embedded_viewport(new_viewport_id, builder, |ui| {
viewport_ui_cb(ui, ViewportClass::EmbeddedWindow)
});
}
IMMEDIATE_VIEWPORT_RENDERER.with(|immediate_viewport_renderer| {
let immediate_viewport_renderer = immediate_viewport_renderer.borrow();
let Some(immediate_viewport_renderer) = immediate_viewport_renderer.as_ref() else {
// This egui backend does not support multiple viewports.
return viewport_ui_cb(self, ViewportClass::Embedded);
return self.show_embedded_viewport(new_viewport_id, builder, |ui| {
viewport_ui_cb(ui, ViewportClass::EmbeddedWindow)
});
};
let ids = self.write(|ctx| {
@@ -3980,8 +4117,8 @@ impl Context {
let viewport = ImmediateViewport {
ids,
builder,
viewport_ui_cb: Box::new(move |context| {
*out = Some(viewport_ui_cb(context, ViewportClass::Immediate));
viewport_ui_cb: Box::new(move |ui| {
*out = Some((viewport_ui_cb)(ui, ViewportClass::Immediate));
}),
};
@@ -3993,6 +4130,20 @@ impl Context {
)
})
}
fn show_embedded_viewport<T>(
&self,
new_viewport_id: ViewportId,
builder: ViewportBuilder,
viewport_ui_cb: impl FnOnce(&mut Ui) -> T,
) -> T {
crate::Window::from_viewport(new_viewport_id, builder)
.collapsible(false)
.show(self, |ui| viewport_ui_cb(ui))
.unwrap_or_else(|| panic!("Window did not show"))
.inner
.unwrap_or_else(|| panic!("Window was collapsed"))
}
}
/// ## Interaction
@@ -4016,7 +4167,7 @@ impl Context {
/// Is this specific widget being dragged?
///
/// A widget that sense both clicks and drags is only marked as "dragged"
/// when the mouse has moved a bit
/// when the mouse has moved a bit.
///
/// See also: [`crate::Response::dragged`].
pub fn is_being_dragged(&self, id: Id) -> bool {
@@ -4030,7 +4181,7 @@ impl Context {
self.interaction_snapshot(|i| i.drag_started)
}
/// This widget was being dragged, but was released this pass
/// This widget was being dragged, but was released this pass.
pub fn drag_stopped_id(&self) -> Option<Id> {
self.interaction_snapshot(|i| i.drag_stopped)
}
@@ -4092,11 +4243,11 @@ mod test {
// A single call, no request to discard:
{
let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| {
let output = ctx.run_ui(Default::default(), |ui| {
num_calls += 1;
assert_eq!(ctx.output(|o| o.num_completed_passes), 0);
assert!(!ctx.output(|o| o.requested_discard()));
assert!(!ctx.will_discard());
assert_eq!(ui.output(|o| o.num_completed_passes), 0);
assert!(!ui.output(|o| o.requested_discard()));
assert!(!ui.will_discard());
});
assert_eq!(num_calls, 1);
assert_eq!(output.platform_output.num_completed_passes, 1);
@@ -4106,10 +4257,10 @@ mod test {
// A single call, with a denied request to discard:
{
let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| {
let output = ctx.run_ui(Default::default(), |ui| {
num_calls += 1;
ctx.request_discard("test");
assert!(!ctx.will_discard(), "The request should have been denied");
ui.request_discard("test");
assert!(!ui.will_discard(), "The request should have been denied");
});
assert_eq!(num_calls, 1);
assert_eq!(output.platform_output.num_completed_passes, 1);
@@ -4137,10 +4288,10 @@ mod test {
// Normal single pass:
{
let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| {
assert_eq!(ctx.output(|o| o.num_completed_passes), 0);
assert!(!ctx.output(|o| o.requested_discard()));
assert!(!ctx.will_discard());
let output = ctx.run_ui(Default::default(), |ui| {
assert_eq!(ui.output(|o| o.num_completed_passes), 0);
assert!(!ui.output(|o| o.requested_discard()));
assert!(!ui.will_discard());
num_calls += 1;
});
assert_eq!(num_calls, 1);
@@ -4151,13 +4302,13 @@ mod test {
// Request discard once:
{
let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| {
assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls);
let output = ctx.run_ui(Default::default(), |ui| {
assert_eq!(ui.output(|o| o.num_completed_passes), num_calls);
assert!(!ctx.will_discard());
assert!(!ui.will_discard());
if num_calls == 0 {
ctx.request_discard("test");
assert!(ctx.will_discard());
ui.request_discard("test");
assert!(ui.will_discard());
}
num_calls += 1;
@@ -4173,15 +4324,15 @@ mod test {
// Request discard twice:
{
let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| {
assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls);
let output = ctx.run_ui(Default::default(), |ui| {
assert_eq!(ui.output(|o| o.num_completed_passes), num_calls);
assert!(!ctx.will_discard());
ctx.request_discard("test");
assert!(!ui.will_discard());
ui.request_discard("test");
if num_calls == 0 {
assert!(ctx.will_discard(), "First request granted");
assert!(ui.will_discard(), "First request granted");
} else {
assert!(!ctx.will_discard(), "Second request should be denied");
assert!(!ui.will_discard(), "Second request should be denied");
}
num_calls += 1;
@@ -4203,13 +4354,13 @@ mod test {
// Request discard three times:
{
let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| {
assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls);
let output = ctx.run_ui(Default::default(), |ui| {
assert_eq!(ui.output(|o| o.num_completed_passes), num_calls);
assert!(!ctx.will_discard());
assert!(!ui.will_discard());
if num_calls <= 2 {
ctx.request_discard("test");
assert!(ctx.will_discard());
ui.request_discard("test");
assert!(ui.will_discard());
}
num_calls += 1;

View File

@@ -548,7 +548,6 @@ pub enum Event {
WindowFocused(bool),
/// An assistive technology (e.g. screen reader) requested an action.
#[cfg(feature = "accesskit")]
AccessKitActionRequest(accesskit::ActionRequest),
/// The reply of a screenshot requested with [`crate::ViewportCommand::Screenshot`].

View File

@@ -128,7 +128,6 @@ pub struct PlatformOutput {
/// The difference in the widget tree since last frame.
///
/// NOTE: this needs to be per-viewport.
#[cfg(feature = "accesskit")]
pub accesskit_update: Option<accesskit::TreeUpdate>,
/// How many ui passes is this the sum of?
@@ -175,7 +174,6 @@ impl PlatformOutput {
mut events,
mutable_text_under_cursor,
ime,
#[cfg(feature = "accesskit")]
accesskit_update,
num_completed_passes,
mut request_discard_reasons,
@@ -190,12 +188,8 @@ impl PlatformOutput {
self.request_discard_reasons
.append(&mut request_discard_reasons);
#[cfg(feature = "accesskit")]
{
// egui produces a complete AccessKit tree for each frame,
// so overwrite rather than appending.
self.accesskit_update = accesskit_update;
}
// egui produces a complete AccessKit tree for each frame, so overwrite rather than append:
self.accesskit_update = accesskit_update;
}
/// Take everything ephemeral (everything except `cursor_icon` currently)

View File

@@ -5,7 +5,7 @@
//! to get callbacks on certain events ([`Plugin::on_begin_pass`], [`Plugin::on_end_pass`]).
use crate::{
Align, Align2, Color32, Context, FontFamily, FontId, Plugin, Rect, Shape, Vec2, WidgetText,
Align, Align2, Color32, Context, FontFamily, FontId, Plugin, Rect, Shape, Ui, Vec2, WidgetText,
text,
};
@@ -57,9 +57,9 @@ impl Plugin for DebugTextPlugin {
"DebugTextPlugin"
}
fn on_end_pass(&mut self, ctx: &Context) {
fn on_end_pass(&mut self, ui: &mut Ui) {
let entries = std::mem::take(&mut self.entries);
Self::paint_entries(ctx, entries);
Self::paint_entries(ui, entries);
}
}
@@ -99,7 +99,7 @@ impl DebugTextPlugin {
let available_width = ctx.content_rect().max.x - pos.x;
let galley = text.into_galley_impl(
ctx,
&ctx.style(),
&ctx.global_style(),
text::TextWrapping::wrap_at_width(available_width),
font_id.clone().into(),
Align::TOP,

View File

@@ -1,6 +1,6 @@
use std::{any::Any, sync::Arc};
use crate::{Context, CursorIcon, Plugin};
use crate::{Context, CursorIcon, Plugin, Ui};
/// Plugin for tracking drag-and-drop payload.
///
@@ -32,12 +32,12 @@ impl Plugin for DragAndDrop {
/// Interrupt drag-and-drop if the user presses the escape key.
///
/// This needs to happen at frame start so we can properly capture the escape key.
fn on_begin_pass(&mut self, ctx: &Context) {
fn on_begin_pass(&mut self, ui: &mut Ui) {
let has_any_payload = self.payload.is_some();
if has_any_payload {
let abort_dnd_due_to_escape_key =
ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
ui.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
if abort_dnd_due_to_escape_key {
self.payload = None;
@@ -50,18 +50,18 @@ impl Plugin for DragAndDrop {
/// This is a catch-all safety net in case user code doesn't capture the drag payload itself.
/// This must happen at end-of-frame such that we don't shadow the mouse release event from user
/// code.
fn on_end_pass(&mut self, ctx: &Context) {
fn on_end_pass(&mut self, ui: &mut Ui) {
let has_any_payload = self.payload.is_some();
if has_any_payload {
let abort_dnd_due_to_mouse_release = ctx.input_mut(|i| i.pointer.any_released());
let abort_dnd_due_to_mouse_release = ui.input_mut(|i| i.pointer.any_released());
if abort_dnd_due_to_mouse_release {
self.payload = None;
} else {
// We set the cursor icon only if its default, as the user code might have
// explicitly set it already.
ctx.output_mut(|o| {
ui.output_mut(|o| {
if o.cursor_icon == CursorIcon::Default {
o.cursor_icon = CursorIcon::Grabbing;
}

View File

@@ -449,7 +449,7 @@ impl Grid {
if ui.is_visible() {
// Try to cover up the glitchy initial frame:
ui.ctx().request_discard("new Grid");
ui.request_discard("new Grid");
}
// Hide the ui this frame, and make things as narrow as possible:

View File

@@ -79,7 +79,6 @@ impl Id {
self.0.get()
}
#[cfg(feature = "accesskit")]
pub(crate) fn accesskit_id(&self) -> accesskit::NodeId {
self.value().into()
}

View File

@@ -855,7 +855,6 @@ impl InputState {
}
}
#[cfg(feature = "accesskit")]
pub fn accesskit_action_requests(
&self,
id: crate::Id,
@@ -873,7 +872,6 @@ impl InputState {
})
}
#[cfg(feature = "accesskit")]
pub fn consume_accesskit_action_requests(
&mut self,
id: crate::Id,
@@ -890,12 +888,10 @@ impl InputState {
});
}
#[cfg(feature = "accesskit")]
pub fn has_accesskit_action_request(&self, id: crate::Id, action: accesskit::Action) -> bool {
self.accesskit_action_requests(id, action).next().is_some()
}
#[cfg(feature = "accesskit")]
pub fn num_accesskit_action_requests(&self, id: crate::Id, action: accesskit::Action) -> usize {
self.accesskit_action_requests(id, action).count()
}
@@ -1267,6 +1263,11 @@ impl PointerState {
self.press_origin
}
/// How far has the pointer moved since the start of the drag (if any)?
pub fn total_drag_delta(&self) -> Option<Vec2> {
Some(self.latest_pos? - self.press_origin?)
}
/// When did the current click/drag originate?
/// `None` if no mouse button is down.
#[inline(always)]

View File

@@ -9,7 +9,7 @@
//! which uses [`eframe`](https://docs.rs/eframe).
//!
//! To create a GUI using egui you first need a [`Context`] (by convention referred to by `ctx`).
//! Then you add a [`Window`] or a [`SidePanel`] to get a [`Ui`], which is what you'll be using to add all the buttons and labels that you need.
//! Then you add a [`Window`] or a [`Panel`] to get a [`Ui`], which is what you'll be using to add all the buttons and labels that you need.
//!
//!
//! ## Feature flags
@@ -43,23 +43,6 @@
//! In some GUI frameworks this would require defining multiple types and functions with callbacks or message handlers,
//! but thanks to `egui` being immediate mode everything is one self-contained function!
//!
//! ### Getting a [`Ui`]
//!
//! Use one of [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`] to
//! get access to an [`Ui`] where you can put widgets. For example:
//!
//! ```
//! # egui::__run_test_ctx(|ctx| {
//! egui::CentralPanel::default().show(&ctx, |ui| {
//! ui.add(egui::Label::new("Hello World!"));
//! ui.label("A shorter and more convenient way to add a label.");
//! if ui.button("Click me").clicked() {
//! // take some action here
//! }
//! });
//! # });
//! ```
//!
//! ### Quick start
//!
//! ```
@@ -195,7 +178,7 @@
//! * lays out the letters `click me` in order to figure out the size of the button
//! * decides where on screen to place the button
//! * check if the mouse is hovering or clicking that location
//! * chose button colors based on if it is being hovered or clicked
//! * choose button colors based on if it is being hovered or clicked
//! * add a [`Shape::Rect`] and [`Shape::Text`] to the list of shapes to be painted later this frame
//! * return a [`Response`] with the [`clicked`](`Response::clicked`) member so the user can check for interactions
//!
@@ -322,7 +305,7 @@
//! when you release the panel/window shrinks again.
//! This is an artifact of immediate mode, and here are some alternatives on how to avoid it:
//!
//! 1. Turn off resizing with [`Window::resizable`], [`SidePanel::resizable`], [`TopBottomPanel::resizable`].
//! 1. Turn off resizing with [`Window::resizable`], [`Panel::resizable`].
//! 2. Wrap your panel contents in a [`ScrollArea`], or use [`Window::vscroll`] and [`Window::hscroll`].
//! 3. Use a justified layout:
//!
@@ -441,6 +424,7 @@ mod ui_stack;
pub mod util;
pub mod viewport;
mod widget_rect;
pub mod widget_style;
pub mod widget_text;
pub mod widgets;
@@ -448,7 +432,6 @@ pub mod widgets;
#[cfg(debug_assertions)]
mod callstack;
#[cfg(feature = "accesskit")]
pub use accesskit;
#[deprecated = "Use the ahash crate directly."]
@@ -692,8 +675,8 @@ pub enum WidgetType {
pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) {
let ctx = Context::default();
ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time)
let _ = ctx.run(Default::default(), |ctx| {
run_ui(ctx);
let _ = ctx.run_ui(Default::default(), |ui| {
run_ui(ui.ctx());
});
}
@@ -701,14 +684,11 @@ pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) {
pub fn __run_test_ui(add_contents: impl Fn(&mut Ui)) {
let ctx = Context::default();
ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time)
let _ = ctx.run(Default::default(), |ctx| {
crate::CentralPanel::default().show(ctx, |ui| {
add_contents(ui);
});
let _ = ctx.run_ui(Default::default(), |ui| {
add_contents(ui);
});
}
#[cfg(feature = "accesskit")]
pub fn accesskit_root_id() -> Id {
Id::new("accesskit_root")
}

View File

@@ -21,7 +21,7 @@ pub use theme::{Theme, ThemePreference};
/// how far the user has scrolled in a [`ScrollArea`](crate::ScrollArea) etc.
///
/// If you want this to persist when closing your app, you should serialize [`Memory`] and store it.
/// For this you need to enable the `persistence`.
/// For this you need to enable the `persistence` feature.
///
/// If you want to store data for your widgets, you should look at [`Memory::data`]
#[derive(Clone, Debug)]
@@ -470,7 +470,6 @@ pub(crate) struct Focus {
/// The ID of a widget to give the focus to in the next frame.
id_next_frame: Option<Id>,
#[cfg(feature = "accesskit")]
id_requested_by_accesskit: Option<accesskit::NodeId>,
/// If set, the next widget that is interested in focus will automatically get it.
@@ -529,10 +528,7 @@ impl Focus {
}
let event_filter = self.focused_widget.map(|w| w.filter).unwrap_or_default();
#[cfg(feature = "accesskit")]
{
self.id_requested_by_accesskit = None;
}
self.id_requested_by_accesskit = None;
self.focus_direction = FocusDirection::None;
@@ -567,16 +563,13 @@ impl Focus {
self.focus_direction = cardinality;
}
#[cfg(feature = "accesskit")]
if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest {
action: accesskit::Action::Focus,
target,
data: None,
}) = event
{
if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest {
action: accesskit::Action::Focus,
target,
data: None,
}) = event
{
self.id_requested_by_accesskit = Some(*target);
}
self.id_requested_by_accesskit = Some(*target);
}
}
}
@@ -606,14 +599,11 @@ impl Focus {
}
fn interested_in_focus(&mut self, id: Id) {
#[cfg(feature = "accesskit")]
{
if self.id_requested_by_accesskit == Some(id.accesskit_id()) {
self.focused_widget = Some(FocusWidget::new(id));
self.id_requested_by_accesskit = None;
self.give_to_next = false;
self.reset_focus();
}
if self.id_requested_by_accesskit == Some(id.accesskit_id()) {
self.focused_widget = Some(FocusWidget::new(id));
self.id_requested_by_accesskit = None;
self.give_to_next = false;
self.reset_focus();
}
// The rect is updated at the end of the frame.
@@ -1282,8 +1272,7 @@ impl Areas {
pub fn top_layer_id(&self, order: Order) -> Option<LayerId> {
self.order
.iter()
.filter(|layer| layer.order == order && !self.is_sublayer(layer))
.next_back()
.rfind(|layer| layer.order == order && !self.is_sublayer(layer))
.copied()
}

View File

@@ -89,7 +89,7 @@ impl ThemePreference {
/// Show radio-buttons to switch between light mode, dark mode and following the system theme.
pub fn radio_buttons(&mut self, ui: &mut crate::Ui) {
ui.horizontal(|ui| {
let system_theme = ui.ctx().input(|i| i.raw.system_theme);
let system_theme = ui.input(|i| i.raw.system_theme);
ui.selectable_value(self, Self::System, "💻 System")
.on_hover_ui(|ui| {

View File

@@ -84,7 +84,7 @@ fn set_menu_style(style: &mut Style) {
}
}
/// The menu bar goes well in a [`crate::TopBottomPanel::top`],
/// The menu bar goes well in a [`crate::Panel::top`],
/// but can also be placed in a [`crate::Window`].
/// In the latter case you may want to wrap it in [`Frame`].
#[deprecated = "Use `egui::MenuBar::new().ui(` instead"]
@@ -186,7 +186,7 @@ fn menu_popup<'c, R>(
.kind(UiKind::Menu)
.order(Order::Foreground)
.fixed_pos(pos)
.default_width(ctx.style().spacing.menu_width)
.default_width(ctx.global_style().spacing.menu_width)
.sense(Sense::hover());
let mut sizing_pass = false;
@@ -395,9 +395,9 @@ impl MenuRoot {
// or button hovered while other menu is open
let mut pos = button.rect.left_bottom();
let menu_frame = Frame::menu(&button.ctx.style());
let menu_frame = Frame::menu(&button.ctx.global_style());
pos.x -= menu_frame.total_margin().left; // Make fist button in menu align with the parent button
pos.y += button.ctx.style().spacing.menu_spacing;
pos.y += button.ctx.global_style().spacing.menu_spacing;
if let Some(root) = root.inner.as_mut() {
let menu_rect = root.menu_state.read().rect;
@@ -634,7 +634,7 @@ impl SubMenu {
/// Usually you don't need to use it directly.
pub struct MenuState {
/// The opened sub-menu and its [`Id`]
sub_menu: Option<(Id, Arc<RwLock<MenuState>>)>,
sub_menu: Option<(Id, Arc<RwLock<Self>>)>,
/// Bounding box of this menu (without the sub-menu),
/// including the frame and everything.
@@ -701,7 +701,7 @@ impl MenuState {
if self.moving_towards_current_submenu(&pointer) {
// We don't close the submenu if the pointer is on its way to hover it.
// ensure to repaint once even when pointer is not moving
ui.ctx().request_repaint();
ui.request_repaint();
} else if !open && button.hovered() {
// TODO(emilk): open menu to the left if there isn't enough space to the right
let mut pos = button.rect.right_top();

View File

@@ -322,7 +322,7 @@ impl Painter {
}
pub fn error(&self, pos: Pos2, text: impl std::fmt::Display) -> Rect {
let color = self.ctx.style().visuals.error_fg_color;
let color = self.ctx.global_style().visuals.error_fg_color;
self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {text}"))
}

View File

@@ -67,7 +67,6 @@ impl ScrollTarget {
}
}
#[cfg(feature = "accesskit")]
#[derive(Clone)]
pub struct AccessKitPassState {
pub nodes: IdMap<accesskit::Node>,
@@ -96,7 +95,7 @@ impl DebugRect {
// Paint rectangle around widget:
{
// Print width and height:
let text_color = if ctx.style().visuals.dark_mode {
let text_color = if ctx.global_style().visuals.dark_mode {
Color32::WHITE
} else {
Color32::BLACK
@@ -225,7 +224,6 @@ pub struct PassState {
/// as when swiping down on a touch-screen or track-pad with natural scrolling.
pub scroll_delta: (Vec2, style::ScrollAnimation),
#[cfg(feature = "accesskit")]
pub accesskit_state: Option<AccessKitPassState>,
/// Highlight these widgets the next pass.
@@ -247,7 +245,6 @@ impl Default for PassState {
used_by_panels: Rect::NAN,
scroll_target: [None, None],
scroll_delta: (Vec2::default(), style::ScrollAnimation::none()),
#[cfg(feature = "accesskit")]
accesskit_state: None,
highlight_next_pass: Default::default(),
@@ -270,7 +267,6 @@ impl PassState {
used_by_panels,
scroll_target,
scroll_delta,
#[cfg(feature = "accesskit")]
accesskit_state,
highlight_next_pass,
@@ -293,10 +289,7 @@ impl PassState {
*debug_rect = None;
}
#[cfg(feature = "accesskit")]
{
*accesskit_state = None;
}
*accesskit_state = None;
highlight_next_pass.clear();
}

View File

@@ -1,4 +1,4 @@
use crate::{Context, FullOutput, RawInput};
use crate::{Context, FullOutput, RawInput, Ui};
use ahash::HashMap;
use epaint::mutex::{Mutex, MutexGuard};
use std::sync::Arc;
@@ -23,13 +23,13 @@ pub trait Plugin: Send + Sync + std::any::Any + 'static {
/// Called at the start of each pass.
///
/// Can be used to show ui, e.g. a [`crate::Window`] or [`crate::SidePanel`].
fn on_begin_pass(&mut self, ctx: &Context) {}
/// Can be used to show ui, e.g. a [`crate::Window`] or [`crate::Panel`].
fn on_begin_pass(&mut self, ui: &mut Ui) {}
/// Called at the end of each pass.
///
/// Can be used to show ui, e.g. a [`crate::Window`].
fn on_end_pass(&mut self, ctx: &Context) {}
fn on_end_pass(&mut self, ui: &mut Ui) {}
/// Called just before the input is processed.
///
@@ -147,17 +147,17 @@ impl PluginsOrdered {
}
}
pub fn on_begin_pass(&self, ctx: &Context) {
pub fn on_begin_pass(&self, ui: &mut Ui) {
profiling::scope!("plugins", "on_begin_pass");
self.for_each_dyn(|p| {
p.on_begin_pass(ctx);
p.on_begin_pass(ui);
});
}
pub fn on_end_pass(&self, ctx: &Context) {
pub fn on_end_pass(&self, ui: &mut Ui) {
profiling::scope!("plugins", "on_end_pass");
self.for_each_dyn(|p| {
p.on_end_pass(ctx);
p.on_end_pass(ui);
});
}
@@ -214,7 +214,7 @@ impl Plugins {
}
/// Generic event callback.
pub type ContextCallback = Arc<dyn Fn(&Context) + Send + Sync>;
pub type ContextCallback = Arc<dyn Fn(&mut Ui) + Send + Sync>;
#[derive(Default)]
pub(crate) struct CallbackPlugin {
@@ -227,21 +227,21 @@ impl Plugin for CallbackPlugin {
"CallbackPlugins"
}
fn on_begin_pass(&mut self, ctx: &Context) {
fn on_begin_pass(&mut self, ui: &mut Ui) {
profiling::function_scope!();
for (_debug_name, cb) in &self.on_begin_plugins {
profiling::scope!("on_begin_pass", *_debug_name);
(cb)(ctx);
(cb)(ui);
}
}
fn on_end_pass(&mut self, ctx: &Context) {
fn on_end_pass(&mut self, ui: &mut Ui) {
profiling::function_scope!();
for (_debug_name, cb) in &self.on_end_plugins {
profiling::scope!("on_end_pass", *_debug_name);
(cb)(ctx);
(cb)(ui);
}
}
}

View File

@@ -396,7 +396,7 @@ impl Response {
self.drag_stopped() && self.ctx.input(|i| i.pointer.button_released(button))
}
/// If dragged, how many points were we dragged and in what direction?
/// If dragged, how many points were we dragged in since last frame?
#[inline]
pub fn drag_delta(&self) -> Vec2 {
if self.dragged() {
@@ -410,7 +410,22 @@ impl Response {
}
}
/// If dragged, how far did the mouse move?
/// If dragged, how many points have we been dragged since the start of the drag?
#[inline]
pub fn total_drag_delta(&self) -> Option<Vec2> {
if self.dragged() {
let mut delta = self.ctx.input(|i| i.pointer.total_drag_delta())?;
if let Some(from_global) = self.ctx.layer_transform_from_global(self.layer_id) {
delta *= from_global.scaling;
}
Some(delta)
} else {
None
}
}
/// If dragged, how far did the mouse move since last frame?
///
/// This will use raw mouse movement if provided by the integration, otherwise will fall back to [`Response::drag_delta`]
/// Raw mouse movement is unaccelerated and unclamped by screen boundaries, and does not relate to any position on the screen.
/// This may be useful in certain situations such as draggable values and 3D cameras, where screen position does not matter.
@@ -418,7 +433,7 @@ impl Response {
pub fn drag_motion(&self) -> Vec2 {
if self.dragged() {
self.ctx
.input(|i| i.pointer.motion().unwrap_or(i.pointer.delta()))
.input(|i| i.pointer.motion().unwrap_or_else(|| i.pointer.delta()))
} else {
Vec2::ZERO
}
@@ -457,7 +472,7 @@ impl Response {
///
/// Only returns something if [`Self::contains_pointer`] is true,
/// the user is drag-dropping something of this type,
/// and they released it this frame
/// and they released it this frame.
#[doc(alias = "drag and drop")]
pub fn dnd_release_payload<Payload: Any + Send + Sync>(&self) -> Option<Arc<Payload>> {
// NOTE: we use `response.contains_pointer` here instead of `hovered`, because
@@ -709,10 +724,9 @@ impl Response {
/// ```
#[must_use]
pub fn interact(&self, sense: Sense) -> Self {
if (self.sense | sense) == self.sense {
// Early-out: we already sense everything we need to sense.
return self.clone();
}
// We could check here if the new Sense equals the old one to avoid the extra create_widget
// call. But that would break calling `interact` on a response from `Context::read_response`
// or `Ui::response`. (See https://github.com/emilk/egui/pull/7713 for more details.)
self.ctx.create_widget(
WidgetRect {
@@ -747,7 +761,7 @@ impl Response {
/// # });
/// ```
pub fn scroll_to_me(&self, align: Option<Align>) {
self.scroll_to_me_animation(align, self.ctx.style().scroll_animation);
self.scroll_to_me_animation(align, self.ctx.global_style().scroll_animation);
}
/// Like [`Self::scroll_to_me`], but allows you to specify the [`crate::style::ScrollAnimation`].
@@ -793,7 +807,6 @@ impl Response {
if let Some(event) = event {
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());
});
@@ -803,7 +816,6 @@ impl Response {
}
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());
});
@@ -814,7 +826,6 @@ impl Response {
self.ctx.output_mut(|o| o.events.push(event));
}
#[cfg(feature = "accesskit")]
pub(crate) fn fill_accesskit_node_common(&self, builder: &mut accesskit::Node) {
if !self.enabled() {
builder.set_disabled();
@@ -833,7 +844,6 @@ impl Response {
}
}
#[cfg(feature = "accesskit")]
fn fill_accesskit_node_from_widget_info(
&self,
builder: &mut accesskit::Node,
@@ -908,14 +918,9 @@ 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());
});
#[cfg(not(feature = "accesskit"))]
{
let _ = id;
}
self
}

View File

@@ -3,7 +3,7 @@
#![allow(clippy::if_same_then_else)]
use emath::Align;
use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, text::FontTweak};
use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, TextOptions, text::FontTweak};
use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
use crate::{
@@ -508,6 +508,12 @@ pub struct ScrollStyle {
/// it more promiment.
pub floating: bool,
/// Extra margin added around the contents of a [`crate::ScrollArea`].
///
/// The scroll bars will be either on top of this margin, or outside of it,
/// depending on the value of [`Self::floating`].
pub content_margin: Margin,
/// The width of the scroll bars at it largest.
pub bar_width: f32,
@@ -591,6 +597,7 @@ impl ScrollStyle {
pub fn solid() -> Self {
Self {
floating: false,
content_margin: Margin::ZERO,
bar_width: 6.0,
handle_min_length: 12.0,
bar_inner_margin: 4.0,
@@ -672,6 +679,9 @@ impl ScrollStyle {
pub fn details_ui(&mut self, ui: &mut Ui) {
let Self {
floating,
content_margin,
bar_width,
handle_min_length,
bar_inner_margin,
@@ -695,6 +705,11 @@ impl ScrollStyle {
ui.selectable_value(floating, true, "Floating");
});
ui.horizontal(|ui| {
ui.label("Content margin:");
content_margin.ui(ui);
});
ui.horizontal(|ui| {
ui.add(DragValue::new(bar_width).range(0.0..=32.0));
ui.label("Full bar width");
@@ -933,8 +948,11 @@ pub struct Visuals {
/// this is more to provide a convenient summary of the rest of the settings.
pub dark_mode: bool,
/// ADVANCED: Controls how we render text.
pub text_alpha_from_coverage: AlphaFromCoverage,
/// Controls how we render text.
///
/// The [`TextOptions::max_texture_side`] is ignored and overruled by
/// [`crate::RawInput::max_texture_side`].
pub text_options: TextOptions,
/// Override default text color for all text.
///
@@ -1392,7 +1410,10 @@ impl Visuals {
pub fn dark() -> Self {
Self {
dark_mode: true,
text_alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
text_options: TextOptions {
alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
..Default::default()
},
override_text_color: None,
weak_text_alpha: 0.6,
weak_text_color: None,
@@ -1455,7 +1476,10 @@ impl Visuals {
pub fn light() -> Self {
Self {
dark_mode: false,
text_alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
text_options: TextOptions {
alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
..Default::default()
},
widgets: Widgets::light(),
selection: Selection::light(),
hyperlink_color: Color32::from_rgb(0, 155, 255),
@@ -1824,6 +1848,10 @@ impl Spacing {
ui.add(window_margin);
ui.end_row();
ui.label("ScrollArea margin");
scroll.content_margin.ui(ui);
ui.end_row();
ui.label("Menu margin");
ui.add(menu_margin);
ui.end_row();
@@ -2088,7 +2116,7 @@ impl Visuals {
pub fn ui(&mut self, ui: &mut crate::Ui) {
let Self {
dark_mode,
text_alpha_from_coverage,
text_options,
override_text_color: _,
weak_text_alpha,
weak_text_color,
@@ -2188,7 +2216,7 @@ impl Visuals {
});
});
ui.collapsing("Text color", |ui| {
ui.collapsing("Text rendering", |ui| {
fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) {
ui.label(label.into().color(*color));
ui.color_edit_button_srgba(color);
@@ -2240,7 +2268,15 @@ impl Visuals {
ui.add_space(4.0);
text_alpha_from_coverage_ui(ui, text_alpha_from_coverage);
let TextOptions {
max_texture_side: _,
alpha_from_coverage,
font_hinting,
} = text_options;
text_alpha_from_coverage_ui(ui, alpha_from_coverage);
ui.checkbox(font_hinting, "Enable font hinting");
});
ui.collapsing("Text cursor", |ui| {
@@ -2351,9 +2387,9 @@ impl Visuals {
}
}
fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut AlphaFromCoverage) {
fn text_alpha_from_coverage_ui(ui: &mut Ui, alpha_from_coverage: &mut AlphaFromCoverage) {
let mut dark_mode_special =
*text_alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
*alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
ui.horizontal(|ui| {
ui.label("Text rendering:");
@@ -2361,9 +2397,9 @@ fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut Alpha
ui.checkbox(&mut dark_mode_special, "Dark-mode special");
if dark_mode_special {
*text_alpha_from_coverage = AlphaFromCoverage::TwoCoverageMinusCoverageSq;
*alpha_from_coverage = AlphaFromCoverage::DARK_MODE_DEFAULT;
} else {
let mut gamma = match text_alpha_from_coverage {
let mut gamma = match alpha_from_coverage {
AlphaFromCoverage::Linear => 1.0,
AlphaFromCoverage::Gamma(gamma) => *gamma,
AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, // approximately the same
@@ -2377,9 +2413,9 @@ fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut Alpha
);
if gamma == 1.0 {
*text_alpha_from_coverage = AlphaFromCoverage::Linear;
*alpha_from_coverage = AlphaFromCoverage::Linear;
} else {
*text_alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
*alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
}
}
});
@@ -2793,6 +2829,7 @@ impl Widget for &mut FontTweak {
scale,
y_offset_factor,
y_offset,
hinting_override,
} = self;
ui.label("Scale");
@@ -2808,6 +2845,19 @@ impl Widget for &mut FontTweak {
ui.add(DragValue::new(y_offset).speed(-0.02));
ui.end_row();
ui.label("hinting_override");
ComboBox::from_id_salt("hinting_override")
.selected_text(match hinting_override {
None => "None",
Some(true) => "Enable",
Some(false) => "Disable",
})
.show_ui(ui, |ui| {
ui.selectable_value(hinting_override, None, "None");
ui.selectable_value(hinting_override, Some(true), "Enable");
ui.selectable_value(hinting_override, Some(false), "Disable");
});
if ui.button("Reset").clicked() {
*self = Default::default();
}

View File

@@ -42,8 +42,9 @@ pub fn update_accesskit_for_text_widget(
for (row_index, row) in galley.rows.iter().enumerate() {
let row_id = parent_id.with(row_index);
#[cfg(feature = "accesskit")]
ctx.register_accesskit_parent(row_id, parent_id);
ctx.accesskit_node_builder(row_id, |builder| {
builder.set_role(accesskit::Role::TextRun);
let rect = global_from_galley * row.rect_without_leading_space();

View File

@@ -190,7 +190,6 @@ impl CCursorRange {
..
} => self.on_key_press(os, galley, modifiers, *key),
#[cfg(feature = "accesskit")]
Event::AccessKitActionRequest(accesskit::ActionRequest {
action: accesskit::Action::SetTextSelection,
target,
@@ -220,7 +219,6 @@ impl CCursorRange {
// ----------------------------------------------------------------------------
#[cfg(feature = "accesskit")]
fn ccursor_from_accesskit_text_position(
id: Id,
galley: &Galley,

View File

@@ -128,8 +128,8 @@ impl Plugin for LabelSelectionState {
"LabelSelectionState"
}
fn on_begin_pass(&mut self, ctx: &Context) {
if ctx.input(|i| i.pointer.any_pressed() && !i.modifiers.shift) {
fn on_begin_pass(&mut self, ui: &mut Ui) {
if ui.input(|i| i.pointer.any_pressed() && !i.modifiers.shift) {
// Maybe a new selection is about to begin, but the old one is over:
// state.selection = None; // TODO(emilk): this makes sense, but doesn't work as expected.
}
@@ -145,9 +145,9 @@ impl Plugin for LabelSelectionState {
self.painted_selections.clear();
}
fn on_end_pass(&mut self, ctx: &Context) {
fn on_end_pass(&mut self, ui: &mut Ui) {
if self.is_dragging {
ctx.set_cursor_icon(CursorIcon::Text);
ui.set_cursor_icon(CursorIcon::Text);
}
if !self.has_reached_primary || !self.has_reached_secondary {
@@ -159,7 +159,7 @@ impl Plugin for LabelSelectionState {
if let Some(selection) = prev_selection {
// This was the first frame of glitch, so hide the
// glitching by removing all painted selections:
ctx.graphics_mut(|layers| {
ui.graphics_mut(|layers| {
if let Some(list) = layers.get_mut(selection.layer_id) {
for (shape_idx, row_selections) in self.painted_selections.drain(..) {
list.mutate_shape(shape_idx, |shape| {
@@ -190,21 +190,21 @@ impl Plugin for LabelSelectionState {
}
}
let pressed_escape = ctx.input(|i| i.key_pressed(crate::Key::Escape));
let clicked_something_else = ctx.input(|i| i.pointer.any_pressed()) && !self.any_hovered;
let pressed_escape = ui.input(|i| i.key_pressed(crate::Key::Escape));
let clicked_something_else = ui.input(|i| i.pointer.any_pressed()) && !self.any_hovered;
let delected_everything = pressed_escape || clicked_something_else;
if delected_everything {
self.selection = None;
}
if ctx.input(|i| i.pointer.any_released()) {
if ui.input(|i| i.pointer.any_released()) {
self.is_dragging = false;
}
let text_to_copy = std::mem::take(&mut self.text_to_copy);
if !text_to_copy.is_empty() {
ctx.copy_text(text_to_copy);
ui.copy_text(text_to_copy);
}
}
}
@@ -499,7 +499,7 @@ impl LabelSelectionState {
let global_from_galley = global_from_layer * layer_from_galley;
if response.hovered() {
ui.ctx().set_cursor_icon(CursorIcon::Text);
ui.set_cursor_icon(CursorIcon::Text);
}
self.any_hovered |= response.hovered();
@@ -624,7 +624,6 @@ impl LabelSelectionState {
);
}
#[cfg(feature = "accesskit")]
super::accesskit_text::update_accesskit_for_text_widget(
ui.ctx(),
response.id,

View File

@@ -1,6 +1,5 @@
//! Helpers regarding text selection for labels and text edit.
#[cfg(feature = "accesskit")]
pub mod accesskit_text;
mod cursor_range;

View File

@@ -208,25 +208,33 @@ fn ccursor_previous_line(text: &str, ccursor: CCursor) -> CCursor {
}
}
fn next_word_boundary_char_index(text: &str, index: usize) -> usize {
for word in text.split_word_bound_indices() {
fn next_word_boundary_char_index(text: &str, cursor_ci: usize) -> usize {
for (word_byte_index, word) in text.split_word_bound_indices() {
let word_ci = char_index_from_byte_index(text, word_byte_index);
// We consider `.` a word boundary.
// At least that's how Mac works when navigating something like `www.example.com`.
for (dot_ci_offset, chr) in word.chars().enumerate() {
let dot_ci = word_ci + dot_ci_offset;
if chr == '.' && cursor_ci < dot_ci {
return dot_ci;
}
}
// Splitting considers contiguous whitespace as one word, such words must be skipped,
// this handles cases for example ' abc' (a space and a word), the cursor is at the beginning
// (before space) - this jumps at the end of 'abc' (this is consistent with text editors
// or browsers)
let ci = char_index_from_byte_index(text, word.0);
if ci > index && !skip_word(word.1) {
return ci;
if cursor_ci < word_ci && !all_word_chars(word) {
return word_ci;
}
}
char_index_from_byte_index(text, text.len())
}
fn skip_word(text: &str) -> bool {
// skip words that contain anything other than alphanumeric characters and underscore
// (i.e. whitespace, dashes, etc.)
!text.chars().any(|c| !is_word_char(c))
fn all_word_chars(text: &str) -> bool {
text.chars().all(is_word_char)
}
fn next_line_boundary_char_index(it: impl Iterator<Item = char>, mut index: usize) -> usize {
@@ -337,6 +345,12 @@ mod test {
assert_eq!(next_word_boundary_char_index("", 0), 0);
assert_eq!(next_word_boundary_char_index("", 1), 0);
// ASCII only
let text = "abc.def.ghi";
assert_eq!(next_word_boundary_char_index(text, 1), 3);
assert_eq!(next_word_boundary_char_index(text, 3), 7);
assert_eq!(next_word_boundary_char_index(text, 7), 11);
// Unicode graphemes, some of which consist of multiple Unicode characters,
// !!! Unicode character is not always what is tranditionally considered a character,
// the values below are correct despite not seeming that way on the first look,

View File

@@ -172,7 +172,7 @@ pub fn paint_text_cursor(
total_duration - time_in_cycle
};
ui.ctx().request_repaint_after_secs(wake_in);
ui.request_repaint_after_secs(wake_in);
} else {
paint_cursor_end(painter, ui.visuals(), primary_cursor_rect);
}

View File

@@ -1,34 +1,13 @@
#![warn(missing_docs)] // Let's keep `Ui` well-documented.
#![allow(clippy::use_self)]
use std::{any::Any, hash::Hash, ops::Deref, sync::Arc};
use emath::GuiRounding as _;
use epaint::mutex::RwLock;
use epaint::text::FontsView;
use std::{any::Any, hash::Hash, sync::Arc};
use crate::ClosableTag;
#[cfg(debug_assertions)]
use crate::Stroke;
use crate::containers::menu;
use crate::{
Align, Color32, Context, CursorIcon, DragAndDrop, Id, InnerResponse, InputState, IntoAtoms,
LayerId, Memory, Order, Painter, PlatformOutput, Pos2, Rangef, Rect, Response, Rgba, RichText,
Sense, Style, TextStyle, TextWrapMode, UiBuilder, UiKind, UiStack, UiStackInfo, Vec2,
WidgetRect, WidgetText,
containers::{CollapsingHeader, CollapsingResponse, Frame},
ecolor::Hsva,
emath, epaint, grid,
layout::{Direction, Layout},
pass_state,
placer::Placer,
pos2, style,
util::IdTypeMap,
vec2, widgets,
widgets::{
Button, Checkbox, DragValue, Hyperlink, Image, ImageSource, Label, Link, RadioButton,
Separator, Spinner, TextEdit, Widget, color_picker,
},
};
use crate::{containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *};
// ----------------------------------------------------------------------------
/// This is what you use to place widgets.
@@ -74,14 +53,14 @@ pub struct Ui {
/// This value is based on where in the hierarchy of widgets this Ui is in,
/// and the value is increment with each added child widget.
/// This works as an Id source only as long as new widgets aren't added or removed.
/// They are therefore only good for Id:s that has no state.
/// They are therefore only good for Id:s that have no state.
next_auto_id_salt: u64,
/// Specifies paint layer, clip rectangle and a reference to [`Context`].
painter: Painter,
/// The [`Style`] (visuals, spacing, etc) of this ui.
/// Commonly many [`Ui`]:s share the same [`Style`].
/// Commonly many [`Ui`]s share the same [`Style`].
/// The [`Ui`] implements copy-on-write for this.
style: Arc<Style>,
@@ -112,6 +91,16 @@ pub struct Ui {
min_rect_already_remembered: bool,
}
/// Allow using [`Ui`] like a [`Context`].
impl Deref for Ui {
type Target = Context;
#[inline]
fn deref(&self) -> &Self::Target {
self.ctx()
}
}
impl Ui {
// ------------------------------------------------------------------------
// Creation:
@@ -119,7 +108,7 @@ impl Ui {
/// Create a new top-level [`Ui`].
///
/// Normally you would not use this directly, but instead use
/// [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`].
/// [`crate::Panel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`].
pub fn new(ctx: Context, id: Id, ui_builder: UiBuilder) -> Self {
let UiBuilder {
id_salt,
@@ -133,11 +122,10 @@ impl Ui {
sizing_pass,
style,
sense,
#[cfg(feature = "accesskit")]
accessibility_parent,
} = ui_builder;
let layer_id = layer_id.unwrap_or(LayerId::background());
let layer_id = layer_id.unwrap_or_else(LayerId::background);
debug_assert!(
id_salt.is_none(),
@@ -148,8 +136,8 @@ impl Ui {
let clip_rect = max_rect;
let layout = layout.unwrap_or_default();
let disabled = disabled || invisible;
let style = style.unwrap_or_else(|| ctx.style());
let sense = sense.unwrap_or(Sense::hover());
let style = style.unwrap_or_else(|| ctx.global_style());
let sense = sense.unwrap_or_else(Sense::hover);
let placer = Placer::new(max_rect, layout);
let ui_stack = UiStack {
@@ -175,7 +163,6 @@ impl Ui {
min_rect_already_remembered: false,
};
#[cfg(feature = "accesskit")]
if let Some(accessibility_parent) = accessibility_parent {
ui.ctx()
.register_accesskit_parent(ui.unique_id, accessibility_parent);
@@ -202,7 +189,6 @@ impl Ui {
ui.set_invisible();
}
#[cfg(feature = "accesskit")]
ui.ctx().accesskit_node_builder(ui.unique_id, |node| {
node.set_role(accesskit::Role::GenericContainer);
});
@@ -273,7 +259,6 @@ impl Ui {
sizing_pass,
style,
sense,
#[cfg(feature = "accesskit")]
accessibility_parent,
} = ui_builder;
@@ -281,7 +266,7 @@ impl Ui {
let id_salt = id_salt.unwrap_or_else(|| Id::from("child"));
let max_rect = max_rect.unwrap_or_else(|| self.available_rect_before_wrap());
let mut layout = layout.unwrap_or(*self.layout());
let mut layout = layout.unwrap_or_else(|| *self.layout());
let enabled = self.enabled && !disabled && !invisible;
if let Some(layer_id) = layer_id {
painter.set_layer_id(layer_id);
@@ -291,7 +276,7 @@ impl Ui {
}
let sizing_pass = self.sizing_pass || sizing_pass;
let style = style.unwrap_or_else(|| self.style.clone());
let sense = sense.unwrap_or(Sense::hover());
let sense = sense.unwrap_or_else(Sense::hover);
if sizing_pass {
// During the sizing pass we want widgets to use up as little space as possible,
@@ -343,7 +328,6 @@ impl Ui {
child_ui.disable();
}
#[cfg(feature = "accesskit")]
child_ui.ctx().register_accesskit_parent(
child_ui.unique_id,
accessibility_parent.unwrap_or(self.unique_id),
@@ -363,7 +347,6 @@ impl Ui {
true,
);
#[cfg(feature = "accesskit")]
child_ui
.ctx()
.accesskit_node_builder(child_ui.unique_id, |node| {
@@ -434,7 +417,7 @@ impl Ui {
/// Mutably borrow internal [`Style`].
/// Changes apply to this [`Ui`] and its subsequent children.
///
/// To set the style of all [`Ui`]:s, use [`Context::set_style_of`].
/// To set the style of all [`Ui`]s, use [`Context::set_style_of`].
///
/// Example:
/// ```
@@ -448,14 +431,14 @@ impl Ui {
/// Changes apply to this [`Ui`] and its subsequent children.
///
/// To set the visuals of all [`Ui`]:s, use [`Context::set_visuals_of`].
/// To set the style of all [`Ui`]s, use [`Context::set_style_of`].
pub fn set_style(&mut self, style: impl Into<Arc<Style>>) {
self.style = style.into();
}
/// Reset to the default style set in [`Context`].
pub fn reset_style(&mut self) {
self.style = self.ctx().style();
self.style = self.ctx().global_style();
}
/// The current spacing options for this [`Ui`].
@@ -465,7 +448,7 @@ impl Ui {
&self.style.spacing
}
/// Mutably borrow internal [`Spacing`](crate::style::Spacing).
/// Mutably borrow internal [`Spacing`].
/// Changes apply to this [`Ui`] and its subsequent children.
///
/// Example:
@@ -488,7 +471,7 @@ impl Ui {
/// Mutably borrow internal `visuals`.
/// Changes apply to this [`Ui`] and its subsequent children.
///
/// To set the visuals of all [`Ui`]:s, use [`Context::set_visuals_of`].
/// To set the visuals of all [`Ui`]s, use [`Context::set_visuals_of`].
///
/// Example:
/// ```
@@ -800,93 +783,6 @@ impl Ui {
}
}
/// # Helpers for accessing the underlying [`Context`].
/// These functions all lock the [`Context`] owned by this [`Ui`].
/// Please see the documentation of [`Context`] for how locking works!
impl Ui {
/// Read-only access to the shared [`InputState`].
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// if ui.input(|i| i.key_pressed(egui::Key::A)) {
/// // …
/// }
/// # });
/// ```
#[inline]
pub fn input<R>(&self, reader: impl FnOnce(&InputState) -> R) -> R {
self.ctx().input(reader)
}
/// Read-write access to the shared [`InputState`].
#[inline]
pub fn input_mut<R>(&self, writer: impl FnOnce(&mut InputState) -> R) -> R {
self.ctx().input_mut(writer)
}
/// Read-only access to the shared [`Memory`].
#[inline]
pub fn memory<R>(&self, reader: impl FnOnce(&Memory) -> R) -> R {
self.ctx().memory(reader)
}
/// Read-write access to the shared [`Memory`].
#[inline]
pub fn memory_mut<R>(&self, writer: impl FnOnce(&mut Memory) -> R) -> R {
self.ctx().memory_mut(writer)
}
/// Read-only access to the shared [`IdTypeMap`], which stores superficial widget state.
#[inline]
pub fn data<R>(&self, reader: impl FnOnce(&IdTypeMap) -> R) -> R {
self.ctx().data(reader)
}
/// Read-write access to the shared [`IdTypeMap`], which stores superficial widget state.
#[inline]
pub fn data_mut<R>(&self, writer: impl FnOnce(&mut IdTypeMap) -> R) -> R {
self.ctx().data_mut(writer)
}
/// Read-only access to the shared [`PlatformOutput`].
///
/// This is what egui outputs each frame.
///
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.output_mut(|o| o.cursor_icon = egui::CursorIcon::Progress);
/// ```
#[inline]
pub fn output<R>(&self, reader: impl FnOnce(&PlatformOutput) -> R) -> R {
self.ctx().output(reader)
}
/// Read-write access to the shared [`PlatformOutput`].
///
/// This is what egui outputs each frame.
///
/// ```
/// # let mut ctx = egui::Context::default();
/// ctx.output_mut(|o| o.cursor_icon = egui::CursorIcon::Progress);
/// ```
#[inline]
pub fn output_mut<R>(&self, writer: impl FnOnce(&mut PlatformOutput) -> R) -> R {
self.ctx().output_mut(writer)
}
/// Read-only access to [`FontsView`].
#[inline]
pub fn fonts<R>(&self, reader: impl FnOnce(&FontsView<'_>) -> R) -> R {
self.ctx().fonts(reader)
}
/// Read-write access to [`FontsView`].
#[inline]
pub fn fonts_mut<R>(&self, reader: impl FnOnce(&mut FontsView<'_>) -> R) -> R {
self.ctx().fonts_mut(reader)
}
}
// ------------------------------------------------------------------------
/// # Sizes etc
@@ -1129,7 +1025,6 @@ impl Ui {
impl Ui {
/// Check for clicks, drags and/or hover on a specific region of this [`Ui`].
pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> Response {
#[cfg(feature = "accesskit")]
self.ctx().register_accesskit_parent(id, self.unique_id);
self.ctx().create_widget(
@@ -1157,8 +1052,8 @@ impl Ui {
self.interact(rect, id, sense)
}
/// Read the [`Ui`]s background [`Response`].
/// It's [`Sense`] will be based on the [`UiBuilder::sense`] used to create this [`Ui`].
/// Read the [`Ui`]'s background [`Response`].
/// Its [`Sense`] will be based on the [`UiBuilder::sense`] used to create this [`Ui`].
///
/// The rectangle of the [`Response`] (and interactive area) will be [`Self::min_rect`]
/// of the last pass.
@@ -1258,7 +1153,7 @@ impl Ui {
/// [`crate::Area`], [`crate::Window`], [`crate::CollapsingHeader`], etc.
///
/// What exactly happens when you close a container depends on the container implementation.
/// [`crate::Area`] e.g. will return true from it's [`Response::should_close`] method.
/// [`crate::Area`] e.g. will return true from its [`Response::should_close`] method.
///
/// If you want to close a specific kind of container, use [`Ui::close_kind`] instead.
///
@@ -1439,7 +1334,7 @@ impl Ui {
crate::StrokeKind::Inside,
);
let stroke = Stroke::new(2.5, Color32::from_rgb(200, 0, 0));
let stroke = crate::Stroke::new(2.5, Color32::from_rgb(200, 0, 0));
let paint_line_seg = |a, b| self.painter().line_segment([a, b], stroke);
if debug_expand_width && too_wide {
@@ -2299,7 +2194,7 @@ impl Ui {
/// Show an image available at the given `uri`.
///
/// ⚠ This will do nothing unless you install some image loaders first!
/// The easiest way to do this is via [`egui_extras::install_image_loaders`](https://docs.rs/egui_extras/latest/egui_extras/fn.install_image_loaders.html).
/// The easiest way to do this is via [`egui_extras::install_image_loaders`](https://docs.rs/egui_extras/latest/egui_extras/loaders/fn.install_image_loaders.html).
///
/// The loaders handle caching image data, sampled textures, etc. across frames, so calling this is immediate-mode safe.
///
@@ -2375,7 +2270,7 @@ impl Ui {
///
/// If the user clicks the button, a full color picker is shown.
/// The given color is in `sRGBA` space without premultiplied alpha.
/// If unsure, what "premultiplied alpha" is, then this is probably the function you want to use.
/// If unsure what "premultiplied alpha" is, then this is probably the function you want to use.
pub fn color_edit_button_srgba_unmultiplied(&mut self, srgba: &mut [u8; 4]) -> Response {
let mut rgba = Rgba::from_srgba_unmultiplied(srgba[0], srgba[1], srgba[2], srgba[3]);
let response =
@@ -3011,7 +2906,7 @@ impl Ui {
///
/// Returns the dropped item, if it was released this frame.
///
/// The given frame is used for its margins, but it color is ignored.
/// The given frame is used for its margins, but the color is ignored.
#[doc(alias = "drag and drop")]
pub fn dnd_drop_zone<Payload, R>(
&mut self,
@@ -3312,7 +3207,7 @@ fn register_rect(ui: &Ui, rect: Rect) {
// Use the debug-painter to avoid clip rect,
// otherwise the content of the widget may cover what we paint here!
let painter = ui.ctx().debug_painter();
let painter = ui.debug_painter();
if debug.hover_shows_next {
ui.placer.debug_paint_cursor(&painter, "next");

View File

@@ -24,7 +24,6 @@ pub struct UiBuilder {
pub sizing_pass: bool,
pub style: Option<Arc<Style>>,
pub sense: Option<Sense>,
#[cfg(feature = "accesskit")]
pub accessibility_parent: Option<Id>,
}
@@ -187,15 +186,9 @@ impl UiBuilder {
///
/// This will override the automatic parent assignment for accessibility purposes.
/// If not set, the parent [`Ui`]'s ID will be used as the accessibility parent.
///
/// This does nothing if the `accesskit` feature is not enabled.
#[cfg_attr(not(feature = "accesskit"), expect(unused_mut, unused_variables))]
#[inline]
pub fn accessibility_parent(mut self, parent_id: Id) -> Self {
#[cfg(feature = "accesskit")]
{
self.accessibility_parent = Some(parent_id);
}
self.accessibility_parent = Some(parent_id);
self
}
}

View File

@@ -12,16 +12,16 @@ pub enum UiKind {
/// A [`crate::CentralPanel`].
CentralPanel,
/// A left [`crate::SidePanel`].
/// A left [`crate::Panel`].
LeftPanel,
/// A right [`crate::SidePanel`].
/// A right [`crate::Panel`].
RightPanel,
/// A top [`crate::TopBottomPanel`].
/// A top [`crate::Panel`].
TopPanel,
/// A bottom [`crate::TopBottomPanel`].
/// A bottom [`crate::Panel`].
BottomPanel,
/// A modal [`crate::Modal`].
@@ -209,7 +209,7 @@ pub struct UiStack {
pub layout_direction: Direction,
pub min_rect: Rect,
pub max_rect: Rect,
pub parent: Option<Arc<UiStack>>,
pub parent: Option<Arc<Self>>,
}
// these methods act on this specific node

View File

@@ -33,7 +33,9 @@
//! In short: immediate viewports are simpler to use, but can waste a lot of CPU time.
//!
//! ### Embedded viewports
//! These are not real, independent viewports, but is a fallback mode for when the integration does not support real viewports. In your callback is called with [`ViewportClass::Embedded`] it means you need to create a [`crate::Window`] to wrap your ui in, which will then be embedded in the parent viewport, unable to escape it.
//! These are not real, independent viewports, but is a fallback mode for when the integration does not support real viewports.
//! In your callback is called with [`ViewportClass::EmbeddedWindow`] it means the viewport is embedded inside of
//! a regular [`crate::Window`], trapped in the parent viewport.
//!
//!
//! ## Using the viewports
@@ -71,7 +73,7 @@ use std::sync::Arc;
use epaint::{Pos2, Vec2};
use crate::{Context, Id};
use crate::{Context, Id, Ui};
// ----------------------------------------------------------------------------
@@ -101,7 +103,10 @@ pub enum ViewportClass {
/// The fallback, when the egui integration doesn't support viewports,
/// or [`crate::Context::embed_viewports`] is set to `true`.
Embedded,
///
/// If you get this, it is because you are already wrapped in a [`crate::Window`]
/// inside of the parent viewport.
EmbeddedWindow,
}
// ----------------------------------------------------------------------------
@@ -258,7 +263,7 @@ impl ViewportIdPair {
}
/// The user-code that shows the ui in the viewport, used for deferred viewports.
pub type DeferredViewportUiCallback = dyn Fn(&Context) + Sync + Send;
pub type DeferredViewportUiCallback = dyn Fn(&mut Ui) + Sync + Send;
/// Render the given viewport, calling the given ui callback.
pub type ImmediateViewportRendererCallback = dyn for<'a> Fn(&Context, ImmediateViewport<'a>);
@@ -1189,7 +1194,7 @@ pub struct ViewportOutput {
/// What type of viewport are we?
///
/// This will never be [`ViewportClass::Embedded`],
/// This will never be [`ViewportClass::EmbeddedWindow`],
/// since those don't result in real viewports.
pub class: ViewportClass,
@@ -1246,5 +1251,5 @@ pub struct ImmediateViewport<'a> {
pub builder: ViewportBuilder,
/// The user-code that shows the GUI.
pub viewport_ui_cb: Box<dyn FnMut(&Context) + 'a>,
pub viewport_ui_cb: Box<dyn FnMut(&mut Ui) + 'a>,
}

View File

@@ -0,0 +1,203 @@
use emath::Vec2;
use epaint::{Color32, FontId, Shadow, Stroke, text::TextWrapMode};
use crate::{
Frame, Response, Style, TextStyle,
style::{WidgetVisuals, Widgets},
};
/// General text style
pub struct TextVisuals {
/// Font used
pub font_id: FontId,
/// Font color
pub color: Color32,
/// Text decoration
pub underline: Stroke,
pub strikethrough: Stroke,
}
/// General widget style
pub struct WidgetStyle {
pub frame: Frame,
pub text: TextVisuals,
pub stroke: Stroke,
}
pub struct ButtonStyle {
pub frame: Frame,
pub text_style: TextVisuals,
}
pub struct CheckboxStyle {
/// Frame around
pub frame: Frame,
/// Text next to it
pub text_style: TextVisuals,
/// Checkbox size
pub checkbox_size: f32,
/// Checkmark size
pub check_size: f32,
/// Frame of the checkbox itself
pub checkbox_frame: Frame,
/// Checkmark stroke
pub check_stroke: Stroke,
}
pub struct LabelStyle {
/// Frame around
pub frame: Frame,
/// Text style
pub text: TextVisuals,
/// Wrap mode used
pub wrap_mode: TextWrapMode,
}
pub struct SeparatorStyle {
/// How much space is allocated in the layout direction
pub spacing: f32,
/// How to paint it
pub stroke: Stroke,
}
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
pub enum WidgetState {
Noninteractive,
#[default]
Inactive,
Hovered,
Active,
}
impl Widgets {
pub fn state(&self, state: WidgetState) -> &WidgetVisuals {
match state {
WidgetState::Noninteractive => &self.noninteractive,
WidgetState::Inactive => &self.inactive,
WidgetState::Hovered => &self.hovered,
WidgetState::Active => &self.active,
}
}
}
impl Response {
pub fn widget_state(&self) -> WidgetState {
if !self.sense.interactive() {
WidgetState::Noninteractive
} else if self.is_pointer_button_down_on() || self.has_focus() || self.clicked() {
WidgetState::Active
} else if self.hovered() || self.highlighted() {
WidgetState::Hovered
} else {
WidgetState::Inactive
}
}
}
impl Style {
pub fn widget_style(&self, state: WidgetState) -> WidgetStyle {
let visuals = self.visuals.widgets.state(state);
let font_id = self.override_font_id.clone();
WidgetStyle {
frame: Frame {
fill: visuals.bg_fill,
stroke: visuals.bg_stroke,
corner_radius: visuals.corner_radius,
inner_margin: self.spacing.button_padding.into(),
..Default::default()
},
stroke: visuals.fg_stroke,
text: TextVisuals {
color: self
.visuals
.override_text_color
.unwrap_or_else(|| visuals.text_color()),
font_id: font_id.unwrap_or_else(|| TextStyle::Body.resolve(self)),
strikethrough: Stroke::NONE,
underline: Stroke::NONE,
},
}
}
pub fn button_style(&self, state: WidgetState, selected: bool) -> ButtonStyle {
let mut visuals = *self.visuals.widgets.state(state);
let mut ws = self.widget_style(state);
if selected {
visuals.weak_bg_fill = self.visuals.selection.bg_fill;
visuals.bg_fill = self.visuals.selection.bg_fill;
visuals.fg_stroke = self.visuals.selection.stroke;
ws.text.color = self.visuals.selection.stroke.color;
}
ButtonStyle {
frame: Frame {
fill: visuals.weak_bg_fill,
stroke: visuals.bg_stroke,
corner_radius: visuals.corner_radius,
outer_margin: (-Vec2::splat(visuals.expansion)).into(),
inner_margin: (self.spacing.button_padding + Vec2::splat(visuals.expansion)
- Vec2::splat(visuals.bg_stroke.width))
.into(),
..Default::default()
},
text_style: ws.text,
}
}
pub fn checkbox_style(&self, state: WidgetState) -> CheckboxStyle {
let visuals = self.visuals.widgets.state(state);
let ws = self.widget_style(state);
CheckboxStyle {
frame: Frame::new(),
checkbox_size: self.spacing.icon_width,
check_size: self.spacing.icon_width_inner,
checkbox_frame: Frame {
fill: visuals.bg_fill,
corner_radius: visuals.corner_radius,
stroke: visuals.bg_stroke,
// Use the inner_margin for the expansion
inner_margin: visuals.expansion.into(),
..Default::default()
},
text_style: ws.text,
check_stroke: ws.stroke,
}
}
pub fn label_style(&self, state: WidgetState) -> LabelStyle {
let ws = self.widget_style(state);
LabelStyle {
frame: Frame {
fill: ws.frame.fill,
inner_margin: 0.0.into(),
outer_margin: 0.0.into(),
stroke: Stroke::NONE,
shadow: Shadow::NONE,
corner_radius: 0.into(),
},
text: ws.text,
wrap_mode: TextWrapMode::Wrap,
}
}
pub fn separator_style(&self, _state: WidgetState) -> SeparatorStyle {
let visuals = self.visuals.noninteractive();
SeparatorStyle {
spacing: 6.0,
stroke: visuals.bg_stroke,
}
}
}

View File

@@ -1,7 +1,10 @@
use epaint::Margin;
use crate::{
Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Color32, CornerRadius, Frame,
Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2,
Widget, WidgetInfo, WidgetText, WidgetType,
widget_style::{ButtonStyle, WidgetState},
};
/// Clickable button with text.
@@ -272,6 +275,7 @@ impl<'a> Button<'a> {
limit_image_size,
} = self;
// Min size height always equal or greater than interact size if not small
if !small {
min_size.y = min_size.y.at_least(ui.spacing().interact_size.y);
}
@@ -290,51 +294,58 @@ impl<'a> Button<'a> {
let has_frame_margin = frame.unwrap_or_else(|| ui.visuals().button_frame);
let id = ui.next_auto_id();
let response: Option<Response> = ui.ctx().read_response(id);
let state = response.map(|r| r.widget_state()).unwrap_or_default();
let ButtonStyle { frame, text_style } = ui.style().button_style(state, selected);
let mut button_padding = if has_frame_margin {
ui.spacing().button_padding
frame.inner_margin
} else {
Vec2::ZERO
Margin::ZERO
};
if small {
button_padding.y = 0.0;
button_padding.bottom = 0;
button_padding.top = 0;
}
let mut prepared = layout
.frame(Frame::new().inner_margin(button_padding))
.min_size(min_size)
.allocate(ui);
// Override global style by local style
let mut frame = frame;
if let Some(fill) = fill {
frame = frame.fill(fill);
}
if let Some(corner_radius) = corner_radius {
frame = frame.corner_radius(corner_radius);
}
if let Some(stroke) = stroke {
frame = frame.stroke(stroke);
}
frame = frame.inner_margin(button_padding);
// Apply the style font and color as fallback
layout = layout
.fallback_font(text_style.font_id.clone())
.fallback_text_color(text_style.color);
// Retrocompatibility with button settings
layout = if has_frame_margin && (state != WidgetState::Inactive || frame_when_inactive) {
layout.frame(frame)
} else {
layout.frame(Frame::new().inner_margin(frame.inner_margin))
};
let mut prepared = layout.min_size(min_size).allocate(ui);
// Get AtomLayoutResponse, empty if not visible
let response = if ui.is_rect_visible(prepared.response.rect) {
let visuals = ui.style().interact_selectable(&prepared.response, selected);
let visible_frame = if frame_when_inactive {
has_frame_margin
} else {
has_frame_margin
&& (prepared.response.hovered()
|| prepared.response.is_pointer_button_down_on()
|| prepared.response.has_focus())
};
if image_tint_follows_text_color {
prepared.map_images(|image| image.tint(visuals.text_color()));
prepared.map_images(|image| image.tint(text_style.color));
}
prepared.fallback_text_color = visuals.text_color();
if visible_frame {
let stroke = stroke.unwrap_or(visuals.bg_stroke);
let fill = fill.unwrap_or(visuals.weak_bg_fill);
prepared.frame = prepared
.frame
.inner_margin(
button_padding + Vec2::splat(visuals.expansion) - Vec2::splat(stroke.width),
)
.outer_margin(-Vec2::splat(visuals.expansion))
.fill(fill)
.stroke(stroke)
.corner_radius(corner_radius.unwrap_or(visuals.corner_radius));
}
prepared.fallback_text_color = text_style.color;
prepared.paint(ui)
} else {

View File

@@ -1,6 +1,8 @@
use emath::Rect;
use crate::{
Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Shape, Ui, Vec2, Widget,
WidgetInfo, WidgetType, epaint, pos2,
WidgetInfo, WidgetType, epaint, pos2, widget_style::CheckboxStyle,
};
// TODO(emilk): allow checkbox without a text label
@@ -55,14 +57,25 @@ impl Widget for Checkbox<'_> {
indeterminate,
} = self;
let spacing = &ui.spacing();
let icon_width = spacing.icon_width;
// Get the widget style by reading the response from the previous pass
let id = ui.next_auto_id();
let response: Option<Response> = ui.ctx().read_response(id);
let state = response.map(|r| r.widget_state()).unwrap_or_default();
let mut min_size = Vec2::splat(spacing.interact_size.y);
min_size.y = min_size.y.at_least(icon_width);
let CheckboxStyle {
check_size,
checkbox_frame,
checkbox_size,
frame,
check_stroke,
text_style,
} = ui.style().checkbox_style(state);
let mut min_size = Vec2::splat(ui.spacing().interact_size.y);
min_size.y = min_size.y.at_least(checkbox_size);
// In order to center the checkbox based on min_size we set the icon height to at least min_size.y
let mut icon_size = Vec2::splat(icon_width);
let mut icon_size = Vec2::splat(checkbox_size);
icon_size.y = icon_size.y.at_least(min_size.y);
let rect_id = Id::new("egui::checkbox");
atoms.push_left(Atom::custom(rect_id, icon_size));
@@ -72,6 +85,7 @@ impl Widget for Checkbox<'_> {
let mut prepared = AtomLayout::new(atoms)
.sense(Sense::click())
.min_size(min_size)
.frame(frame)
.allocate(ui);
if prepared.response.clicked() {
@@ -96,18 +110,21 @@ impl Widget for Checkbox<'_> {
});
if ui.is_rect_visible(prepared.response.rect) {
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
let visuals = *ui.style().interact(&prepared.response);
prepared.fallback_text_color = visuals.text_color();
prepared.fallback_text_color = text_style.color;
let response = prepared.paint(ui);
if let Some(rect) = response.rect(rect_id) {
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
let big_icon_rect = Rect::from_center_size(
pos2(rect.left() + checkbox_size / 2.0, rect.center().y),
Vec2::splat(checkbox_size),
);
let small_icon_rect =
Rect::from_center_size(big_icon_rect.center(), Vec2::splat(check_size));
ui.painter().add(epaint::RectShape::new(
big_icon_rect.expand(visuals.expansion),
visuals.corner_radius,
visuals.bg_fill,
visuals.bg_stroke,
big_icon_rect.expand(checkbox_frame.inner_margin.left.into()),
checkbox_frame.corner_radius,
checkbox_frame.fill,
checkbox_frame.stroke,
epaint::StrokeKind::Inside,
));
@@ -116,7 +133,7 @@ impl Widget for Checkbox<'_> {
ui.painter().add(Shape::hline(
small_icon_rect.x_range(),
small_icon_rect.center().y,
visuals.fg_stroke,
check_stroke,
));
} else if *checked {
// Check mark:
@@ -126,7 +143,7 @@ impl Widget for Checkbox<'_> {
pos2(small_icon_rect.center().x, small_icon_rect.bottom()),
pos2(small_icon_rect.right(), small_icon_rect.top()),
],
visuals.fg_stroke,
check_stroke,
));
}
}

View File

@@ -378,7 +378,7 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsvag: &mut HsvaGamma, alpha: Alpha) {
}
fn input_type_button_ui(ui: &mut Ui) {
let mut input_type = ui.ctx().style().visuals.numeric_color_space;
let mut input_type = ui.global_style().visuals.numeric_color_space;
if input_type.toggle_button_ui(ui).changed() {
ui.ctx().all_styles_mut(|s| {
s.visuals.numeric_color_space = input_type;
@@ -402,9 +402,9 @@ fn srgba_edit_ui(ui: &mut Ui, [r, g, b, a]: &mut [u8; 4], alpha: Alpha) -> bool
.clicked()
{
if alpha == Alpha::Opaque {
ui.ctx().copy_text(format!("{r}, {g}, {b}"));
ui.copy_text(format!("{r}, {g}, {b}"));
} else {
ui.ctx().copy_text(format!("{r}, {g}, {b}, {a}"));
ui.copy_text(format!("{r}, {g}, {b}, {a}"));
}
}
edited |= DragValue::new(r).speed(0.5).prefix("R ").ui(ui).changed();
@@ -443,10 +443,9 @@ fn rgba_edit_ui(ui: &mut Ui, [r, g, b, a]: &mut [f32; 4], alpha: Alpha) -> bool
.clicked()
{
if alpha == Alpha::Opaque {
ui.ctx().copy_text(format!("{r:.03}, {g:.03}, {b:.03}"));
ui.copy_text(format!("{r:.03}, {g:.03}, {b:.03}"));
} else {
ui.ctx()
.copy_text(format!("{r:.03}, {g:.03}, {b:.03}, {a:.03}"));
ui.copy_text(format!("{r:.03}, {g:.03}, {b:.03}, {a:.03}"));
}
}

View File

@@ -489,27 +489,21 @@ impl Widget for DragValue<'_> {
- input.count_and_consume_key(Modifiers::NONE, Key::ArrowDown) as f64;
}
#[cfg(feature = "accesskit")]
{
use accesskit::Action;
change += input.num_accesskit_action_requests(id, Action::Increment) as f64
- input.num_accesskit_action_requests(id, Action::Decrement) as f64;
}
use accesskit::Action;
change += input.num_accesskit_action_requests(id, Action::Increment) as f64
- input.num_accesskit_action_requests(id, Action::Decrement) as f64;
change
});
#[cfg(feature = "accesskit")]
{
ui.input(|input| {
use accesskit::{Action, ActionData};
ui.input(|input| {
for request in input.accesskit_action_requests(id, Action::SetValue) {
if let Some(ActionData::NumericValue(new_value)) = request.data {
value = new_value;
}
for request in input.accesskit_action_requests(id, Action::SetValue) {
if let Some(ActionData::NumericValue(new_value)) = request.data {
value = new_value;
}
});
}
}
});
if clamp_existing_to_range {
value = clamp_value_to_range(value, range.clone());
@@ -630,7 +624,7 @@ impl Widget for DragValue<'_> {
ui.memory_mut(|mem| mem.request_focus(id));
select_all_text(ui, id, response.id, &value_text);
} else if response.dragged() {
ui.ctx().set_cursor_icon(cursor_icon);
ui.set_cursor_icon(cursor_icon);
let mdelta = response.drag_delta();
let delta_points = mdelta.x - mdelta.y; // Increase to the right and up
@@ -669,7 +663,6 @@ impl Widget for DragValue<'_> {
response.widget_info(|| WidgetInfo::drag_value(ui.is_enabled(), value));
#[cfg(feature = "accesskit")]
ui.ctx().accesskit_node_builder(response.id, |builder| {
use accesskit::Action;
// If either end of the range is unbounded, it's better

View File

@@ -65,7 +65,7 @@ impl Widget for Link {
}
if response.hovered() {
ui.ctx().set_cursor_icon(CursorIcon::PointingHand);
ui.set_cursor_icon(CursorIcon::PointingHand);
}
}
@@ -130,12 +130,12 @@ impl Widget for Hyperlink {
let response = ui.add(Link::new(text));
if response.clicked_with_open_in_background() {
ui.ctx().open_url(crate::OpenUrl {
ui.open_url(crate::OpenUrl {
url: url.clone(),
new_tab: true,
});
} else if response.clicked() {
ui.ctx().open_url(crate::OpenUrl {
ui.open_url(crate::OpenUrl {
url: url.clone(),
new_tab,
});

View File

@@ -681,7 +681,7 @@ pub fn paint_texture_load_result(
}
Ok(TexturePoll::Pending { .. }) => {
let show_loading_spinner =
show_loading_spinner.unwrap_or(ui.visuals().image_loading_spinners);
show_loading_spinner.unwrap_or_else(|| ui.visuals().image_loading_spinners);
if show_loading_spinner {
Spinner::new().paint_at(ui, rect);
}

View File

@@ -248,7 +248,9 @@ impl Label {
layout_job.halign = Align::LEFT;
layout_job.justify = false;
} else {
layout_job.halign = self.halign.unwrap_or(ui.layout().horizontal_placement());
layout_job.halign = self
.halign
.unwrap_or_else(|| ui.layout().horizontal_placement());
layout_job.justify = ui.layout().horizontal_justify();
}

View File

@@ -143,7 +143,7 @@ pub fn global_theme_preference_switch(ui: &mut Ui) {
/// Show larger buttons for switching between light and dark mode (globally).
pub fn global_theme_preference_buttons(ui: &mut Ui) {
let mut theme_preference = ui.ctx().options(|opt| opt.theme_preference);
let mut theme_preference = ui.options(|opt| opt.theme_preference);
theme_preference.radio_buttons(ui);
ui.ctx().set_theme(theme_preference);
}

View File

@@ -118,7 +118,7 @@ impl Widget for ProgressBar {
let desired_width =
desired_width.unwrap_or_else(|| ui.available_size_before_wrap().x.at_least(96.0));
let height = desired_height.unwrap_or(ui.spacing().interact_size.y);
let height = desired_height.unwrap_or_else(|| ui.spacing().interact_size.y);
let (outer_rect, response) =
ui.allocate_exact_size(vec2(desired_width, height), Sense::hover());
@@ -135,7 +135,7 @@ impl Widget for ProgressBar {
if ui.is_rect_visible(response.rect) {
if animate {
ui.ctx().request_repaint();
ui.request_repaint();
}
let visuals = ui.style().visuals.clone();

View File

@@ -1,4 +1,4 @@
use crate::{Response, Sense, Ui, Vec2, Widget, vec2};
use crate::{Response, Sense, Ui, Vec2, Widget, vec2, widget_style::SeparatorStyle};
/// A visual separator. A horizontal or vertical line (depending on [`crate::Layout`]).
///
@@ -13,7 +13,7 @@ use crate::{Response, Sense, Ui, Vec2, Widget, vec2};
/// ```
#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
pub struct Separator {
spacing: f32,
spacing: Option<f32>,
grow: f32,
is_horizontal_line: Option<bool>,
}
@@ -21,7 +21,7 @@ pub struct Separator {
impl Default for Separator {
fn default() -> Self {
Self {
spacing: 6.0,
spacing: None,
grow: 0.0,
is_horizontal_line: None,
}
@@ -38,7 +38,7 @@ impl Separator {
/// this is the width of the separator widget.
#[inline]
pub fn spacing(mut self, spacing: f32) -> Self {
self.spacing = spacing;
self.spacing = Some(spacing);
self
}
@@ -93,6 +93,18 @@ impl Widget for Separator {
is_horizontal_line,
} = self;
// Get the widget style by reading the response from the previous pass
let id = ui.next_auto_id();
let response: Option<Response> = ui.ctx().read_response(id);
let state = response.map(|r| r.widget_state()).unwrap_or_default();
let SeparatorStyle {
spacing: spacing_style,
stroke,
} = ui.style().separator_style(state);
// override the spacing if not set
let spacing = spacing.unwrap_or(spacing_style);
let is_horizontal_line = is_horizontal_line
.unwrap_or_else(|| ui.is_grid() || !ui.layout().main_dir().is_horizontal());
@@ -111,7 +123,6 @@ impl Widget for Separator {
let (rect, response) = ui.allocate_at_least(size, Sense::hover());
if ui.is_rect_visible(response.rect) {
let stroke = ui.visuals().widgets.noninteractive.bg_stroke;
let painter = ui.painter();
if is_horizontal_line {
painter.hline(

View File

@@ -687,7 +687,7 @@ impl Slider<'_> {
let mut increment = 0usize;
if response.has_focus() {
ui.ctx().memory_mut(|m| {
ui.memory_mut(|m| {
m.set_focus_lock_filter(
response.id,
EventFilter {
@@ -716,14 +716,11 @@ impl Slider<'_> {
});
}
#[cfg(feature = "accesskit")]
{
ui.input(|input| {
use accesskit::Action;
ui.input(|input| {
decrement += input.num_accesskit_action_requests(response.id, Action::Decrement);
increment += input.num_accesskit_action_requests(response.id, Action::Increment);
});
}
decrement += input.num_accesskit_action_requests(response.id, Action::Decrement);
increment += input.num_accesskit_action_requests(response.id, Action::Increment);
});
let kb_step = increment as f32 - decrement as f32;
@@ -759,17 +756,14 @@ impl Slider<'_> {
self.set_value(new_value);
}
#[cfg(feature = "accesskit")]
{
ui.input(|input| {
use accesskit::{Action, ActionData};
ui.input(|input| {
for request in input.accesskit_action_requests(response.id, Action::SetValue) {
if let Some(ActionData::NumericValue(new_value)) = request.data {
self.set_value(new_value);
}
for request in input.accesskit_action_requests(response.id, Action::SetValue) {
if let Some(ActionData::NumericValue(new_value)) = request.data {
self.set_value(new_value);
}
});
}
}
});
// Paint it:
if ui.is_rect_visible(response.rect) {
@@ -978,7 +972,6 @@ impl Slider<'_> {
}
response.widget_info(|| WidgetInfo::slider(ui.is_enabled(), value, self.text.text()));
#[cfg(feature = "accesskit")]
ui.ctx().accesskit_node_builder(response.id, |builder| {
use accesskit::Action;
builder.set_min_numeric_value(*self.range.start());

View File

@@ -37,7 +37,7 @@ impl Spinner {
/// Paint the spinner in the given rectangle.
pub fn paint_at(&self, ui: &Ui, rect: Rect) {
if ui.is_rect_visible(rect) {
ui.ctx().request_repaint(); // because it is animated
ui.request_repaint(); // because it is animated
let color = self
.color

View File

@@ -256,7 +256,7 @@ impl<'t> TextEdit<'t> {
/// so it is strongly suggested that you cache the results of any syntax highlighter
/// so as not to waste CPU highlighting the same string every frame.
///
/// The arguments is the enclosing [`Ui`] (so you can access e.g. [`Ui::fonts`]),
/// The arguments is the enclosing [`Ui`] (so you can access e.g. [`Context::fonts`]),
/// the text and the wrap width.
///
/// ```
@@ -496,7 +496,7 @@ impl TextEdit<'_> {
} = self;
let text_color = text_color
.or(ui.visuals().override_text_color)
.or_else(|| ui.visuals().override_text_color)
// .unwrap_or_else(|| ui.style().interact(&response).text_color()); // too bright
.unwrap_or_else(|| ui.visuals().widgets.inactive.text_color());
@@ -605,12 +605,12 @@ impl TextEdit<'_> {
if did_interact || response.clicked() {
ui.memory_mut(|mem| mem.request_focus(response.id));
state.last_interaction_time = ui.ctx().input(|i| i.time);
state.last_interaction_time = ui.input(|i| i.time);
}
}
if interactive && response.hovered() {
ui.ctx().set_cursor_icon(CursorIcon::Text);
ui.set_cursor_icon(CursorIcon::Text);
}
let mut cursor_range = None;
@@ -691,7 +691,7 @@ impl TextEdit<'_> {
if ui.is_rect_visible(rect) {
if text.as_str().is_empty() && !hint_text.is_empty() {
let hint_text_color = ui.visuals().weak_text_color();
let hint_text_font_id = hint_text_font.unwrap_or(font_id.into());
let hint_text_font_id = hint_text_font.unwrap_or_else(|| font_id.into());
let galley = if multiline {
hint_text.into_galley(
ui,
@@ -721,39 +721,42 @@ impl TextEdit<'_> {
paint_text_selection(&mut galley, ui.visuals(), &cursor_range, None);
}
if !clip_text {
// Allocate additional space if edits were made this frame that changed the size. This is important so that,
// if there's a ScrollArea, it can properly scroll to the cursor.
// Condition `!clip_text` is important to avoid breaking layout for `TextEdit::singleline` (PR #5640)
let extra_size = galley.size() - rect.size();
if extra_size.x > 0.0 || extra_size.y > 0.0 {
match ui.layout().main_dir() {
crate::Direction::LeftToRight | crate::Direction::TopDown => {
ui.allocate_rect(
Rect::from_min_size(outer_rect.max, extra_size),
Sense::hover(),
);
}
crate::Direction::RightToLeft => {
ui.allocate_rect(
Rect::from_min_size(
emath::pos2(outer_rect.min.x - extra_size.x, outer_rect.max.y),
extra_size,
),
Sense::hover(),
);
}
crate::Direction::BottomUp => {
ui.allocate_rect(
Rect::from_min_size(
emath::pos2(outer_rect.min.x, outer_rect.max.y - extra_size.y),
extra_size,
),
Sense::hover(),
);
}
// Allocate additional space if edits were made this frame that changed the size. This is important so that,
// if there's a ScrollArea, it can properly scroll to the cursor.
// Condition `!clip_text` is important to avoid breaking layout for `TextEdit::singleline` (PR #5640)
if !clip_text
&& let extra_size = galley.size() - rect.size()
&& (extra_size.x > 0.0 || extra_size.y > 0.0)
{
match ui.layout().main_dir() {
crate::Direction::LeftToRight | crate::Direction::TopDown => {
ui.allocate_rect(
Rect::from_min_size(outer_rect.max, extra_size),
Sense::hover(),
);
}
crate::Direction::RightToLeft => {
ui.allocate_rect(
Rect::from_min_size(
emath::pos2(outer_rect.min.x - extra_size.x, outer_rect.max.y),
extra_size,
),
Sense::hover(),
);
}
crate::Direction::BottomUp => {
ui.allocate_rect(
Rect::from_min_size(
emath::pos2(outer_rect.min.x, outer_rect.max.y - extra_size.y),
extra_size,
),
Sense::hover(),
);
}
}
} else {
// Avoid an ID shift during this pass if the textedit grow
ui.skip_ahead_auto_ids(1);
}
painter.galley(galley_pos, galley.clone(), text_color);
@@ -768,7 +771,7 @@ impl TextEdit<'_> {
}
if text.is_mutable() && interactive {
let now = ui.ctx().input(|i| i.time);
let now = ui.input(|i| i.time);
if response.changed() || selection_changed {
state.last_interaction_time = now;
}
@@ -777,7 +780,7 @@ impl TextEdit<'_> {
// This is for two reasons:
// * Don't give the impression that the user can type into a window without focus
// * Don't repaint the ui because of a blinking cursor in an app that is not in focus
let viewport_has_focus = ui.ctx().input(|i| i.focused);
let viewport_has_focus = ui.input(|i| i.focused);
if viewport_has_focus {
text_selection::visuals::paint_text_cursor(
ui,
@@ -844,7 +847,6 @@ impl TextEdit<'_> {
});
}
#[cfg(feature = "accesskit")]
{
let role = if password {
accesskit::Role::PasswordInput
@@ -923,7 +925,7 @@ fn events(
let copy_if_not_password = |ui: &Ui, text: String| {
if !password {
ui.ctx().copy_text(text);
ui.copy_text(text);
}
};

View File

@@ -23,7 +23,7 @@ crate-type = ["cdylib", "rlib"]
[features]
default = ["glow", "persistence"]
default = ["wgpu", "persistence"]
# image_viewer adds about 0.9 MB of WASM
web_app = ["http", "persistence"]

View File

@@ -1,14 +1,15 @@
use accesskit::{Action, ActionRequest, NodeId};
use accesskit_consumer::{FilterResult, Node, Tree, TreeChangeHandler};
use eframe::epaint::text::TextWrapMode;
use egui::collapsing_header::CollapsingState;
use egui::{
Button, Color32, Context, Event, Frame, FullOutput, Id, Key, KeyboardShortcut, Label,
Modifiers, RawInput, RichText, ScrollArea, SidePanel, TopBottomPanel, Ui,
};
use std::mem;
/// This [`egui::Plugin`] adds an inspector Panel.
use accesskit::{Action, ActionRequest, NodeId};
use accesskit_consumer::{FilterResult, Node, Tree, TreeChangeHandler};
use eframe::epaint::text::TextWrapMode;
use egui::{
Button, Color32, Event, Frame, FullOutput, Id, Key, KeyboardShortcut, Label, Modifiers, Panel,
RawInput, RichText, ScrollArea, Ui, collapsing_header::CollapsingState,
};
/// This [`egui::Plugin`] adds an inspector panel.
///
/// It can be opened with the `(Cmd/Ctrl)+Alt+I`. It shows the current AccessKit tree and details
/// for the selected node.
@@ -70,8 +71,8 @@ impl egui::Plugin for AccessibilityInspectorPlugin {
}
}
fn on_begin_pass(&mut self, ctx: &Context) {
if ctx.input_mut(|i| {
fn on_begin_pass(&mut self, ui: &mut Ui) {
if ui.input_mut(|i| {
i.consume_shortcut(&KeyboardShortcut::new(
Modifiers::COMMAND | Modifiers::ALT,
Key::I,
@@ -84,12 +85,12 @@ impl egui::Plugin for AccessibilityInspectorPlugin {
return;
}
ctx.enable_accesskit();
ui.enable_accesskit();
SidePanel::right(Self::id()).show(ctx, |ui| {
Panel::right(Self::id()).show_inside(ui, |ui| {
ui.heading("🔎 AccessKit Inspector");
if let Some(selected_node) = self.selected_node {
TopBottomPanel::bottom(Self::id().with("details_panel"))
Panel::bottom(Self::id().with("details_panel"))
.frame(Frame::new())
.show_separator_line(false)
.show_inside(ui, |ui| {
@@ -121,7 +122,7 @@ impl AccessibilityInspectorPlugin {
let node_response = ui.ctx().read_response(selected_node);
if let Some(widget_response) = node_response {
ui.ctx().debug_painter().debug_rect(
ui.debug_painter().debug_rect(
widget_response.rect,
ui.style_mut().visuals.selection.bg_fill,
"",
@@ -198,8 +199,8 @@ impl AccessibilityInspectorPlugin {
}
let label = node
.label()
.or(node.value())
.unwrap_or(node.id().0.to_string());
.or_else(|| node.value())
.unwrap_or_else(|| node.id().0.to_string());
let label = format!("({:?}) {}", node.role(), label);
// Safety: This is safe since the `accesskit::NodeId` was created from an `egui::Id`.
@@ -232,8 +233,7 @@ impl AccessibilityInspectorPlugin {
let widget_response = ui.ctx().read_response(egui_node_id);
if let Some(widget_response) = widget_response {
ui.ctx()
.debug_painter()
ui.debug_painter()
.debug_rect(widget_response.rect, Color32::RED, "");
}
}

View File

@@ -22,26 +22,27 @@ impl Custom3d {
}
}
impl eframe::App for Custom3d {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::both()
.auto_shrink(false)
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("The triangle is being painted using ");
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
ui.label(" (OpenGL).");
});
ui.label("It's not a very impressive demo, but it shows you can embed 3D inside of egui.");
egui::Frame::canvas(ui.style()).show(ui, |ui| {
self.custom_painting(ui);
});
ui.label("Drag to rotate!");
ui.add(egui_demo_lib::egui_github_link_file!());
impl crate::DemoApp for Custom3d {
fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
// TODO(emilk): Use `ScrollArea::inner_margin`
egui::CentralPanel::default().show_inside(ui, |ui| {
egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("The triangle is being painted using ");
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
ui.label(" (OpenGL).");
});
ui.label(
"It's not a very impressive demo, but it shows you can embed 3D inside of egui.",
);
egui::Frame::canvas(ui.style()).show(ui, |ui| {
self.custom_painting(ui);
});
ui.label("Drag to rotate!");
ui.add(egui_demo_lib::egui_github_link_file!());
});
});
}

View File

@@ -98,26 +98,27 @@ impl Custom3d {
}
}
impl eframe::App for Custom3d {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::both()
.auto_shrink(false)
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("The triangle is being painted using ");
ui.hyperlink_to("WGPU", "https://wgpu.rs");
ui.label(" (Portable Rust graphics API awesomeness)");
});
ui.label("It's not a very impressive demo, but it shows you can embed 3D inside of egui.");
egui::Frame::canvas(ui.style()).show(ui, |ui| {
self.custom_painting(ui);
});
ui.label("Drag to rotate!");
ui.add(egui_demo_lib::egui_github_link_file!());
impl crate::DemoApp for Custom3d {
fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
// TODO(emilk): Use `ScrollArea::inner_margin`
egui::CentralPanel::default().show_inside(ui, |ui| {
egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("The triangle is being painted using ");
ui.hyperlink_to("WGPU", "https://wgpu.rs");
ui.label(" (Portable Rust graphics API awesomeness)");
});
ui.label(
"It's not a very impressive demo, but it shows you can embed 3D inside of egui.",
);
egui::Frame::canvas(ui.style()).show(ui, |ui| {
self.custom_painting(ui);
});
ui.label("Drag to rotate!");
ui.add(egui_demo_lib::egui_github_link_file!());
});
});
}
}

View File

@@ -41,7 +41,7 @@ impl FractalClock {
pub fn ui(&mut self, ui: &mut Ui, seconds_since_midnight: Option<f64>) {
if !self.paused {
self.time = seconds_since_midnight.unwrap_or_else(|| ui.input(|i| i.time));
ui.ctx().request_repaint();
ui.request_repaint();
}
let painter = Painter::new(

View File

@@ -59,16 +59,16 @@ impl Default for HttpApp {
}
}
impl eframe::App for HttpApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| {
impl crate::DemoApp for HttpApp {
fn demo_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
egui::Panel::bottom("http_bottom").show_inside(ui, |ui| {
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {
ui.add(egui_demo_lib::egui_github_link_file!())
})
});
egui::CentralPanel::default().show(ctx, |ui| {
egui::CentralPanel::default().show_inside(ui, |ui| {
let prev_url = self.url.clone();
let trigger_fetch = ui_url(ui, frame, &mut self.url);
@@ -80,7 +80,7 @@ impl eframe::App for HttpApp {
});
if trigger_fetch {
let ctx = ctx.clone();
let ctx = ui.ctx().clone();
let (sender, promise) = Promise::new();
let request = ehttp::Request::get(&self.url);
ehttp::fetch(request, move |response| {
@@ -195,7 +195,7 @@ fn ui_resource(ui: &mut egui::Ui, resource: &Resource) {
if let Some(text) = &text {
let tooltip = "Click to copy the response body";
if ui.button("📋").on_hover_text(tooltip).clicked() {
ui.ctx().copy_text(text.clone());
ui.copy_text(text.clone());
}
ui.separator();
}
@@ -222,10 +222,10 @@ fn syntax_highlighting(
) -> Option<ColoredText> {
let extension_and_rest: Vec<&str> = response.url.rsplitn(2, '.').collect();
let extension = extension_and_rest.first()?;
let theme = egui_extras::syntax_highlighting::CodeTheme::from_style(&ctx.style());
let theme = egui_extras::syntax_highlighting::CodeTheme::from_style(&ctx.global_style());
Some(ColoredText(egui_extras::syntax_highlighting::highlight(
ctx,
&ctx.style(),
&ctx.global_style(),
&theme,
text,
extension,

View File

@@ -2,8 +2,6 @@ use egui::ImageFit;
use egui::Slider;
use egui::Vec2;
use egui::emath::Rot2;
use egui::panel::Side;
use egui::panel::TopBottomSide;
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct ImageViewer {
@@ -50,15 +48,15 @@ impl Default for ImageViewer {
}
}
impl eframe::App for ImageViewer {
fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) {
egui::TopBottomPanel::new(TopBottomSide::Top, "url bar").show(ctx, |ui| {
impl crate::DemoApp for ImageViewer {
fn demo_ui(&mut self, ui: &mut egui::Ui, _: &mut eframe::Frame) {
egui::Panel::top("url bar").show_inside(ui, |ui| {
ui.horizontal_centered(|ui| {
let label = ui.label("URI:");
ui.text_edit_singleline(&mut self.uri_edit_text)
.labelled_by(label.id);
if ui.small_button("").clicked() {
ctx.forget_image(&self.current_uri);
ui.ctx().forget_image(&self.current_uri);
self.uri_edit_text = self.uri_edit_text.trim().to_owned();
self.current_uri = self.uri_edit_text.clone();
}
@@ -73,7 +71,7 @@ impl eframe::App for ImageViewer {
});
});
egui::SidePanel::new(Side::Left, "controls").show(ctx, |ui| {
egui::Panel::left("controls").show_inside(ui, |ui| {
// uv
ui.label("UV");
ui.add(Slider::new(&mut self.image_options.uv.min.x, 0.0..=1.0).text("min x"));
@@ -199,7 +197,7 @@ impl eframe::App for ImageViewer {
}
});
egui::CentralPanel::default().show(ctx, |ui| {
egui::CentralPanel::default().show_inside(ui, |ui| {
egui::ScrollArea::both().show(ui, |ui| {
let mut image = egui::Image::from_uri(&self.current_uri);
image = image.uv(self.image_options.uv);

View File

@@ -94,7 +94,7 @@ impl BackendPanel {
self.egui_windows.checkboxes(ui);
#[cfg(debug_assertions)]
if ui.ctx().style().debug.debug_on_hover_with_all_modifiers {
if ui.global_style().debug.debug_on_hover_with_all_modifiers {
ui.separator();
ui.label("Press down all modifiers and hover a widget to see a callstack for it");
}
@@ -102,9 +102,9 @@ impl BackendPanel {
#[cfg(target_arch = "wasm32")]
{
ui.separator();
let mut screen_reader = ui.ctx().options(|o| o.screen_reader);
let mut screen_reader = ui.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");
ui.ctx().options_mut(|o| o.screen_reader = screen_reader);
ui.options_mut(|o| o.screen_reader = screen_reader);
}
if cfg!(debug_assertions) && cfg!(target_arch = "wasm32") {
@@ -119,7 +119,7 @@ impl BackendPanel {
if !cfg!(target_arch = "wasm32") {
ui.separator();
if ui.button("Quit").clicked() {
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
ui.send_viewport_cmd(egui::ViewportCommand::Close);
}
}
}
@@ -168,7 +168,7 @@ impl BackendPanel {
ui.horizontal(|ui| {
if ui.button("Request discard").clicked() {
ui.ctx().request_discard("Manual button click");
ui.request_discard("Manual button click");
if !ui.ctx().will_discard() {
ui.label("Discard denied!");
@@ -305,8 +305,7 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
.on_hover_text("Fullscreen the window")
.changed()
{
ui.ctx()
.send_viewport_cmd(egui::ViewportCommand::Fullscreen(fullscreen));
ui.send_viewport_cmd(egui::ViewportCommand::Fullscreen(fullscreen));
}
}
@@ -333,10 +332,8 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
});
if let Some(size) = size {
ui.ctx()
.send_viewport_cmd(egui::ViewportCommand::InnerSize(size));
ui.ctx()
.send_viewport_cmd(egui::ViewportCommand::Fullscreen(false));
ui.send_viewport_cmd(egui::ViewportCommand::InnerSize(size));
ui.send_viewport_cmd(egui::ViewportCommand::Fullscreen(false));
ui.close();
}
});

View File

@@ -15,6 +15,14 @@ pub(crate) fn seconds_since_midnight() -> f64 {
time.num_seconds_from_midnight() as f64 + 1e-9 * (time.nanosecond() as f64)
}
/// Trait that wraps different parts of the demo app.
pub trait DemoApp {
fn demo_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame);
#[cfg(feature = "glow")]
fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) {}
}
// ----------------------------------------------------------------------------
#[cfg(feature = "accessibility_inspector")]
pub mod accessibility_inspector;

View File

@@ -1,4 +1,4 @@
use egui_demo_lib::is_mobile;
use egui_demo_lib::{DemoWindows, is_mobile};
#[cfg(feature = "glow")]
use eframe::glow;
@@ -6,29 +6,25 @@ use eframe::glow;
#[cfg(target_arch = "wasm32")]
use core::any::Any;
use crate::DemoApp;
#[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
struct EasyMarkApp {
editor: egui_demo_lib::easy_mark::EasyMarkEditor,
}
impl eframe::App for EasyMarkApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
self.editor.panels(ctx);
impl DemoApp for EasyMarkApp {
fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
self.editor.panels(ui);
}
}
// ----------------------------------------------------------------------------
#[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct DemoApp {
demo_windows: egui_demo_lib::DemoWindows,
}
impl eframe::App for DemoApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
self.demo_windows.ui(ctx);
impl DemoApp for DemoWindows {
fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
self.ui(ui);
}
}
@@ -41,17 +37,17 @@ pub struct FractalClockApp {
pub mock_time: Option<f64>,
}
impl eframe::App for FractalClockApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default()
.frame(
egui::Frame::dark_canvas(&ctx.style())
.stroke(egui::Stroke::NONE)
.corner_radius(0),
)
.show(ctx, |ui| {
self.fractal_clock
.ui(ui, self.mock_time.or(Some(crate::seconds_since_midnight())));
impl DemoApp for FractalClockApp {
fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
egui::Frame::dark_canvas(ui.style())
.stroke(egui::Stroke::NONE)
.corner_radius(0)
.show(ui, |ui| {
self.fractal_clock.ui(
ui,
self.mock_time
.or_else(|| Some(crate::seconds_since_midnight())),
);
});
}
}
@@ -64,13 +60,13 @@ pub struct ColorTestApp {
color_test: egui_demo_lib::ColorTest,
}
impl eframe::App for ColorTestApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
impl DemoApp for ColorTestApp {
fn demo_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show_inside(ui, |ui| {
if frame.is_web() {
ui.label(
"NOTE: Some old browsers stuck on WebGL1 without sRGB support will not pass the color test.",
);
"NOTE: Some old browsers stuck on WebGL1 without sRGB support will not pass the color test.",
);
ui.separator();
}
egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
@@ -155,7 +151,7 @@ enum Command {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct State {
demo: DemoApp,
demo: DemoWindows,
easy_mark_editor: EasyMarkApp,
#[cfg(feature = "http")]
http: crate::apps::HttpApp,
@@ -209,34 +205,34 @@ impl WrapApp {
pub fn apps_iter_mut(
&mut self,
) -> impl Iterator<Item = (&'static str, Anchor, &mut dyn eframe::App)> {
) -> impl Iterator<Item = (&'static str, Anchor, &mut dyn DemoApp)> {
let mut vec = vec![
(
"✨ Demos",
Anchor::Demo,
&mut self.state.demo as &mut dyn eframe::App,
&mut self.state.demo as &mut dyn DemoApp,
),
(
"🖹 EasyMark editor",
Anchor::EasyMarkEditor,
&mut self.state.easy_mark_editor as &mut dyn eframe::App,
&mut self.state.easy_mark_editor as &mut dyn DemoApp,
),
#[cfg(feature = "http")]
(
"⬇ HTTP",
Anchor::Http,
&mut self.state.http as &mut dyn eframe::App,
&mut self.state.http as &mut dyn DemoApp,
),
(
"🕑 Fractal Clock",
Anchor::Clock,
&mut self.state.clock as &mut dyn eframe::App,
&mut self.state.clock as &mut dyn DemoApp,
),
#[cfg(feature = "image_viewer")]
(
"🖼 Image Viewer",
Anchor::ImageViewer,
&mut self.state.image_viewer as &mut dyn eframe::App,
&mut self.state.image_viewer as &mut dyn DemoApp,
),
];
@@ -245,14 +241,14 @@ impl WrapApp {
vec.push((
"🔺 3D painting",
Anchor::Custom3d,
custom3d as &mut dyn eframe::App,
custom3d as &mut dyn DemoApp,
));
}
vec.push((
"🎨 Rendering test",
Anchor::Rendering,
&mut self.state.rendering_test as &mut dyn eframe::App,
&mut self.state.rendering_test as &mut dyn DemoApp,
));
vec.into_iter()
@@ -275,7 +271,7 @@ impl eframe::App for WrapApp {
color.to_normalized_gamma_f32()
}
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
#[cfg(target_arch = "wasm32")]
if let Some(anchor) = frame
.info()
@@ -289,34 +285,36 @@ impl eframe::App for WrapApp {
}
#[cfg(not(target_arch = "wasm32"))]
if ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::F11)) {
let fullscreen = ctx.input(|i| i.viewport().fullscreen.unwrap_or(false));
ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(!fullscreen));
if ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::F11)) {
let fullscreen = ui.input(|i| i.viewport().fullscreen.unwrap_or(false));
ui.send_viewport_cmd(egui::ViewportCommand::Fullscreen(!fullscreen));
}
let mut cmd = Command::Nothing;
egui::TopBottomPanel::top("wrap_app_top_bar")
egui::Panel::top("wrap_app_top_bar")
.frame(egui::Frame::new().inner_margin(4))
.show(ctx, |ui| {
.show_inside(ui, |ui| {
ui.horizontal_wrapped(|ui| {
ui.visuals_mut().button_frame = false;
self.bar_contents(ui, frame, &mut cmd);
});
});
self.state.backend_panel.update(ctx, frame);
self.state.backend_panel.update(ui.ctx(), frame);
if !is_mobile(ctx) {
cmd = self.backend_panel(ctx, frame);
}
egui::CentralPanel::no_frame().show_inside(ui, |ui| {
if !is_mobile(ui.ctx()) {
cmd = self.backend_panel(ui, frame);
}
self.show_selected_app(ctx, frame);
self.show_selected_app(ui, frame);
});
self.state.backend_panel.end_of_frame(ctx);
self.state.backend_panel.end_of_frame(ui.ctx());
self.ui_file_drag_and_drop(ctx);
self.ui_file_drag_and_drop(ui.ctx());
self.run_cmd(ctx, cmd);
self.run_cmd(ui.ctx(), cmd);
}
#[cfg(feature = "glow")]
@@ -333,17 +331,16 @@ impl eframe::App for WrapApp {
}
impl WrapApp {
fn backend_panel(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) -> Command {
fn backend_panel(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) -> Command {
// The backend-panel can be toggled on/off.
// We show a little animation when the user switches it.
let is_open =
self.state.backend_panel.open || ctx.memory(|mem| mem.everything_is_visible());
let is_open = self.state.backend_panel.open || ui.memory(|mem| mem.everything_is_visible());
let mut cmd = Command::Nothing;
egui::SidePanel::left("backend_panel")
egui::Panel::left("backend_panel")
.resizable(false)
.show_animated(ctx, is_open, |ui| {
.show_animated_inside(ui, is_open, |ui| {
ui.add_space(4.0);
ui.vertical_centered(|ui| {
ui.heading("💻 Backend");
@@ -382,7 +379,7 @@ impl WrapApp {
.on_hover_text("Forget scroll, positions, sizes etc")
.clicked()
{
ui.ctx().memory_mut(|mem| *mem = Default::default());
ui.memory_mut(|mem| *mem = Default::default());
ui.close();
}
@@ -393,11 +390,11 @@ impl WrapApp {
});
}
fn show_selected_app(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
fn show_selected_app(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
let selected_anchor = self.state.selected_anchor;
for (_name, anchor, app) in self.apps_iter_mut() {
if anchor == selected_anchor || ctx.memory(|mem| mem.everything_is_visible()) {
app.update(ctx, frame);
if anchor == selected_anchor || ui.memory(|mem| mem.everything_is_visible()) {
app.demo_ui(ui, frame);
}
}
}
@@ -409,7 +406,7 @@ impl WrapApp {
if is_mobile(ui.ctx()) {
ui.menu_button("💻 Backend", |ui| {
ui.set_style(ui.ctx().style()); // ignore the "menu" style set by `menu_button`.
ui.set_style(ui.global_style()); // ignore the "menu" style set by `menu_button`.
self.backend_panel_contents(ui, frame, cmd);
});
} else {
@@ -426,8 +423,7 @@ impl WrapApp {
{
selected_anchor = anchor;
if frame.is_web() {
ui.ctx()
.open_url(egui::OpenUrl::same_tab(format!("#{anchor}")));
ui.open_url(egui::OpenUrl::same_tab(format!("#{anchor}")));
}
}
}
@@ -439,7 +435,7 @@ impl WrapApp {
if clock_button(ui, crate::seconds_since_midnight()).clicked() {
self.state.selected_anchor = Anchor::Clock;
if frame.is_web() {
ui.ctx().open_url(egui::OpenUrl::same_tab("#clock"));
ui.open_url(egui::OpenUrl::same_tab("#clock"));
}
}
}
@@ -477,7 +473,7 @@ impl WrapApp {
content_rect.center(),
Align2::CENTER_CENTER,
text,
TextStyle::Heading.resolve(&ctx.style()),
TextStyle::Heading.resolve(&ctx.global_style()),
Color32::WHITE,
);
}

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:44a68dc4d3aeebeb2d296c5c8e03aac330e1e4552364084347b710326c88f70c
size 335794
oid sha256:7051c34854469652d2d953f3110ebcf6fd60f8ee9a2b0c134d9f7255ef180ce5
size 335353

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e9a760fe4a695e6321f00e40bfa76fd0195bee7157a1217572765e3f146ea2cc
size 93640
oid sha256:49823cfa4dfba54e54d0122f2bbb246c1daad2ca3ba15071c1ca44eeb3662855
size 92791

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a1670bbfc1f0a71e20cbbeb73625c148b680963bc503d9b48e9cc43e704d7c54
size 181671
oid sha256:1b65b6b1a3afe41337b8fe537525677284e49bd90be29cddb837787162ee452a
size 169596

Some files were not shown because too many files have changed in this diff Show More