1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-01 14:20:04 -04:00

Merge branch 'main' of github.com:AdrienZianne/egui

This commit is contained in:
adrien
2025-12-17 11:33:44 +01:00
315 changed files with 2973 additions and 1770 deletions

View File

@@ -1,21 +1,8 @@
name: Check spelling and links name: Link checker
on: [pull_request] # on: [pull_request] # Disabled because it is so broken
on: workflow_dispatch
jobs: 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: lychee:
name: lychee name: lychee
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -33,4 +20,3 @@ jobs:
uses: lycheeverse/lychee-action@v2 uses: lycheeverse/lychee-action@v2
with: with:
args: "'**/*.md' '**/*.toml' --exclude localhost --exclude reddit.com" # I guess reddit doesn't like github action IPs args: "'**/*.md' '**/*.toml' --exclude localhost --exclude reddit.com" # I guess reddit doesn't like github action IPs

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

@@ -14,6 +14,11 @@ This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script. Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
## 0.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 ## 0.33.2 - 2025-11-13
### ⭐ Added ### ⭐ 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 `Plugin::on_widget_under_pointer` to support widget inspector [#7652](https://github.com/emilk/egui/pull/7652) by [@juancampa](https://github.com/juancampa)

View File

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

View File

@@ -24,7 +24,7 @@ members = [
edition = "2024" edition = "2024"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
rust-version = "1.88" rust-version = "1.88"
version = "0.33.2" version = "0.33.3"
[profile.release] [profile.release]
@@ -55,23 +55,22 @@ opt-level = 2
[workspace.dependencies] [workspace.dependencies]
emath = { version = "0.33.2", path = "crates/emath", default-features = false } emath = { version = "0.33.3", path = "crates/emath", default-features = false }
ecolor = { version = "0.33.2", path = "crates/ecolor", default-features = false } ecolor = { version = "0.33.3", path = "crates/ecolor", default-features = false }
epaint = { version = "0.33.2", path = "crates/epaint", default-features = false } epaint = { version = "0.33.3", path = "crates/epaint", default-features = false }
epaint_default_fonts = { version = "0.33.2", path = "crates/epaint_default_fonts" } epaint_default_fonts = { version = "0.33.3", path = "crates/epaint_default_fonts" }
egui = { version = "0.33.2", path = "crates/egui", default-features = false } egui = { version = "0.33.3", path = "crates/egui", default-features = false }
egui-winit = { version = "0.33.2", path = "crates/egui-winit", default-features = false } egui-winit = { version = "0.33.3", path = "crates/egui-winit", default-features = false }
egui_extras = { version = "0.33.2", path = "crates/egui_extras", default-features = false } egui_extras = { version = "0.33.3", path = "crates/egui_extras", default-features = false }
egui-wgpu = { version = "0.33.2", path = "crates/egui-wgpu", default-features = false } egui-wgpu = { version = "0.33.3", path = "crates/egui-wgpu", default-features = false }
egui_demo_lib = { version = "0.33.2", path = "crates/egui_demo_lib", default-features = false } egui_demo_lib = { version = "0.33.3", path = "crates/egui_demo_lib", default-features = false }
egui_glow = { version = "0.33.2", path = "crates/egui_glow", default-features = false } egui_glow = { version = "0.33.3", path = "crates/egui_glow", default-features = false }
egui_kittest = { version = "0.33.2", path = "crates/egui_kittest", default-features = false } egui_kittest = { version = "0.33.3", path = "crates/egui_kittest", default-features = false }
eframe = { version = "0.33.2", path = "crates/eframe", default-features = false } eframe = { version = "0.33.3", path = "crates/eframe", default-features = false }
accesskit = "0.21.1" accesskit = "0.21.1"
accesskit_consumer = "0.30.1" accesskit_consumer = "0.30.1"
accesskit_winit = "0.29.1" accesskit_winit = "0.29.1"
ab_glyph = "0.2.32"
ahash = { version = "0.8.12", default-features = false, features = [ 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 "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
"std", "std",
@@ -88,7 +87,7 @@ criterion = { version = "0.7.0", default-features = false }
dify = { version = "0.7.4", default-features = false } dify = { version = "0.7.4", default-features = false }
directories = "6.0.0" directories = "6.0.0"
document-features = "0.2.11" 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" enum-map = "2.7.3"
env_logger = { version = "0.11.8", default-features = false } env_logger = { version = "0.11.8", default-features = false }
glow = "0.16.0" glow = "0.16.0"
@@ -122,8 +121,10 @@ rayon = "1.11.0"
resvg = { version = "0.45.1", default-features = false } resvg = { version = "0.45.1", default-features = false }
rfd = "0.15.4" rfd = "0.15.4"
ron = "0.11.0" ron = "0.11.0"
self_cell = "1.2.1"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
similar-asserts = "1.7.0" similar-asserts = "1.7.0"
skrifa = { version = "0.37.0", default-features = false, features = ["std", "autohint_shaping"] }
smallvec = "1.15.1" smallvec = "1.15.1"
smithay-clipboard = "0.7.2" smithay-clipboard = "0.7.2"
static_assertions = "1.1.0" static_assertions = "1.1.0"
@@ -131,9 +132,11 @@ syntect = { version = "5.3.0", default-features = false }
tempfile = "3.23.0" tempfile = "3.23.0"
thiserror = "2.0.17" thiserror = "2.0.17"
tokio = "1.47.1" tokio = "1.47.1"
toml = "0.8"
type-map = "0.5.1" type-map = "0.5.1"
unicode_names2 = { version = "2.0.0", default-features = false } unicode_names2 = { version = "2.0.0", default-features = false }
unicode-segmentation = "1.12.0" 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 = "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" wasm-bindgen-futures = "0.4.0"
wayland-cursor = { version = "0.31.11", default-features = false } wayland-cursor = { version = "0.31.11", default-features = false }
@@ -277,6 +280,7 @@ non_zero_suggestions = "warn"
nonstandard_macro_braces = "warn" nonstandard_macro_braces = "warn"
option_as_ref_cloned = "warn" option_as_ref_cloned = "warn"
option_option = "warn" option_option = "warn"
or_fun_call = "warn"
path_buf_push_overwrite = "warn" path_buf_push_overwrite = "warn"
pathbuf_init_then_push = "warn" pathbuf_init_then_push = "warn"
precedence_bits = "warn" precedence_bits = "warn"

View File

@@ -6,6 +6,10 @@ This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script. Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
## 0.33.3 - 2025-12-11
Nothing new
## 0.33.2 - 2025-11-13 ## 0.33.2 - 2025-11-13
Nothing new Nothing new

View File

@@ -7,6 +7,10 @@ This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script. Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
## 0.33.3 - 2025-12-11
Nothing new
## 0.33.2 - 2025-11-13 ## 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) * 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) * 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)

View File

@@ -135,6 +135,31 @@ 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). /// 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 { 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.
///
/// 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. /// 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`]. /// Put your widgets into a [`egui::Panel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
@@ -146,7 +171,10 @@ pub trait App {
/// This is called for the root viewport ([`egui::ViewportId::ROOT`]). /// This is called for the root viewport ([`egui::ViewportId::ROOT`]).
/// Use [`egui::Context::show_viewport_deferred`] to spawn additional viewports (windows). /// Use [`egui::Context::show_viewport_deferred`] to spawn additional viewports (windows).
/// (A "viewport" in egui means an native OS window). /// (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. /// Get a handle to the app.
/// ///

View File

@@ -35,7 +35,7 @@
//! //!
//! impl MyEguiApp { //! impl MyEguiApp {
//! fn new(cc: &eframe::CreationContext<'_>) -> Self { //! 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). //! // 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 //! // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
//! // for e.g. egui::PaintCallback. //! // for e.g. egui::PaintCallback.
@@ -44,8 +44,8 @@
//! } //! }
//! //!
//! impl eframe::App for MyEguiApp { //! impl eframe::App for MyEguiApp {
//! fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { //! fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
//! egui::CentralPanel::default().show(ctx, |ui| { //! egui::CentralPanel::default().show_inside(ui, |ui| {
//! ui.heading("Hello World!"); //! ui.heading("Hello World!");
//! }); //! });
//! } //! }
@@ -232,7 +232,7 @@ pub mod icon_data;
/// ///
/// impl MyEguiApp { /// impl MyEguiApp {
/// fn new(cc: &eframe::CreationContext<'_>) -> Self { /// 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). /// // 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 /// // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
/// // for e.g. egui::PaintCallback. /// // for e.g. egui::PaintCallback.
@@ -241,8 +241,8 @@ pub mod icon_data;
/// } /// }
/// ///
/// impl eframe::App for MyEguiApp { /// impl eframe::App for MyEguiApp {
/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { /// fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
/// egui::CentralPanel::default().show(ctx, |ui| { /// egui::CentralPanel::default().show_inside(ui, |ui| {
/// ui.heading("Hello World!"); /// ui.heading("Hello World!");
/// }); /// });
/// } /// }
@@ -312,8 +312,8 @@ pub fn run_native(
/// } /// }
/// ///
/// impl eframe::App for MyEguiApp { /// impl eframe::App for MyEguiApp {
/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { /// fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
/// egui::CentralPanel::default().show(ctx, |ui| { /// egui::CentralPanel::default().show_inside(ui, |ui| {
/// ui.heading("Hello World!"); /// ui.heading("Hello World!");
/// }); /// });
/// } /// }
@@ -399,8 +399,9 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer {
/// let mut age = 42; /// let mut age = 42;
/// ///
/// let options = eframe::NativeOptions::default(); /// let options = eframe::NativeOptions::default();
/// eframe::run_simple_native("My egui App", options, move |ctx, _frame| { /// eframe::run_ui_native("My egui App", options, move |ui, _frame| {
/// egui::CentralPanel::default().show(ctx, |ui| { /// // 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.heading("My egui Application");
/// ui.horizontal(|ui| { /// ui.horizontal(|ui| {
/// let name_label = ui.label("Your name: "); /// let name_label = ui.label("Your name: ");
@@ -421,6 +422,65 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer {
/// This function can fail if we fail to set up a graphics context. /// This function can fail if we fail to set up a graphics context.
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] #[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`].
/// 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_simple_native("My egui App", options, move |ctx, _frame| {
/// egui::CentralPanel::default().show(ctx, |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.
#[deprecated = "Use run_ui_native instead"]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub fn run_simple_native( pub fn run_simple_native(
app_name: &str, app_name: &str,
native_options: NativeOptions, native_options: NativeOptions,
@@ -431,6 +491,8 @@ pub fn run_simple_native(
} }
impl<U: FnMut(&egui::Context, &mut Frame) + 'static> App for SimpleApp<U> { 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) { fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
(self.update_fun)(ctx, frame); (self.update_fun)(ctx, frame);
} }

View File

@@ -272,14 +272,27 @@ impl EpiIntegration {
app.raw_input_hook(&self.egui_ctx, &mut raw_input); 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 { if let Some(viewport_ui_cb) = viewport_ui_cb {
// Child viewport // Child viewport
profiling::scope!("viewport_callback"); profiling::scope!("viewport_callback");
viewport_ui_cb(egui_ctx); viewport_ui_cb(ui);
} else { } 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

@@ -571,7 +571,7 @@ impl GlowWinitRunning<'_> {
.options_mut(|opt| opt.begin_pass(&raw_input)); .options_mut(|opt| opt.begin_pass(&raw_input));
let clear_color = self let clear_color = self
.app .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 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. 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: // vsync - don't count as frame-time:
frame_timer.pause(); frame_timer.pause();
profiling::scope!("swap_buffers"); profiling::scope!("swap_buffers");
let context = current_gl_context let context = current_gl_context.as_ref().ok_or_else(|| {
.as_ref() egui_glow::PainterError::from(
.ok_or(egui_glow::PainterError::from(
"failed to get current context to swap buffers".to_owned(), "failed to get current context to swap buffers".to_owned(),
))?; )
})?;
gl_surface.swap_buffers(context)?; gl_surface.swap_buffers(context)?;
frame_timer.resume(); frame_timer.resume();
@@ -1362,7 +1362,7 @@ fn initialize_or_update_viewport(
ids: ViewportIdPair, ids: ViewportIdPair,
class: ViewportClass, class: ViewportClass,
mut builder: ViewportBuilder, 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 { ) -> &mut Viewport {
profiling::function_scope!(); profiling::function_scope!();
@@ -1494,8 +1494,8 @@ fn render_immediate_viewport(
shapes, shapes,
pixels_per_point, pixels_per_point,
viewport_output, viewport_output,
} = egui_ctx.run(input, |ctx| { } = egui_ctx.run_ui(input, |ui| {
viewport_ui_cb(ctx); viewport_ui_cb(ui);
}); });
// --------------------------------------------------- // ---------------------------------------------------

View File

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

View File

@@ -273,8 +273,13 @@ impl AppRunner {
self.app.raw_input_hook(&self.egui_ctx, &mut raw_input); self.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| {
self.app.update(egui_ctx, &mut self.frame); 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 { let egui::FullOutput {
platform_output, platform_output,
@@ -331,7 +336,7 @@ impl AppRunner {
} }
if let Err(err) = self.painter.paint_and_update_textures( if let Err(err) = self.painter.paint_and_update_textures(
self.app.clear_color(&self.egui_ctx.style().visuals), self.app.clear_color(&self.egui_ctx.global_style().visuals),
&clipped_primitives, &clipped_primitives,
self.egui_ctx.pixels_per_point(), self.egui_ctx.pixels_per_point(),
&textures_delta, &textures_delta,

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); let prevent_default = should_prevent_default_for_key(runner, &modifiers, egui_key);
// log::debug!( if false {
// "On keydown {:?} {egui_key:?}, has_focus: {has_focus}, egui_wants_keyboard: {}, prevent_default: {prevent_default}", log::debug!(
// event.key().as_str(), "On keydown {:?} {egui_key:?}, has_focus: {has_focus}, egui_wants_keyboard: {}, prevent_default: {prevent_default}",
// runner.egui_ctx().wants_keyboard_input() event.key().as_str(),
// ); runner.egui_ctx().egui_wants_keyboard_input()
);
}
if prevent_default { if prevent_default {
event.prevent_default(); event.prevent_default();

View File

@@ -243,13 +243,26 @@ impl WebPainter for WebPainterWgpu {
depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| { depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| {
wgpu::RenderPassDepthStencilAttachment { wgpu::RenderPassDepthStencilAttachment {
view, view,
depth_ops: Some(wgpu::Operations { depth_ops: self
load: wgpu::LoadOp::Clear(1.0), .depth_stencil_format
// It is very unlikely that the depth buffer is needed after egui finished rendering .is_some_and(|depth_stencil_format| {
// so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) depth_stencil_format.has_depth_aspect()
store: wgpu::StoreOp::Discard, })
}), .then_some(wgpu::Operations {
stencil_ops: None, 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"), label: Some("egui_render"),

View File

@@ -6,6 +6,10 @@ This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script. Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
## 0.33.3 - 2025-12-11
Nothing new
## 0.33.2 - 2025-11-13 ## 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) * Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman)

View File

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

View File

@@ -5,6 +5,10 @@ This file is updated upon each release.
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script. Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
## 0.33.3 - 2025-12-11
Nothing new
## 0.33.2 - 2025-11-13 ## 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) * Don't enable `arboard` on iOS [#7663](https://github.com/emilk/egui/pull/7663) by [@irh](https://github.com/irh)

View File

@@ -309,21 +309,21 @@ impl State {
self.on_mouse_button_input(*state, *button); self.on_mouse_button_input(*state, *button);
EventResponse { EventResponse {
repaint: true, repaint: true,
consumed: self.egui_ctx.wants_pointer_input(), consumed: self.egui_ctx.egui_wants_pointer_input(),
} }
} }
WindowEvent::MouseWheel { delta, phase, .. } => { WindowEvent::MouseWheel { delta, phase, .. } => {
self.on_mouse_wheel(window, *delta, *phase); self.on_mouse_wheel(window, *delta, *phase);
EventResponse { EventResponse {
repaint: true, repaint: true,
consumed: self.egui_ctx.wants_pointer_input(), consumed: self.egui_ctx.egui_wants_pointer_input(),
} }
} }
WindowEvent::CursorMoved { position, .. } => { WindowEvent::CursorMoved { position, .. } => {
self.on_cursor_moved(window, *position); self.on_cursor_moved(window, *position);
EventResponse { EventResponse {
repaint: true, repaint: true,
consumed: self.egui_ctx.is_using_pointer(), consumed: self.egui_ctx.egui_is_using_pointer(),
} }
} }
WindowEvent::CursorLeft { .. } => { WindowEvent::CursorLeft { .. } => {
@@ -340,8 +340,10 @@ impl State {
let consumed = match touch.phase { let consumed = match touch.phase {
winit::event::TouchPhase::Started winit::event::TouchPhase::Started
| winit::event::TouchPhase::Ended | winit::event::TouchPhase::Ended
| winit::event::TouchPhase::Cancelled => self.egui_ctx.wants_pointer_input(), | winit::event::TouchPhase::Cancelled => {
winit::event::TouchPhase::Moved => self.egui_ctx.is_using_pointer(), self.egui_ctx.egui_wants_pointer_input()
}
winit::event::TouchPhase::Moved => self.egui_ctx.egui_is_using_pointer(),
}; };
EventResponse { EventResponse {
repaint: true, repaint: true,
@@ -392,7 +394,7 @@ impl State {
EventResponse { EventResponse {
repaint: true, repaint: true,
consumed: self.egui_ctx.wants_keyboard_input(), consumed: self.egui_ctx.egui_wants_keyboard_input(),
} }
} }
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
@@ -413,7 +415,7 @@ impl State {
self.on_keyboard_input(event); self.on_keyboard_input(event);
// When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes. // 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 || event.logical_key
== winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab); == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
EventResponse { EventResponse {
@@ -528,7 +530,7 @@ impl State {
self.egui_input.events.push(egui::Event::Zoom(zoom_factor)); self.egui_input.events.push(egui::Event::Zoom(zoom_factor));
EventResponse { EventResponse {
repaint: true, 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())); .push(egui::Event::Rotate(-delta.to_radians()));
EventResponse { EventResponse {
repaint: true, repaint: true,
consumed: self.egui_ctx.wants_pointer_input(), consumed: self.egui_ctx.egui_wants_pointer_input(),
} }
} }
@@ -556,7 +558,7 @@ impl State {
}); });
EventResponse { EventResponse {
repaint: true, repaint: true,
consumed: self.egui_ctx.wants_pointer_input(), consumed: self.egui_ctx.egui_wants_pointer_input(),
} }
} }
} }

View File

@@ -82,7 +82,7 @@ impl<'a> AtomKind<'a> {
) -> (Vec2, SizedAtomKind<'a>) { ) -> (Vec2, SizedAtomKind<'a>) {
match self { match self {
AtomKind::Text(text) => { 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); let galley = text.into_galley(ui, Some(wrap_mode), available_size.x, fallback_font);
(galley.intrinsic_size(), SizedAtomKind::Text(galley)) (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 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 the TextWrapMode is not Extend, ensure there is some item marked as `shrink`.
// If none is found, mark the first text item 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 = let fallback_text_color =
fallback_text_color.unwrap_or_else(|| ui.style().visuals.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 // The size available for the content
let available_inner_size = ui.available_size() - frame.total_margin().sum(); 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 // during the sizing pass we will use this as the max size
let mut size = default_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() { if size.x.is_nan() {
size.x = default_area_size.x; size.x = default_area_size.x;
} }
@@ -634,7 +634,8 @@ impl Prepared {
{ {
let age = let age =
ctx.input(|i| (i.time - last_became_visible_at) as f32 + i.predicted_dt / 2.0); 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 let opacity = emath::easing::quadratic_out(opacity); // slow fade-out = quick fade-in
ui.multiply_opacity(opacity); ui.multiply_opacity(opacity);
if opacity < 1.0 { if opacity < 1.0 {
@@ -711,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 // 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`. // 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 spacing = 16.0;
let left = available_rect.left() + spacing; let left = available_rect.left() + spacing;

View File

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

View File

@@ -207,7 +207,7 @@ impl MenuState {
/// egui::MenuBar::new().ui(ui, |ui| { /// egui::MenuBar::new().ui(ui, |ui| {
/// ui.menu_button("File", |ui| { /// ui.menu_button("File", |ui| {
/// if ui.button("Quit").clicked() { /// if ui.button("Quit").clicked() {
/// ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); /// ui.send_viewport_cmd(egui::ViewportCommand::Close);
/// } /// }
/// }); /// });
/// }); /// });
@@ -556,7 +556,7 @@ impl SubMenu {
if is_moving_towards_rect { if is_moving_towards_rect {
// We need to repaint while this is true, so we can detect when // We need to repaint while this is true, so we can detect when
// the pointer is no longer moving towards the rect // the pointer is no longer moving towards the rect
ui.ctx().request_repaint(); ui.request_repaint();
} }
let hovering_other_menu_entry = is_open let hovering_other_menu_entry = is_open
&& !is_hovered && !is_hovered

View File

@@ -291,13 +291,13 @@ impl<'a> PanelSizer<'a> {
/// See the [module level docs](crate::containers::panel) for more details. /// See the [module level docs](crate::containers::panel) for more details.
/// ///
/// ``` /// ```
/// # egui::__run_test_ctx(|ctx| { /// # egui::__run_test_ui(|ui| {
/// egui::Panel::left("my_left_panel").show(ctx, |ui| { /// egui::Panel::left("my_left_panel").show_inside(ui, |ui| {
/// ui.label("Hello World!"); /// ui.label("Hello World!");
/// }); /// });
/// # }); /// # });
/// ``` /// ```
#[must_use = "You should call .show()"] #[must_use = "You should call .show_inside()"]
pub struct Panel { pub struct Panel {
side: PanelSide, side: PanelSide,
id: Id, id: Id,
@@ -518,6 +518,7 @@ impl Panel {
} }
/// Show the panel at the top level. /// Show the panel at the top level.
#[deprecated = "Use show_inside() instead"]
pub fn show<R>( pub fn show<R>(
self, self,
ctx: &Context, ctx: &Context,
@@ -528,12 +529,15 @@ impl Panel {
/// Show the panel if `is_expanded` is `true`, /// Show the panel if `is_expanded` is `true`,
/// otherwise don't show it, but with a nice animation between collapsed and expanded. /// otherwise don't show it, but with a nice animation between collapsed and expanded.
#[deprecated = "Use show_animated_inside() instead"]
pub fn show_animated<R>( pub fn show_animated<R>(
self, self,
ctx: &Context, ctx: &Context,
is_expanded: bool, is_expanded: bool,
add_contents: impl FnOnce(&mut Ui) -> R, add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> { ) -> Option<InnerResponse<R>> {
#![expect(deprecated)]
let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded); let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded);
let animated_panel = self.get_animated_panel(ctx, is_expanded)?; let animated_panel = self.get_animated_panel(ctx, is_expanded)?;
@@ -572,6 +576,7 @@ impl Panel {
} }
/// Show either a collapsed or a expanded panel, with a nice animation between. /// Show either a collapsed or a expanded panel, with a nice animation between.
#[deprecated = "Use show_animated_between_inside() instead"]
pub fn show_animated_between<R>( pub fn show_animated_between<R>(
ctx: &Context, ctx: &Context,
is_expanded: bool, is_expanded: bool,
@@ -579,6 +584,8 @@ impl Panel {
expanded_panel: Self, expanded_panel: Self,
add_contents: impl FnOnce(&mut Ui, f32) -> R, add_contents: impl FnOnce(&mut Ui, f32) -> R,
) -> Option<InnerResponse<R>> { ) -> Option<InnerResponse<R>> {
#![expect(deprecated)]
let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded); let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded);
// Get either the fake or the real panel to animate // Get either the fake or the real panel to animate
@@ -713,7 +720,7 @@ impl Panel {
} }
if resize_hover || is_resizing { if resize_hover || is_resizing {
ui.ctx().set_cursor_icon(self.cursor_icon(&panel_sizer)); ui.set_cursor_icon(self.cursor_icon(&panel_sizer));
} }
PanelState { rect }.store(ui.ctx(), id); PanelState { rect }.store(ui.ctx(), id);
@@ -754,7 +761,7 @@ impl Panel {
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>, add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> { ) -> InnerResponse<R> {
let side = self.side; let side = self.side;
let available_rect = ctx.available_rect(); let available_rect = ctx.globally_available_rect();
let mut panel_ui = Ui::new( let mut panel_ui = Ui::new(
ctx.clone(), ctx.clone(),
self.id, self.id,
@@ -934,14 +941,14 @@ impl Panel {
}; };
let get_spacing_size = || match panel.side { let get_spacing_size = || match panel.side {
PanelSide::Vertical(_) => ctx.style().spacing.interact_size.x, PanelSide::Vertical(_) => ctx.global_style().spacing.interact_size.x,
PanelSide::Horizontal(_) => ctx.style().spacing.interact_size.y, PanelSide::Horizontal(_) => ctx.global_style().spacing.interact_size.y,
}; };
PanelState::load(ctx, panel.id) PanelState::load(ctx, panel.id)
.map(get_rect_state_size) .map(get_rect_state_size)
.or(panel.default_size) .or(panel.default_size)
.unwrap_or(get_spacing_size()) .unwrap_or_else(get_spacing_size)
} }
} }
@@ -950,6 +957,9 @@ impl Panel {
/// A panel that covers the remainder of the screen, /// A panel that covers the remainder of the screen,
/// i.e. whatever area is left after adding other panels. /// i.e. whatever area is left after adding other panels.
/// ///
/// This acts very similar to [`Frame::central_panel`], but always expands
/// to use up all available space.
///
/// The order in which you add panels matter! /// The order in which you add panels matter!
/// The first panel you add will always be the outermost, and the last you add will always be the innermost. /// The first panel you add will always be the outermost, and the last you add will always be the innermost.
/// ///
@@ -960,31 +970,41 @@ impl Panel {
/// See the [module level docs](crate::containers::panel) for more details. /// See the [module level docs](crate::containers::panel) for more details.
/// ///
/// ``` /// ```
/// # egui::__run_test_ctx(|ctx| { /// # egui::__run_test_ui(|ui| {
/// egui::Panel::top("my_panel").show(ctx, |ui| { /// egui::Panel::top("my_panel").show_inside(ui, |ui| {
/// ui.label("Hello World! From `Panel`, that must be before `CentralPanel`!"); /// ui.label("Hello World! From `Panel`, that must be before `CentralPanel`!");
/// }); /// });
/// egui::CentralPanel::default().show(ctx, |ui| { /// egui::CentralPanel::default().show_inside(ui, |ui| {
/// ui.label("Hello World!"); /// ui.label("Hello World!");
/// }); /// });
/// # }); /// # });
/// ``` /// ```
#[must_use = "You should call .show()"] #[must_use = "You should call .show_inside()"]
#[derive(Default)] #[derive(Default)]
pub struct CentralPanel { pub struct CentralPanel {
frame: Option<Frame>, frame: Option<Frame>,
} }
impl CentralPanel { impl CentralPanel {
/// A central panel with no margin or background color
pub fn no_frame() -> Self {
Self {
frame: Some(Frame::NONE),
}
}
/// A central panel with a background color and some inner margins
pub fn default_margins() -> Self {
Self { frame: None }
}
/// Change the background color, margins, etc. /// Change the background color, margins, etc.
#[inline] #[inline]
pub fn frame(mut self, frame: Frame) -> Self { pub fn frame(mut self, frame: Frame) -> Self {
self.frame = Some(frame); self.frame = Some(frame);
self self
} }
}
impl CentralPanel {
/// Show the panel inside a [`Ui`]. /// Show the panel inside a [`Ui`].
pub fn show_inside<R>( pub fn show_inside<R>(
self, self,
@@ -1012,10 +1032,15 @@ impl CentralPanel {
panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475) panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475)
let frame = frame.unwrap_or_else(|| Frame::central_panel(ui.style())); let frame = frame.unwrap_or_else(|| Frame::central_panel(ui.style()));
frame.show(&mut panel_ui, |ui| { let response = frame.show(&mut panel_ui, |ui| {
ui.expand_to_include_rect(ui.max_rect()); // Expand frame to include it all ui.expand_to_include_rect(ui.max_rect()); // Expand frame to include it all
add_contents(ui) add_contents(ui)
}) });
// Use up space in the parent:
ui.advance_cursor_after_rect(response.response.rect);
response
} }
/// Show the panel at the top level. /// Show the panel at the top level.
@@ -1040,7 +1065,7 @@ impl CentralPanel {
id, id,
UiBuilder::new() UiBuilder::new()
.layer_id(LayerId::background()) .layer_id(LayerId::background())
.max_rect(ctx.available_rect().round_ui()), .max_rect(ctx.globally_available_rect().round_ui()),
); );
panel_ui.set_clip_rect(ctx.content_rect()); panel_ui.set_clip_rect(ctx.content_rect());

View File

@@ -465,7 +465,7 @@ impl<'a> Popup<'a> {
pub fn get_best_align(&self) -> RectAlign { pub fn get_best_align(&self) -> RectAlign {
let expected_popup_size = self let expected_popup_size = self
.get_expected_size() .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 { let Some(anchor_rect) = self.anchor.rect(self.id, &self.ctx) else {
return self.rect_align; return self.rect_align;
@@ -473,6 +473,7 @@ impl<'a> Popup<'a> {
RectAlign::find_best_align( RectAlign::find_best_align(
#[expect(clippy::iter_on_empty_collections)] #[expect(clippy::iter_on_empty_collections)]
#[expect(clippy::or_fun_call)]
once(self.rect_align).chain( once(self.rect_align).chain(
self.alternative_aligns self.alternative_aligns
// Need the empty slice so the iters have the same type so we can unwrap_or // 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(|| { 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 let default_size = self
.default_size .default_size
@@ -362,20 +362,20 @@ impl Resize {
paint_resize_corner(ui, &corner_response); paint_resize_corner(ui, &corner_response);
if corner_response.hovered() || corner_response.dragged() { 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); state.store(ui.ctx(), id);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
if ui.ctx().style().debug.show_resize { if ui.global_style().debug.show_resize {
ui.ctx().debug_painter().debug_rect( ui.debug_painter().debug_rect(
Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size), Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size),
Color32::GREEN, Color32::GREEN,
"desired_size", "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), Rect::from_min_size(content_ui.min_rect().left_top(), state.last_content_size),
Color32::LIGHT_BLUE, Color32::LIGHT_BLUE,
"last_content_size", "last_content_size",

View File

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

View File

@@ -856,11 +856,11 @@ impl ScrollArea {
if response.dragged() if response.dragged()
&& let Some(cursor) = on_drag_cursor && let Some(cursor) = on_drag_cursor
{ {
ui.ctx().set_cursor_icon(cursor); ui.set_cursor_icon(cursor);
} else if response.hovered() } else if response.hovered()
&& let Some(cursor) = on_hover_cursor && let Some(cursor) = on_hover_cursor
{ {
ui.ctx().set_cursor_icon(cursor); ui.set_cursor_icon(cursor);
} }
} }
@@ -1125,7 +1125,7 @@ impl Prepared {
target_offset, target_offset,
}); });
} }
ui.ctx().request_repaint(); ui.request_repaint();
} }
} }
} }
@@ -1178,7 +1178,7 @@ impl Prepared {
&& direction_enabled[0] != direction_enabled[1]; && direction_enabled[0] != direction_enabled[1];
for d in 0..2 { for d in 0..2 {
if direction_enabled[d] { if direction_enabled[d] {
let scroll_delta = ui.ctx().input(|input| { let scroll_delta = ui.input(|input| {
if always_scroll_enabled_direction { if always_scroll_enabled_direction {
// no bidirectional scrolling; allow horizontal scrolling without pressing shift // no bidirectional scrolling; allow horizontal scrolling without pressing shift
input.smooth_scroll_delta()[0] + input.smooth_scroll_delta()[1] input.smooth_scroll_delta()[0] + input.smooth_scroll_delta()[1]
@@ -1195,7 +1195,7 @@ impl Prepared {
state.offset[d] -= scroll_delta; state.offset[d] -= scroll_delta;
// Clear scroll delta so no parent scroll will use it: // Clear scroll delta so no parent scroll will use it:
ui.ctx().input_mut(|input| { ui.input_mut(|input| {
if always_scroll_enabled_direction { if always_scroll_enabled_direction {
input.smooth_scroll_delta()[0] = 0.0; input.smooth_scroll_delta()[0] = 0.0;
input.smooth_scroll_delta()[1] = 0.0; input.smooth_scroll_delta()[1] = 0.0;
@@ -1475,7 +1475,7 @@ impl Prepared {
ui.advance_cursor_after_rect(outer_rect); ui.advance_cursor_after_rect(outer_rect);
if show_scroll_this_frame != state.show_scroll { if show_scroll_this_frame != state.show_scroll {
ui.ctx().request_repaint(); ui.request_repaint();
} }
let available_offset = content_size - inner_rect.size(); let available_offset = content_size - inner_rect.size();

View File

@@ -41,7 +41,7 @@ impl Tooltip<'_> {
parent_widget: Id, parent_widget: Id,
anchor: impl Into<PopupAnchor>, anchor: impl Into<PopupAnchor>,
) -> Self { ) -> Self {
let width = ctx.style().spacing.tooltip_width; let width = ctx.global_style().spacing.tooltip_width;
Self { Self {
popup: Popup::new(parent_widget, ctx, anchor.into(), parent_layer) popup: Popup::new(parent_widget, ctx, anchor.into(), parent_layer)
.kind(PopupKind::Tooltip) .kind(PopupKind::Tooltip)
@@ -58,7 +58,7 @@ impl Tooltip<'_> {
let popup = Popup::from_response(response) let popup = Popup::from_response(response)
.kind(PopupKind::Tooltip) .kind(PopupKind::Tooltip)
.gap(4.0) .gap(4.0)
.width(response.ctx.style().spacing.tooltip_width) .width(response.ctx.global_style().spacing.tooltip_width)
.sense(Sense::hover()); .sense(Sense::hover());
Self { Self {
popup, popup,
@@ -229,7 +229,7 @@ impl Tooltip<'_> {
return false; return false;
} }
let style = response.ctx.style(); let style = response.ctx.global_style();
let tooltip_delay = style.interaction.tooltip_delay; let tooltip_delay = style.interaction.tooltip_delay;
let tooltip_grace_time = style.interaction.tooltip_grace_time; 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. /// Assign a unique id to the Window. Required if the title changes, or is shared with another window.
#[inline] #[inline]
pub fn id(mut self, id: Id) -> Self { pub fn id(mut self, id: Id) -> Self {
@@ -440,9 +475,11 @@ impl Window<'_> {
fade_out, fade_out,
} = self; } = self;
let header_color = let header_color = frame.map_or_else(
frame.map_or_else(|| ctx.style().visuals.widgets.open.weak_bg_fill, |f| f.fill); || ctx.global_style().visuals.widgets.open.weak_bg_fill,
let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.style())); |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_explicitly_closed = matches!(open, Some(false));
let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible()); 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 // 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 (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 let title_bar_inner_height = ctx
.fonts_mut(|fonts| title.font_height(fonts, &style)) .fonts_mut(|fonts| title.font_height(fonts, &style))
.at_least(style.spacing.interact_size.y); .at_least(style.spacing.interact_size.y);
@@ -930,8 +967,8 @@ fn resize_interaction(
let id = Id::new(layer_id).with("edge_drag"); let id = Id::new(layer_id).with("edge_drag");
let side_grab_radius = ctx.style().interaction.resize_grab_radius_side; let side_grab_radius = ctx.global_style().interaction.resize_grab_radius_side;
let corner_grab_radius = ctx.style().interaction.resize_grab_radius_corner; let corner_grab_radius = ctx.global_style().interaction.resize_grab_radius_corner;
let vetrtical_rect = |a: Pos2, b: Pos2| { let vetrtical_rect = |a: Pos2, b: Pos2| {
Rect::from_min_max(a, b).expand2(vec2(side_grab_radius, -corner_grab_radius)) Rect::from_min_max(a, b).expand2(vec2(side_grab_radius, -corner_grab_radius))
@@ -1141,8 +1178,7 @@ impl TitleBar {
title_bar_height_with_margin: f32, title_bar_height_with_margin: f32,
) -> Self { ) -> Self {
if false { if false {
ui.ctx() ui.debug_painter()
.debug_painter()
.debug_rect(ui.min_rect(), Color32::GREEN, "outer_min_rect"); .debug_rect(ui.min_rect(), Color32::GREEN, "outer_min_rect");
} }
@@ -1170,8 +1206,7 @@ impl TitleBar {
let min_rect = Rect::from_min_size(ui.min_rect().min, min_inner_size); let min_rect = Rect::from_min_size(ui.min_rect().min, min_inner_size);
if false { if false {
ui.ctx() ui.debug_painter()
.debug_painter()
.debug_rect(min_rect, Color32::LIGHT_BLUE, "min_rect"); .debug_rect(min_rect, Color32::LIGHT_BLUE, "min_rect");
} }
@@ -1208,8 +1243,7 @@ impl TitleBar {
let title_inner_rect = self.inner_rect; let title_inner_rect = self.inner_rect;
if false { if false {
ui.ctx() ui.debug_painter()
.debug_painter()
.debug_rect(self.inner_rect, Color32::RED, "TitleBar"); .debug_rect(self.inner_rect, Color32::RED, "TitleBar");
} }
@@ -1248,8 +1282,7 @@ impl TitleBar {
// Paint separator between title and content: // Paint separator between title and content:
let content_rect = content_response.rect; let content_rect = content_response.rect;
if false { if false {
ui.ctx() ui.debug_painter()
.debug_painter()
.debug_rect(content_rect, Color32::RED, "content_rect"); .debug_rect(content_rect, Color32::RED, "content_rect");
} }
let y = title_inner_rect.bottom() + window_frame.stroke.width / 2.0; let y = title_inner_rect.bottom() + window_frame.stroke.width / 2.0;
@@ -1263,11 +1296,8 @@ impl TitleBar {
let double_click_rect = title_inner_rect.shrink2(vec2(32.0, 0.0)); let double_click_rect = title_inner_rect.shrink2(vec2(32.0, 0.0));
if false { if false {
ui.ctx().debug_painter().debug_rect( ui.debug_painter()
double_click_rect, .debug_rect(double_click_rect, Color32::GREEN, "double_click_rect");
Color32::GREEN,
"double_click_rect",
);
} }
let id = ui.unique_id().with("__window_title_bar"); 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, ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText,
SafeAreaInsets, ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, SafeAreaInsets, ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui,
ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet,
ViewportOutput, Widget as _, WidgetRect, WidgetText, ViewportOutput, Visuals, Widget as _, WidgetRect, WidgetText,
animation_manager::AnimationManager, animation_manager::AnimationManager,
containers::{self, area::AreaState}, containers::{self, area::AreaState},
data::output::PlatformOutput, data::output::PlatformOutput,
@@ -34,8 +34,7 @@ use crate::{
os::OperatingSystem, os::OperatingSystem,
output::FullOutput, output::FullOutput,
pass_state::PassState, pass_state::PassState,
plugin, plugin::{self, TypedPluginHandle},
plugin::TypedPluginHandle,
resize, response, scroll_area, resize, response, scroll_area,
util::IdTypeMap, util::IdTypeMap,
viewport::ViewportClass, viewport::ViewportClass,
@@ -194,7 +193,7 @@ impl ContextImpl {
pub struct ViewportState { pub struct ViewportState {
/// The type of viewport. /// The type of viewport.
/// ///
/// This will never be [`ViewportClass::Embedded`], /// This will never be [`ViewportClass::EmbeddedWindow`],
/// since those don't result in real viewports. /// since those don't result in real viewports.
pub class: ViewportClass, pub class: ViewportClass,
@@ -564,7 +563,10 @@ impl ContextImpl {
log::trace!("Adding new fonts"); 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; let mut is_new = false;
@@ -573,16 +575,12 @@ impl ContextImpl {
is_new = true; is_new = true;
profiling::scope!("Fonts::new"); profiling::scope!("Fonts::new");
Fonts::new( Fonts::new(text_options, self.font_definitions.clone())
max_texture_side,
text_alpha_from_coverage,
self.font_definitions.clone(),
)
}); });
{ {
profiling::scope!("Fonts::begin_pass"); profiling::scope!("Fonts::begin_pass");
fonts.begin_pass(max_texture_side, text_alpha_from_coverage); fonts.begin_pass(text_options);
} }
} }
@@ -611,7 +609,7 @@ impl ContextImpl {
} }
let parent_id = find_accesskit_parent(&state.parent_map, builders, id) 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(); let parent_builder = builders.get_mut(&parent_id).unwrap();
parent_builder.push_child(id.accesskit_id()); parent_builder.push_child(id.accesskit_id());
@@ -758,6 +756,51 @@ impl Context {
writer(&mut self.0.write()) writer(&mut self.0.write())
} }
/// 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.
///
/// 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. /// Run the ui code for one frame.
/// ///
/// At most [`Options::max_passes`] calls will be issued to `run_ui`, /// At most [`Options::max_passes`] calls will be issued to `run_ui`,
@@ -781,8 +824,17 @@ impl Context {
/// }); /// });
/// // handle full_output /// // handle full_output
/// ``` /// ```
///
/// ## See also
/// * [`Self::run_ui`]
#[must_use] #[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!(); profiling::function_scope!();
let viewport_id = new_input.viewport_id; let viewport_id = new_input.viewport_id;
let max_passes = self.write(|ctx| ctx.memory.options.max_passes.get()); let max_passes = self.write(|ctx| ctx.memory.options.max_passes.get());
@@ -877,9 +929,6 @@ impl Context {
plugins.on_input(&mut new_input); plugins.on_input(&mut new_input);
self.write(|ctx| ctx.begin_pass(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`]. /// See [`Self::begin_pass`].
@@ -1101,7 +1150,7 @@ impl Context {
let content_rect = self.content_rect(); let content_rect = self.content_rect();
let text = format!("🔥 {text}"); 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(); let painter = self.debug_painter();
painter.rect_stroke(widget_rect, 0.0, (1.0, color), StrokeKind::Outside); painter.rect_stroke(widget_rect, 0.0, (1.0, color), StrokeKind::Outside);
@@ -1458,7 +1507,7 @@ impl Context {
Painter::new(self.clone(), layer_id, content_rect) 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 { pub fn debug_painter(&self) -> Painter {
Self::layer_painter(self, LayerId::debug()) Self::layer_painter(self, LayerId::debug())
} }
@@ -1556,7 +1605,7 @@ impl Context {
.. ..
} = ModifierNames::SYMBOLS; } = ModifierNames::SYMBOLS;
let font_id = TextStyle::Body.resolve(&self.style()); let font_id = TextStyle::Body.resolve(&self.global_style());
self.fonts_mut(|f| { self.fonts_mut(|f| {
let mut font = f.fonts.font(&font_id.family); let mut font = f.fonts.font(&font_id.family);
font.has_glyphs(alt) font.has_glyphs(alt)
@@ -1957,15 +2006,12 @@ impl Context {
pub fn set_fonts(&self, font_definitions: FontDefinitions) { pub fn set_fonts(&self, font_definitions: FontDefinitions) {
profiling::function_scope!(); profiling::function_scope!();
let mut update_fonts = true; let update_fonts = self.read(|ctx| {
// NOTE: this comparison is expensive since it checks TTF data for equality
self.read(|ctx| { // TODO(valadaptive): add_font only checks the *names* for equality. Change this?
if let Some(current_fonts) = ctx.fonts.as_ref() { ctx.fonts
// NOTE: this comparison is expensive since it checks TTF data for equality .as_ref()
if current_fonts.definitions() == &font_definitions { .is_none_or(|fonts| fonts.definitions() != &font_definitions)
update_fonts = false; // no need to update
}
}
}); });
if update_fonts { if update_fonts {
@@ -2008,13 +2054,13 @@ impl Context {
} }
/// The [`Theme`] used to select the appropriate [`Style`] (dark or light) /// 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 { pub fn theme(&self) -> Theme {
self.options(|opt| opt.theme()) self.options(|opt| opt.theme())
} }
/// The [`Theme`] used to select between dark and light [`Self::style`] /// 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: /// Example:
/// ``` /// ```
@@ -2025,37 +2071,70 @@ impl Context {
self.options_mut(|opt| opt.theme_preference = theme_preference.into()); 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> { pub fn style(&self) -> Arc<Style> {
self.options(|opt| opt.style().clone()) 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. /// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
/// ///
/// Example: /// Example:
/// ``` /// ```
/// # let mut ctx = egui::Context::default(); /// # 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); /// 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)) { pub fn style_mut(&self, mutate_style: impl FnOnce(&mut Style)) {
self.options_mut(|opt| mutate_style(Arc::make_mut(opt.style_mut()))); 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. /// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
/// ///
/// You can also change this using [`Self::style_mut`]. /// You can also change this using [`Self::style_mut`].
/// ///
/// You can use [`Ui::style_mut`] to change the style of a single [`Ui`]. /// 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>>) { pub fn set_style(&self, style: impl Into<Arc<Style>>) {
self.options_mut(|opt| *opt.style_mut() = style.into()); 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: /// Example:
/// ``` /// ```
@@ -2071,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> { pub fn style_of(&self, theme: Theme) -> Arc<Style> {
self.options(|opt| match theme { self.options(|opt| match theme {
Theme::Dark => opt.dark_style.clone(), Theme::Dark => opt.dark_style.clone(),
@@ -2079,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: /// Example:
/// ``` /// ```
@@ -2095,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. /// Use [`Self::set_theme`] to choose between dark and light mode.
/// ///
/// You can also change this using [`Self::style_mut_of`]. /// You can also change this using [`Self::style_mut_of`].
@@ -2109,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`]. /// You can also use [`Ui::visuals_mut`] to change the visuals of a single [`Ui`].
/// ///
@@ -2122,7 +2201,7 @@ impl Context {
self.style_mut_of(theme, |style| style.visuals = visuals); 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`]. /// You can also use [`Ui::visuals_mut`] to change the visuals of a single [`Ui`].
/// ///
@@ -2301,15 +2380,14 @@ impl Context {
crate::gui_zoom::zoom_with_keyboard(self); 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)] #[cfg(debug_assertions)]
self.debug_painting(); self.debug_painting();
let mut output = self.write(|ctx| ctx.end_pass()); let mut output = self.write(|ctx| ctx.end_pass());
let plugins = self.read(|ctx| ctx.plugins.ordered_plugins());
plugins.on_output(&mut output); plugins.on_output(&mut output);
output output
} }
@@ -2341,7 +2419,7 @@ impl Context {
} }
}; };
if self.style().debug.show_interactive_widgets { if self.global_style().debug.show_interactive_widgets {
// Show all interactive widgets: // Show all interactive widgets:
let rects = self.write(|ctx| ctx.viewport().this_pass.widgets.clone()); let rects = self.write(|ctx| ctx.viewport().this_pass.widgets.clone());
for (layer_id, rects) in rects.layers() { for (layer_id, rects) in rects.layers() {
@@ -2419,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 hits = self.write(|ctx| ctx.viewport().hits.clone());
let WidgetHits { let WidgetHits {
close, close,
@@ -2721,12 +2799,18 @@ impl Context {
} }
/// How much space is still available after panels have been added. /// 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() 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. /// 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| { self.write(|ctx| {
let mut used = ctx.viewport().this_pass.used_by_panels; let mut used = ctx.viewport().this_pass.used_by_panels;
for (_id, window) in ctx.memory.areas().visible_windows() { for (_id, window) in ctx.memory.areas().visible_windows() {
@@ -2736,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. /// How much space is used by panels and windows.
/// ///
/// You can shrink your egui area to this size and still fit all egui components. /// 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 { 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? /// 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()); let pointer_pos = self.input(|i| i.pointer.interact_pos());
if let Some(pointer_pos) = pointer_pos { if let Some(pointer_pos) = pointer_pos {
if let Some(layer) = self.layer_id_at(pointer_pos) { if let Some(layer) = self.layer_id_at(pointer_pos) {
@@ -2763,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). /// 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. /// 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 /// 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). /// 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. /// 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 { pub fn wants_pointer_input(&self) -> bool {
self.is_using_pointer() self.egui_wants_pointer_input()
|| (self.is_pointer_over_area() && !self.input(|i| i.pointer.any_down()))
} }
/// Is egui currently using the pointer position (e.g. dragging a slider)? /// 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. /// 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()) 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`]). /// 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()) 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. /// 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, /// If you call this after the widget has been fully rendered,
@@ -2800,7 +2929,7 @@ impl Context {
/// ///
/// This only works with the old, deprecated [`crate::menu`] API. /// This only works with the old, deprecated [`crate::menu`] API.
#[expect(deprecated)] #[expect(deprecated)]
#[deprecated = "Use `is_popup_open` instead"] #[deprecated = "Use `any_popup_open` instead"]
pub fn is_context_menu_open(&self) -> bool { pub fn is_context_menu_open(&self) -> bool {
self.data(|d| { self.data(|d| {
d.get_temp::<crate::menu::BarState>(crate::menu::CONTEXT_MENU_ID_STR.into()) d.get_temp::<crate::menu::BarState>(crate::menu::CONTEXT_MENU_ID_STR.into())
@@ -2811,6 +2940,18 @@ impl Context {
/// Is a popup or (context) menu open? /// Is a popup or (context) menu open?
/// ///
/// Will return false for [`crate::Tooltip`]s (which are technically popups as well). /// 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 { pub fn is_popup_open(&self) -> bool {
self.pass_state_mut(|fs| { self.pass_state_mut(|fs| {
fs.layers fs.layers
@@ -2836,7 +2977,7 @@ impl Context {
self.input(|i| i.pointer.hover_pos()) 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`] /// Latest position of the mouse, but ignoring any [`crate::Event::PointerGone`]
/// if there were interactions this pass. /// if there were interactions this pass.
@@ -2909,7 +3050,7 @@ impl Context {
/// Moves the given area to the top in its [`Order`]. /// 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) { pub fn move_to_top(&self, layer_id: LayerId) {
self.memory_mut(|mem| mem.areas_mut().move_to_top(layer_id)); self.memory_mut(|mem| mem.areas_mut().move_to_top(layer_id));
} }
@@ -2991,7 +3132,7 @@ impl Context {
/// The animation time is taken from [`Style::animation_time`]. /// The animation time is taken from [`Style::animation_time`].
#[track_caller] // To track repaint cause #[track_caller] // To track repaint cause
pub fn animate_bool(&self, id: Id, value: bool) -> f32 { 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) self.animate_bool_with_time_and_easing(id, value, animation_time, emath::easing::linear)
} }
@@ -3007,7 +3148,7 @@ impl Context {
/// Like [`Self::animate_bool`] but allows you to control the easing function. /// Like [`Self::animate_bool`] but allows you to control the easing function.
#[track_caller] // To track repaint cause #[track_caller] // To track repaint cause
pub fn animate_bool_with_easing(&self, id: Id, value: bool, easing: fn(f32) -> f32) -> f32 { 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) self.animate_bool_with_time_and_easing(id, value, animation_time, easing)
} }
@@ -3028,7 +3169,7 @@ impl Context {
/// for a responsive start and a slow end. /// for a responsive start and a slow end.
/// ///
/// The easing function flips when `target_value` is `false`, /// 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 #[track_caller] // To track repaint cause
pub fn animate_bool_with_time_and_easing( pub fn animate_bool_with_time_and_easing(
&self, &self,
@@ -3140,16 +3281,16 @@ impl Context {
ui.label("Is using pointer") ui.label("Is using pointer")
.on_hover_text("Is egui currently using the pointer actively (e.g. dragging a slider)?"); .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.end_row();
ui.label("Wants pointer input") 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)."); .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.end_row();
ui.label("Wants keyboard input").on_hover_text("Is egui currently listening for text input?"); 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.end_row();
ui.label("Keyboard focus widget").on_hover_text("Is egui currently listening for text input?"); ui.label("Keyboard focus widget").on_hover_text("Is egui currently listening for text input?");
@@ -3414,9 +3555,7 @@ impl Context {
let response = let response =
ui.add(Label::new(RichText::new(text).monospace()).sense(Sense::click())); ui.add(Label::new(RichText::new(text).monospace()).sense(Sense::click()));
if response.hovered() && is_visible { if response.hovered() && is_visible {
ui.ctx() ui.debug_painter().debug_rect(area.rect(), Color32::RED, "");
.debug_painter()
.debug_rect(area.rect(), Color32::RED, "");
} }
} else { } else {
ui.monospace(layer_id.short_debug_format()); ui.monospace(layer_id.short_debug_format());
@@ -3875,21 +4014,23 @@ impl Context {
/// ///
/// If [`Context::embed_viewports`] is `true` (e.g. if the current egui /// If [`Context::embed_viewports`] is `true` (e.g. if the current egui
/// backend does not support multiple viewports), the given callback /// backend does not support multiple viewports), the given callback
/// will be called immediately, embedding the new viewport in the current one. /// will be called immediately, embedding the new viewport in the current one,
/// You can check this with the [`ViewportClass`] given in the callback. /// inside of a [`crate::Window`].
/// If you find [`ViewportClass::Embedded`], you need to create a new [`crate::Window`] for you content. /// You can know by checking for [`ViewportClass::EmbeddedWindow`].
/// ///
/// See [`crate::viewport`] for more information about viewports. /// See [`crate::viewport`] for more information about viewports.
pub fn show_viewport_deferred( pub fn show_viewport_deferred(
&self, &self,
new_viewport_id: ViewportId, new_viewport_id: ViewportId,
viewport_builder: ViewportBuilder, 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!(); profiling::function_scope!();
if self.embed_viewports() { 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 { } else {
self.write(|ctx| { self.write(|ctx| {
ctx.viewport_parents ctx.viewport_parents
@@ -3899,8 +4040,8 @@ impl Context {
viewport.class = ViewportClass::Deferred; viewport.class = ViewportClass::Deferred;
viewport.builder = viewport_builder; viewport.builder = viewport_builder;
viewport.used = true; viewport.used = true;
viewport.viewport_ui_cb = Some(Arc::new(move |ctx| { viewport.viewport_ui_cb = Some(Arc::new(move |ui| {
(viewport_ui_cb)(ctx, ViewportClass::Deferred); (viewport_ui_cb)(ui, ViewportClass::Deferred);
})); }));
}); });
} }
@@ -3909,7 +4050,7 @@ impl Context {
/// Show an immediate viewport, creating a new native window, if possible. /// 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 /// 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. /// which effectively produce double work for two viewports, and triple work for three viewports, etc.
/// To avoid this, use [`Self::show_viewport_deferred`] instead. /// To avoid this, use [`Self::show_viewport_deferred`] instead.
/// ///
@@ -3927,28 +4068,32 @@ impl Context {
/// ///
/// If [`Context::embed_viewports`] is `true` (e.g. if the current egui /// If [`Context::embed_viewports`] is `true` (e.g. if the current egui
/// backend does not support multiple viewports), the given callback /// backend does not support multiple viewports), the given callback
/// will be called immediately, embedding the new viewport in the current one. /// will be called immediately, embedding the new viewport in the current one,
/// You can check this with the [`ViewportClass`] given in the callback. /// inside of a [`crate::Window`].
/// If you find [`ViewportClass::Embedded`], you need to create a new [`crate::Window`] for you content. /// You can know by checking for [`ViewportClass::EmbeddedWindow`].
/// ///
/// See [`crate::viewport`] for more information about viewports. /// See [`crate::viewport`] for more information about viewports.
pub fn show_viewport_immediate<T>( pub fn show_viewport_immediate<T>(
&self, &self,
new_viewport_id: ViewportId, new_viewport_id: ViewportId,
builder: ViewportBuilder, builder: ViewportBuilder,
mut viewport_ui_cb: impl FnMut(&Self, ViewportClass) -> T, mut viewport_ui_cb: impl FnMut(&mut Ui, ViewportClass) -> T,
) -> T { ) -> T {
profiling::function_scope!(); profiling::function_scope!();
if self.embed_viewports() { 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| { IMMEDIATE_VIEWPORT_RENDERER.with(|immediate_viewport_renderer| {
let immediate_viewport_renderer = immediate_viewport_renderer.borrow(); let immediate_viewport_renderer = immediate_viewport_renderer.borrow();
let Some(immediate_viewport_renderer) = immediate_viewport_renderer.as_ref() else { let Some(immediate_viewport_renderer) = immediate_viewport_renderer.as_ref() else {
// This egui backend does not support multiple viewports. // 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| { let ids = self.write(|ctx| {
@@ -3972,8 +4117,8 @@ impl Context {
let viewport = ImmediateViewport { let viewport = ImmediateViewport {
ids, ids,
builder, builder,
viewport_ui_cb: Box::new(move |context| { viewport_ui_cb: Box::new(move |ui| {
*out = Some(viewport_ui_cb(context, ViewportClass::Immediate)); *out = Some((viewport_ui_cb)(ui, ViewportClass::Immediate));
}), }),
}; };
@@ -3985,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 /// ## Interaction
@@ -4008,7 +4167,7 @@ impl Context {
/// Is this specific widget being dragged? /// Is this specific widget being dragged?
/// ///
/// A widget that sense both clicks and drags is only marked as "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`]. /// See also: [`crate::Response::dragged`].
pub fn is_being_dragged(&self, id: Id) -> bool { pub fn is_being_dragged(&self, id: Id) -> bool {
@@ -4022,7 +4181,7 @@ impl Context {
self.interaction_snapshot(|i| i.drag_started) 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> { pub fn drag_stopped_id(&self) -> Option<Id> {
self.interaction_snapshot(|i| i.drag_stopped) self.interaction_snapshot(|i| i.drag_stopped)
} }
@@ -4084,11 +4243,11 @@ mod test {
// A single call, no request to discard: // A single call, no request to discard:
{ {
let mut num_calls = 0; let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| { let output = ctx.run_ui(Default::default(), |ui| {
num_calls += 1; num_calls += 1;
assert_eq!(ctx.output(|o| o.num_completed_passes), 0); assert_eq!(ui.output(|o| o.num_completed_passes), 0);
assert!(!ctx.output(|o| o.requested_discard())); assert!(!ui.output(|o| o.requested_discard()));
assert!(!ctx.will_discard()); assert!(!ui.will_discard());
}); });
assert_eq!(num_calls, 1); assert_eq!(num_calls, 1);
assert_eq!(output.platform_output.num_completed_passes, 1); assert_eq!(output.platform_output.num_completed_passes, 1);
@@ -4098,10 +4257,10 @@ mod test {
// A single call, with a denied request to discard: // A single call, with a denied request to discard:
{ {
let mut num_calls = 0; let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| { let output = ctx.run_ui(Default::default(), |ui| {
num_calls += 1; num_calls += 1;
ctx.request_discard("test"); ui.request_discard("test");
assert!(!ctx.will_discard(), "The request should have been denied"); assert!(!ui.will_discard(), "The request should have been denied");
}); });
assert_eq!(num_calls, 1); assert_eq!(num_calls, 1);
assert_eq!(output.platform_output.num_completed_passes, 1); assert_eq!(output.platform_output.num_completed_passes, 1);
@@ -4129,10 +4288,10 @@ mod test {
// Normal single pass: // Normal single pass:
{ {
let mut num_calls = 0; let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| { let output = ctx.run_ui(Default::default(), |ui| {
assert_eq!(ctx.output(|o| o.num_completed_passes), 0); assert_eq!(ui.output(|o| o.num_completed_passes), 0);
assert!(!ctx.output(|o| o.requested_discard())); assert!(!ui.output(|o| o.requested_discard()));
assert!(!ctx.will_discard()); assert!(!ui.will_discard());
num_calls += 1; num_calls += 1;
}); });
assert_eq!(num_calls, 1); assert_eq!(num_calls, 1);
@@ -4143,13 +4302,13 @@ mod test {
// Request discard once: // Request discard once:
{ {
let mut num_calls = 0; let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| { let output = ctx.run_ui(Default::default(), |ui| {
assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls); assert_eq!(ui.output(|o| o.num_completed_passes), num_calls);
assert!(!ctx.will_discard()); assert!(!ui.will_discard());
if num_calls == 0 { if num_calls == 0 {
ctx.request_discard("test"); ui.request_discard("test");
assert!(ctx.will_discard()); assert!(ui.will_discard());
} }
num_calls += 1; num_calls += 1;
@@ -4165,15 +4324,15 @@ mod test {
// Request discard twice: // Request discard twice:
{ {
let mut num_calls = 0; let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| { let output = ctx.run_ui(Default::default(), |ui| {
assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls); assert_eq!(ui.output(|o| o.num_completed_passes), num_calls);
assert!(!ctx.will_discard()); assert!(!ui.will_discard());
ctx.request_discard("test"); ui.request_discard("test");
if num_calls == 0 { if num_calls == 0 {
assert!(ctx.will_discard(), "First request granted"); assert!(ui.will_discard(), "First request granted");
} else { } else {
assert!(!ctx.will_discard(), "Second request should be denied"); assert!(!ui.will_discard(), "Second request should be denied");
} }
num_calls += 1; num_calls += 1;
@@ -4195,13 +4354,13 @@ mod test {
// Request discard three times: // Request discard three times:
{ {
let mut num_calls = 0; let mut num_calls = 0;
let output = ctx.run(Default::default(), |ctx| { let output = ctx.run_ui(Default::default(), |ui| {
assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls); assert_eq!(ui.output(|o| o.num_completed_passes), num_calls);
assert!(!ctx.will_discard()); assert!(!ui.will_discard());
if num_calls <= 2 { if num_calls <= 2 {
ctx.request_discard("test"); ui.request_discard("test");
assert!(ctx.will_discard()); assert!(ui.will_discard());
} }
num_calls += 1; num_calls += 1;

View File

@@ -5,7 +5,7 @@
//! to get callbacks on certain events ([`Plugin::on_begin_pass`], [`Plugin::on_end_pass`]). //! to get callbacks on certain events ([`Plugin::on_begin_pass`], [`Plugin::on_end_pass`]).
use crate::{ 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, text,
}; };
@@ -57,9 +57,9 @@ impl Plugin for DebugTextPlugin {
"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); 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 available_width = ctx.content_rect().max.x - pos.x;
let galley = text.into_galley_impl( let galley = text.into_galley_impl(
ctx, ctx,
&ctx.style(), &ctx.global_style(),
text::TextWrapping::wrap_at_width(available_width), text::TextWrapping::wrap_at_width(available_width),
font_id.clone().into(), font_id.clone().into(),
Align::TOP, Align::TOP,

View File

@@ -1,6 +1,6 @@
use std::{any::Any, sync::Arc}; use std::{any::Any, sync::Arc};
use crate::{Context, CursorIcon, Plugin}; use crate::{Context, CursorIcon, Plugin, Ui};
/// Plugin for tracking drag-and-drop payload. /// 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. /// 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. /// 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(); let has_any_payload = self.payload.is_some();
if has_any_payload { if has_any_payload {
let abort_dnd_due_to_escape_key = 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 { if abort_dnd_due_to_escape_key {
self.payload = None; 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 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 /// This must happen at end-of-frame such that we don't shadow the mouse release event from user
/// code. /// 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(); let has_any_payload = self.payload.is_some();
if has_any_payload { 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 { if abort_dnd_due_to_mouse_release {
self.payload = None; self.payload = None;
} else { } else {
// We set the cursor icon only if its default, as the user code might have // We set the cursor icon only if its default, as the user code might have
// explicitly set it already. // explicitly set it already.
ctx.output_mut(|o| { ui.output_mut(|o| {
if o.cursor_icon == CursorIcon::Default { if o.cursor_icon == CursorIcon::Default {
o.cursor_icon = CursorIcon::Grabbing; o.cursor_icon = CursorIcon::Grabbing;
} }

View File

@@ -449,7 +449,7 @@ impl Grid {
if ui.is_visible() { if ui.is_visible() {
// Try to cover up the glitchy initial frame: // 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: // Hide the ui this frame, and make things as narrow as possible:

View File

@@ -43,23 +43,6 @@
//! In some GUI frameworks this would require defining multiple types and functions with callbacks or message handlers, //! 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! //! but thanks to `egui` being immediate mode everything is one self-contained function!
//! //!
//! ### Getting a [`Ui`]
//!
//! Use one of [`Panel`], [`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 //! ### Quick start
//! //!
//! ``` //! ```
@@ -195,7 +178,7 @@
//! * lays out the letters `click me` in order to figure out the size of the button //! * lays out the letters `click me` in order to figure out the size of the button
//! * decides where on screen to place the button //! * decides where on screen to place the button
//! * check if the mouse is hovering or clicking that location //! * 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 //! * 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 //! * return a [`Response`] with the [`clicked`](`Response::clicked`) member so the user can check for interactions
//! //!
@@ -441,6 +424,7 @@ mod ui_stack;
pub mod util; pub mod util;
pub mod viewport; pub mod viewport;
mod widget_rect; mod widget_rect;
pub mod widget_style;
pub mod widget_text; pub mod widget_text;
pub mod widgets; pub mod widgets;
@@ -691,8 +675,8 @@ pub enum WidgetType {
pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) { pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) {
let ctx = Context::default(); let ctx = Context::default();
ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time) ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time)
let _ = ctx.run(Default::default(), |ctx| { let _ = ctx.run_ui(Default::default(), |ui| {
run_ui(ctx); run_ui(ui.ctx());
}); });
} }
@@ -700,10 +684,8 @@ pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) {
pub fn __run_test_ui(add_contents: impl Fn(&mut Ui)) { pub fn __run_test_ui(add_contents: impl Fn(&mut Ui)) {
let ctx = Context::default(); let ctx = Context::default();
ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time) ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time)
let _ = ctx.run(Default::default(), |ctx| { let _ = ctx.run_ui(Default::default(), |ui| {
crate::CentralPanel::default().show(ctx, |ui| { add_contents(ui);
add_contents(ui);
});
}); });
} }

View File

@@ -21,7 +21,7 @@ pub use theme::{Theme, ThemePreference};
/// how far the user has scrolled in a [`ScrollArea`](crate::ScrollArea) etc. /// 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. /// 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`] /// If you want to store data for your widgets, you should look at [`Memory::data`]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View File

@@ -89,7 +89,7 @@ impl ThemePreference {
/// Show radio-buttons to switch between light mode, dark mode and following the system theme. /// 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) { pub fn radio_buttons(&mut self, ui: &mut crate::Ui) {
ui.horizontal(|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") ui.selectable_value(self, Self::System, "💻 System")
.on_hover_ui(|ui| { .on_hover_ui(|ui| {

View File

@@ -186,7 +186,7 @@ fn menu_popup<'c, R>(
.kind(UiKind::Menu) .kind(UiKind::Menu)
.order(Order::Foreground) .order(Order::Foreground)
.fixed_pos(pos) .fixed_pos(pos)
.default_width(ctx.style().spacing.menu_width) .default_width(ctx.global_style().spacing.menu_width)
.sense(Sense::hover()); .sense(Sense::hover());
let mut sizing_pass = false; let mut sizing_pass = false;
@@ -395,9 +395,9 @@ impl MenuRoot {
// or button hovered while other menu is open // or button hovered while other menu is open
let mut pos = button.rect.left_bottom(); 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.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() { if let Some(root) = root.inner.as_mut() {
let menu_rect = root.menu_state.read().rect; let menu_rect = root.menu_state.read().rect;
@@ -701,7 +701,7 @@ impl MenuState {
if self.moving_towards_current_submenu(&pointer) { if self.moving_towards_current_submenu(&pointer) {
// We don't close the submenu if the pointer is on its way to hover it. // 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 // ensure to repaint once even when pointer is not moving
ui.ctx().request_repaint(); ui.request_repaint();
} else if !open && button.hovered() { } else if !open && button.hovered() {
// TODO(emilk): open menu to the left if there isn't enough space to the right // TODO(emilk): open menu to the left if there isn't enough space to the right
let mut pos = button.rect.right_top(); 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 { 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}")) self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {text}"))
} }

View File

@@ -95,7 +95,7 @@ impl DebugRect {
// Paint rectangle around widget: // Paint rectangle around widget:
{ {
// Print width and height: // 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 Color32::WHITE
} else { } else {
Color32::BLACK Color32::BLACK

View File

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

View File

@@ -433,7 +433,7 @@ impl Response {
pub fn drag_motion(&self) -> Vec2 { pub fn drag_motion(&self) -> Vec2 {
if self.dragged() { if self.dragged() {
self.ctx self.ctx
.input(|i| i.pointer.motion().unwrap_or(i.pointer.delta())) .input(|i| i.pointer.motion().unwrap_or_else(|| i.pointer.delta()))
} else { } else {
Vec2::ZERO Vec2::ZERO
} }
@@ -472,7 +472,7 @@ impl Response {
/// ///
/// Only returns something if [`Self::contains_pointer`] is true, /// Only returns something if [`Self::contains_pointer`] is true,
/// the user is drag-dropping something of this type, /// 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")] #[doc(alias = "drag and drop")]
pub fn dnd_release_payload<Payload: Any + Send + Sync>(&self) -> Option<Arc<Payload>> { pub fn dnd_release_payload<Payload: Any + Send + Sync>(&self) -> Option<Arc<Payload>> {
// NOTE: we use `response.contains_pointer` here instead of `hovered`, because // NOTE: we use `response.contains_pointer` here instead of `hovered`, because
@@ -761,7 +761,7 @@ impl Response {
/// # }); /// # });
/// ``` /// ```
pub fn scroll_to_me(&self, align: Option<Align>) { 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`]. /// Like [`Self::scroll_to_me`], but allows you to specify the [`crate::style::ScrollAnimation`].

View File

@@ -3,7 +3,7 @@
#![allow(clippy::if_same_then_else)] #![allow(clippy::if_same_then_else)]
use emath::Align; 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 std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
use crate::{ use crate::{
@@ -948,8 +948,11 @@ pub struct Visuals {
/// this is more to provide a convenient summary of the rest of the settings. /// this is more to provide a convenient summary of the rest of the settings.
pub dark_mode: bool, pub dark_mode: bool,
/// ADVANCED: Controls how we render text. /// Controls how we render text.
pub text_alpha_from_coverage: AlphaFromCoverage, ///
/// 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. /// Override default text color for all text.
/// ///
@@ -1407,7 +1410,10 @@ impl Visuals {
pub fn dark() -> Self { pub fn dark() -> Self {
Self { Self {
dark_mode: true, 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, override_text_color: None,
weak_text_alpha: 0.6, weak_text_alpha: 0.6,
weak_text_color: None, weak_text_color: None,
@@ -1470,7 +1476,10 @@ impl Visuals {
pub fn light() -> Self { pub fn light() -> Self {
Self { Self {
dark_mode: false, 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(), widgets: Widgets::light(),
selection: Selection::light(), selection: Selection::light(),
hyperlink_color: Color32::from_rgb(0, 155, 255), hyperlink_color: Color32::from_rgb(0, 155, 255),
@@ -2107,7 +2116,7 @@ impl Visuals {
pub fn ui(&mut self, ui: &mut crate::Ui) { pub fn ui(&mut self, ui: &mut crate::Ui) {
let Self { let Self {
dark_mode, dark_mode,
text_alpha_from_coverage, text_options,
override_text_color: _, override_text_color: _,
weak_text_alpha, weak_text_alpha,
weak_text_color, weak_text_color,
@@ -2207,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>) { fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) {
ui.label(label.into().color(*color)); ui.label(label.into().color(*color));
ui.color_edit_button_srgba(color); ui.color_edit_button_srgba(color);
@@ -2259,7 +2268,15 @@ impl Visuals {
ui.add_space(4.0); 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| { ui.collapsing("Text cursor", |ui| {
@@ -2370,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 = let mut dark_mode_special =
*text_alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq; *alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Text rendering:"); ui.label("Text rendering:");
@@ -2380,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"); ui.checkbox(&mut dark_mode_special, "Dark-mode special");
if dark_mode_special { if dark_mode_special {
*text_alpha_from_coverage = AlphaFromCoverage::TwoCoverageMinusCoverageSq; *alpha_from_coverage = AlphaFromCoverage::DARK_MODE_DEFAULT;
} else { } else {
let mut gamma = match text_alpha_from_coverage { let mut gamma = match alpha_from_coverage {
AlphaFromCoverage::Linear => 1.0, AlphaFromCoverage::Linear => 1.0,
AlphaFromCoverage::Gamma(gamma) => *gamma, AlphaFromCoverage::Gamma(gamma) => *gamma,
AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, // approximately the same AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, // approximately the same
@@ -2396,9 +2413,9 @@ fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut Alpha
); );
if gamma == 1.0 { if gamma == 1.0 {
*text_alpha_from_coverage = AlphaFromCoverage::Linear; *alpha_from_coverage = AlphaFromCoverage::Linear;
} else { } else {
*text_alpha_from_coverage = AlphaFromCoverage::Gamma(gamma); *alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
} }
} }
}); });
@@ -2812,6 +2829,7 @@ impl Widget for &mut FontTweak {
scale, scale,
y_offset_factor, y_offset_factor,
y_offset, y_offset,
hinting_override,
} = self; } = self;
ui.label("Scale"); ui.label("Scale");
@@ -2827,6 +2845,19 @@ impl Widget for &mut FontTweak {
ui.add(DragValue::new(y_offset).speed(-0.02)); ui.add(DragValue::new(y_offset).speed(-0.02));
ui.end_row(); 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() { if ui.button("Reset").clicked() {
*self = Default::default(); *self = Default::default();
} }

View File

@@ -128,8 +128,8 @@ impl Plugin for LabelSelectionState {
"LabelSelectionState" "LabelSelectionState"
} }
fn on_begin_pass(&mut self, ctx: &Context) { fn on_begin_pass(&mut self, ui: &mut Ui) {
if ctx.input(|i| i.pointer.any_pressed() && !i.modifiers.shift) { 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: // 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. // 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(); 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 { 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 { if !self.has_reached_primary || !self.has_reached_secondary {
@@ -159,7 +159,7 @@ impl Plugin for LabelSelectionState {
if let Some(selection) = prev_selection { if let Some(selection) = prev_selection {
// This was the first frame of glitch, so hide the // This was the first frame of glitch, so hide the
// glitching by removing all painted selections: // glitching by removing all painted selections:
ctx.graphics_mut(|layers| { ui.graphics_mut(|layers| {
if let Some(list) = layers.get_mut(selection.layer_id) { if let Some(list) = layers.get_mut(selection.layer_id) {
for (shape_idx, row_selections) in self.painted_selections.drain(..) { for (shape_idx, row_selections) in self.painted_selections.drain(..) {
list.mutate_shape(shape_idx, |shape| { 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 pressed_escape = ui.input(|i| i.key_pressed(crate::Key::Escape));
let clicked_something_else = ctx.input(|i| i.pointer.any_pressed()) && !self.any_hovered; let clicked_something_else = ui.input(|i| i.pointer.any_pressed()) && !self.any_hovered;
let delected_everything = pressed_escape || clicked_something_else; let delected_everything = pressed_escape || clicked_something_else;
if delected_everything { if delected_everything {
self.selection = None; self.selection = None;
} }
if ctx.input(|i| i.pointer.any_released()) { if ui.input(|i| i.pointer.any_released()) {
self.is_dragging = false; self.is_dragging = false;
} }
let text_to_copy = std::mem::take(&mut self.text_to_copy); let text_to_copy = std::mem::take(&mut self.text_to_copy);
if !text_to_copy.is_empty() { 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; let global_from_galley = global_from_layer * layer_from_galley;
if response.hovered() { if response.hovered() {
ui.ctx().set_cursor_icon(CursorIcon::Text); ui.set_cursor_icon(CursorIcon::Text);
} }
self.any_hovered |= response.hovered(); self.any_hovered |= response.hovered();

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 { fn next_word_boundary_char_index(text: &str, cursor_ci: usize) -> usize {
for word in text.split_word_bound_indices() { 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, // 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 // 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 // (before space) - this jumps at the end of 'abc' (this is consistent with text editors
// or browsers) // or browsers)
let ci = char_index_from_byte_index(text, word.0); if cursor_ci < word_ci && !all_word_chars(word) {
if ci > index && !skip_word(word.1) { return word_ci;
return ci;
} }
} }
char_index_from_byte_index(text, text.len()) char_index_from_byte_index(text, text.len())
} }
fn skip_word(text: &str) -> bool { fn all_word_chars(text: &str) -> bool {
// skip words that contain anything other than alphanumeric characters and underscore text.chars().all(is_word_char)
// (i.e. whitespace, dashes, etc.)
!text.chars().any(|c| !is_word_char(c))
} }
fn next_line_boundary_char_index(it: impl Iterator<Item = char>, mut index: usize) -> usize { 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("", 0), 0);
assert_eq!(next_word_boundary_char_index("", 1), 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 graphemes, some of which consist of multiple Unicode characters,
// !!! Unicode character is not always what is tranditionally considered a character, // !!! 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, // 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 total_duration - time_in_cycle
}; };
ui.ctx().request_repaint_after_secs(wake_in); ui.request_repaint_after_secs(wake_in);
} else { } else {
paint_cursor_end(painter, ui.visuals(), primary_cursor_rect); paint_cursor_end(painter, ui.visuals(), primary_cursor_rect);
} }

View File

@@ -1,34 +1,13 @@
#![warn(missing_docs)] // Let's keep `Ui` well-documented. #![warn(missing_docs)] // Let's keep `Ui` well-documented.
#![allow(clippy::use_self)] #![allow(clippy::use_self)]
use std::{any::Any, hash::Hash, ops::Deref, sync::Arc};
use emath::GuiRounding as _; use emath::GuiRounding as _;
use epaint::mutex::RwLock; 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::containers::menu;
use crate::{ use crate::{containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *};
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,
},
};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
/// This is what you use to place 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, /// 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. /// 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. /// 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, next_auto_id_salt: u64,
/// Specifies paint layer, clip rectangle and a reference to [`Context`]. /// Specifies paint layer, clip rectangle and a reference to [`Context`].
painter: Painter, painter: Painter,
/// The [`Style`] (visuals, spacing, etc) of this ui. /// 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. /// The [`Ui`] implements copy-on-write for this.
style: Arc<Style>, style: Arc<Style>,
@@ -112,6 +91,16 @@ pub struct Ui {
min_rect_already_remembered: bool, 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 { impl Ui {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Creation: // Creation:
@@ -136,7 +125,7 @@ impl Ui {
accessibility_parent, accessibility_parent,
} = ui_builder; } = ui_builder;
let layer_id = layer_id.unwrap_or(LayerId::background()); let layer_id = layer_id.unwrap_or_else(LayerId::background);
debug_assert!( debug_assert!(
id_salt.is_none(), id_salt.is_none(),
@@ -147,8 +136,8 @@ impl Ui {
let clip_rect = max_rect; let clip_rect = max_rect;
let layout = layout.unwrap_or_default(); let layout = layout.unwrap_or_default();
let disabled = disabled || invisible; let disabled = disabled || invisible;
let style = style.unwrap_or_else(|| ctx.style()); let style = style.unwrap_or_else(|| ctx.global_style());
let sense = sense.unwrap_or(Sense::hover()); let sense = sense.unwrap_or_else(Sense::hover);
let placer = Placer::new(max_rect, layout); let placer = Placer::new(max_rect, layout);
let ui_stack = UiStack { let ui_stack = UiStack {
@@ -277,7 +266,7 @@ impl Ui {
let id_salt = id_salt.unwrap_or_else(|| Id::from("child")); 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 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; let enabled = self.enabled && !disabled && !invisible;
if let Some(layer_id) = layer_id { if let Some(layer_id) = layer_id {
painter.set_layer_id(layer_id); painter.set_layer_id(layer_id);
@@ -287,7 +276,7 @@ impl Ui {
} }
let sizing_pass = self.sizing_pass || sizing_pass; let sizing_pass = self.sizing_pass || sizing_pass;
let style = style.unwrap_or_else(|| self.style.clone()); 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 { if sizing_pass {
// During the sizing pass we want widgets to use up as little space as possible, // During the sizing pass we want widgets to use up as little space as possible,
@@ -428,7 +417,7 @@ impl Ui {
/// Mutably borrow internal [`Style`]. /// Mutably borrow internal [`Style`].
/// Changes apply to this [`Ui`] and its subsequent children. /// 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: /// Example:
/// ``` /// ```
@@ -442,14 +431,14 @@ impl Ui {
/// Changes apply to this [`Ui`] and its subsequent children. /// 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>>) { pub fn set_style(&mut self, style: impl Into<Arc<Style>>) {
self.style = style.into(); self.style = style.into();
} }
/// Reset to the default style set in [`Context`]. /// Reset to the default style set in [`Context`].
pub fn reset_style(&mut self) { pub fn reset_style(&mut self) {
self.style = self.ctx().style(); self.style = self.ctx().global_style();
} }
/// The current spacing options for this [`Ui`]. /// The current spacing options for this [`Ui`].
@@ -459,7 +448,7 @@ impl Ui {
&self.style.spacing &self.style.spacing
} }
/// Mutably borrow internal [`Spacing`](crate::style::Spacing). /// Mutably borrow internal [`Spacing`].
/// Changes apply to this [`Ui`] and its subsequent children. /// Changes apply to this [`Ui`] and its subsequent children.
/// ///
/// Example: /// Example:
@@ -482,7 +471,7 @@ impl Ui {
/// Mutably borrow internal `visuals`. /// Mutably borrow internal `visuals`.
/// Changes apply to this [`Ui`] and its subsequent children. /// 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: /// Example:
/// ``` /// ```
@@ -794,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 /// # Sizes etc
@@ -1150,8 +1052,8 @@ impl Ui {
self.interact(rect, id, sense) self.interact(rect, id, sense)
} }
/// Read the [`Ui`]s background [`Response`]. /// Read the [`Ui`]'s background [`Response`].
/// It's [`Sense`] will be based on the [`UiBuilder::sense`] used to create this [`Ui`]. /// 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`] /// The rectangle of the [`Response`] (and interactive area) will be [`Self::min_rect`]
/// of the last pass. /// of the last pass.
@@ -1251,7 +1153,7 @@ impl Ui {
/// [`crate::Area`], [`crate::Window`], [`crate::CollapsingHeader`], etc. /// [`crate::Area`], [`crate::Window`], [`crate::CollapsingHeader`], etc.
/// ///
/// What exactly happens when you close a container depends on the container implementation. /// 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. /// If you want to close a specific kind of container, use [`Ui::close_kind`] instead.
/// ///
@@ -1432,7 +1334,7 @@ impl Ui {
crate::StrokeKind::Inside, 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); let paint_line_seg = |a, b| self.painter().line_segment([a, b], stroke);
if debug_expand_width && too_wide { if debug_expand_width && too_wide {
@@ -2292,7 +2194,7 @@ impl Ui {
/// Show an image available at the given `uri`. /// Show an image available at the given `uri`.
/// ///
/// ⚠ This will do nothing unless you install some image loaders first! /// ⚠ 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. /// The loaders handle caching image data, sampled textures, etc. across frames, so calling this is immediate-mode safe.
/// ///
@@ -2368,7 +2270,7 @@ impl Ui {
/// ///
/// If the user clicks the button, a full color picker is shown. /// If the user clicks the button, a full color picker is shown.
/// The given color is in `sRGBA` space without premultiplied alpha. /// 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 { 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 mut rgba = Rgba::from_srgba_unmultiplied(srgba[0], srgba[1], srgba[2], srgba[3]);
let response = let response =
@@ -3004,7 +2906,7 @@ impl Ui {
/// ///
/// Returns the dropped item, if it was released this frame. /// 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")] #[doc(alias = "drag and drop")]
pub fn dnd_drop_zone<Payload, R>( pub fn dnd_drop_zone<Payload, R>(
&mut self, &mut self,
@@ -3305,7 +3207,7 @@ fn register_rect(ui: &Ui, rect: Rect) {
// Use the debug-painter to avoid clip rect, // Use the debug-painter to avoid clip rect,
// otherwise the content of the widget may cover what we paint here! // 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 { if debug.hover_shows_next {
ui.placer.debug_paint_cursor(&painter, "next"); ui.placer.debug_paint_cursor(&painter, "next");

View File

@@ -33,7 +33,9 @@
//! In short: immediate viewports are simpler to use, but can waste a lot of CPU time. //! In short: immediate viewports are simpler to use, but can waste a lot of CPU time.
//! //!
//! ### Embedded viewports //! ### 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 //! ## Using the viewports
@@ -71,7 +73,7 @@ use std::sync::Arc;
use epaint::{Pos2, Vec2}; 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, /// The fallback, when the egui integration doesn't support viewports,
/// or [`crate::Context::embed_viewports`] is set to `true`. /// 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. /// 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. /// Render the given viewport, calling the given ui callback.
pub type ImmediateViewportRendererCallback = dyn for<'a> Fn(&Context, ImmediateViewport<'a>); pub type ImmediateViewportRendererCallback = dyn for<'a> Fn(&Context, ImmediateViewport<'a>);
@@ -1189,7 +1194,7 @@ pub struct ViewportOutput {
/// What type of viewport are we? /// 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. /// since those don't result in real viewports.
pub class: ViewportClass, pub class: ViewportClass,
@@ -1246,5 +1251,5 @@ pub struct ImmediateViewport<'a> {
pub builder: ViewportBuilder, pub builder: ViewportBuilder,
/// The user-code that shows the GUI. /// 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::{ use crate::{
Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Color32, CornerRadius, Frame, Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Color32, CornerRadius, Frame,
Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2, Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2,
Widget, WidgetInfo, WidgetText, WidgetType, Widget, WidgetInfo, WidgetText, WidgetType,
widget_style::{ButtonStyle, WidgetState},
}; };
/// Clickable button with text. /// Clickable button with text.
@@ -272,6 +275,7 @@ impl<'a> Button<'a> {
limit_image_size, limit_image_size,
} = self; } = self;
// Min size height always equal or greater than interact size if not small
if !small { if !small {
min_size.y = min_size.y.at_least(ui.spacing().interact_size.y); 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 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 { let mut button_padding = if has_frame_margin {
ui.spacing().button_padding frame.inner_margin
} else { } else {
Vec2::ZERO Margin::ZERO
}; };
if small { if small {
button_padding.y = 0.0; button_padding.bottom = 0;
button_padding.top = 0;
} }
let mut prepared = layout // Override global style by local style
.frame(Frame::new().inner_margin(button_padding)) let mut frame = frame;
.min_size(min_size) if let Some(fill) = fill {
.allocate(ui); 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 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 { 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(); prepared.fallback_text_color = text_style.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.paint(ui) prepared.paint(ui)
} else { } else {

View File

@@ -1,6 +1,8 @@
use emath::Rect;
use crate::{ use crate::{
Atom, AtomLayout, Atoms, Id, IntoAtoms, NumExt as _, Response, Sense, Shape, Ui, Vec2, Widget, 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 // TODO(emilk): allow checkbox without a text label
@@ -55,14 +57,25 @@ impl Widget for Checkbox<'_> {
indeterminate, indeterminate,
} = self; } = self;
let spacing = &ui.spacing(); // Get the widget style by reading the response from the previous pass
let icon_width = spacing.icon_width; 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); let CheckboxStyle {
min_size.y = min_size.y.at_least(icon_width); 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 // 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); icon_size.y = icon_size.y.at_least(min_size.y);
let rect_id = Id::new("egui::checkbox"); let rect_id = Id::new("egui::checkbox");
atoms.push_left(Atom::custom(rect_id, icon_size)); atoms.push_left(Atom::custom(rect_id, icon_size));
@@ -72,6 +85,7 @@ impl Widget for Checkbox<'_> {
let mut prepared = AtomLayout::new(atoms) let mut prepared = AtomLayout::new(atoms)
.sense(Sense::click()) .sense(Sense::click())
.min_size(min_size) .min_size(min_size)
.frame(frame)
.allocate(ui); .allocate(ui);
if prepared.response.clicked() { if prepared.response.clicked() {
@@ -96,18 +110,21 @@ impl Widget for Checkbox<'_> {
}); });
if ui.is_rect_visible(prepared.response.rect) { if ui.is_rect_visible(prepared.response.rect) {
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful prepared.fallback_text_color = text_style.color;
let visuals = *ui.style().interact(&prepared.response);
prepared.fallback_text_color = visuals.text_color();
let response = prepared.paint(ui); let response = prepared.paint(ui);
if let Some(rect) = response.rect(rect_id) { 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( ui.painter().add(epaint::RectShape::new(
big_icon_rect.expand(visuals.expansion), big_icon_rect.expand(checkbox_frame.inner_margin.left.into()),
visuals.corner_radius, checkbox_frame.corner_radius,
visuals.bg_fill, checkbox_frame.fill,
visuals.bg_stroke, checkbox_frame.stroke,
epaint::StrokeKind::Inside, epaint::StrokeKind::Inside,
)); ));
@@ -116,7 +133,7 @@ impl Widget for Checkbox<'_> {
ui.painter().add(Shape::hline( ui.painter().add(Shape::hline(
small_icon_rect.x_range(), small_icon_rect.x_range(),
small_icon_rect.center().y, small_icon_rect.center().y,
visuals.fg_stroke, check_stroke,
)); ));
} else if *checked { } else if *checked {
// Check mark: // Check mark:
@@ -126,7 +143,7 @@ impl Widget for Checkbox<'_> {
pos2(small_icon_rect.center().x, small_icon_rect.bottom()), pos2(small_icon_rect.center().x, small_icon_rect.bottom()),
pos2(small_icon_rect.right(), small_icon_rect.top()), 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) { 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() { if input_type.toggle_button_ui(ui).changed() {
ui.ctx().all_styles_mut(|s| { ui.ctx().all_styles_mut(|s| {
s.visuals.numeric_color_space = input_type; 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() .clicked()
{ {
if alpha == Alpha::Opaque { if alpha == Alpha::Opaque {
ui.ctx().copy_text(format!("{r}, {g}, {b}")); ui.copy_text(format!("{r}, {g}, {b}"));
} else { } 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(); 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() .clicked()
{ {
if alpha == Alpha::Opaque { 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 { } else {
ui.ctx() ui.copy_text(format!("{r:.03}, {g:.03}, {b:.03}, {a:.03}"));
.copy_text(format!("{r:.03}, {g:.03}, {b:.03}, {a:.03}"));
} }
} }

View File

@@ -624,7 +624,7 @@ impl Widget for DragValue<'_> {
ui.memory_mut(|mem| mem.request_focus(id)); ui.memory_mut(|mem| mem.request_focus(id));
select_all_text(ui, id, response.id, &value_text); select_all_text(ui, id, response.id, &value_text);
} else if response.dragged() { } else if response.dragged() {
ui.ctx().set_cursor_icon(cursor_icon); ui.set_cursor_icon(cursor_icon);
let mdelta = response.drag_delta(); let mdelta = response.drag_delta();
let delta_points = mdelta.x - mdelta.y; // Increase to the right and up let delta_points = mdelta.x - mdelta.y; // Increase to the right and up

View File

@@ -65,7 +65,7 @@ impl Widget for Link {
} }
if response.hovered() { 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)); let response = ui.add(Link::new(text));
if response.clicked_with_open_in_background() { if response.clicked_with_open_in_background() {
ui.ctx().open_url(crate::OpenUrl { ui.open_url(crate::OpenUrl {
url: url.clone(), url: url.clone(),
new_tab: true, new_tab: true,
}); });
} else if response.clicked() { } else if response.clicked() {
ui.ctx().open_url(crate::OpenUrl { ui.open_url(crate::OpenUrl {
url: url.clone(), url: url.clone(),
new_tab, new_tab,
}); });

View File

@@ -681,7 +681,7 @@ pub fn paint_texture_load_result(
} }
Ok(TexturePoll::Pending { .. }) => { Ok(TexturePoll::Pending { .. }) => {
let show_loading_spinner = 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 { if show_loading_spinner {
Spinner::new().paint_at(ui, rect); Spinner::new().paint_at(ui, rect);
} }

View File

@@ -248,7 +248,9 @@ impl Label {
layout_job.halign = Align::LEFT; layout_job.halign = Align::LEFT;
layout_job.justify = false; layout_job.justify = false;
} else { } 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(); 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). /// Show larger buttons for switching between light and dark mode (globally).
pub fn global_theme_preference_buttons(ui: &mut Ui) { 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); theme_preference.radio_buttons(ui);
ui.ctx().set_theme(theme_preference); ui.ctx().set_theme(theme_preference);
} }

View File

@@ -118,7 +118,7 @@ impl Widget for ProgressBar {
let desired_width = let desired_width =
desired_width.unwrap_or_else(|| ui.available_size_before_wrap().x.at_least(96.0)); 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) = let (outer_rect, response) =
ui.allocate_exact_size(vec2(desired_width, height), Sense::hover()); 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 ui.is_rect_visible(response.rect) {
if animate { if animate {
ui.ctx().request_repaint(); ui.request_repaint();
} }
let visuals = ui.style().visuals.clone(); 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`]). /// 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);`"] #[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
pub struct Separator { pub struct Separator {
spacing: f32, spacing: Option<f32>,
grow: f32, grow: f32,
is_horizontal_line: Option<bool>, is_horizontal_line: Option<bool>,
} }
@@ -21,7 +21,7 @@ pub struct Separator {
impl Default for Separator { impl Default for Separator {
fn default() -> Self { fn default() -> Self {
Self { Self {
spacing: 6.0, spacing: None,
grow: 0.0, grow: 0.0,
is_horizontal_line: None, is_horizontal_line: None,
} }
@@ -38,7 +38,7 @@ impl Separator {
/// this is the width of the separator widget. /// this is the width of the separator widget.
#[inline] #[inline]
pub fn spacing(mut self, spacing: f32) -> Self { pub fn spacing(mut self, spacing: f32) -> Self {
self.spacing = spacing; self.spacing = Some(spacing);
self self
} }
@@ -93,6 +93,18 @@ impl Widget for Separator {
is_horizontal_line, is_horizontal_line,
} = self; } = 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 let is_horizontal_line = is_horizontal_line
.unwrap_or_else(|| ui.is_grid() || !ui.layout().main_dir().is_horizontal()); .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()); let (rect, response) = ui.allocate_at_least(size, Sense::hover());
if ui.is_rect_visible(response.rect) { if ui.is_rect_visible(response.rect) {
let stroke = ui.visuals().widgets.noninteractive.bg_stroke;
let painter = ui.painter(); let painter = ui.painter();
if is_horizontal_line { if is_horizontal_line {
painter.hline( painter.hline(

View File

@@ -687,7 +687,7 @@ impl Slider<'_> {
let mut increment = 0usize; let mut increment = 0usize;
if response.has_focus() { if response.has_focus() {
ui.ctx().memory_mut(|m| { ui.memory_mut(|m| {
m.set_focus_lock_filter( m.set_focus_lock_filter(
response.id, response.id,
EventFilter { EventFilter {

View File

@@ -37,7 +37,7 @@ impl Spinner {
/// Paint the spinner in the given rectangle. /// Paint the spinner in the given rectangle.
pub fn paint_at(&self, ui: &Ui, rect: Rect) { pub fn paint_at(&self, ui: &Ui, rect: Rect) {
if ui.is_rect_visible(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 let color = self
.color .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 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. /// 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. /// the text and the wrap width.
/// ///
/// ``` /// ```
@@ -496,7 +496,7 @@ impl TextEdit<'_> {
} = self; } = self;
let text_color = text_color 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.style().interact(&response).text_color()); // too bright
.unwrap_or_else(|| ui.visuals().widgets.inactive.text_color()); .unwrap_or_else(|| ui.visuals().widgets.inactive.text_color());
@@ -605,12 +605,12 @@ impl TextEdit<'_> {
if did_interact || response.clicked() { if did_interact || response.clicked() {
ui.memory_mut(|mem| mem.request_focus(response.id)); 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() { if interactive && response.hovered() {
ui.ctx().set_cursor_icon(CursorIcon::Text); ui.set_cursor_icon(CursorIcon::Text);
} }
let mut cursor_range = None; let mut cursor_range = None;
@@ -691,7 +691,7 @@ impl TextEdit<'_> {
if ui.is_rect_visible(rect) { if ui.is_rect_visible(rect) {
if text.as_str().is_empty() && !hint_text.is_empty() { if text.as_str().is_empty() && !hint_text.is_empty() {
let hint_text_color = ui.visuals().weak_text_color(); 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 { let galley = if multiline {
hint_text.into_galley( hint_text.into_galley(
ui, ui,
@@ -721,39 +721,42 @@ impl TextEdit<'_> {
paint_text_selection(&mut galley, ui.visuals(), &cursor_range, None); 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,
// 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.
// 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)
// Condition `!clip_text` is important to avoid breaking layout for `TextEdit::singleline` (PR #5640) if !clip_text
let extra_size = galley.size() - rect.size(); && let extra_size = galley.size() - rect.size()
if extra_size.x > 0.0 || extra_size.y > 0.0 { && (extra_size.x > 0.0 || extra_size.y > 0.0)
match ui.layout().main_dir() { {
crate::Direction::LeftToRight | crate::Direction::TopDown => { match ui.layout().main_dir() {
ui.allocate_rect( crate::Direction::LeftToRight | crate::Direction::TopDown => {
Rect::from_min_size(outer_rect.max, extra_size), ui.allocate_rect(
Sense::hover(), Rect::from_min_size(outer_rect.max, extra_size),
); Sense::hover(),
} );
crate::Direction::RightToLeft => { }
ui.allocate_rect( crate::Direction::RightToLeft => {
Rect::from_min_size( ui.allocate_rect(
emath::pos2(outer_rect.min.x - extra_size.x, outer_rect.max.y), Rect::from_min_size(
extra_size, emath::pos2(outer_rect.min.x - extra_size.x, outer_rect.max.y),
), extra_size,
Sense::hover(), ),
); Sense::hover(),
} );
crate::Direction::BottomUp => { }
ui.allocate_rect( crate::Direction::BottomUp => {
Rect::from_min_size( ui.allocate_rect(
emath::pos2(outer_rect.min.x, outer_rect.max.y - extra_size.y), Rect::from_min_size(
extra_size, emath::pos2(outer_rect.min.x, outer_rect.max.y - extra_size.y),
), extra_size,
Sense::hover(), ),
); 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); painter.galley(galley_pos, galley.clone(), text_color);
@@ -768,7 +771,7 @@ impl TextEdit<'_> {
} }
if text.is_mutable() && interactive { 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 { if response.changed() || selection_changed {
state.last_interaction_time = now; state.last_interaction_time = now;
} }
@@ -777,7 +780,7 @@ impl TextEdit<'_> {
// This is for two reasons: // This is for two reasons:
// * Don't give the impression that the user can type into a window without focus // * 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 // * 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 { if viewport_has_focus {
text_selection::visuals::paint_text_cursor( text_selection::visuals::paint_text_cursor(
ui, ui,
@@ -922,7 +925,7 @@ fn events(
let copy_if_not_password = |ui: &Ui, text: String| { let copy_if_not_password = |ui: &Ui, text: String| {
if !password { if !password {
ui.ctx().copy_text(text); ui.copy_text(text);
} }
}; };

View File

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

View File

@@ -22,26 +22,27 @@ impl Custom3d {
} }
} }
impl eframe::App for Custom3d { impl crate::DemoApp for Custom3d {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| { // TODO(emilk): Use `ScrollArea::inner_margin`
egui::ScrollArea::both() egui::CentralPanel::default().show_inside(ui, |ui| {
.auto_shrink(false) egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
.show(ui, |ui| { ui.horizontal(|ui| {
ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0;
ui.spacing_mut().item_spacing.x = 0.0; ui.label("The triangle is being painted using ");
ui.label("The triangle is being painted using "); ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow"); ui.label(" (OpenGL).");
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!());
}); });
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 { impl crate::DemoApp for Custom3d {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| { // TODO(emilk): Use `ScrollArea::inner_margin`
egui::ScrollArea::both() egui::CentralPanel::default().show_inside(ui, |ui| {
.auto_shrink(false) egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
.show(ui, |ui| { ui.horizontal(|ui| {
ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 0.0;
ui.spacing_mut().item_spacing.x = 0.0; ui.label("The triangle is being painted using ");
ui.label("The triangle is being painted using "); ui.hyperlink_to("WGPU", "https://wgpu.rs");
ui.hyperlink_to("WGPU", "https://wgpu.rs"); ui.label(" (Portable Rust graphics API awesomeness)");
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!());
}); });
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>) { pub fn ui(&mut self, ui: &mut Ui, seconds_since_midnight: Option<f64>) {
if !self.paused { if !self.paused {
self.time = seconds_since_midnight.unwrap_or_else(|| ui.input(|i| i.time)); self.time = seconds_since_midnight.unwrap_or_else(|| ui.input(|i| i.time));
ui.ctx().request_repaint(); ui.request_repaint();
} }
let painter = Painter::new( let painter = Painter::new(

View File

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

View File

@@ -48,15 +48,15 @@ impl Default for ImageViewer {
} }
} }
impl eframe::App for ImageViewer { impl crate::DemoApp for ImageViewer {
fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { fn demo_ui(&mut self, ui: &mut egui::Ui, _: &mut eframe::Frame) {
egui::Panel::top("url bar").show(ctx, |ui| { egui::Panel::top("url bar").show_inside(ui, |ui| {
ui.horizontal_centered(|ui| { ui.horizontal_centered(|ui| {
let label = ui.label("URI:"); let label = ui.label("URI:");
ui.text_edit_singleline(&mut self.uri_edit_text) ui.text_edit_singleline(&mut self.uri_edit_text)
.labelled_by(label.id); .labelled_by(label.id);
if ui.small_button("").clicked() { 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.uri_edit_text = self.uri_edit_text.trim().to_owned();
self.current_uri = self.uri_edit_text.clone(); self.current_uri = self.uri_edit_text.clone();
} }
@@ -71,7 +71,7 @@ impl eframe::App for ImageViewer {
}); });
}); });
egui::Panel::left("controls").show(ctx, |ui| { egui::Panel::left("controls").show_inside(ui, |ui| {
// uv // uv
ui.label("UV"); ui.label("UV");
ui.add(Slider::new(&mut self.image_options.uv.min.x, 0.0..=1.0).text("min x")); ui.add(Slider::new(&mut self.image_options.uv.min.x, 0.0..=1.0).text("min x"));
@@ -197,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| { egui::ScrollArea::both().show(ui, |ui| {
let mut image = egui::Image::from_uri(&self.current_uri); let mut image = egui::Image::from_uri(&self.current_uri);
image = image.uv(self.image_options.uv); image = image.uv(self.image_options.uv);

View File

@@ -94,7 +94,7 @@ impl BackendPanel {
self.egui_windows.checkboxes(ui); self.egui_windows.checkboxes(ui);
#[cfg(debug_assertions)] #[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.separator();
ui.label("Press down all modifiers and hover a widget to see a callstack for it"); 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")] #[cfg(target_arch = "wasm32")]
{ {
ui.separator(); 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.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") { if cfg!(debug_assertions) && cfg!(target_arch = "wasm32") {
@@ -119,7 +119,7 @@ impl BackendPanel {
if !cfg!(target_arch = "wasm32") { if !cfg!(target_arch = "wasm32") {
ui.separator(); ui.separator();
if ui.button("Quit").clicked() { 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| { ui.horizontal(|ui| {
if ui.button("Request discard").clicked() { if ui.button("Request discard").clicked() {
ui.ctx().request_discard("Manual button click"); ui.request_discard("Manual button click");
if !ui.ctx().will_discard() { if !ui.ctx().will_discard() {
ui.label("Discard denied!"); 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") .on_hover_text("Fullscreen the window")
.changed() .changed()
{ {
ui.ctx() ui.send_viewport_cmd(egui::ViewportCommand::Fullscreen(fullscreen));
.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 { if let Some(size) = size {
ui.ctx() ui.send_viewport_cmd(egui::ViewportCommand::InnerSize(size));
.send_viewport_cmd(egui::ViewportCommand::InnerSize(size)); ui.send_viewport_cmd(egui::ViewportCommand::Fullscreen(false));
ui.ctx()
.send_viewport_cmd(egui::ViewportCommand::Fullscreen(false));
ui.close(); 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) 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")] #[cfg(feature = "accessibility_inspector")]
pub mod 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")] #[cfg(feature = "glow")]
use eframe::glow; use eframe::glow;
@@ -6,29 +6,25 @@ use eframe::glow;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use core::any::Any; use core::any::Any;
use crate::DemoApp;
#[derive(Default)] #[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
struct EasyMarkApp { struct EasyMarkApp {
editor: egui_demo_lib::easy_mark::EasyMarkEditor, editor: egui_demo_lib::easy_mark::EasyMarkEditor,
} }
impl eframe::App for EasyMarkApp { impl DemoApp for EasyMarkApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
self.editor.panels(ctx); self.editor.panels(ui);
} }
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#[derive(Default)] impl DemoApp for DemoWindows {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
pub struct DemoApp { self.ui(ui);
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);
} }
} }
@@ -41,17 +37,17 @@ pub struct FractalClockApp {
pub mock_time: Option<f64>, pub mock_time: Option<f64>,
} }
impl eframe::App for FractalClockApp { impl DemoApp for FractalClockApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
egui::CentralPanel::default() egui::Frame::dark_canvas(ui.style())
.frame( .stroke(egui::Stroke::NONE)
egui::Frame::dark_canvas(&ctx.style()) .corner_radius(0)
.stroke(egui::Stroke::NONE) .show(ui, |ui| {
.corner_radius(0), self.fractal_clock.ui(
) ui,
.show(ctx, |ui| { self.mock_time
self.fractal_clock .or_else(|| Some(crate::seconds_since_midnight())),
.ui(ui, self.mock_time.or(Some(crate::seconds_since_midnight()))); );
}); });
} }
} }
@@ -64,13 +60,13 @@ pub struct ColorTestApp {
color_test: egui_demo_lib::ColorTest, color_test: egui_demo_lib::ColorTest,
} }
impl eframe::App for ColorTestApp { impl DemoApp for ColorTestApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { fn demo_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show_inside(ui, |ui| {
if frame.is_web() { if frame.is_web() {
ui.label( 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(); ui.separator();
} }
egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| { 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", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(default))]
pub struct State { pub struct State {
demo: DemoApp, demo: DemoWindows,
easy_mark_editor: EasyMarkApp, easy_mark_editor: EasyMarkApp,
#[cfg(feature = "http")] #[cfg(feature = "http")]
http: crate::apps::HttpApp, http: crate::apps::HttpApp,
@@ -209,34 +205,34 @@ impl WrapApp {
pub fn apps_iter_mut( pub fn apps_iter_mut(
&mut self, &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![ let mut vec = vec![
( (
"✨ Demos", "✨ Demos",
Anchor::Demo, Anchor::Demo,
&mut self.state.demo as &mut dyn eframe::App, &mut self.state.demo as &mut dyn DemoApp,
), ),
( (
"🖹 EasyMark editor", "🖹 EasyMark editor",
Anchor::EasyMarkEditor, 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")] #[cfg(feature = "http")]
( (
"⬇ HTTP", "⬇ HTTP",
Anchor::Http, Anchor::Http,
&mut self.state.http as &mut dyn eframe::App, &mut self.state.http as &mut dyn DemoApp,
), ),
( (
"🕑 Fractal Clock", "🕑 Fractal Clock",
Anchor::Clock, Anchor::Clock,
&mut self.state.clock as &mut dyn eframe::App, &mut self.state.clock as &mut dyn DemoApp,
), ),
#[cfg(feature = "image_viewer")] #[cfg(feature = "image_viewer")]
( (
"🖼 Image Viewer", "🖼 Image Viewer",
Anchor::ImageViewer, 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(( vec.push((
"🔺 3D painting", "🔺 3D painting",
Anchor::Custom3d, Anchor::Custom3d,
custom3d as &mut dyn eframe::App, custom3d as &mut dyn DemoApp,
)); ));
} }
vec.push(( vec.push((
"🎨 Rendering test", "🎨 Rendering test",
Anchor::Rendering, Anchor::Rendering,
&mut self.state.rendering_test as &mut dyn eframe::App, &mut self.state.rendering_test as &mut dyn DemoApp,
)); ));
vec.into_iter() vec.into_iter()
@@ -275,7 +271,7 @@ impl eframe::App for WrapApp {
color.to_normalized_gamma_f32() 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")] #[cfg(target_arch = "wasm32")]
if let Some(anchor) = frame if let Some(anchor) = frame
.info() .info()
@@ -289,34 +285,36 @@ impl eframe::App for WrapApp {
} }
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
if ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::F11)) { if ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::F11)) {
let fullscreen = ctx.input(|i| i.viewport().fullscreen.unwrap_or(false)); let fullscreen = ui.input(|i| i.viewport().fullscreen.unwrap_or(false));
ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(!fullscreen)); ui.send_viewport_cmd(egui::ViewportCommand::Fullscreen(!fullscreen));
} }
let mut cmd = Command::Nothing; let mut cmd = Command::Nothing;
egui::Panel::top("wrap_app_top_bar") egui::Panel::top("wrap_app_top_bar")
.frame(egui::Frame::new().inner_margin(4)) .frame(egui::Frame::new().inner_margin(4))
.show(ctx, |ui| { .show_inside(ui, |ui| {
ui.horizontal_wrapped(|ui| { ui.horizontal_wrapped(|ui| {
ui.visuals_mut().button_frame = false; ui.visuals_mut().button_frame = false;
self.bar_contents(ui, frame, &mut cmd); 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) { egui::CentralPanel::no_frame().show_inside(ui, |ui| {
cmd = self.backend_panel(ctx, frame); 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")] #[cfg(feature = "glow")]
@@ -333,17 +331,16 @@ impl eframe::App for WrapApp {
} }
impl 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. // The backend-panel can be toggled on/off.
// We show a little animation when the user switches it. // We show a little animation when the user switches it.
let is_open = let is_open = self.state.backend_panel.open || ui.memory(|mem| mem.everything_is_visible());
self.state.backend_panel.open || ctx.memory(|mem| mem.everything_is_visible());
let mut cmd = Command::Nothing; let mut cmd = Command::Nothing;
egui::Panel::left("backend_panel") egui::Panel::left("backend_panel")
.resizable(false) .resizable(false)
.show_animated(ctx, is_open, |ui| { .show_animated_inside(ui, is_open, |ui| {
ui.add_space(4.0); ui.add_space(4.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.heading("💻 Backend"); ui.heading("💻 Backend");
@@ -382,7 +379,7 @@ impl WrapApp {
.on_hover_text("Forget scroll, positions, sizes etc") .on_hover_text("Forget scroll, positions, sizes etc")
.clicked() .clicked()
{ {
ui.ctx().memory_mut(|mem| *mem = Default::default()); ui.memory_mut(|mem| *mem = Default::default());
ui.close(); 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; let selected_anchor = self.state.selected_anchor;
for (_name, anchor, app) in self.apps_iter_mut() { for (_name, anchor, app) in self.apps_iter_mut() {
if anchor == selected_anchor || ctx.memory(|mem| mem.everything_is_visible()) { if anchor == selected_anchor || ui.memory(|mem| mem.everything_is_visible()) {
app.update(ctx, frame); app.demo_ui(ui, frame);
} }
} }
} }
@@ -409,7 +406,7 @@ impl WrapApp {
if is_mobile(ui.ctx()) { if is_mobile(ui.ctx()) {
ui.menu_button("💻 Backend", |ui| { 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); self.backend_panel_contents(ui, frame, cmd);
}); });
} else { } else {
@@ -426,8 +423,7 @@ impl WrapApp {
{ {
selected_anchor = anchor; selected_anchor = anchor;
if frame.is_web() { if frame.is_web() {
ui.ctx() ui.open_url(egui::OpenUrl::same_tab(format!("#{anchor}")));
.open_url(egui::OpenUrl::same_tab(format!("#{anchor}")));
} }
} }
} }
@@ -439,7 +435,7 @@ impl WrapApp {
if clock_button(ui, crate::seconds_since_midnight()).clicked() { if clock_button(ui, crate::seconds_since_midnight()).clicked() {
self.state.selected_anchor = Anchor::Clock; self.state.selected_anchor = Anchor::Clock;
if frame.is_web() { 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(), content_rect.center(),
Align2::CENTER_CENTER, Align2::CENTER_CENTER,
text, text,
TextStyle::Heading.resolve(&ctx.style()), TextStyle::Heading.resolve(&ctx.global_style()),
Color32::WHITE, Color32::WHITE,
); );
} }

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:784cbcdfd8deaf61e7b663f9416d67724e6a6a189a20ba3351908aa5c5f2deff oid sha256:7051c34854469652d2d953f3110ebcf6fd60f8ee9a2b0c134d9f7255ef180ce5
size 336159 size 335353

View File

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

View File

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

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:44ea7ac8c8e22eb51fbcb63f00c8510de0e6ae126d19ab44c5d708d979b5362b oid sha256:9c3af0c37a6997abe549dd28450c41d3d18bbc99d9577997d493566fbb7f9277
size 100345 size 96709

View File

@@ -28,8 +28,8 @@ pub fn criterion_benchmark(c: &mut Criterion) {
// The most end-to-end benchmark. // The most end-to-end benchmark.
c.bench_function("demo_with_tessellate__realistic", |b| { c.bench_function("demo_with_tessellate__realistic", |b| {
b.iter(|| { b.iter(|| {
let full_output = ctx.run(RawInput::default(), |ctx| { let full_output = ctx.run_ui(RawInput::default(), |ui| {
demo_windows.ui(ctx); demo_windows.ui(ui);
}); });
ctx.tessellate(full_output.shapes, full_output.pixels_per_point) ctx.tessellate(full_output.shapes, full_output.pixels_per_point)
}); });
@@ -37,14 +37,14 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("demo_no_tessellate", |b| { c.bench_function("demo_no_tessellate", |b| {
b.iter(|| { b.iter(|| {
ctx.run(RawInput::default(), |ctx| { ctx.run_ui(RawInput::default(), |ui| {
demo_windows.ui(ctx); demo_windows.ui(ui);
}) })
}); });
}); });
let full_output = ctx.run(RawInput::default(), |ctx| { let full_output = ctx.run_ui(RawInput::default(), |ui| {
demo_windows.ui(ctx); demo_windows.ui(ui);
}); });
c.bench_function("demo_only_tessellate", |b| { c.bench_function("demo_only_tessellate", |b| {
b.iter(|| ctx.tessellate(full_output.shapes.clone(), full_output.pixels_per_point)); b.iter(|| ctx.tessellate(full_output.shapes.clone(), full_output.pixels_per_point));
@@ -57,8 +57,8 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let mut demo_windows = egui_demo_lib::DemoWindows::default(); let mut demo_windows = egui_demo_lib::DemoWindows::default();
c.bench_function("demo_full_no_tessellate", |b| { c.bench_function("demo_full_no_tessellate", |b| {
b.iter(|| { b.iter(|| {
ctx.run(RawInput::default(), |ctx| { ctx.run_ui(RawInput::default(), |ui| {
demo_windows.ui(ctx); demo_windows.ui(ui);
}) })
}); });
}); });
@@ -66,10 +66,10 @@ pub fn criterion_benchmark(c: &mut Criterion) {
{ {
let ctx = egui::Context::default(); let ctx = egui::Context::default();
let _ = ctx.run(RawInput::default(), |ctx| { let _ = ctx.run_ui(RawInput::default(), |ui| {
c.bench_function("label &str", |b| { c.bench_function("label &str", |b| {
b.iter_batched_ref( b.iter_batched_ref(
|| create_benchmark_ui(ctx), || create_benchmark_ui(ui),
|ui| { |ui| {
ui.label("the quick brown fox jumps over the lazy dog"); ui.label("the quick brown fox jumps over the lazy dog");
}, },
@@ -78,7 +78,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
}); });
c.bench_function("label format!", |b| { c.bench_function("label format!", |b| {
b.iter_batched_ref( b.iter_batched_ref(
|| create_benchmark_ui(ctx), || create_benchmark_ui(ui),
|ui| { |ui| {
ui.label("the quick brown fox jumps over the lazy dog".to_owned()); ui.label("the quick brown fox jumps over the lazy dog".to_owned());
}, },
@@ -90,7 +90,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
{ {
let ctx = egui::Context::default(); let ctx = egui::Context::default();
let _ = ctx.run(RawInput::default(), |ctx| { let _ = ctx.run_ui(RawInput::default(), |ui| {
let mut group = c.benchmark_group("button"); let mut group = c.benchmark_group("button");
// To ensure we have a valid image, let's use the font texture. The size // To ensure we have a valid image, let's use the font texture. The size
@@ -99,7 +99,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
group.bench_function("1_button_text", |b| { group.bench_function("1_button_text", |b| {
b.iter_batched_ref( b.iter_batched_ref(
|| create_benchmark_ui(ctx), || create_benchmark_ui(ui),
|ui| { |ui| {
ui.add(Button::new("Hello World")); ui.add(Button::new("Hello World"));
}, },
@@ -108,7 +108,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
}); });
group.bench_function("2_button_text_image", |b| { group.bench_function("2_button_text_image", |b| {
b.iter_batched_ref( b.iter_batched_ref(
|| create_benchmark_ui(ctx), || create_benchmark_ui(ui),
|ui| { |ui| {
ui.add(Button::image_and_text(image, "Hello World")); ui.add(Button::image_and_text(image, "Hello World"));
}, },
@@ -117,7 +117,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
}); });
group.bench_function("3_button_text_image_right_text", |b| { group.bench_function("3_button_text_image_right_text", |b| {
b.iter_batched_ref( b.iter_batched_ref(
|| create_benchmark_ui(ctx), || create_benchmark_ui(ui),
|ui| { |ui| {
ui.add(Button::image_and_text(image, "Hello World").right_text("")); ui.add(Button::image_and_text(image, "Hello World").right_text(""));
}, },
@@ -126,7 +126,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
}); });
group.bench_function("4_button_italic", |b| { group.bench_function("4_button_italic", |b| {
b.iter_batched_ref( b.iter_batched_ref(
|| create_benchmark_ui(ctx), || create_benchmark_ui(ui),
|ui| { |ui| {
ui.add(Button::new(RichText::new("Hello World").italics())); ui.add(Button::new(RichText::new("Hello World").italics()));
}, },
@@ -161,15 +161,11 @@ pub fn criterion_benchmark(c: &mut Criterion) {
{ {
let pixels_per_point = 1.0; let pixels_per_point = 1.0;
let max_texture_side = 8 * 1024;
let wrap_width = 512.0; let wrap_width = 512.0;
let font_id = egui::FontId::default(); let font_id = egui::FontId::default();
let text_color = egui::Color32::WHITE; let text_color = egui::Color32::WHITE;
let mut fonts = egui::epaint::text::Fonts::new( let mut fonts =
max_texture_side, egui::epaint::text::Fonts::new(Default::default(), egui::FontDefinitions::default());
egui::epaint::AlphaFromCoverage::default(),
egui::FontDefinitions::default(),
);
{ {
c.bench_function("text_layout_uncached", |b| { c.bench_function("text_layout_uncached", |b| {
b.iter(|| { b.iter(|| {
@@ -209,7 +205,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
let mut rng = rand::rng(); let mut rng = rand::rng();
b.iter(|| { b.iter(|| {
fonts.begin_pass(max_texture_side, egui::epaint::AlphaFromCoverage::default()); fonts.begin_pass(egui::epaint::TextOptions::default());
// Delete a random character, simulating a user making an edit in a long file: // Delete a random character, simulating a user making an edit in a long file:
let mut new_string = string.clone(); let mut new_string = string.clone();

View File

@@ -40,7 +40,7 @@ impl crate::View for DancingStrings {
.on_hover_text("Demonstrates how a path can have varying color across its length."); .on_hover_text("Demonstrates how a path can have varying color across its length.");
Frame::canvas(ui.style()).show(ui, |ui| { Frame::canvas(ui.style()).show(ui, |ui| {
ui.ctx().request_repaint(); ui.request_repaint();
let time = ui.input(|i| i.time); let time = ui.input(|i| i.time);
let desired_size = ui.available_width() * vec2(1.0, 0.35); let desired_size = ui.available_width() * vec2(1.0, 0.35);

View File

@@ -195,11 +195,11 @@ impl Default for DemoWindows {
impl DemoWindows { impl DemoWindows {
/// Show the app ui (menu bar and windows). /// Show the app ui (menu bar and windows).
pub fn ui(&mut self, ctx: &Context) { pub fn ui(&mut self, ui: &mut egui::Ui) {
if is_mobile(ctx) { if is_mobile(ui.ctx()) {
self.mobile_ui(ctx); self.mobile_ui(ui);
} else { } else {
self.desktop_ui(ctx); self.desktop_ui(ui);
} }
} }
@@ -207,36 +207,36 @@ impl DemoWindows {
self.open.contains(About::default().name()) self.open.contains(About::default().name())
} }
fn mobile_ui(&mut self, ctx: &Context) { fn mobile_ui(&mut self, ui: &mut egui::Ui) {
if self.about_is_open() { if self.about_is_open() {
let mut close = false; let mut close = false;
egui::CentralPanel::default().show(ctx, |ui| {
egui::ScrollArea::vertical() egui::ScrollArea::vertical()
.auto_shrink(false) .auto_shrink(false)
.show(ui, |ui| { .show(ui, |ui| {
self.groups.about.ui(ui); self.groups.about.ui(ui);
ui.add_space(12.0); ui.add_space(12.0);
ui.vertical_centered_justified(|ui| { ui.vertical_centered_justified(|ui| {
if ui if ui
.button(egui::RichText::new("Continue to the demo!").size(20.0)) .button(egui::RichText::new("Continue to the demo!").size(20.0))
.clicked() .clicked()
{ {
close = true; close = true;
} }
});
}); });
}); });
if close { if close {
set_open(&mut self.open, About::default().name(), false); set_open(&mut self.open, About::default().name(), false);
} }
} else { } else {
self.mobile_top_bar(ctx); self.mobile_top_bar(ui);
self.groups.windows(ctx, &mut self.open); self.groups.windows(ui.ctx(), &mut self.open);
} }
} }
fn mobile_top_bar(&mut self, ctx: &Context) { fn mobile_top_bar(&mut self, ui: &mut egui::Ui) {
egui::Panel::top("menu_bar").show(ctx, |ui| { egui::Panel::top("menu_bar").show_inside(ui, |ui| {
menu::MenuBar::new() menu::MenuBar::new()
.config(menu::MenuConfig::new().style(StyleModifier::default())) .config(menu::MenuConfig::new().style(StyleModifier::default()))
.ui(ui, |ui| { .ui(ui, |ui| {
@@ -261,12 +261,12 @@ impl DemoWindows {
}); });
} }
fn desktop_ui(&mut self, ctx: &Context) { fn desktop_ui(&mut self, ui: &mut egui::Ui) {
egui::Panel::right("egui_demo_panel") egui::Panel::right("egui_demo_panel")
.resizable(false) .resizable(false)
.default_size(160.0) .default_size(160.0)
.min_size(160.0) .min_size(160.0)
.show(ctx, |ui| { .show_inside(ui, |ui| {
ui.add_space(4.0); ui.add_space(4.0);
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.heading("✒ egui demos"); ui.heading("✒ egui demos");
@@ -289,13 +289,13 @@ impl DemoWindows {
self.demo_list_ui(ui); self.demo_list_ui(ui);
}); });
egui::Panel::top("menu_bar").show(ctx, |ui| { egui::Panel::top("menu_bar").show_inside(ui, |ui| {
menu::MenuBar::new().ui(ui, |ui| { menu::MenuBar::new().ui(ui, |ui| {
file_menu_button(ui); file_menu_button(ui);
}); });
}); });
self.groups.windows(ctx, &mut self.open); self.groups.windows(ui.ctx(), &mut self.open);
} }
fn demo_list_ui(&mut self, ui: &mut egui::Ui) { fn demo_list_ui(&mut self, ui: &mut egui::Ui) {
@@ -304,7 +304,7 @@ impl DemoWindows {
self.groups.checkboxes(ui, &mut self.open); self.groups.checkboxes(ui, &mut self.open);
ui.separator(); ui.separator();
if ui.button("Organize windows").clicked() { if ui.button("Organize windows").clicked() {
ui.ctx().memory_mut(|mem| mem.reset_areas()); ui.memory_mut(|mem| mem.reset_areas());
} }
}); });
}); });
@@ -323,11 +323,11 @@ fn file_menu_button(ui: &mut Ui) {
// or else they would only be checked if the "File" menu was actually open! // or else they would only be checked if the "File" menu was actually open!
if ui.input_mut(|i| i.consume_shortcut(&organize_shortcut)) { if ui.input_mut(|i| i.consume_shortcut(&organize_shortcut)) {
ui.ctx().memory_mut(|mem| mem.reset_areas()); ui.memory_mut(|mem| mem.reset_areas());
} }
if ui.input_mut(|i| i.consume_shortcut(&reset_shortcut)) { if ui.input_mut(|i| i.consume_shortcut(&reset_shortcut)) {
ui.ctx().memory_mut(|mem| *mem = Default::default()); ui.memory_mut(|mem| *mem = Default::default());
} }
ui.menu_button("File", |ui| { ui.menu_button("File", |ui| {
@@ -352,7 +352,7 @@ fn file_menu_button(ui: &mut Ui) {
) )
.clicked() .clicked()
{ {
ui.ctx().memory_mut(|mem| mem.reset_areas()); ui.memory_mut(|mem| mem.reset_areas());
} }
if ui if ui
@@ -363,7 +363,7 @@ fn file_menu_button(ui: &mut Ui) {
.on_hover_text("Forget scroll, positions, sizes etc") .on_hover_text("Forget scroll, positions, sizes etc")
.clicked() .clicked()
{ {
ui.ctx().memory_mut(|mem| *mem = Default::default()); ui.memory_mut(|mem| *mem = Default::default());
} }
}); });
} }
@@ -372,7 +372,7 @@ fn file_menu_button(ui: &mut Ui) {
mod tests { mod tests {
use crate::{Demo as _, demo::demo_app_windows::DemoGroups}; use crate::{Demo as _, demo::demo_app_windows::DemoGroups};
use egui_kittest::kittest::{NodeT as _, Queryable as _}; use egui_kittest::kittest::Queryable as _;
use egui_kittest::{Harness, OsThreshold, SnapshotOptions, SnapshotResults}; use egui_kittest::{Harness, OsThreshold, SnapshotOptions, SnapshotResults};
#[test] #[test]
@@ -394,12 +394,15 @@ mod tests {
let name = remove_leading_emoji(demo.name()); let name = remove_leading_emoji(demo.name());
let mut harness = Harness::new(|ctx| { let mut harness = Harness::new_ui(|ui| {
egui_extras::install_image_loaders(ctx); egui_extras::install_image_loaders(ui);
demo.show(ctx, &mut true); demo.show(ui, &mut true);
}); });
let window = harness.queryable_node().children().next().unwrap(); let window = harness
.get_all_by_role(egui::accesskit::Role::Window)
.next()
.unwrap();
// TODO(lucasmerlin): Windows should probably have a label? // TODO(lucasmerlin): Windows should probably have a label?
//let window = harness.get_by_label(name); //let window = harness.get_by_label(name);

View File

@@ -22,17 +22,12 @@ impl crate::Demo for ExtraViewport {
egui::ViewportBuilder::default() egui::ViewportBuilder::default()
.with_title(self.name()) .with_title(self.name())
.with_inner_size([400.0, 512.0]), .with_inner_size([400.0, 512.0]),
|ctx, class| { |ui, class| {
if class == egui::ViewportClass::Embedded { if class == egui::ViewportClass::EmbeddedWindow {
// Not a real viewport // Not a real viewport
egui::Window::new(self.name()) ui.label("This egui integration does not support multiple viewports");
.id(id)
.open(open)
.show(ctx, |ui| {
ui.label("This egui integration does not support multiple viewports");
});
} else { } else {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show_inside(ui, |ui| {
viewport_content(ui, ctx, open); viewport_content(ui, ctx, open);
}); });
} }

View File

@@ -102,7 +102,7 @@ impl crate::View for FontBook {
}; };
if ui.add(button).on_hover_ui(tooltip_ui).clicked() { if ui.add(button).on_hover_ui(tooltip_ui).clicked() {
ui.ctx().copy_text(chr.to_string()); ui.copy_text(chr.to_string());
} }
} }
} }

View File

@@ -541,12 +541,12 @@ fn ui_stack_demo(ui: &mut Ui) {
let response = ui.label(format!("{:?}", node.id)); let response = ui.label(format!("{:?}", node.id));
if response.hovered() { if response.hovered() {
ui.ctx().debug_painter().debug_rect( ui.debug_painter().debug_rect(
node.max_rect, node.max_rect,
Color32::GREEN, Color32::GREEN,
"max_rect", "max_rect",
); );
ui.ctx().debug_painter().circle_filled( ui.debug_painter().circle_filled(
node.min_rect.min, node.min_rect.min,
2.0, 2.0,
Color32::RED, Color32::RED,

View File

@@ -149,7 +149,7 @@ impl crate::View for Modals {
*user_modal_open = false; *user_modal_open = false;
} else { } else {
*save_progress = Some(progress + 0.003); *save_progress = Some(progress + 0.003);
ui.ctx().request_repaint(); ui.request_repaint();
} }
}); });
} }
@@ -176,9 +176,9 @@ mod tests {
..Modals::default() ..Modals::default()
}; };
let mut harness = Harness::new_state( let mut harness = Harness::new_ui_state(
|ctx, modals| { |ui, modals| {
modals.show(ctx, &mut true); modals.show(ui, &mut true);
}, },
initial_state, initial_state,
); );
@@ -204,9 +204,9 @@ mod tests {
..Modals::default() ..Modals::default()
}; };
let mut harness = Harness::new_state( let mut harness = Harness::new_ui_state(
|ctx, modals| { |ui, modals| {
modals.show(ctx, &mut true); modals.show(ui, &mut true);
}, },
initial_state, initial_state,
); );
@@ -228,9 +228,9 @@ mod tests {
..Modals::default() ..Modals::default()
}; };
let mut harness = Harness::new_state( let mut harness = Harness::new_ui_state(
|ctx, modals| { |ui, modals| {
modals.show(ctx, &mut true); modals.show(ui, &mut true);
}, },
initial_state, initial_state,
); );
@@ -258,9 +258,9 @@ mod tests {
..Modals::default() ..Modals::default()
}; };
let mut harness = Harness::new_state( let mut harness = Harness::new_ui_state(
|ctx, modals| { |ui, modals| {
modals.show(ctx, &mut true); modals.show(ui, &mut true);
}, },
initial_state, initial_state,
); );

View File

@@ -141,7 +141,7 @@ impl MultiTouch {
let delay = 0.5; let delay = 0.5;
if time_since_last_touch < delay { if time_since_last_touch < delay {
ui.ctx().request_repaint(); ui.request_repaint();
} else { } else {
// seconds after which half the amount of zoom/rotation will be reverted: // seconds after which half the amount of zoom/rotation will be reverted:
let half_life = let half_life =
@@ -157,7 +157,7 @@ impl MultiTouch {
self.zoom = 1. + ((self.zoom - 1.) * half_life_factor); self.zoom = 1. + ((self.zoom - 1.) * half_life_factor);
self.rotation *= half_life_factor; self.rotation *= half_life_factor;
self.translation *= half_life_factor; self.translation *= half_life_factor;
ui.ctx().request_repaint(); ui.request_repaint();
} }
} }
} }

View File

@@ -73,8 +73,7 @@ impl PaintBezier {
ui.collapsing("Global tessellation options", |ui| { ui.collapsing("Global tessellation options", |ui| {
let mut tessellation_options = ui.ctx().tessellation_options(|to| *to); let mut tessellation_options = ui.ctx().tessellation_options(|to| *to);
tessellation_options.ui(ui); tessellation_options.ui(ui);
ui.ctx() ui.tessellation_options_mut(|to| *to = tessellation_options);
.tessellation_options_mut(|to| *to = tessellation_options);
}); });
ui.radio_value(&mut self.degree, 3, "Quadratic Bézier"); ui.radio_value(&mut self.degree, 3, "Quadratic Bézier");

View File

@@ -72,6 +72,7 @@ impl crate::View for Panels {
}); });
}); });
// TODO(emilk): This extra panel is superfluous - just use what's left of `ui` instead
egui::CentralPanel::default().show_inside(ui, |ui| { egui::CentralPanel::default().show_inside(ui, |ui| {
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.heading("Central Panel"); ui.heading("Central Panel");

View File

@@ -43,12 +43,11 @@ impl crate::View for Screenshot {
let capture = ui.button("📷 Take Screenshot").clicked(); let capture = ui.button("📷 Take Screenshot").clicked();
ui.checkbox(&mut self.continuous, "Capture continuously"); ui.checkbox(&mut self.continuous, "Capture continuously");
if capture || self.continuous { if capture || self.continuous {
ui.ctx() ui.send_viewport_cmd(ViewportCommand::Screenshot(UserData::default()));
.send_viewport_cmd(ViewportCommand::Screenshot(UserData::default()));
} }
}); });
let image = ui.ctx().input(|i| { let image = ui.input(|i| {
i.events i.events
.iter() .iter()
.filter_map(|e| { .filter_map(|e| {

View File

@@ -114,7 +114,7 @@ impl ScrollAppearance {
visibility, visibility,
} = self; } = self;
let mut scroll = ui.ctx().style().spacing.scroll; let mut scroll = ui.global_style().spacing.scroll;
scroll.ui(ui); scroll.ui(ui);
@@ -388,6 +388,6 @@ impl crate::View for ScrollStickTo {
); );
self.n_items += 1; self.n_items += 1;
ui.ctx().request_repaint(); ui.request_repaint();
} }
} }

View File

@@ -32,7 +32,7 @@ impl crate::View for ClipboardTest {
.horizontal(|ui| { .horizontal(|ui| {
let text_edit_response = ui.text_edit_singleline(&mut self.text); let text_edit_response = ui.text_edit_singleline(&mut self.text);
if ui.button("📋").clicked() { if ui.button("📋").clicked() {
ui.ctx().copy_text(self.text.clone()); ui.copy_text(self.text.clone());
} }
text_edit_response text_edit_response
}) })
@@ -48,7 +48,7 @@ impl crate::View for ClipboardTest {
] { ] {
if ui.button(name).clicked() { if ui.button(name).clicked() {
// Next frame we should get a copy/cut/paste-event… // Next frame we should get a copy/cut/paste-event…
ui.ctx().send_viewport_cmd(cmd); ui.send_viewport_cmd(cmd);
// …that should en up here: // …that should en up here:
text_edit_response.request_focus(); text_edit_response.request_focus();
@@ -69,7 +69,7 @@ impl crate::View for ClipboardTest {
ui.ctx().try_load_image(&uri, Default::default()) ui.ctx().try_load_image(&uri, Default::default())
&& ui.button("📋").clicked() && ui.button("📋").clicked()
{ {
ui.ctx().copy_image((*image).clone()); ui.copy_image((*image).clone());
} }
}); });

View File

@@ -17,7 +17,7 @@ impl crate::Demo for IdTest {
impl crate::View for IdTest { impl crate::View for IdTest {
fn ui(&mut self, ui: &mut egui::Ui) { fn ui(&mut self, ui: &mut egui::Ui) {
// Make sure the warnings are on (by default they are only on in debug builds). // Make sure the warnings are on (by default they are only on in debug builds).
ui.ctx().options_mut(|opt| opt.warn_on_id_clash = true); ui.options_mut(|opt| opt.warn_on_id_clash = true);
ui.heading("Name collision example"); ui.heading("Name collision example");

View File

@@ -357,11 +357,13 @@ fn rect_shape_ui(ui: &mut egui::Ui, shape: &mut RectShape) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::View as _; use crate::View as _;
use egui_kittest::SnapshotResults;
use super::*; use super::*;
#[test] #[test]
fn snapshot_tessellation_test() { fn snapshot_tessellation_test() {
let mut results = SnapshotResults::new();
for (name, shape) in TessellationTest::interesting_shapes() { for (name, shape) in TessellationTest::interesting_shapes() {
let mut test = TessellationTest { let mut test = TessellationTest {
shape, shape,
@@ -375,6 +377,7 @@ mod tests {
harness.run(); harness.run();
harness.snapshot(format!("tessellation_test/{name}")); harness.snapshot(format!("tessellation_test/{name}"));
results.extend_harness(&mut harness);
} }
} }
} }

View File

@@ -92,7 +92,7 @@ impl crate::View for TextEditDemo {
.cursor .cursor
.set_char_range(Some(egui::text::CCursorRange::one(ccursor))); .set_char_range(Some(egui::text::CCursorRange::one(ccursor)));
state.store(ui.ctx(), text_edit_id); state.store(ui.ctx(), text_edit_id);
ui.ctx().memory_mut(|mem| mem.request_focus(text_edit_id)); // give focus back to the [`TextEdit`]. ui.memory_mut(|mem| mem.request_focus(text_edit_id)); // give focus back to the [`TextEdit`].
} }
} }
@@ -104,7 +104,7 @@ impl crate::View for TextEditDemo {
.cursor .cursor
.set_char_range(Some(egui::text::CCursorRange::one(ccursor))); .set_char_range(Some(egui::text::CCursorRange::one(ccursor)));
state.store(ui.ctx(), text_edit_id); state.store(ui.ctx(), text_edit_id);
ui.ctx().memory_mut(|mem| mem.request_focus(text_edit_id)); // give focus back to the [`TextEdit`]. ui.memory_mut(|mem| mem.request_focus(text_edit_id)); // give focus back to the [`TextEdit`].
} }
} }
}); });
@@ -120,9 +120,9 @@ mod tests {
#[test] #[test]
pub fn should_type() { pub fn should_type() {
let text = "Hello, world!".to_owned(); let text = "Hello, world!".to_owned();
let mut harness = Harness::new_state( let mut harness = Harness::new_ui_state(
move |ctx, text| { move |ui, text| {
CentralPanel::default().show(ctx, |ui| { CentralPanel::default().show_inside(ui, |ui| {
ui.text_edit_singleline(text); ui.text_edit_singleline(text);
}); });
}, },

View File

@@ -52,7 +52,7 @@ impl crate::View for TextLayoutDemo {
use egui::text::LayoutJob; use egui::text::LayoutJob;
let pixels_per_point = ui.ctx().pixels_per_point(); let pixels_per_point = ui.pixels_per_point();
let points_per_pixel = 1.0 / pixels_per_point; let points_per_pixel = 1.0 / pixels_per_point;
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {

View File

@@ -69,6 +69,6 @@ impl crate::View for UndoRedoDemo {
}); });
self.undoer self.undoer
.feed_state(ui.ctx().input(|input| input.time), &self.state); .feed_state(ui.input(|input| input.time), &self.state);
} }
} }

View File

@@ -310,7 +310,7 @@ mod tests {
use super::*; use super::*;
use crate::View as _; use crate::View as _;
use egui::Vec2; use egui::Vec2;
use egui_kittest::Harness; use egui_kittest::{Harness, SnapshotResults};
#[test] #[test]
pub fn should_match_screenshot() { pub fn should_match_screenshot() {
@@ -320,6 +320,8 @@ mod tests {
..Default::default() ..Default::default()
}; };
let mut results = SnapshotResults::new();
for pixels_per_point in [1, 2] { for pixels_per_point in [1, 2] {
for theme in [egui::Theme::Light, egui::Theme::Dark] { for theme in [egui::Theme::Light, egui::Theme::Dark] {
let mut harness = Harness::builder() let mut harness = Harness::builder()
@@ -339,6 +341,7 @@ mod tests {
}; };
let image_name = format!("widget_gallery_{theme_name}_x{pixels_per_point}"); let image_name = format!("widget_gallery_{theme_name}_x{pixels_per_point}");
harness.snapshot(&image_name); harness.snapshot(&image_name);
results.extend_harness(&mut harness);
} }
} }
} }

View File

@@ -32,15 +32,15 @@ impl Default for EasyMarkEditor {
} }
impl EasyMarkEditor { impl EasyMarkEditor {
pub fn panels(&mut self, ctx: &egui::Context) { pub fn panels(&mut self, ui: &mut egui::Ui) {
egui::Panel::bottom("easy_mark_bottom").show(ctx, |ui| { egui::Panel::bottom("easy_mark_bottom").show_inside(ui, |ui| {
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| { ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {
ui.add(crate::egui_github_link_file!()) ui.add(crate::egui_github_link_file!())
}) })
}); });
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show_inside(ui, |ui| {
self.ui(ui); self.ui(ui);
}); });
} }

View File

@@ -73,8 +73,8 @@ fn test_egui_e2e() {
const NUM_FRAMES: usize = 5; const NUM_FRAMES: usize = 5;
for _ in 0..NUM_FRAMES { for _ in 0..NUM_FRAMES {
let full_output = ctx.run(raw_input.clone(), |ctx| { let full_output = ctx.run_ui(raw_input.clone(), |ui| {
demo_windows.ui(ctx); demo_windows.ui(ui);
}); });
let clipped_primitives = ctx.tessellate(full_output.shapes, full_output.pixels_per_point); let clipped_primitives = ctx.tessellate(full_output.shapes, full_output.pixels_per_point);
assert!(!clipped_primitives.is_empty()); assert!(!clipped_primitives.is_empty());
@@ -92,8 +92,8 @@ fn test_egui_zero_window_size() {
const NUM_FRAMES: usize = 5; const NUM_FRAMES: usize = 5;
for _ in 0..NUM_FRAMES { for _ in 0..NUM_FRAMES {
let full_output = ctx.run(raw_input.clone(), |ctx| { let full_output = ctx.run_ui(raw_input.clone(), |ui| {
demo_windows.ui(ctx); demo_windows.ui(ui);
}); });
let clipped_primitives = ctx.tessellate(full_output.shapes, full_output.pixels_per_point); let clipped_primitives = ctx.tessellate(full_output.shapes, full_output.pixels_per_point);
assert!( assert!(

View File

@@ -461,7 +461,7 @@ fn pixel_test_strokes(ui: &mut Ui) {
egui::Color32::BLACK egui::Color32::BLACK
}; };
let pixels_per_point = ui.ctx().pixels_per_point(); let pixels_per_point = ui.pixels_per_point();
for thickness_pixels in 1..=3 { for thickness_pixels in 1..=3 {
let thickness_pixels = thickness_pixels as f32; let thickness_pixels = thickness_pixels as f32;
@@ -506,7 +506,7 @@ fn pixel_test_squares(ui: &mut Ui) {
egui::Color32::BLACK egui::Color32::BLACK
}; };
let pixels_per_point = ui.ctx().pixels_per_point(); let pixels_per_point = ui.pixels_per_point();
let num_squares = (pixels_per_point * 10.0).round().max(10.0) as u32; let num_squares = (pixels_per_point * 10.0).round().max(10.0) as u32;
let size_pixels = vec2( let size_pixels = vec2(
@@ -532,7 +532,7 @@ fn pixel_test_squares(ui: &mut Ui) {
} }
fn pixel_test_lines(ui: &mut Ui) { fn pixel_test_lines(ui: &mut Ui) {
let pixels_per_point = ui.ctx().pixels_per_point(); let pixels_per_point = ui.pixels_per_point();
let n = (96.0 * pixels_per_point) as usize; let n = (96.0 * pixels_per_point) as usize;
ui.label("The lines should be exactly one physical pixel wide, one physical pixel apart."); ui.label("The lines should be exactly one physical pixel wide, one physical pixel apart.");

View File

@@ -3,6 +3,7 @@ use egui_kittest::Harness;
#[test] #[test]
fn test_image_blending() { fn test_image_blending() {
let mut results = egui_kittest::SnapshotResults::new();
for pixels_per_point in [1.0, 2.0] { for pixels_per_point in [1.0, 2.0] {
let mut harness = Harness::builder() let mut harness = Harness::builder()
.with_pixels_per_point(pixels_per_point) .with_pixels_per_point(pixels_per_point)
@@ -21,5 +22,6 @@ fn test_image_blending() {
harness.run(); harness.run();
harness.fit_contents(); harness.fit_contents();
harness.snapshot(format!("image_blending/image_x{pixels_per_point}")); harness.snapshot(format!("image_blending/image_x{pixels_per_point}"));
results.extend_harness(&mut harness);
} }
} }

View File

@@ -3,6 +3,7 @@ use egui_kittest::{Harness, kittest::Queryable as _};
#[test] #[test]
fn test_kerning() { fn test_kerning() {
let mut results = egui_kittest::SnapshotResults::new();
for pixels_per_point in [1.0, 2.0] { for pixels_per_point in [1.0, 2.0] {
for theme in [egui::Theme::Dark, egui::Theme::Light] { for theme in [egui::Theme::Dark, egui::Theme::Light] {
let mut harness = Harness::builder() let mut harness = Harness::builder()
@@ -24,12 +25,14 @@ fn test_kerning() {
egui::Theme::Light => "light", egui::Theme::Light => "light",
} }
)); ));
results.extend_harness(&mut harness);
} }
} }
} }
#[test] #[test]
fn test_italics() { fn test_italics() {
let mut results = egui_kittest::SnapshotResults::new();
for pixels_per_point in [1.0, 2.0_f32.sqrt(), 2.0] { for pixels_per_point in [1.0, 2.0_f32.sqrt(), 2.0] {
for theme in [egui::Theme::Dark, egui::Theme::Light] { for theme in [egui::Theme::Dark, egui::Theme::Light] {
let mut harness = Harness::builder() let mut harness = Harness::builder()
@@ -49,6 +52,7 @@ fn test_italics() {
egui::Theme::Light => "light", egui::Theme::Light => "light",
} }
)); ));
results.extend_harness(&mut harness);
} }
} }
} }

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