mirror of
https://github.com/emilk/egui.git
synced 2026-06-27 23:13:13 -04:00
update with main
This commit is contained in:
2
.github/workflows/cargo_machete.yml
vendored
2
.github/workflows/cargo_machete.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
steps:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.88
|
||||
toolchain: 1.92
|
||||
- name: Machete install
|
||||
## The official cargo-machete action
|
||||
uses: bnjbvr/cargo-machete@v0.9.1
|
||||
|
||||
2
.github/workflows/deploy_web_demo.yml
vendored
2
.github/workflows/deploy_web_demo.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
with:
|
||||
profile: minimal
|
||||
target: wasm32-unknown-unknown
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.92.0
|
||||
override: true
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
name: Check spelling and links
|
||||
on: [pull_request]
|
||||
name: Link checker
|
||||
# on: [pull_request] # Disabled because it is so broken
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
typos:
|
||||
# https://github.com/crate-ci/typos
|
||||
# Add exceptions to .typos.toml
|
||||
# install and run locally: cargo install typos-cli && typos
|
||||
name: typos
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check spelling of entire workspace
|
||||
uses: crate-ci/typos@v1.38.0
|
||||
|
||||
lychee:
|
||||
name: lychee
|
||||
runs-on: ubuntu-latest
|
||||
@@ -33,4 +20,3 @@ jobs:
|
||||
uses: lycheeverse/lychee-action@v2
|
||||
with:
|
||||
args: "'**/*.md' '**/*.toml' --exclude localhost --exclude reddit.com" # I guess reddit doesn't like github action IPs
|
||||
|
||||
2
.github/workflows/preview_build.yml
vendored
2
.github/workflows/preview_build.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.92.0
|
||||
targets: wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
|
||||
14
.github/workflows/rust.yml
vendored
14
.github/workflows/rust.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.92.0
|
||||
|
||||
- name: Install packages (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.92.0
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
- run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
with:
|
||||
rust-version: "1.88.0"
|
||||
rust-version: "1.92.0"
|
||||
log-level: error
|
||||
command: check
|
||||
arguments: --target ${{ matrix.target }}
|
||||
@@ -164,7 +164,7 @@ jobs:
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.92.0
|
||||
targets: aarch64-linux-android
|
||||
|
||||
- name: Set up cargo cache
|
||||
@@ -186,7 +186,7 @@ jobs:
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.92.0
|
||||
targets: aarch64-apple-ios
|
||||
|
||||
- name: Set up cargo cache
|
||||
@@ -206,7 +206,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.92.0
|
||||
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@@ -228,7 +228,7 @@ jobs:
|
||||
lfs: true
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
toolchain: 1.92.0
|
||||
|
||||
- name: Set up cargo cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
17
.github/workflows/typos.yml
vendored
Normal file
17
.github/workflows/typos.yml
vendored
Normal 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
|
||||
@@ -5,8 +5,8 @@
|
||||
[default.extend-words]
|
||||
ime = "ime" # Input Method Editor
|
||||
nknown = "nknown" # part of @55nknown username
|
||||
isse = "isse" # part of @IsseW username
|
||||
tye = "tye" # part of @tye-exe username
|
||||
isse = "isse" # part of @IsseW username
|
||||
tye = "tye" # part of @tye-exe username
|
||||
ro = "ro" # read-only, also part of the username @Phen-Ro
|
||||
typ = "typ" # Often used because `type` is a keyword in Rust
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
* Treat `.` as a word-splitter in text navigation [#7741](https://github.com/emilk/egui/pull/7741) by [@emilk](https://github.com/emilk)
|
||||
* Change text color of selected text [#7691](https://github.com/emilk/egui/pull/7691) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
## 0.33.2 - 2025-11-13
|
||||
### ⭐ Added
|
||||
* Add `Plugin::on_widget_under_pointer` to support widget inspector [#7652](https://github.com/emilk/egui/pull/7652) by [@juancampa](https://github.com/juancampa)
|
||||
|
||||
196
Cargo.lock
196
Cargo.lock
@@ -863,6 +863,15 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a18ef4657441fb193b65f34dc39b3781f0dfec23d3bd94d0eeb4e88cde421edb"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-hex"
|
||||
version = "0.2.0"
|
||||
@@ -1248,7 +1257,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
|
||||
|
||||
[[package]]
|
||||
name = "ecolor"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cint",
|
||||
@@ -1260,7 +1269,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "eframe"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1299,7 +1308,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"ahash",
|
||||
@@ -1319,7 +1328,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui-wgpu"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1337,7 +1346,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui-winit"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"accesskit_winit",
|
||||
"arboard",
|
||||
@@ -1360,7 +1369,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_demo_app"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
@@ -1390,7 +1399,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_demo_lib"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"criterion",
|
||||
@@ -1407,7 +1416,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_extras"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"chrono",
|
||||
@@ -1426,7 +1435,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_glow"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
@@ -1445,7 +1454,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_kittest"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"dify",
|
||||
"document-features",
|
||||
@@ -1457,13 +1466,15 @@ dependencies = [
|
||||
"kittest",
|
||||
"open",
|
||||
"pollster",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui_tests"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"egui",
|
||||
"egui_extras",
|
||||
@@ -1473,9 +1484,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ehttp"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59a81c221a1e4dad06cb9c9deb19aea1193a5eea084e8cd42d869068132bf876"
|
||||
checksum = "04499d3c719edecfad5c9b46031726c8540905d73be6d7e4f9788c4a298da908"
|
||||
dependencies = [
|
||||
"document-features",
|
||||
"js-sys",
|
||||
@@ -1493,7 +1504,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "emath"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
@@ -1591,9 +1602,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "epaint"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
"criterion",
|
||||
@@ -1607,13 +1617,16 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"profiling",
|
||||
"rayon",
|
||||
"self_cell",
|
||||
"serde",
|
||||
"similar-asserts",
|
||||
"skrifa",
|
||||
"vello_cpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epaint_default_fonts"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
@@ -1637,6 +1650,15 @@ version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
|
||||
|
||||
[[package]]
|
||||
name = "euclid"
|
||||
version = "0.22.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.3.1"
|
||||
@@ -1704,6 +1726,15 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fearless_simd"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fb2907d1f08b2b316b9223ced5b0e89d87028ba8deae9764741dba8ff7f3903"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "file_dialog"
|
||||
version = "0.1.0"
|
||||
@@ -1747,6 +1778,15 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "font-types"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontconfig-parser"
|
||||
version = "0.5.7"
|
||||
@@ -2526,6 +2566,17 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kurbo"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce9729cc38c18d86123ab736fd2e7151763ba226ac2490ec092d1dd148825e32"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"euclid",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -2575,6 +2626,12 @@ dependencies = [
|
||||
"redox_syscall 0.5.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linebender_resource_handle"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
@@ -3251,6 +3308,19 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
||||
|
||||
[[package]]
|
||||
name = "peniko"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3c76095c9a636173600478e0373218c7b955335048c2bcd12dc6a79657649d8"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"color",
|
||||
"kurbo 0.12.0",
|
||||
"linebender_resource_handle",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@@ -3381,9 +3451,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.14"
|
||||
version = "0.17.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0"
|
||||
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
@@ -3425,7 +3495,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||
|
||||
[[package]]
|
||||
name = "popups"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"env_logger",
|
||||
@@ -3690,6 +3760,16 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "read-fonts"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"font-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
@@ -3983,6 +4063,12 @@ dependencies = [
|
||||
"tiny-skia",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "self_cell"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
@@ -4036,6 +4122,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_windows"
|
||||
version = "0.1.0"
|
||||
@@ -4101,6 +4196,16 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "skrifa"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"read-fonts",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
@@ -4222,7 +4327,7 @@ version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
|
||||
dependencies = [
|
||||
"kurbo",
|
||||
"kurbo 0.11.1",
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
@@ -4491,11 +4596,26 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
@@ -4504,6 +4624,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
@@ -4707,7 +4829,7 @@ dependencies = [
|
||||
"flate2",
|
||||
"fontdb",
|
||||
"imagesize",
|
||||
"kurbo",
|
||||
"kurbo 0.11.1",
|
||||
"log",
|
||||
"pico-args",
|
||||
"roxmltree",
|
||||
@@ -4735,6 +4857,30 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "vello_common"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a235ba928b3109ad9e7696270edb09445a52ae1c7c08e6d31a19b1cdd6cbc24a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"fearless_simd",
|
||||
"log",
|
||||
"peniko",
|
||||
"skrifa",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vello_cpu"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0bd1fcf9c1814f17a491e07113623d44e3ec1125a9f3401f5e047d6d326da21"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"vello_common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
@@ -5645,9 +5791,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.3"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -5748,7 +5894,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.33.2"
|
||||
version = "0.33.3"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
|
||||
48
Cargo.toml
48
Cargo.toml
@@ -23,8 +23,8 @@ members = [
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.88"
|
||||
version = "0.33.2"
|
||||
rust-version = "1.92"
|
||||
version = "0.33.3"
|
||||
|
||||
|
||||
[profile.release]
|
||||
@@ -55,23 +55,22 @@ opt-level = 2
|
||||
|
||||
|
||||
[workspace.dependencies]
|
||||
emath = { version = "0.33.2", path = "crates/emath", default-features = false }
|
||||
ecolor = { version = "0.33.2", path = "crates/ecolor", default-features = false }
|
||||
epaint = { version = "0.33.2", path = "crates/epaint", default-features = false }
|
||||
epaint_default_fonts = { version = "0.33.2", path = "crates/epaint_default_fonts" }
|
||||
egui = { version = "0.33.2", path = "crates/egui", default-features = false }
|
||||
egui-winit = { version = "0.33.2", path = "crates/egui-winit", default-features = false }
|
||||
egui_extras = { version = "0.33.2", path = "crates/egui_extras", default-features = false }
|
||||
egui-wgpu = { version = "0.33.2", path = "crates/egui-wgpu", default-features = false }
|
||||
egui_demo_lib = { version = "0.33.2", path = "crates/egui_demo_lib", default-features = false }
|
||||
egui_glow = { version = "0.33.2", path = "crates/egui_glow", default-features = false }
|
||||
egui_kittest = { version = "0.33.2", path = "crates/egui_kittest", default-features = false }
|
||||
eframe = { version = "0.33.2", path = "crates/eframe", default-features = false }
|
||||
emath = { version = "0.33.3", path = "crates/emath", default-features = false }
|
||||
ecolor = { version = "0.33.3", path = "crates/ecolor", default-features = false }
|
||||
epaint = { version = "0.33.3", path = "crates/epaint", default-features = false }
|
||||
epaint_default_fonts = { version = "0.33.3", path = "crates/epaint_default_fonts" }
|
||||
egui = { version = "0.33.3", path = "crates/egui", default-features = false }
|
||||
egui-winit = { version = "0.33.3", path = "crates/egui-winit", default-features = false }
|
||||
egui_extras = { version = "0.33.3", path = "crates/egui_extras", default-features = false }
|
||||
egui-wgpu = { version = "0.33.3", path = "crates/egui-wgpu", default-features = false }
|
||||
egui_demo_lib = { version = "0.33.3", path = "crates/egui_demo_lib", default-features = false }
|
||||
egui_glow = { version = "0.33.3", path = "crates/egui_glow", default-features = false }
|
||||
egui_kittest = { version = "0.33.3", path = "crates/egui_kittest", default-features = false }
|
||||
eframe = { version = "0.33.3", path = "crates/eframe", default-features = false }
|
||||
|
||||
accesskit = "0.21.1"
|
||||
accesskit_consumer = "0.30.1"
|
||||
accesskit_winit = "0.29.1"
|
||||
ab_glyph = "0.2.32"
|
||||
ahash = { version = "0.8.12", default-features = false, features = [
|
||||
"no-rng", # we don't need DOS-protection, so we let users opt-in to it instead
|
||||
"std",
|
||||
@@ -88,7 +87,7 @@ criterion = { version = "0.7.0", default-features = false }
|
||||
dify = { version = "0.7.4", default-features = false }
|
||||
directories = "6.0.0"
|
||||
document-features = "0.2.11"
|
||||
ehttp = { version = "0.5.0", default-features = false }
|
||||
ehttp = { version = "0.6.0", default-features = false }
|
||||
enum-map = "2.7.3"
|
||||
env_logger = { version = "0.11.8", default-features = false }
|
||||
glow = "0.16.0"
|
||||
@@ -122,8 +121,10 @@ rayon = "1.11.0"
|
||||
resvg = { version = "0.45.1", default-features = false }
|
||||
rfd = "0.15.4"
|
||||
ron = "0.11.0"
|
||||
self_cell = "1.2.1"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
similar-asserts = "1.7.0"
|
||||
skrifa = { version = "0.37.0", default-features = false, features = ["std", "autohint_shaping"] }
|
||||
smallvec = "1.15.1"
|
||||
smithay-clipboard = "0.7.2"
|
||||
static_assertions = "1.1.0"
|
||||
@@ -131,9 +132,11 @@ syntect = { version = "5.3.0", default-features = false }
|
||||
tempfile = "3.23.0"
|
||||
thiserror = "2.0.17"
|
||||
tokio = "1.47.1"
|
||||
toml = "0.8"
|
||||
type-map = "0.5.1"
|
||||
unicode_names2 = { version = "2.0.0", default-features = false }
|
||||
unicode-segmentation = "1.12.0"
|
||||
vello_cpu = { version = "0.0.4", default-features = false, features = ["std"] }
|
||||
wasm-bindgen = "0.2.100" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml
|
||||
wasm-bindgen-futures = "0.4.0"
|
||||
wayland-cursor = { version = "0.31.11", default-features = false }
|
||||
@@ -181,7 +184,9 @@ branches_sharing_code = "warn"
|
||||
char_lit_as_u8 = "warn"
|
||||
checked_conversions = "warn"
|
||||
clear_with_drain = "warn"
|
||||
clone_on_ref_ptr = "warn"
|
||||
cloned_instead_of_copied = "warn"
|
||||
coerce_container_to_any = "warn"
|
||||
dbg_macro = "warn"
|
||||
debug_assert_with_mut_call = "warn"
|
||||
default_union_representation = "warn"
|
||||
@@ -191,7 +196,9 @@ disallowed_methods = "warn" # See clippy.toml
|
||||
disallowed_names = "warn" # See clippy.toml
|
||||
disallowed_script_idents = "warn" # See clippy.toml
|
||||
disallowed_types = "warn" # See clippy.toml
|
||||
doc_broken_link = "warn"
|
||||
doc_comment_double_space_linebreaks = "warn"
|
||||
doc_include_without_cfg = "warn"
|
||||
doc_link_with_quotes = "warn"
|
||||
doc_markdown = "warn"
|
||||
elidable_lifetime_names = "warn"
|
||||
@@ -224,6 +231,7 @@ inefficient_to_string = "warn"
|
||||
infinite_loop = "warn"
|
||||
into_iter_without_iter = "warn"
|
||||
invalid_upcast_comparisons = "warn"
|
||||
ip_constant = "warn"
|
||||
iter_filter_is_ok = "warn"
|
||||
iter_filter_is_some = "warn"
|
||||
iter_not_returning_iterator = "warn"
|
||||
@@ -277,6 +285,7 @@ non_zero_suggestions = "warn"
|
||||
nonstandard_macro_braces = "warn"
|
||||
option_as_ref_cloned = "warn"
|
||||
option_option = "warn"
|
||||
or_fun_call = "warn"
|
||||
path_buf_push_overwrite = "warn"
|
||||
pathbuf_init_then_push = "warn"
|
||||
precedence_bits = "warn"
|
||||
@@ -295,6 +304,7 @@ ref_patterns = "warn"
|
||||
rest_pat_in_fully_bound_structs = "warn"
|
||||
return_and_then = "warn"
|
||||
same_functions_in_if_condition = "warn"
|
||||
self_only_used_in_recursion = "warn"
|
||||
semicolon_if_nothing_returned = "warn"
|
||||
set_contains_or_insert = "warn"
|
||||
single_char_pattern = "warn"
|
||||
@@ -306,7 +316,6 @@ string_add = "warn"
|
||||
string_add_assign = "warn"
|
||||
string_lit_as_bytes = "warn"
|
||||
string_lit_chars_any = "warn"
|
||||
string_to_string = "warn"
|
||||
suspicious_command_arg_space = "warn"
|
||||
suspicious_xor_used_as_pow = "warn"
|
||||
todo = "warn"
|
||||
@@ -315,7 +324,7 @@ trailing_empty_array = "warn"
|
||||
trait_duplication_in_bounds = "warn"
|
||||
transmute_ptr_to_ptr = "warn"
|
||||
tuple_array_conversions = "warn"
|
||||
unchecked_duration_subtraction = "warn"
|
||||
unchecked_time_subtraction = "warn"
|
||||
undocumented_unsafe_blocks = "warn"
|
||||
unimplemented = "warn"
|
||||
uninhabited_references = "warn"
|
||||
@@ -335,6 +344,7 @@ unused_peekable = "warn"
|
||||
unused_rounding = "warn"
|
||||
unused_self = "warn"
|
||||
unused_trait_names = "warn"
|
||||
unwrap_used = "warn"
|
||||
use_self = "warn"
|
||||
useless_let_if_seq = "warn"
|
||||
useless_transmute = "warn"
|
||||
@@ -344,10 +354,10 @@ zero_sized_map_values = "warn"
|
||||
|
||||
|
||||
# TODO(emilk): maybe enable more of these lints?
|
||||
cast_possible_wrap = "allow"
|
||||
comparison_chain = "allow"
|
||||
should_panic_without_expect = "allow"
|
||||
too_many_lines = "allow"
|
||||
unwrap_used = "allow" # TODO(emilk): We really wanna warn on this one
|
||||
|
||||
# These are meh:
|
||||
assigning_clones = "allow" # No please
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Section identical to scripts/clippy_wasm/clippy.toml:
|
||||
|
||||
msrv = "1.88"
|
||||
msrv = "1.92"
|
||||
|
||||
allow-unwrap-in-tests = true
|
||||
|
||||
@@ -39,9 +39,9 @@ disallowed-methods = [
|
||||
# but we cannot disable them all here (because of e.g. https://github.com/rust-lang/rust-clippy/issues/10406)
|
||||
# so we do that in `clipppy_wasm.toml` instead.
|
||||
|
||||
{ path = "std::env::temp_dir", readon = "Use the tempfile crate instead" },
|
||||
{ path = "std::env::temp_dir", reason = "Use the tempfile crate instead" },
|
||||
{ path = "std::panic::catch_unwind", reason = "We compile with `panic = abort" },
|
||||
{ path = "std::thread::spawn", readon = "Use `std::thread::Builder` and name the thread" },
|
||||
{ path = "std::thread::spawn", reason = "Use `std::thread::Builder` and name the thread" },
|
||||
]
|
||||
|
||||
# https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.33.2 - 2025-11-13
|
||||
Nothing new
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ impl Hsva {
|
||||
/// From linear RGBA with premultiplied alpha
|
||||
#[inline]
|
||||
pub fn from_rgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
#![expect(clippy::many_single_char_names)]
|
||||
if a <= 0.0 {
|
||||
if r == 0.0 && b == 0.0 && a == 0.0 {
|
||||
Self::default()
|
||||
@@ -57,7 +57,7 @@ impl Hsva {
|
||||
/// From linear RGBA without premultiplied alpha
|
||||
#[inline]
|
||||
pub fn from_rgba_unmultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
#![expect(clippy::many_single_char_names)]
|
||||
let (h, s, v) = hsv_from_rgb([r, g, b]);
|
||||
Self { h, s, v, a }
|
||||
}
|
||||
@@ -189,7 +189,7 @@ impl From<Color32> for Hsva {
|
||||
/// All ranges in 0-1, rgb is linear.
|
||||
#[inline]
|
||||
pub fn hsv_from_rgb([r, g, b]: [f32; 3]) -> (f32, f32, f32) {
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
#![expect(clippy::many_single_char_names)]
|
||||
let min = r.min(g.min(b));
|
||||
let max = r.max(g.max(b)); // value
|
||||
|
||||
@@ -213,7 +213,7 @@ pub fn hsv_from_rgb([r, g, b]: [f32; 3]) -> (f32, f32, f32) {
|
||||
/// All ranges in 0-1, rgb is linear.
|
||||
#[inline]
|
||||
pub fn rgb_from_hsv((h, s, v): (f32, f32, f32)) -> [f32; 3] {
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
#![expect(clippy::many_single_char_names)]
|
||||
let h = (h.fract() + 1.0).fract(); // wrap
|
||||
let s = s.clamp(0.0, 1.0);
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
|
||||
//!
|
||||
|
||||
#![allow(clippy::wrong_self_convention)]
|
||||
#![expect(clippy::wrong_self_convention)]
|
||||
|
||||
#[cfg(feature = "cint")]
|
||||
mod cint_impl;
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.33.2 - 2025-11-13
|
||||
* Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman)
|
||||
* Make sure `native_pixels_per_point` is set during app creation [#7683](https://github.com/emilk/egui/pull/7683) by [@emilk](https://github.com/emilk)
|
||||
|
||||
@@ -251,7 +251,10 @@ web-sys = { workspace = true, features = [
|
||||
] }
|
||||
|
||||
# optional web:
|
||||
egui-wgpu = { workspace = true, optional = true, features = ["capture"] } # if wgpu is used, use it without (!) winit
|
||||
egui-wgpu = { workspace = true, optional = true, features = [
|
||||
# if wgpu is used, use it without (!) winit:
|
||||
"capture",
|
||||
] }
|
||||
wgpu = { workspace = true, optional = true }
|
||||
|
||||
# Native dev dependencies for testing
|
||||
|
||||
@@ -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).
|
||||
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.
|
||||
///
|
||||
/// 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`]).
|
||||
/// Use [`egui::Context::show_viewport_deferred`] to spawn additional viewports (windows).
|
||||
/// (A "viewport" in egui means an native OS window).
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame);
|
||||
#[deprecated = "Use Self::ui instead"]
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
|
||||
_ = (ctx, frame);
|
||||
}
|
||||
|
||||
/// Get a handle to the app.
|
||||
///
|
||||
@@ -763,6 +791,7 @@ impl Frame {
|
||||
/// This function will take the ownership of your [`glow::Texture`], so please do not delete your [`glow::Texture`] after registering.
|
||||
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
|
||||
pub fn register_native_glow_texture(&mut self, native: glow::Texture) -> egui::TextureId {
|
||||
#[expect(clippy::unwrap_used)]
|
||||
self.glow_register_native_texture.as_mut().unwrap()(native)
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
//!
|
||||
//! impl MyEguiApp {
|
||||
//! fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
|
||||
//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_global_style.
|
||||
//! // Restore app state using cc.storage (requires the "persistence" feature).
|
||||
//! // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
|
||||
//! // for e.g. egui::PaintCallback.
|
||||
@@ -44,8 +44,8 @@
|
||||
//! }
|
||||
//!
|
||||
//! impl eframe::App for MyEguiApp {
|
||||
//! fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
//! egui::CentralPanel::default().show(ctx, |ui| {
|
||||
//! fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
//! egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||
//! ui.heading("Hello World!");
|
||||
//! });
|
||||
//! }
|
||||
@@ -142,7 +142,6 @@
|
||||
//!
|
||||
|
||||
#![warn(missing_docs)] // let's keep eframe well-documented
|
||||
#![allow(clippy::needless_doctest_main)]
|
||||
|
||||
// Limitation imposed by `accesskit_winit`:
|
||||
// https://github.com/AccessKit/accesskit/tree/accesskit-v0.18.0/platforms/winit#android-activity-compatibility`
|
||||
@@ -232,7 +231,7 @@ pub mod icon_data;
|
||||
///
|
||||
/// impl MyEguiApp {
|
||||
/// fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
|
||||
/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_global_style.
|
||||
/// // Restore app state using cc.storage (requires the "persistence" feature).
|
||||
/// // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
|
||||
/// // for e.g. egui::PaintCallback.
|
||||
@@ -241,8 +240,8 @@ pub mod icon_data;
|
||||
/// }
|
||||
///
|
||||
/// impl eframe::App for MyEguiApp {
|
||||
/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||
/// fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
/// egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||
/// ui.heading("Hello World!");
|
||||
/// });
|
||||
/// }
|
||||
@@ -253,7 +252,7 @@ pub mod icon_data;
|
||||
/// This function can fail if we fail to set up a graphics context.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
|
||||
#[allow(clippy::needless_pass_by_value, clippy::allow_attributes)]
|
||||
#[allow(clippy::allow_attributes, clippy::needless_pass_by_value)]
|
||||
pub fn run_native(
|
||||
app_name: &str,
|
||||
mut native_options: NativeOptions,
|
||||
@@ -312,8 +311,8 @@ pub fn run_native(
|
||||
/// }
|
||||
///
|
||||
/// impl eframe::App for MyEguiApp {
|
||||
/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
/// egui::CentralPanel::default().show(ctx, |ui| {
|
||||
/// fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
/// egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||
/// ui.heading("Hello World!");
|
||||
/// });
|
||||
/// }
|
||||
@@ -399,8 +398,9 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer {
|
||||
/// 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| {
|
||||
/// eframe::run_ui_native("My egui App", options, move |ui, _frame| {
|
||||
/// // Wrap everything in a CentralPanel so we get some margins and a background color:
|
||||
/// egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||
/// ui.heading("My egui Application");
|
||||
/// ui.horizontal(|ui| {
|
||||
/// let name_label = ui.label("Your name: ");
|
||||
@@ -421,6 +421,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.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
|
||||
pub fn run_ui_native(
|
||||
app_name: &str,
|
||||
native_options: NativeOptions,
|
||||
ui_fun: impl FnMut(&mut egui::Ui, &mut Frame) + 'static,
|
||||
) -> Result {
|
||||
struct SimpleApp<U> {
|
||||
ui_fun: U,
|
||||
}
|
||||
|
||||
impl<U: FnMut(&mut egui::Ui, &mut Frame) + 'static> App for SimpleApp<U> {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame) {
|
||||
(self.ui_fun)(ui, frame);
|
||||
}
|
||||
}
|
||||
|
||||
run_native(
|
||||
app_name,
|
||||
native_options,
|
||||
Box::new(|_cc| Ok(Box::new(SimpleApp { ui_fun }))),
|
||||
)
|
||||
}
|
||||
|
||||
/// The simplest way to get started when writing a native app.
|
||||
///
|
||||
/// This does NOT support persistence of custom user data. For that you need to use [`run_native`].
|
||||
/// 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(
|
||||
app_name: &str,
|
||||
native_options: NativeOptions,
|
||||
@@ -431,6 +490,8 @@ pub fn run_simple_native(
|
||||
}
|
||||
|
||||
impl<U: FnMut(&egui::Context, &mut Frame) + 'static> App for SimpleApp<U> {
|
||||
fn ui(&mut self, _ui: &mut egui::Ui, _frame: &mut Frame) {}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
|
||||
(self.update_fun)(ctx, frame);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ enum AppIconStatus {
|
||||
NotSetTryAgain,
|
||||
|
||||
/// We successfully set the icon and it should be visible now.
|
||||
#[allow(dead_code, clippy::allow_attributes)] // Not used on Linux
|
||||
#[allow(clippy::allow_attributes, dead_code)] // Not used on Linux
|
||||
Set,
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ fn set_title_and_icon(_title: &str, _icon_data: Option<&IconData>) -> AppIconSta
|
||||
#[cfg(target_os = "macos")]
|
||||
return set_title_and_icon_mac(_title, _icon_data);
|
||||
|
||||
#[allow(unreachable_code, clippy::allow_attributes)]
|
||||
#[allow(clippy::allow_attributes, unreachable_code)]
|
||||
AppIconStatus::NotSetIgnored
|
||||
}
|
||||
|
||||
|
||||
@@ -272,14 +272,27 @@ impl EpiIntegration {
|
||||
|
||||
app.raw_input_hook(&self.egui_ctx, &mut raw_input);
|
||||
|
||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
let full_output = self.egui_ctx.run_ui(raw_input, |ui| {
|
||||
if let Some(viewport_ui_cb) = viewport_ui_cb {
|
||||
// Child viewport
|
||||
profiling::scope!("viewport_callback");
|
||||
viewport_ui_cb(egui_ctx);
|
||||
viewport_ui_cb(ui);
|
||||
} else {
|
||||
profiling::scope!("App::update");
|
||||
app.update(egui_ctx, &mut self.frame);
|
||||
{
|
||||
profiling::scope!("App::logic");
|
||||
app.logic(ui.ctx(), &mut self.frame);
|
||||
}
|
||||
|
||||
{
|
||||
profiling::scope!("App::update");
|
||||
#[expect(deprecated)]
|
||||
app.update(ui.ctx(), &mut self.frame);
|
||||
}
|
||||
|
||||
{
|
||||
profiling::scope!("App::ui");
|
||||
app.ui(ui, &mut self.frame);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -363,6 +376,7 @@ impl EpiIntegration {
|
||||
|
||||
fn load_default_egui_icon() -> egui::IconData {
|
||||
profiling::function_scope!();
|
||||
#[expect(clippy::unwrap_used)]
|
||||
crate::icon_data::from_png_bytes(&include_bytes!("../../data/icon.png")[..]).unwrap()
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
//! There is a bunch of improvements we could do,
|
||||
//! like removing a bunch of `unwraps`.
|
||||
|
||||
#![allow(clippy::undocumented_unsafe_blocks)]
|
||||
#![expect(clippy::undocumented_unsafe_blocks)]
|
||||
#![expect(clippy::unwrap_used)]
|
||||
|
||||
use std::{cell::RefCell, num::NonZeroU32, rc::Rc, sync::Arc, time::Instant};
|
||||
|
||||
@@ -216,7 +217,7 @@ impl<'app> GlowWinitApp<'app> {
|
||||
storage.as_deref(),
|
||||
&mut self.native_options,
|
||||
)?;
|
||||
let gl = painter.gl().clone();
|
||||
let gl = Arc::clone(painter.gl());
|
||||
|
||||
let max_texture_side = painter.max_texture_side();
|
||||
glutin.max_texture_side = Some(max_texture_side);
|
||||
@@ -234,9 +235,9 @@ impl<'app> GlowWinitApp<'app> {
|
||||
&self.app_name,
|
||||
&self.native_options,
|
||||
storage,
|
||||
Some(gl.clone()),
|
||||
Some(Arc::clone(&gl)),
|
||||
Some(Box::new({
|
||||
let painter = painter.clone();
|
||||
let painter = Rc::clone(&painter);
|
||||
move |native| painter.borrow_mut().register_native_texture(native)
|
||||
})),
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
@@ -244,7 +245,7 @@ impl<'app> GlowWinitApp<'app> {
|
||||
);
|
||||
|
||||
{
|
||||
let event_loop_proxy = self.repaint_proxy.clone();
|
||||
let event_loop_proxy = Arc::clone(&self.repaint_proxy);
|
||||
integration
|
||||
.egui_ctx
|
||||
.set_request_repaint_callback(move |info| {
|
||||
@@ -571,7 +572,7 @@ impl GlowWinitRunning<'_> {
|
||||
.options_mut(|opt| opt.begin_pass(&raw_input));
|
||||
let clear_color = self
|
||||
.app
|
||||
.clear_color(&self.integration.egui_ctx.style().visuals);
|
||||
.clear_color(&self.integration.egui_ctx.global_style().visuals);
|
||||
|
||||
let has_many_viewports = self.glutin.borrow().viewports.len() > 1;
|
||||
let clear_before_update = !has_many_viewports; // HACK: for some reason, an early clear doesn't "take" on Mac with multiple viewports.
|
||||
@@ -719,11 +720,11 @@ impl GlowWinitRunning<'_> {
|
||||
// vsync - don't count as frame-time:
|
||||
frame_timer.pause();
|
||||
profiling::scope!("swap_buffers");
|
||||
let context = current_gl_context
|
||||
.as_ref()
|
||||
.ok_or(egui_glow::PainterError::from(
|
||||
let context = current_gl_context.as_ref().ok_or_else(|| {
|
||||
egui_glow::PainterError::from(
|
||||
"failed to get current context to swap buffers".to_owned(),
|
||||
))?;
|
||||
)
|
||||
})?;
|
||||
|
||||
gl_surface.swap_buffers(context)?;
|
||||
frame_timer.resume();
|
||||
@@ -1362,7 +1363,7 @@ fn initialize_or_update_viewport(
|
||||
ids: ViewportIdPair,
|
||||
class: ViewportClass,
|
||||
mut builder: ViewportBuilder,
|
||||
viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
|
||||
viewport_ui_cb: Option<Arc<dyn Fn(&mut egui::Ui) + Send + Sync>>,
|
||||
) -> &mut Viewport {
|
||||
profiling::function_scope!();
|
||||
|
||||
@@ -1494,8 +1495,8 @@ fn render_immediate_viewport(
|
||||
shapes,
|
||||
pixels_per_point,
|
||||
viewport_output,
|
||||
} = egui_ctx.run(input, |ctx| {
|
||||
viewport_ui_cb(ctx);
|
||||
} = egui_ctx.run_ui(input, |ui| {
|
||||
viewport_ui_cb(ui);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
@@ -349,8 +349,6 @@ pub fn run_glow(
|
||||
mut native_options: epi::NativeOptions,
|
||||
app_creator: epi::AppCreator<'_>,
|
||||
) -> Result {
|
||||
#![allow(clippy::needless_return_with_question_mark)] // False positive
|
||||
|
||||
use super::glow_integration::GlowWinitApp;
|
||||
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
@@ -387,8 +385,6 @@ pub fn run_wgpu(
|
||||
mut native_options: epi::NativeOptions,
|
||||
app_creator: epi::AppCreator<'_>,
|
||||
) -> Result {
|
||||
#![allow(clippy::needless_return_with_question_mark)] // False positive
|
||||
|
||||
use super::wgpu_integration::WgpuWinitApp;
|
||||
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
|
||||
@@ -219,7 +219,7 @@ impl<'app> WgpuWinitApp<'app> {
|
||||
|
||||
{
|
||||
profiling::scope!("set_window");
|
||||
pollster::block_on(painter.set_window(ViewportId::ROOT, Some(window.clone())))?;
|
||||
pollster::block_on(painter.set_window(ViewportId::ROOT, Some(Arc::clone(&window))))?;
|
||||
}
|
||||
|
||||
let wgpu_render_state = painter.render_state();
|
||||
@@ -238,7 +238,7 @@ impl<'app> WgpuWinitApp<'app> {
|
||||
);
|
||||
|
||||
{
|
||||
let event_loop_proxy = self.repaint_proxy.clone();
|
||||
let event_loop_proxy = Arc::clone(&self.repaint_proxy);
|
||||
|
||||
egui_ctx.set_request_repaint_callback(move |info| {
|
||||
log::trace!("request_repaint_callback: {info:?}");
|
||||
@@ -256,7 +256,7 @@ impl<'app> WgpuWinitApp<'app> {
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(unused_mut, clippy::allow_attributes)] // used for accesskit
|
||||
#[allow(clippy::allow_attributes, unused_mut)] // used for accesskit
|
||||
let mut egui_winit = egui_winit::State::new(
|
||||
egui_ctx.clone(),
|
||||
ViewportId::ROOT,
|
||||
@@ -610,7 +610,7 @@ impl WgpuWinitRunning<'_> {
|
||||
|
||||
{
|
||||
profiling::scope!("set_window");
|
||||
pollster::block_on(painter.set_window(viewport_id, Some(window.clone())))?;
|
||||
pollster::block_on(painter.set_window(viewport_id, Some(Arc::clone(window))))?;
|
||||
}
|
||||
|
||||
let Some(egui_winit) = egui_winit.as_mut() else {
|
||||
@@ -690,7 +690,7 @@ impl WgpuWinitRunning<'_> {
|
||||
let vsync_secs = painter.paint_and_update_textures(
|
||||
viewport_id,
|
||||
pixels_per_point,
|
||||
app.clear_color(&egui_ctx.style().visuals),
|
||||
app.clear_color(&egui_ctx.global_style().visuals),
|
||||
&clipped_primitives,
|
||||
&textures_delta,
|
||||
screenshot_commands,
|
||||
@@ -919,7 +919,7 @@ impl Viewport {
|
||||
let window = Arc::new(window);
|
||||
|
||||
if let Err(err) =
|
||||
pollster::block_on(painter.set_window(viewport_id, Some(window.clone())))
|
||||
pollster::block_on(painter.set_window(viewport_id, Some(Arc::clone(&window))))
|
||||
{
|
||||
log::error!("on set_window: viewport_id {viewport_id:?} {err}");
|
||||
}
|
||||
@@ -1027,8 +1027,8 @@ fn render_immediate_viewport(
|
||||
shapes,
|
||||
pixels_per_point,
|
||||
viewport_output,
|
||||
} = egui_ctx.run(input, |ctx| {
|
||||
viewport_ui_cb(ctx);
|
||||
} = egui_ctx.run_ui(input, |ui| {
|
||||
viewport_ui_cb(ui);
|
||||
});
|
||||
|
||||
// ------------------------------------------
|
||||
@@ -1051,7 +1051,8 @@ fn render_immediate_viewport(
|
||||
|
||||
{
|
||||
profiling::scope!("set_window");
|
||||
if let Err(err) = pollster::block_on(painter.set_window(ids.this, Some(window.clone()))) {
|
||||
if let Err(err) = pollster::block_on(painter.set_window(ids.this, Some(Arc::clone(window))))
|
||||
{
|
||||
log::error!(
|
||||
"when rendering viewport_id={:?}, set_window Error {err}",
|
||||
ids.this
|
||||
@@ -1155,7 +1156,7 @@ fn initialize_or_update_viewport<'a>(
|
||||
ids: ViewportIdPair,
|
||||
class: ViewportClass,
|
||||
mut builder: ViewportBuilder,
|
||||
viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
|
||||
viewport_ui_cb: Option<Arc<dyn Fn(&mut egui::Ui) + Send + Sync>>,
|
||||
painter: &mut egui_wgpu::winit::Painter,
|
||||
) -> &'a mut Viewport {
|
||||
use std::collections::btree_map::Entry;
|
||||
|
||||
@@ -27,7 +27,10 @@ pub fn create_egui_context(storage: Option<&dyn crate::Storage>) -> egui::Contex
|
||||
|
||||
egui_ctx.options_mut(|o| {
|
||||
// eframe supports multi-pass (Context::request_discard).
|
||||
o.max_passes = 2.try_into().unwrap();
|
||||
#[expect(clippy::unwrap_used)]
|
||||
{
|
||||
o.max_passes = 2.try_into().unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
let memory = crate::native::epi_integration::load_egui_memory(storage).unwrap_or_default();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![allow(dead_code)] // not everything is used on wasm
|
||||
#![allow(clippy::allow_attributes, dead_code)] // not used on all platforms
|
||||
|
||||
use web_time::Instant;
|
||||
|
||||
@@ -23,7 +23,7 @@ impl Stopwatch {
|
||||
}
|
||||
|
||||
pub fn pause(&mut self) {
|
||||
let start = self.start.take().unwrap();
|
||||
let start = self.start.take().expect("Stopwatch is not running");
|
||||
let duration = start.elapsed();
|
||||
self.total_time_ns += duration.as_nanos();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use egui::{TexturesDelta, UserData, ViewportCommand};
|
||||
|
||||
use crate::{App, epi, web::web_painter::WebPainter};
|
||||
@@ -5,14 +7,14 @@ use crate::{App, epi, web::web_painter::WebPainter};
|
||||
use super::{NeedRepaint, now_sec, text_agent::TextAgent};
|
||||
|
||||
pub struct AppRunner {
|
||||
#[allow(dead_code, clippy::allow_attributes)]
|
||||
#[allow(clippy::allow_attributes, dead_code)]
|
||||
pub(crate) web_options: crate::WebOptions,
|
||||
pub(crate) frame: epi::Frame,
|
||||
egui_ctx: egui::Context,
|
||||
painter: Box<dyn WebPainter>,
|
||||
pub(crate) input: super::WebInput,
|
||||
app: Box<dyn epi::App>,
|
||||
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
||||
pub(crate) needs_repaint: Arc<NeedRepaint>,
|
||||
last_save_time: f64,
|
||||
pub(crate) text_agent: TextAgent,
|
||||
|
||||
@@ -63,7 +65,7 @@ impl AppRunner {
|
||||
canvas,
|
||||
&web_options,
|
||||
)?;
|
||||
gl = Some(painter.gl().clone());
|
||||
gl = Some(Arc::clone(painter.gl()));
|
||||
Box::new(painter) as Box<dyn WebPainter>
|
||||
}
|
||||
|
||||
@@ -138,10 +140,9 @@ impl AppRunner {
|
||||
wgpu_render_state,
|
||||
};
|
||||
|
||||
let needs_repaint: std::sync::Arc<NeedRepaint> =
|
||||
std::sync::Arc::new(NeedRepaint::new(web_options.max_fps));
|
||||
let needs_repaint: Arc<NeedRepaint> = Arc::new(NeedRepaint::new(web_options.max_fps));
|
||||
{
|
||||
let needs_repaint = needs_repaint.clone();
|
||||
let needs_repaint = Arc::clone(&needs_repaint);
|
||||
egui_ctx.set_request_repaint_callback(move |info| {
|
||||
needs_repaint.repaint_after(info.delay.as_secs_f64());
|
||||
});
|
||||
@@ -273,8 +274,13 @@ impl AppRunner {
|
||||
|
||||
self.app.raw_input_hook(&self.egui_ctx, &mut raw_input);
|
||||
|
||||
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
|
||||
self.app.update(egui_ctx, &mut self.frame);
|
||||
let full_output = self.egui_ctx.run_ui(raw_input, |ui| {
|
||||
self.app.logic(ui.ctx(), &mut self.frame);
|
||||
|
||||
#[expect(deprecated)]
|
||||
self.app.update(ui.ctx(), &mut self.frame);
|
||||
|
||||
self.app.ui(ui, &mut self.frame);
|
||||
});
|
||||
let egui::FullOutput {
|
||||
platform_output,
|
||||
@@ -331,7 +337,7 @@ impl AppRunner {
|
||||
}
|
||||
|
||||
if let Err(err) = self.painter.paint_and_update_textures(
|
||||
self.app.clear_color(&self.egui_ctx.style().visuals),
|
||||
self.app.clear_color(&self.egui_ctx.global_style().visuals),
|
||||
&clipped_primitives,
|
||||
self.egui_ctx.pixels_per_point(),
|
||||
&textures_delta,
|
||||
|
||||
@@ -137,25 +137,24 @@ fn install_keydown(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), J
|
||||
&& !modifiers.command
|
||||
// When text agent is focused, it is responsible for handling input events
|
||||
&& !runner.text_agent.has_focus()
|
||||
&& let Some(text) = text_from_keyboard_event(&event)
|
||||
{
|
||||
if let Some(text) = text_from_keyboard_event(&event) {
|
||||
let egui_event = egui::Event::Text(text);
|
||||
let should_stop_propagation =
|
||||
(runner.web_options.should_stop_propagation)(&egui_event);
|
||||
let should_prevent_default =
|
||||
(runner.web_options.should_prevent_default)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
let egui_event = egui::Event::Text(text);
|
||||
let should_stop_propagation =
|
||||
(runner.web_options.should_stop_propagation)(&egui_event);
|
||||
let should_prevent_default =
|
||||
(runner.web_options.should_prevent_default)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
// If this is indeed text, then prevent any other action.
|
||||
if should_prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
// If this is indeed text, then prevent any other action.
|
||||
if should_prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
|
||||
// Use web options to tell if the event should be propagated to parent elements.
|
||||
if should_stop_propagation {
|
||||
event.stop_propagation();
|
||||
}
|
||||
// Use web options to tell if the event should be propagated to parent elements.
|
||||
if should_stop_propagation {
|
||||
event.stop_propagation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,11 +195,13 @@ pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner)
|
||||
|
||||
let prevent_default = should_prevent_default_for_key(runner, &modifiers, egui_key);
|
||||
|
||||
// log::debug!(
|
||||
// "On keydown {:?} {egui_key:?}, has_focus: {has_focus}, egui_wants_keyboard: {}, prevent_default: {prevent_default}",
|
||||
// event.key().as_str(),
|
||||
// runner.egui_ctx().wants_keyboard_input()
|
||||
// );
|
||||
if false {
|
||||
log::debug!(
|
||||
"On keydown {:?} {egui_key:?}, has_focus: {has_focus}, egui_wants_keyboard: {}, prevent_default: {prevent_default}",
|
||||
event.key().as_str(),
|
||||
runner.egui_ctx().egui_wants_keyboard_input()
|
||||
);
|
||||
}
|
||||
|
||||
if prevent_default {
|
||||
event.prevent_default();
|
||||
@@ -319,30 +320,28 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul
|
||||
return; // The eframe app is not interested
|
||||
}
|
||||
|
||||
if let Some(data) = event.clipboard_data() {
|
||||
if let Ok(text) = data.get_data("text") {
|
||||
let text = text.replace("\r\n", "\n");
|
||||
if let Some(data) = event.clipboard_data()
|
||||
&& let Ok(text) = data.get_data("text")
|
||||
{
|
||||
let text = text.replace("\r\n", "\n");
|
||||
|
||||
let mut should_stop_propagation = true;
|
||||
let mut should_prevent_default = true;
|
||||
if !text.is_empty() {
|
||||
let egui_event = egui::Event::Paste(text);
|
||||
should_stop_propagation =
|
||||
(runner.web_options.should_stop_propagation)(&egui_event);
|
||||
should_prevent_default =
|
||||
(runner.web_options.should_prevent_default)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
}
|
||||
let mut should_stop_propagation = true;
|
||||
let mut should_prevent_default = true;
|
||||
if !text.is_empty() {
|
||||
let egui_event = egui::Event::Paste(text);
|
||||
should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event);
|
||||
should_prevent_default = (runner.web_options.should_prevent_default)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
runner.needs_repaint.repaint_asap();
|
||||
}
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if should_stop_propagation {
|
||||
event.stop_propagation();
|
||||
}
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if should_stop_propagation {
|
||||
event.stop_propagation();
|
||||
}
|
||||
|
||||
if should_prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
if should_prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
}
|
||||
})?;
|
||||
@@ -560,45 +559,44 @@ fn install_pointerup(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
|
||||
if is_interested_in_pointer_event(
|
||||
runner,
|
||||
egui::pos2(event.client_x() as f32, event.client_y() as f32),
|
||||
) {
|
||||
if let Some(button) = button_from_mouse_event(&event) {
|
||||
let modifiers = runner.input.raw.modifiers;
|
||||
let egui_event = egui::Event::PointerButton {
|
||||
pos,
|
||||
button,
|
||||
pressed: false,
|
||||
modifiers,
|
||||
};
|
||||
let should_stop_propagation =
|
||||
(runner.web_options.should_stop_propagation)(&egui_event);
|
||||
let should_prevent_default =
|
||||
(runner.web_options.should_prevent_default)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
) && let Some(button) = button_from_mouse_event(&event)
|
||||
{
|
||||
let modifiers = runner.input.raw.modifiers;
|
||||
let egui_event = egui::Event::PointerButton {
|
||||
pos,
|
||||
button,
|
||||
pressed: false,
|
||||
modifiers,
|
||||
};
|
||||
let should_stop_propagation =
|
||||
(runner.web_options.should_stop_propagation)(&egui_event);
|
||||
let should_prevent_default =
|
||||
(runner.web_options.should_prevent_default)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
|
||||
// Previously on iOS, the canvas would not receive focus on
|
||||
// any touch event, which resulted in the on-screen keyboard
|
||||
// not working when focusing on a text field in an egui app.
|
||||
// This attempts to fix that by forcing the focus on any
|
||||
// click on the canvas.
|
||||
runner.canvas().focus().ok();
|
||||
// Previously on iOS, the canvas would not receive focus on
|
||||
// any touch event, which resulted in the on-screen keyboard
|
||||
// not working when focusing on a text field in an egui app.
|
||||
// This attempts to fix that by forcing the focus on any
|
||||
// click on the canvas.
|
||||
runner.canvas().focus().ok();
|
||||
|
||||
// In Safari we are only allowed to do certain things
|
||||
// (like playing audio, start a download, etc)
|
||||
// on user action, such as a click.
|
||||
// So we need to run the app logic here and now:
|
||||
runner.logic();
|
||||
// In Safari we are only allowed to do certain things
|
||||
// (like playing audio, start a download, etc)
|
||||
// on user action, such as a click.
|
||||
// So we need to run the app logic here and now:
|
||||
runner.logic();
|
||||
|
||||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
// Make sure we paint the output of the above logic call asap:
|
||||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
if should_prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
if should_prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if should_stop_propagation {
|
||||
event.stop_propagation();
|
||||
}
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if should_stop_propagation {
|
||||
event.stop_propagation();
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -711,29 +709,27 @@ fn install_touchstart(runner_ref: &WebRunner, target: &EventTarget) -> Result<()
|
||||
|
||||
fn install_touchmove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
|
||||
runner_ref.add_event_listener(target, "touchmove", |event: web_sys::TouchEvent, runner| {
|
||||
if let Some((pos, touch)) = primary_touch_pos(runner, &event) {
|
||||
if is_interested_in_pointer_event(
|
||||
if let Some((pos, touch)) = primary_touch_pos(runner, &event)
|
||||
&& is_interested_in_pointer_event(
|
||||
runner,
|
||||
egui::pos2(touch.client_x() as f32, touch.client_y() as f32),
|
||||
) {
|
||||
let egui_event = egui::Event::PointerMoved(pos);
|
||||
let should_stop_propagation =
|
||||
(runner.web_options.should_stop_propagation)(&egui_event);
|
||||
let should_prevent_default =
|
||||
(runner.web_options.should_prevent_default)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
)
|
||||
{
|
||||
let egui_event = egui::Event::PointerMoved(pos);
|
||||
let should_stop_propagation = (runner.web_options.should_stop_propagation)(&egui_event);
|
||||
let should_prevent_default = (runner.web_options.should_prevent_default)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
|
||||
push_touches(runner, egui::TouchPhase::Move, &event);
|
||||
runner.needs_repaint.repaint();
|
||||
push_touches(runner, egui::TouchPhase::Move, &event);
|
||||
runner.needs_repaint.repaint();
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if should_stop_propagation {
|
||||
event.stop_propagation();
|
||||
}
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if should_stop_propagation {
|
||||
event.stop_propagation();
|
||||
}
|
||||
|
||||
if should_prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
if should_prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -741,50 +737,49 @@ fn install_touchmove(runner_ref: &WebRunner, target: &EventTarget) -> Result<(),
|
||||
|
||||
fn install_touchend(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> {
|
||||
runner_ref.add_event_listener(target, "touchend", |event: web_sys::TouchEvent, runner| {
|
||||
if let Some((pos, touch)) = primary_touch_pos(runner, &event) {
|
||||
if is_interested_in_pointer_event(
|
||||
if let Some((pos, touch)) = primary_touch_pos(runner, &event)
|
||||
&& is_interested_in_pointer_event(
|
||||
runner,
|
||||
egui::pos2(touch.client_x() as f32, touch.client_y() as f32),
|
||||
) {
|
||||
// First release mouse to click:
|
||||
let mut should_stop_propagation = true;
|
||||
let mut should_prevent_default = true;
|
||||
let egui_event = egui::Event::PointerButton {
|
||||
pos,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: false,
|
||||
modifiers: runner.input.raw.modifiers,
|
||||
};
|
||||
should_stop_propagation &=
|
||||
(runner.web_options.should_stop_propagation)(&egui_event);
|
||||
should_prevent_default &= (runner.web_options.should_prevent_default)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
// Then remove hover effect:
|
||||
should_stop_propagation &=
|
||||
(runner.web_options.should_stop_propagation)(&egui::Event::PointerGone);
|
||||
should_prevent_default &=
|
||||
(runner.web_options.should_prevent_default)(&egui::Event::PointerGone);
|
||||
runner.input.raw.events.push(egui::Event::PointerGone);
|
||||
)
|
||||
{
|
||||
// First release mouse to click:
|
||||
let mut should_stop_propagation = true;
|
||||
let mut should_prevent_default = true;
|
||||
let egui_event = egui::Event::PointerButton {
|
||||
pos,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: false,
|
||||
modifiers: runner.input.raw.modifiers,
|
||||
};
|
||||
should_stop_propagation &= (runner.web_options.should_stop_propagation)(&egui_event);
|
||||
should_prevent_default &= (runner.web_options.should_prevent_default)(&egui_event);
|
||||
runner.input.raw.events.push(egui_event);
|
||||
// Then remove hover effect:
|
||||
should_stop_propagation &=
|
||||
(runner.web_options.should_stop_propagation)(&egui::Event::PointerGone);
|
||||
should_prevent_default &=
|
||||
(runner.web_options.should_prevent_default)(&egui::Event::PointerGone);
|
||||
runner.input.raw.events.push(egui::Event::PointerGone);
|
||||
|
||||
push_touches(runner, egui::TouchPhase::End, &event);
|
||||
push_touches(runner, egui::TouchPhase::End, &event);
|
||||
|
||||
runner.needs_repaint.repaint_asap();
|
||||
runner.needs_repaint.repaint_asap();
|
||||
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if should_stop_propagation {
|
||||
event.stop_propagation();
|
||||
}
|
||||
// Use web options to tell if the web event should be propagated to parent elements based on the egui event.
|
||||
if should_stop_propagation {
|
||||
event.stop_propagation();
|
||||
}
|
||||
|
||||
if should_prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
if should_prevent_default {
|
||||
event.prevent_default();
|
||||
}
|
||||
|
||||
// Fix virtual keyboard IOS
|
||||
// Need call focus at the same time of event
|
||||
if runner.text_agent.has_focus() {
|
||||
runner.text_agent.set_focus(false);
|
||||
runner.text_agent.set_focus(true);
|
||||
}
|
||||
// Fix virtual keyboard IOS
|
||||
// Need call focus at the same time of event
|
||||
if runner.text_agent.has_focus() {
|
||||
runner.text_agent.set_focus(false);
|
||||
runner.text_agent.set_focus(true);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! [`egui`] bindings for web apps (compiling to WASM).
|
||||
|
||||
#![allow(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>`
|
||||
#![expect(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>`
|
||||
#![expect(clippy::unwrap_used)] // TODO(emilk): remove unwraps
|
||||
|
||||
mod app_runner;
|
||||
mod backend;
|
||||
@@ -145,18 +146,18 @@ fn canvas_content_rect(canvas: &web_sys::HtmlCanvasElement) -> egui::Rect {
|
||||
);
|
||||
|
||||
// We need to subtract padding and border:
|
||||
if let Some(window) = web_sys::window() {
|
||||
if let Ok(Some(style)) = window.get_computed_style(canvas) {
|
||||
let get_property = |name: &str| -> Option<f32> {
|
||||
let property = style.get_property_value(name).ok()?;
|
||||
property.trim_end_matches("px").parse::<f32>().ok()
|
||||
};
|
||||
if let Some(window) = web_sys::window()
|
||||
&& let Ok(Some(style)) = window.get_computed_style(canvas)
|
||||
{
|
||||
let get_property = |name: &str| -> Option<f32> {
|
||||
let property = style.get_property_value(name).ok()?;
|
||||
property.trim_end_matches("px").parse::<f32>().ok()
|
||||
};
|
||||
|
||||
rect.min.x += get_property("padding-left").unwrap_or_default();
|
||||
rect.min.y += get_property("padding-top").unwrap_or_default();
|
||||
rect.max.x -= get_property("padding-right").unwrap_or_default();
|
||||
rect.max.y -= get_property("padding-bottom").unwrap_or_default();
|
||||
}
|
||||
rect.min.x += get_property("padding-left").unwrap_or_default();
|
||||
rect.min.y += get_property("padding-top").unwrap_or_default();
|
||||
rect.max.x -= get_property("padding-right").unwrap_or_default();
|
||||
rect.max.y -= get_property("padding-bottom").unwrap_or_default();
|
||||
}
|
||||
|
||||
rect
|
||||
@@ -284,8 +285,8 @@ fn create_clipboard_item(mime: &str, bytes: &[u8]) -> Result<web_sys::ClipboardI
|
||||
|
||||
let items = js_sys::Object::new();
|
||||
|
||||
// SAFETY: I hope so
|
||||
#[expect(unsafe_code, unused_unsafe)] // Weird false positive
|
||||
// SAFETY: I hope so
|
||||
unsafe {
|
||||
js_sys::Reflect::set(&items, &JsValue::from_str(mime), &blob)?
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ impl log::Log for WebLogger {
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record<'_>) {
|
||||
#![allow(clippy::match_same_arms)]
|
||||
#![expect(clippy::match_same_arms)]
|
||||
|
||||
if !self.enabled(record.metadata()) {
|
||||
return;
|
||||
@@ -110,7 +110,7 @@ mod console {
|
||||
/// * `tokio-1.24.1/src/runtime/runtime.rs`
|
||||
/// * `rerun/src/main.rs`
|
||||
/// * `core/src/ops/function.rs`
|
||||
#[allow(dead_code, clippy::allow_attributes)] // only used on web and in tests
|
||||
#[allow(clippy::allow_attributes, dead_code)] // only used on web and in tests
|
||||
fn shorten_file_path(file_path: &str) -> &str {
|
||||
if let Some(i) = file_path.rfind("/src/") {
|
||||
if let Some(prev_slash) = file_path[..i].rfind('/') {
|
||||
|
||||
@@ -28,7 +28,7 @@ impl WebPainterGlow {
|
||||
let (gl, shader_prefix) =
|
||||
init_glow_context_from_canvas(&canvas, options.webgl_context_option)?;
|
||||
|
||||
#[allow(clippy::arc_with_non_send_sync, clippy::allow_attributes)] // For wasm
|
||||
#[allow(clippy::allow_attributes, clippy::arc_with_non_send_sync)] // For wasm
|
||||
let gl = std::sync::Arc::new(gl);
|
||||
|
||||
let painter = egui_glow::Painter::new(gl, shader_prefix, None, options.dithering)
|
||||
@@ -91,7 +91,7 @@ impl WebPainter for WebPainterGlow {
|
||||
for data in data {
|
||||
events.push(Event::Screenshot {
|
||||
viewport_id: ViewportId::default(),
|
||||
image: image.clone(),
|
||||
image: Arc::clone(&image),
|
||||
user_data: data,
|
||||
});
|
||||
}
|
||||
@@ -192,17 +192,13 @@ fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool {
|
||||
.get_extension("WEBGL_debug_renderer_info")
|
||||
.unwrap()
|
||||
.is_some()
|
||||
{
|
||||
if let Ok(renderer) =
|
||||
&& let Ok(renderer) =
|
||||
gl.get_parameter(web_sys::WebglDebugRendererInfo::UNMASKED_RENDERER_WEBGL)
|
||||
{
|
||||
if let Some(renderer) = renderer.as_string() {
|
||||
if renderer.contains("Apple") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
&& let Some(renderer) = renderer.as_string()
|
||||
&& renderer.contains("Apple")
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ impl WebPainterWgpu {
|
||||
surface_configuration,
|
||||
depth_stencil_format,
|
||||
depth_texture_view: None,
|
||||
on_surface_error: options.wgpu_options.on_surface_error.clone(),
|
||||
on_surface_error: Arc::clone(&options.wgpu_options.on_surface_error) as _,
|
||||
screen_capture_state: None,
|
||||
capture_tx,
|
||||
capture_rx,
|
||||
@@ -243,13 +243,26 @@ impl WebPainter for WebPainterWgpu {
|
||||
depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| {
|
||||
wgpu::RenderPassDepthStencilAttachment {
|
||||
view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
// It is very unlikely that the depth buffer is needed after egui finished rendering
|
||||
// so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon)
|
||||
store: wgpu::StoreOp::Discard,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
depth_ops: self
|
||||
.depth_stencil_format
|
||||
.is_some_and(|depth_stencil_format| {
|
||||
depth_stencil_format.has_depth_aspect()
|
||||
})
|
||||
.then_some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
// It is very unlikely that the depth buffer is needed after egui finished rendering
|
||||
// so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon)
|
||||
store: wgpu::StoreOp::Discard,
|
||||
}),
|
||||
stencil_ops: self
|
||||
.depth_stencil_format
|
||||
.is_some_and(|depth_stencil_format| {
|
||||
depth_stencil_format.has_stencil_aspect()
|
||||
})
|
||||
.then_some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(0),
|
||||
store: wgpu::StoreOp::Discard,
|
||||
}),
|
||||
}
|
||||
}),
|
||||
label: Some("egui_render"),
|
||||
@@ -269,14 +282,12 @@ impl WebPainter for WebPainterWgpu {
|
||||
|
||||
let mut capture_buffer = None;
|
||||
|
||||
if capture {
|
||||
if let Some(capture_state) = &mut self.screen_capture_state {
|
||||
capture_buffer = Some(capture_state.copy_textures(
|
||||
&render_state.device,
|
||||
&output_frame,
|
||||
&mut encoder,
|
||||
));
|
||||
}
|
||||
if capture && let Some(capture_state) = &mut self.screen_capture_state {
|
||||
capture_buffer = Some(capture_state.copy_textures(
|
||||
&render_state.device,
|
||||
&output_frame,
|
||||
&mut encoder,
|
||||
));
|
||||
}
|
||||
|
||||
Some((output_frame, capture_buffer))
|
||||
@@ -288,16 +299,16 @@ impl WebPainter for WebPainterWgpu {
|
||||
.submit(user_cmd_bufs.into_iter().chain([encoder.finish()]));
|
||||
|
||||
if let Some((frame, capture_buffer)) = frame_and_capture_buffer {
|
||||
if let Some(capture_buffer) = capture_buffer {
|
||||
if let Some(capture_state) = &self.screen_capture_state {
|
||||
capture_state.read_screen_rgba(
|
||||
self.ctx.clone(),
|
||||
capture_buffer,
|
||||
capture_data,
|
||||
self.capture_tx.clone(),
|
||||
ViewportId::ROOT,
|
||||
);
|
||||
}
|
||||
if let Some(capture_buffer) = capture_buffer
|
||||
&& let Some(capture_state) = &self.screen_capture_state
|
||||
{
|
||||
capture_state.read_screen_rgba(
|
||||
self.ctx.clone(),
|
||||
capture_buffer,
|
||||
capture_data,
|
||||
self.capture_tx.clone(),
|
||||
ViewportId::ROOT,
|
||||
);
|
||||
}
|
||||
|
||||
frame.present();
|
||||
@@ -323,7 +334,7 @@ impl WebPainter for WebPainterWgpu {
|
||||
events.push(Event::Screenshot {
|
||||
viewport_id,
|
||||
user_data: data,
|
||||
image: screenshot.clone(),
|
||||
image: Arc::clone(&screenshot),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.33.2 - 2025-11-13
|
||||
* Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman)
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ impl CaptureState {
|
||||
// It would be more efficient to reuse the Buffer, e.g. via some kind of ring buffer, but
|
||||
// for most screenshot use cases this should be fine. When taking many screenshots (e.g. for a video)
|
||||
// it might make sense to revisit this and implement a more efficient solution.
|
||||
#[allow(clippy::arc_with_non_send_sync, clippy::allow_attributes)] // For wasm
|
||||
#[allow(clippy::allow_attributes, clippy::arc_with_non_send_sync)] // For wasm
|
||||
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("egui_screen_capture_buffer"),
|
||||
size: (self.padding.padded_bytes_per_row * self.texture.height()) as u64,
|
||||
@@ -186,9 +186,9 @@ impl CaptureState {
|
||||
tx: CaptureSender,
|
||||
viewport_id: ViewportId,
|
||||
) {
|
||||
#[allow(clippy::arc_with_non_send_sync, clippy::allow_attributes)] // For wasm
|
||||
#[allow(clippy::allow_attributes, clippy::arc_with_non_send_sync)] // For wasm
|
||||
let buffer = Arc::new(buffer);
|
||||
let buffer_clone = buffer.clone();
|
||||
let buffer_clone = Arc::clone(&buffer);
|
||||
let buffer_slice = buffer_clone.slice(..);
|
||||
let format = self.texture.format();
|
||||
let tex_extent = self.texture.size();
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
#![doc = document_features::document_features!()]
|
||||
//!
|
||||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
pub use wgpu;
|
||||
|
||||
/// Low-level painting of [`egui`](https://github.com/emilk/egui) on [`wgpu`].
|
||||
@@ -247,7 +245,7 @@ impl RenderState {
|
||||
|
||||
// On wasm, depending on feature flags, wgpu objects may or may not implement sync.
|
||||
// It doesn't make sense to switch to Rc for that special usecase, so simply disable the lint.
|
||||
#[allow(clippy::arc_with_non_send_sync, clippy::allow_attributes)] // For wasm
|
||||
#[allow(clippy::allow_attributes, clippy::arc_with_non_send_sync)] // For wasm
|
||||
Ok(Self {
|
||||
adapter,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![allow(unsafe_code)]
|
||||
#![expect(clippy::unwrap_used)] // TODO(emilk): avoid unwraps
|
||||
|
||||
use std::{borrow::Cow, num::NonZeroU64, ops::Range};
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ impl WgpuSetup {
|
||||
pub async fn new_instance(&self) -> wgpu::Instance {
|
||||
match self {
|
||||
Self::CreateNew(create_new) => {
|
||||
#[allow(unused_mut, clippy::allow_attributes)]
|
||||
#[allow(clippy::allow_attributes, unused_mut)]
|
||||
let mut backends = create_new.instance_descriptor.backends;
|
||||
|
||||
// Don't try WebGPU if we're not in a secure context.
|
||||
@@ -134,7 +134,7 @@ impl Clone for WgpuSetupCreateNew {
|
||||
instance_descriptor: self.instance_descriptor.clone(),
|
||||
power_preference: self.power_preference,
|
||||
native_adapter_selector: self.native_adapter_selector.clone(),
|
||||
device_descriptor: self.device_descriptor.clone(),
|
||||
device_descriptor: Arc::clone(&self.device_descriptor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![allow(clippy::undocumented_unsafe_blocks)]
|
||||
#![expect(clippy::missing_errors_doc)]
|
||||
#![expect(clippy::undocumented_unsafe_blocks)]
|
||||
#![expect(clippy::unwrap_used)] // TODO(emilk): avoid unwraps
|
||||
#![expect(unsafe_code)]
|
||||
|
||||
use crate::{RenderState, SurfaceErrorAction, WgpuConfiguration, renderer};
|
||||
use crate::{
|
||||
@@ -526,13 +528,28 @@ impl Painter {
|
||||
depth_stencil_attachment: self.depth_texture_view.get(&viewport_id).map(|view| {
|
||||
wgpu::RenderPassDepthStencilAttachment {
|
||||
view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
// It is very unlikely that the depth buffer is needed after egui finished rendering
|
||||
// so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon)
|
||||
store: wgpu::StoreOp::Discard,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
depth_ops: self
|
||||
.options
|
||||
.depth_stencil_format
|
||||
.is_some_and(|depth_stencil_format| {
|
||||
depth_stencil_format.has_depth_aspect()
|
||||
})
|
||||
.then_some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
// It is very unlikely that the depth buffer is needed after egui finished rendering
|
||||
// so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon)
|
||||
store: wgpu::StoreOp::Discard,
|
||||
}),
|
||||
stencil_ops: self
|
||||
.options
|
||||
.depth_stencil_format
|
||||
.is_some_and(|depth_stencil_format| {
|
||||
depth_stencil_format.has_stencil_aspect()
|
||||
})
|
||||
.then_some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(0),
|
||||
store: wgpu::StoreOp::Discard,
|
||||
}),
|
||||
}
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
@@ -614,7 +631,7 @@ impl Painter {
|
||||
events.push(Event::Screenshot {
|
||||
viewport_id,
|
||||
user_data: data,
|
||||
image: screenshot.clone(),
|
||||
image: Arc::clone(&screenshot),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.33.2 - 2025-11-13
|
||||
* Don't enable `arboard` on iOS [#7663](https://github.com/emilk/egui/pull/7663) by [@irh](https://github.com/irh)
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ fn init_arboard() -> Option<arboard::Clipboard> {
|
||||
fn init_smithay_clipboard(
|
||||
raw_display_handle: Option<RawDisplayHandle>,
|
||||
) -> Option<smithay_clipboard::Clipboard> {
|
||||
#![allow(clippy::undocumented_unsafe_blocks)]
|
||||
#![expect(clippy::undocumented_unsafe_blocks)]
|
||||
|
||||
profiling::function_scope!();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
|
||||
//!
|
||||
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
#![expect(clippy::manual_range_contains)]
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub use accesskit_winit;
|
||||
@@ -309,21 +309,21 @@ impl State {
|
||||
self.on_mouse_button_input(*state, *button);
|
||||
EventResponse {
|
||||
repaint: true,
|
||||
consumed: self.egui_ctx.wants_pointer_input(),
|
||||
consumed: self.egui_ctx.egui_wants_pointer_input(),
|
||||
}
|
||||
}
|
||||
WindowEvent::MouseWheel { delta, phase, .. } => {
|
||||
self.on_mouse_wheel(window, *delta, *phase);
|
||||
EventResponse {
|
||||
repaint: true,
|
||||
consumed: self.egui_ctx.wants_pointer_input(),
|
||||
consumed: self.egui_ctx.egui_wants_pointer_input(),
|
||||
}
|
||||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
self.on_cursor_moved(window, *position);
|
||||
EventResponse {
|
||||
repaint: true,
|
||||
consumed: self.egui_ctx.is_using_pointer(),
|
||||
consumed: self.egui_ctx.egui_is_using_pointer(),
|
||||
}
|
||||
}
|
||||
WindowEvent::CursorLeft { .. } => {
|
||||
@@ -340,8 +340,10 @@ impl State {
|
||||
let consumed = match touch.phase {
|
||||
winit::event::TouchPhase::Started
|
||||
| winit::event::TouchPhase::Ended
|
||||
| winit::event::TouchPhase::Cancelled => self.egui_ctx.wants_pointer_input(),
|
||||
winit::event::TouchPhase::Moved => self.egui_ctx.is_using_pointer(),
|
||||
| winit::event::TouchPhase::Cancelled => {
|
||||
self.egui_ctx.egui_wants_pointer_input()
|
||||
}
|
||||
winit::event::TouchPhase::Moved => self.egui_ctx.egui_is_using_pointer(),
|
||||
};
|
||||
EventResponse {
|
||||
repaint: true,
|
||||
@@ -392,7 +394,7 @@ impl State {
|
||||
|
||||
EventResponse {
|
||||
repaint: true,
|
||||
consumed: self.egui_ctx.wants_keyboard_input(),
|
||||
consumed: self.egui_ctx.egui_wants_keyboard_input(),
|
||||
}
|
||||
}
|
||||
WindowEvent::KeyboardInput {
|
||||
@@ -413,7 +415,7 @@ impl State {
|
||||
self.on_keyboard_input(event);
|
||||
|
||||
// When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes.
|
||||
let consumed = self.egui_ctx.wants_keyboard_input()
|
||||
let consumed = self.egui_ctx.egui_wants_keyboard_input()
|
||||
|| event.logical_key
|
||||
== winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
|
||||
EventResponse {
|
||||
@@ -528,7 +530,7 @@ impl State {
|
||||
self.egui_input.events.push(egui::Event::Zoom(zoom_factor));
|
||||
EventResponse {
|
||||
repaint: true,
|
||||
consumed: self.egui_ctx.wants_pointer_input(),
|
||||
consumed: self.egui_ctx.egui_wants_pointer_input(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,7 +543,7 @@ impl State {
|
||||
.push(egui::Event::Rotate(-delta.to_radians()));
|
||||
EventResponse {
|
||||
repaint: true,
|
||||
consumed: self.egui_ctx.wants_pointer_input(),
|
||||
consumed: self.egui_ctx.egui_wants_pointer_input(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,7 +558,7 @@ impl State {
|
||||
});
|
||||
EventResponse {
|
||||
repaint: true,
|
||||
consumed: self.egui_ctx.wants_pointer_input(),
|
||||
consumed: self.egui_ctx.egui_wants_pointer_input(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ mod ios {
|
||||
|
||||
let app = UIApplication::sharedApplication(main_thread_marker);
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
#[expect(unsafe_code)]
|
||||
unsafe {
|
||||
// Look for the first window scene that's in the foreground
|
||||
for scene in app.connectedScenes() {
|
||||
|
||||
@@ -82,7 +82,7 @@ impl<'a> AtomKind<'a> {
|
||||
) -> (Vec2, SizedAtomKind<'a>) {
|
||||
match self {
|
||||
AtomKind::Text(text) => {
|
||||
let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode());
|
||||
let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
|
||||
let galley = text.into_galley(ui, Some(wrap_mode), available_size.x, fallback_font);
|
||||
(galley.intrinsic_size(), SizedAtomKind::Text(galley))
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ impl<'a> AtomLayout<'a> {
|
||||
|
||||
let fallback_font = fallback_font.unwrap_or_default();
|
||||
|
||||
let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode());
|
||||
let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());
|
||||
|
||||
// If the TextWrapMode is not Extend, ensure there is some item marked as `shrink`.
|
||||
// If none is found, mark the first text item as `shrink`.
|
||||
@@ -188,7 +188,7 @@ impl<'a> AtomLayout<'a> {
|
||||
|
||||
let fallback_text_color =
|
||||
fallback_text_color.unwrap_or_else(|| ui.style().visuals.text_color());
|
||||
let gap = gap.unwrap_or(ui.spacing().icon_spacing);
|
||||
let gap = gap.unwrap_or_else(|| ui.spacing().icon_spacing);
|
||||
|
||||
// The size available for the content
|
||||
let available_inner_size = ui.available_size() - frame.total_margin().sum();
|
||||
|
||||
@@ -198,8 +198,7 @@ macro_rules! all_the_atoms {
|
||||
$($T: IntoAtoms<'a>),*
|
||||
{
|
||||
fn collect(self, _atoms: &mut Atoms<'a>) {
|
||||
#[allow(clippy::allow_attributes)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::allow_attributes, non_snake_case)]
|
||||
let ($($T),*) = self;
|
||||
$($T.collect(_atoms);)*
|
||||
}
|
||||
|
||||
1
crates/egui/src/cache/cache_storage.rs
vendored
1
crates/egui/src/cache/cache_storage.rs
vendored
@@ -28,6 +28,7 @@ pub struct CacheStorage {
|
||||
|
||||
impl CacheStorage {
|
||||
pub fn cache<Cache: CacheTrait + Default>(&mut self) -> &mut Cache {
|
||||
#[expect(clippy::unwrap_used)]
|
||||
self.caches
|
||||
.entry(std::any::TypeId::of::<Cache>())
|
||||
.or_insert_with(|| Box::<Cache>::default())
|
||||
|
||||
@@ -172,7 +172,7 @@ impl Area {
|
||||
|
||||
/// Set the [`UiStackInfo`] of the area's [`Ui`].
|
||||
///
|
||||
/// Default to [`UiStackInfo::new(UiKind::GenericArea)`].
|
||||
/// Default to [`UiStackInfo`] with kind [`UiKind::GenericArea`].
|
||||
#[inline]
|
||||
pub fn info(mut self, info: UiStackInfo) -> Self {
|
||||
self.info = info;
|
||||
@@ -459,7 +459,7 @@ impl Area {
|
||||
state.pivot_pos = Some(new_pos);
|
||||
}
|
||||
state.pivot_pos.get_or_insert_with(|| {
|
||||
default_pos.unwrap_or_else(|| automatic_area_position(ctx, layer_id))
|
||||
default_pos.unwrap_or_else(|| automatic_area_position(ctx, constrain_rect, layer_id))
|
||||
});
|
||||
state.interactable = interactable;
|
||||
|
||||
@@ -469,7 +469,7 @@ impl Area {
|
||||
// during the sizing pass we will use this as the max size
|
||||
let mut size = default_size;
|
||||
|
||||
let default_area_size = ctx.style().spacing.default_area_size;
|
||||
let default_area_size = ctx.global_style().spacing.default_area_size;
|
||||
if size.x.is_nan() {
|
||||
size.x = default_area_size.x;
|
||||
}
|
||||
@@ -523,6 +523,7 @@ impl Area {
|
||||
enabled,
|
||||
},
|
||||
true,
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
// Used to prevent drift
|
||||
@@ -634,7 +635,8 @@ impl Prepared {
|
||||
{
|
||||
let age =
|
||||
ctx.input(|i| (i.time - last_became_visible_at) as f32 + i.predicted_dt / 2.0);
|
||||
let opacity = crate::remap_clamp(age, 0.0..=ctx.style().animation_time, 0.0..=1.0);
|
||||
let opacity =
|
||||
crate::remap_clamp(age, 0.0..=ctx.global_style().animation_time, 0.0..=1.0);
|
||||
let opacity = emath::easing::quadratic_out(opacity); // slow fade-out = quick fade-in
|
||||
ui.multiply_opacity(opacity);
|
||||
if opacity < 1.0 {
|
||||
@@ -698,7 +700,7 @@ fn pointer_pressed_on_area(ctx: &Context, layer_id: LayerId) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn automatic_area_position(ctx: &Context, layer_id: LayerId) -> Pos2 {
|
||||
fn automatic_area_position(ctx: &Context, constrain_rect: Rect, layer_id: LayerId) -> Pos2 {
|
||||
let mut existing: Vec<Rect> = ctx.memory(|mem| {
|
||||
mem.areas()
|
||||
.visible_windows()
|
||||
@@ -709,13 +711,9 @@ fn automatic_area_position(ctx: &Context, layer_id: LayerId) -> Pos2 {
|
||||
});
|
||||
existing.sort_by_key(|r| r.left().round() as i32);
|
||||
|
||||
// NOTE: for the benefit of the egui demo, we position the windows so they don't
|
||||
// cover the side panels, which means we use `available_rect` here instead of `constrain_rect` or `screen_rect`.
|
||||
let available_rect = ctx.available_rect();
|
||||
|
||||
let spacing = 16.0;
|
||||
let left = available_rect.left() + spacing;
|
||||
let top = available_rect.top() + spacing;
|
||||
let left = constrain_rect.left() + spacing;
|
||||
let top = constrain_rect.top() + spacing;
|
||||
|
||||
if existing.is_empty() {
|
||||
return pos2(left, top);
|
||||
@@ -725,6 +723,7 @@ fn automatic_area_position(ctx: &Context, layer_id: LayerId) -> Pos2 {
|
||||
let mut column_bbs = vec![existing[0]];
|
||||
|
||||
for &rect in &existing {
|
||||
#[expect(clippy::unwrap_used)]
|
||||
let current_column_bb = column_bbs.last_mut().unwrap();
|
||||
if rect.left() < current_column_bb.right() {
|
||||
// same column
|
||||
@@ -749,14 +748,15 @@ fn automatic_area_position(ctx: &Context, layer_id: LayerId) -> Pos2 {
|
||||
|
||||
// Find first column with some available space at the bottom of it:
|
||||
for col_bb in &column_bbs {
|
||||
if col_bb.bottom() < available_rect.center().y {
|
||||
if col_bb.bottom() < constrain_rect.center().y {
|
||||
return pos2(col_bb.left(), col_bb.bottom() + spacing);
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe we can fit a new column?
|
||||
#[expect(clippy::unwrap_used)]
|
||||
let rightmost = column_bbs.last().unwrap().right();
|
||||
if rightmost + 200.0 < available_rect.right() {
|
||||
if rightmost + 200.0 < constrain_rect.right() {
|
||||
return pos2(rightmost + spacing, top);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ impl CollapsingState {
|
||||
|
||||
pub fn toggle(&mut self, ui: &Ui) {
|
||||
self.state.open = !self.state.open;
|
||||
ui.ctx().request_repaint();
|
||||
ui.request_repaint();
|
||||
}
|
||||
|
||||
/// 0 for closed, 1 for open, with tweening
|
||||
|
||||
@@ -336,7 +336,7 @@ impl Frame {
|
||||
impl Frame {
|
||||
/// How much extra space the frame uses up compared to the content.
|
||||
///
|
||||
/// [`Self::inner_margin`] + [`Self.stroke`]`.width` + [`Self::outer_margin`].
|
||||
/// [`Self::inner_margin`] + [`Self::stroke`]`.width` + [`Self::outer_margin`].
|
||||
#[inline]
|
||||
pub fn total_margin(&self) -> MarginF32 {
|
||||
MarginF32::from(self.inner_margin)
|
||||
|
||||
@@ -207,7 +207,7 @@ impl MenuState {
|
||||
/// egui::MenuBar::new().ui(ui, |ui| {
|
||||
/// ui.menu_button("File", |ui| {
|
||||
/// if ui.button("Quit").clicked() {
|
||||
/// ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
/// ui.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
/// }
|
||||
/// });
|
||||
/// });
|
||||
@@ -444,11 +444,8 @@ impl SubMenu {
|
||||
let mut menu_config = self.config.unwrap_or_else(|| parent_config.clone());
|
||||
menu_config.bar = false;
|
||||
|
||||
let menu_root_response = ui
|
||||
.ctx()
|
||||
.read_response(menu_id)
|
||||
// Since we are a child of that ui, this should always exist
|
||||
.unwrap();
|
||||
#[expect(clippy::unwrap_used)] // Since we are a child of that ui, this should always exist
|
||||
let menu_root_response = ui.ctx().read_response(menu_id).unwrap();
|
||||
|
||||
let hover_pos = ui.ctx().pointer_hover_pos();
|
||||
|
||||
@@ -556,7 +553,7 @@ impl SubMenu {
|
||||
if is_moving_towards_rect {
|
||||
// We need to repaint while this is true, so we can detect when
|
||||
// the pointer is no longer moving towards the rect
|
||||
ui.ctx().request_repaint();
|
||||
ui.request_repaint();
|
||||
}
|
||||
let hovering_other_menu_entry = is_open
|
||||
&& !is_hovered
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Old and deprecated API for popups. Use [`Popup`] instead.
|
||||
#![allow(deprecated)]
|
||||
#![expect(deprecated)]
|
||||
|
||||
use crate::containers::tooltip::Tooltip;
|
||||
use crate::{
|
||||
|
||||
@@ -255,9 +255,7 @@ impl<'a> PanelSizer<'a> {
|
||||
let side = self.panel.side;
|
||||
let size_range = self.panel.size_range;
|
||||
|
||||
if is_resizing && pointer.is_some() {
|
||||
let pointer = pointer.unwrap();
|
||||
|
||||
if is_resizing && let Some(pointer) = pointer {
|
||||
match side {
|
||||
PanelSide::Vertical(side) => {
|
||||
self.size = (pointer.x - side.side_x(self.panel_rect)).abs();
|
||||
@@ -291,13 +289,13 @@ impl<'a> PanelSizer<'a> {
|
||||
/// See the [module level docs](crate::containers::panel) for more details.
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ctx(|ctx| {
|
||||
/// egui::Panel::left("my_left_panel").show(ctx, |ui| {
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// egui::Panel::left("my_left_panel").show_inside(ui, |ui| {
|
||||
/// ui.label("Hello World!");
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
#[must_use = "You should call .show()"]
|
||||
#[must_use = "You should call .show_inside()"]
|
||||
pub struct Panel {
|
||||
side: PanelSide,
|
||||
id: Id,
|
||||
@@ -518,6 +516,7 @@ impl Panel {
|
||||
}
|
||||
|
||||
/// Show the panel at the top level.
|
||||
#[deprecated = "Use show_inside() instead"]
|
||||
pub fn show<R>(
|
||||
self,
|
||||
ctx: &Context,
|
||||
@@ -528,12 +527,15 @@ impl Panel {
|
||||
|
||||
/// Show the panel if `is_expanded` is `true`,
|
||||
/// 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>(
|
||||
self,
|
||||
ctx: &Context,
|
||||
is_expanded: bool,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> Option<InnerResponse<R>> {
|
||||
#![expect(deprecated)]
|
||||
|
||||
let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded);
|
||||
|
||||
let animated_panel = self.get_animated_panel(ctx, is_expanded)?;
|
||||
@@ -572,6 +574,7 @@ impl Panel {
|
||||
}
|
||||
|
||||
/// 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>(
|
||||
ctx: &Context,
|
||||
is_expanded: bool,
|
||||
@@ -579,6 +582,8 @@ impl Panel {
|
||||
expanded_panel: Self,
|
||||
add_contents: impl FnOnce(&mut Ui, f32) -> R,
|
||||
) -> Option<InnerResponse<R>> {
|
||||
#![expect(deprecated)]
|
||||
|
||||
let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded);
|
||||
|
||||
// Get either the fake or the real panel to animate
|
||||
@@ -713,7 +718,7 @@ impl Panel {
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -753,6 +758,8 @@ impl Panel {
|
||||
ctx: &Context,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> InnerResponse<R> {
|
||||
#![expect(deprecated)]
|
||||
|
||||
let side = self.side;
|
||||
let available_rect = ctx.available_rect();
|
||||
let mut panel_ui = Ui::new(
|
||||
@@ -802,9 +809,7 @@ impl Panel {
|
||||
let resize_id = self.id.with("__resize");
|
||||
let resize_response = ui.ctx().read_response(resize_id);
|
||||
|
||||
if resize_response.is_some() {
|
||||
let resize_response = resize_response.unwrap();
|
||||
|
||||
if let Some(resize_response) = resize_response {
|
||||
// NOTE(sharky98): The original code was initializing to
|
||||
// false first, but it doesn't seem necessary.
|
||||
let is_resizing = resize_response.dragged();
|
||||
@@ -934,14 +939,14 @@ impl Panel {
|
||||
};
|
||||
|
||||
let get_spacing_size = || match panel.side {
|
||||
PanelSide::Vertical(_) => ctx.style().spacing.interact_size.x,
|
||||
PanelSide::Horizontal(_) => ctx.style().spacing.interact_size.y,
|
||||
PanelSide::Vertical(_) => ctx.global_style().spacing.interact_size.x,
|
||||
PanelSide::Horizontal(_) => ctx.global_style().spacing.interact_size.y,
|
||||
};
|
||||
|
||||
PanelState::load(ctx, panel.id)
|
||||
.map(get_rect_state_size)
|
||||
.or(panel.default_size)
|
||||
.unwrap_or(get_spacing_size())
|
||||
.unwrap_or_else(get_spacing_size)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -950,6 +955,9 @@ impl Panel {
|
||||
/// A panel that covers the remainder of the screen,
|
||||
/// 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 first panel you add will always be the outermost, and the last you add will always be the innermost.
|
||||
///
|
||||
@@ -960,31 +968,41 @@ impl Panel {
|
||||
/// See the [module level docs](crate::containers::panel) for more details.
|
||||
///
|
||||
/// ```
|
||||
/// # egui::__run_test_ctx(|ctx| {
|
||||
/// egui::Panel::top("my_panel").show(ctx, |ui| {
|
||||
/// # egui::__run_test_ui(|ui| {
|
||||
/// egui::Panel::top("my_panel").show_inside(ui, |ui| {
|
||||
/// 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!");
|
||||
/// });
|
||||
/// # });
|
||||
/// ```
|
||||
#[must_use = "You should call .show()"]
|
||||
#[must_use = "You should call .show_inside()"]
|
||||
#[derive(Default)]
|
||||
pub struct CentralPanel {
|
||||
frame: Option<Frame>,
|
||||
}
|
||||
|
||||
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.
|
||||
#[inline]
|
||||
pub fn frame(mut self, frame: Frame) -> Self {
|
||||
self.frame = Some(frame);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl CentralPanel {
|
||||
/// Show the panel inside a [`Ui`].
|
||||
pub fn show_inside<R>(
|
||||
self,
|
||||
@@ -1012,13 +1030,19 @@ impl CentralPanel {
|
||||
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()));
|
||||
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
|
||||
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.
|
||||
#[deprecated = "Use show_inside() instead"]
|
||||
pub fn show<R>(
|
||||
self,
|
||||
ctx: &Context,
|
||||
@@ -1033,6 +1057,8 @@ impl CentralPanel {
|
||||
ctx: &Context,
|
||||
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> InnerResponse<R> {
|
||||
#![expect(deprecated)]
|
||||
|
||||
let id = Id::new((ctx.viewport_id(), "central_panel"));
|
||||
|
||||
let mut panel_ui = Ui::new(
|
||||
|
||||
@@ -465,7 +465,7 @@ impl<'a> Popup<'a> {
|
||||
pub fn get_best_align(&self) -> RectAlign {
|
||||
let expected_popup_size = self
|
||||
.get_expected_size()
|
||||
.unwrap_or(vec2(self.width.unwrap_or(0.0), 0.0));
|
||||
.unwrap_or_else(|| vec2(self.width.unwrap_or(0.0), 0.0));
|
||||
|
||||
let Some(anchor_rect) = self.anchor.rect(self.id, &self.ctx) else {
|
||||
return self.rect_align;
|
||||
@@ -473,6 +473,7 @@ impl<'a> Popup<'a> {
|
||||
|
||||
RectAlign::find_best_align(
|
||||
#[expect(clippy::iter_on_empty_collections)]
|
||||
#[expect(clippy::or_fun_call)]
|
||||
once(self.rect_align).chain(
|
||||
self.alternative_aligns
|
||||
// Need the empty slice so the iters have the same type so we can unwrap_or
|
||||
|
||||
@@ -213,7 +213,7 @@ impl Resize {
|
||||
});
|
||||
|
||||
let mut state = State::load(ui.ctx(), id).unwrap_or_else(|| {
|
||||
ui.ctx().request_repaint(); // counter frame delay
|
||||
ui.request_repaint(); // counter frame delay
|
||||
|
||||
let default_size = self
|
||||
.default_size
|
||||
@@ -362,20 +362,20 @@ impl Resize {
|
||||
paint_resize_corner(ui, &corner_response);
|
||||
|
||||
if corner_response.hovered() || corner_response.dragged() {
|
||||
ui.ctx().set_cursor_icon(CursorIcon::ResizeNwSe);
|
||||
ui.set_cursor_icon(CursorIcon::ResizeNwSe);
|
||||
}
|
||||
}
|
||||
|
||||
state.store(ui.ctx(), id);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
if ui.ctx().style().debug.show_resize {
|
||||
ui.ctx().debug_painter().debug_rect(
|
||||
if ui.global_style().debug.show_resize {
|
||||
ui.debug_painter().debug_rect(
|
||||
Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size),
|
||||
Color32::GREEN,
|
||||
"desired_size",
|
||||
);
|
||||
ui.ctx().debug_painter().debug_rect(
|
||||
ui.debug_painter().debug_rect(
|
||||
Rect::from_min_size(content_ui.min_rect().left_top(), state.last_content_size),
|
||||
Color32::LIGHT_BLUE,
|
||||
"last_content_size",
|
||||
|
||||
@@ -244,8 +244,8 @@ impl Scene {
|
||||
&& resp.contains_pointer()
|
||||
{
|
||||
let pointer_in_scene = to_global.inverse() * mouse_pos;
|
||||
let zoom_delta = ui.ctx().input(|i| i.zoom_delta());
|
||||
let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta());
|
||||
let zoom_delta = ui.input(|i| i.zoom_delta());
|
||||
let pan_delta = ui.input(|i| i.smooth_scroll_delta());
|
||||
|
||||
// Most of the time we can return early. This is also important to
|
||||
// avoid `ui_from_scene` to change slightly due to floating point errors.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! See [`ScrollArea`] for docs.
|
||||
|
||||
#![allow(clippy::needless_range_loop)]
|
||||
#![expect(clippy::needless_range_loop)]
|
||||
|
||||
use std::ops::{Add, AddAssign, BitOr, BitOrAssign};
|
||||
|
||||
@@ -9,7 +9,8 @@ use epaint::Margin;
|
||||
|
||||
use crate::{
|
||||
Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder,
|
||||
UiKind, UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp,
|
||||
UiKind, UiStackInfo, Vec2, Vec2b, WidgetInfo, emath, epaint, lerp, pass_state, pos2, remap,
|
||||
remap_clamp,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -856,11 +857,11 @@ impl ScrollArea {
|
||||
if response.dragged()
|
||||
&& let Some(cursor) = on_drag_cursor
|
||||
{
|
||||
ui.ctx().set_cursor_icon(cursor);
|
||||
ui.set_cursor_icon(cursor);
|
||||
} else if response.hovered()
|
||||
&& let Some(cursor) = on_hover_cursor
|
||||
{
|
||||
ui.ctx().set_cursor_icon(cursor);
|
||||
ui.set_cursor_icon(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1125,7 +1126,7 @@ impl Prepared {
|
||||
target_offset,
|
||||
});
|
||||
}
|
||||
ui.ctx().request_repaint();
|
||||
ui.request_repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1178,7 +1179,7 @@ impl Prepared {
|
||||
&& direction_enabled[0] != direction_enabled[1];
|
||||
for d in 0..2 {
|
||||
if direction_enabled[d] {
|
||||
let scroll_delta = ui.ctx().input(|input| {
|
||||
let scroll_delta = ui.input(|input| {
|
||||
if always_scroll_enabled_direction {
|
||||
// no bidirectional scrolling; allow horizontal scrolling without pressing shift
|
||||
input.smooth_scroll_delta()[0] + input.smooth_scroll_delta()[1]
|
||||
@@ -1195,7 +1196,7 @@ impl Prepared {
|
||||
state.offset[d] -= scroll_delta;
|
||||
|
||||
// Clear scroll delta so no parent scroll will use it:
|
||||
ui.ctx().input_mut(|input| {
|
||||
ui.input_mut(|input| {
|
||||
if always_scroll_enabled_direction {
|
||||
input.smooth_scroll_delta()[0] = 0.0;
|
||||
input.smooth_scroll_delta()[1] = 0.0;
|
||||
@@ -1241,46 +1242,17 @@ impl Prepared {
|
||||
continue;
|
||||
}
|
||||
|
||||
let interact_id = id.with(d);
|
||||
|
||||
// Margin on either side of the scroll bar:
|
||||
let inner_margin = show_factor * scroll_style.bar_inner_margin;
|
||||
let outer_margin = show_factor * scroll_style.bar_outer_margin;
|
||||
|
||||
// top/bottom of a horizontal scroll (d==0).
|
||||
// left/rigth of a vertical scroll (d==1).
|
||||
let mut cross = if scroll_style.floating {
|
||||
// The bounding rect of a fully visible bar.
|
||||
// When we hover this area, we should show the full bar:
|
||||
let max_bar_rect = if d == 0 {
|
||||
outer_rect.with_min_y(outer_rect.max.y - outer_margin - scroll_style.bar_width)
|
||||
} else {
|
||||
outer_rect.with_min_x(outer_rect.max.x - outer_margin - scroll_style.bar_width)
|
||||
};
|
||||
// bottom of a horizontal scroll (d==0).
|
||||
// right of a vertical scroll (d==1).
|
||||
let mut max_cross = outer_rect.max[1 - d] - outer_margin;
|
||||
|
||||
let is_hovering_bar_area = is_hovering_outer_rect
|
||||
&& ui.rect_contains_pointer(max_bar_rect)
|
||||
&& !is_dragging_background
|
||||
|| state.scroll_bar_interaction[d];
|
||||
|
||||
let is_hovering_bar_area_t = ui
|
||||
.ctx()
|
||||
.animate_bool_responsive(id.with((d, "bar_hover")), is_hovering_bar_area);
|
||||
|
||||
let width = show_factor
|
||||
* lerp(
|
||||
scroll_style.floating_width..=scroll_style.bar_width,
|
||||
is_hovering_bar_area_t,
|
||||
);
|
||||
|
||||
let max_cross = outer_rect.max[1 - d] - outer_margin;
|
||||
let min_cross = max_cross - width;
|
||||
Rangef::new(min_cross, max_cross)
|
||||
} else {
|
||||
let min_cross = inner_rect.max[1 - d] + inner_margin;
|
||||
let max_cross = outer_rect.max[1 - d] - outer_margin;
|
||||
Rangef::new(min_cross, max_cross)
|
||||
};
|
||||
|
||||
if ui.clip_rect().max[1 - d] < cross.max + outer_margin {
|
||||
if ui.clip_rect().max[1 - d] - outer_margin < max_cross {
|
||||
// Move the scrollbar so it is visible. This is needed in some cases.
|
||||
// For instance:
|
||||
// * When we have a vertical-only scroll area in a top level panel,
|
||||
@@ -1290,21 +1262,59 @@ impl Prepared {
|
||||
// is outside the clip rectangle.
|
||||
// Really this should use the tighter clip_rect that ignores clip_rect_margin, but we don't store that.
|
||||
// clip_rect_margin is quite a hack. It would be nice to get rid of it.
|
||||
let width = cross.max - cross.min;
|
||||
cross.max = ui.clip_rect().max[1 - d] - outer_margin;
|
||||
cross.min = cross.max - width;
|
||||
max_cross = ui.clip_rect().max[1 - d] - outer_margin;
|
||||
}
|
||||
|
||||
let outer_scroll_bar_rect = if d == 0 {
|
||||
Rect::from_min_max(
|
||||
pos2(scroll_bar_rect.left(), cross.min),
|
||||
pos2(scroll_bar_rect.right(), cross.max),
|
||||
)
|
||||
let full_width = scroll_style.bar_width;
|
||||
|
||||
// The bounding rect of a fully visible bar.
|
||||
// When we hover this area, we should show the full bar:
|
||||
let max_bar_rect = if d == 0 {
|
||||
outer_rect.with_min_y(max_cross - full_width)
|
||||
} else {
|
||||
Rect::from_min_max(
|
||||
pos2(cross.min, scroll_bar_rect.top()),
|
||||
pos2(cross.max, scroll_bar_rect.bottom()),
|
||||
)
|
||||
outer_rect.with_min_x(max_cross - full_width)
|
||||
};
|
||||
|
||||
let sense = if scroll_source.scroll_bar && ui.is_enabled() {
|
||||
Sense::click_and_drag()
|
||||
} else {
|
||||
Sense::hover()
|
||||
};
|
||||
|
||||
// We always sense interaction with the full width, even if we antimate it growing/shrinking.
|
||||
// This is to present a more consistent target for our hit test code,
|
||||
// and to avoid producing jitter in "thin widget" heuristics there.
|
||||
// Also: it make sense to detect any hover where the scroll bar _will_ be.
|
||||
let response = ui.interact(max_bar_rect, interact_id, sense);
|
||||
|
||||
response.widget_info(|| WidgetInfo::new(crate::WidgetType::ScrollBar));
|
||||
|
||||
// top/bottom of a horizontal scroll (d==0).
|
||||
// left/rigth of a vertical scroll (d==1).
|
||||
let cross = if scroll_style.floating {
|
||||
let is_hovering_bar_area = response.hovered() || state.scroll_bar_interaction[d];
|
||||
|
||||
let is_hovering_bar_area_t = ui
|
||||
.ctx()
|
||||
.animate_bool_responsive(id.with((d, "bar_hover")), is_hovering_bar_area);
|
||||
|
||||
let width = show_factor
|
||||
* lerp(
|
||||
scroll_style.floating_width..=full_width,
|
||||
is_hovering_bar_area_t,
|
||||
);
|
||||
|
||||
let min_cross = max_cross - width;
|
||||
Rangef::new(min_cross, max_cross)
|
||||
} else {
|
||||
let min_cross = inner_rect.max[1 - d] + inner_margin;
|
||||
Rangef::new(min_cross, max_cross)
|
||||
};
|
||||
|
||||
let outer_scroll_bar_rect = if d == 0 {
|
||||
Rect::from_x_y_ranges(scroll_bar_rect.x_range(), cross)
|
||||
} else {
|
||||
Rect::from_x_y_ranges(cross, scroll_bar_rect.y_range())
|
||||
};
|
||||
|
||||
let from_content = |content| {
|
||||
@@ -1344,14 +1354,6 @@ impl Prepared {
|
||||
|
||||
let handle_rect = calculate_handle_rect(d, &state.offset);
|
||||
|
||||
let interact_id = id.with(d);
|
||||
let sense = if scroll_source.scroll_bar && ui.is_enabled() {
|
||||
Sense::click_and_drag()
|
||||
} else {
|
||||
Sense::hover()
|
||||
};
|
||||
let response = ui.interact(outer_scroll_bar_rect, interact_id, sense);
|
||||
|
||||
state.scroll_bar_interaction[d] = response.hovered() || response.dragged();
|
||||
|
||||
if let Some(pointer_pos) = response.interact_pointer_pos() {
|
||||
@@ -1475,7 +1477,7 @@ impl Prepared {
|
||||
ui.advance_cursor_after_rect(outer_rect);
|
||||
|
||||
if show_scroll_this_frame != state.show_scroll {
|
||||
ui.ctx().request_repaint();
|
||||
ui.request_repaint();
|
||||
}
|
||||
|
||||
let available_offset = content_size - inner_rect.size();
|
||||
|
||||
@@ -41,7 +41,7 @@ impl Tooltip<'_> {
|
||||
parent_widget: Id,
|
||||
anchor: impl Into<PopupAnchor>,
|
||||
) -> Self {
|
||||
let width = ctx.style().spacing.tooltip_width;
|
||||
let width = ctx.global_style().spacing.tooltip_width;
|
||||
Self {
|
||||
popup: Popup::new(parent_widget, ctx, anchor.into(), parent_layer)
|
||||
.kind(PopupKind::Tooltip)
|
||||
@@ -58,7 +58,7 @@ impl Tooltip<'_> {
|
||||
let popup = Popup::from_response(response)
|
||||
.kind(PopupKind::Tooltip)
|
||||
.gap(4.0)
|
||||
.width(response.ctx.style().spacing.tooltip_width)
|
||||
.width(response.ctx.global_style().spacing.tooltip_width)
|
||||
.sense(Sense::hover());
|
||||
Self {
|
||||
popup,
|
||||
@@ -229,7 +229,7 @@ impl Tooltip<'_> {
|
||||
return false;
|
||||
}
|
||||
|
||||
let style = response.ctx.style();
|
||||
let style = response.ctx.global_style();
|
||||
|
||||
let tooltip_delay = style.interaction.tooltip_delay;
|
||||
let tooltip_grace_time = style.interaction.tooltip_grace_time;
|
||||
|
||||
@@ -70,6 +70,41 @@ impl<'open> Window<'open> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a [`Window`] that follows the given viewport.
|
||||
pub fn from_viewport(id: ViewportId, viewport: ViewportBuilder) -> Self {
|
||||
let ViewportBuilder {
|
||||
title,
|
||||
app_id,
|
||||
inner_size,
|
||||
min_inner_size,
|
||||
max_inner_size,
|
||||
resizable,
|
||||
decorations,
|
||||
title_shown,
|
||||
minimize_button,
|
||||
.. // A lot of things not implemented yet
|
||||
} = viewport;
|
||||
|
||||
let mut window = Self::new(title.or(app_id).unwrap_or_else(String::new)).id(Id::new(id));
|
||||
|
||||
if let Some(inner_size) = inner_size {
|
||||
window = window.default_size(inner_size);
|
||||
}
|
||||
if let Some(min_inner_size) = min_inner_size {
|
||||
window = window.min_size(min_inner_size);
|
||||
}
|
||||
if let Some(max_inner_size) = max_inner_size {
|
||||
window = window.max_size(max_inner_size);
|
||||
}
|
||||
if let Some(resizable) = resizable {
|
||||
window = window.resizable(resizable);
|
||||
}
|
||||
window = window.title_bar(decorations.unwrap_or(true) && title_shown.unwrap_or(true));
|
||||
window = window.collapsible(minimize_button.unwrap_or(true));
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
/// Assign a unique id to the Window. Required if the title changes, or is shared with another window.
|
||||
#[inline]
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
@@ -440,9 +475,11 @@ impl Window<'_> {
|
||||
fade_out,
|
||||
} = self;
|
||||
|
||||
let style = ctx.global_style();
|
||||
|
||||
let header_color =
|
||||
frame.map_or_else(|| ctx.style().visuals.widgets.open.weak_bg_fill, |f| f.fill);
|
||||
let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));
|
||||
frame.map_or_else(|| style.visuals.widgets.open.weak_bg_fill, |f| f.fill);
|
||||
let mut window_frame = frame.unwrap_or_else(|| Frame::window(&style));
|
||||
|
||||
let is_explicitly_closed = matches!(open, Some(false));
|
||||
let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible());
|
||||
@@ -474,7 +511,6 @@ impl Window<'_> {
|
||||
|
||||
// Calculate roughly how much larger the full window inner size is compared to the content rect
|
||||
let (title_bar_height_with_margin, title_content_spacing) = if with_title_bar {
|
||||
let style = ctx.style();
|
||||
let title_bar_inner_height = ctx
|
||||
.fonts_mut(|fonts| title.font_height(fonts, &style))
|
||||
.at_least(style.spacing.interact_size.y);
|
||||
@@ -505,7 +541,7 @@ impl Window<'_> {
|
||||
|
||||
// First check for resize to avoid frame delay:
|
||||
let last_frame_outer_rect = area.state().rect();
|
||||
let resize_interaction = resize_interaction(
|
||||
let resize_interaction = do_resize_interaction(
|
||||
ctx,
|
||||
possible,
|
||||
area.id(),
|
||||
@@ -586,6 +622,17 @@ impl Window<'_> {
|
||||
.map_or((None, None), |ir| (Some(ir.inner), Some(ir.response)));
|
||||
|
||||
let outer_rect = frame.end(&mut area_content_ui).rect;
|
||||
|
||||
// Do resize interaction _again_, to move their widget rectangles on TOP of the rest of the window.
|
||||
let resize_interaction = do_resize_interaction(
|
||||
ctx,
|
||||
possible,
|
||||
area.id(),
|
||||
area_layer_id,
|
||||
last_frame_outer_rect,
|
||||
window_frame,
|
||||
);
|
||||
|
||||
paint_resize_corner(
|
||||
&area_content_ui,
|
||||
&possible,
|
||||
@@ -887,7 +934,7 @@ fn move_and_resize_window(ctx: &Context, id: Id, interaction: &ResizeInteraction
|
||||
Some(rect.round_ui())
|
||||
}
|
||||
|
||||
fn resize_interaction(
|
||||
fn do_resize_interaction(
|
||||
ctx: &Context,
|
||||
possible: PossibleInteractions,
|
||||
_accessibility_parent: Id,
|
||||
@@ -921,7 +968,16 @@ fn resize_interaction(
|
||||
enabled: true,
|
||||
},
|
||||
true,
|
||||
InteractOptions {
|
||||
// We call this multiple times.
|
||||
// First to read the result (to avoid frame delay)
|
||||
// and the second time to move it to the top, above the window contents.
|
||||
move_to_top: true,
|
||||
},
|
||||
);
|
||||
|
||||
response.widget_info(|| WidgetInfo::new(crate::WidgetType::ResizeHandle));
|
||||
|
||||
SideResponse {
|
||||
hover: response.hovered(),
|
||||
drag: response.dragged(),
|
||||
@@ -930,8 +986,10 @@ fn resize_interaction(
|
||||
|
||||
let id = Id::new(layer_id).with("edge_drag");
|
||||
|
||||
let side_grab_radius = ctx.style().interaction.resize_grab_radius_side;
|
||||
let corner_grab_radius = ctx.style().interaction.resize_grab_radius_corner;
|
||||
let style = ctx.global_style();
|
||||
|
||||
let side_grab_radius = style.interaction.resize_grab_radius_side;
|
||||
let corner_grab_radius = style.interaction.resize_grab_radius_corner;
|
||||
|
||||
let vetrtical_rect = |a: Pos2, b: Pos2| {
|
||||
Rect::from_min_max(a, b).expand2(vec2(side_grab_radius, -corner_grab_radius))
|
||||
@@ -1141,8 +1199,7 @@ impl TitleBar {
|
||||
title_bar_height_with_margin: f32,
|
||||
) -> Self {
|
||||
if false {
|
||||
ui.ctx()
|
||||
.debug_painter()
|
||||
ui.debug_painter()
|
||||
.debug_rect(ui.min_rect(), Color32::GREEN, "outer_min_rect");
|
||||
}
|
||||
|
||||
@@ -1170,8 +1227,7 @@ impl TitleBar {
|
||||
let min_rect = Rect::from_min_size(ui.min_rect().min, min_inner_size);
|
||||
|
||||
if false {
|
||||
ui.ctx()
|
||||
.debug_painter()
|
||||
ui.debug_painter()
|
||||
.debug_rect(min_rect, Color32::LIGHT_BLUE, "min_rect");
|
||||
}
|
||||
|
||||
@@ -1208,8 +1264,7 @@ impl TitleBar {
|
||||
let title_inner_rect = self.inner_rect;
|
||||
|
||||
if false {
|
||||
ui.ctx()
|
||||
.debug_painter()
|
||||
ui.debug_painter()
|
||||
.debug_rect(self.inner_rect, Color32::RED, "TitleBar");
|
||||
}
|
||||
|
||||
@@ -1240,7 +1295,7 @@ impl TitleBar {
|
||||
let text_pos = text_pos - self.title_galley.rect.min.to_vec2();
|
||||
ui.painter().galley(
|
||||
text_pos,
|
||||
self.title_galley.clone(),
|
||||
Arc::clone(&self.title_galley),
|
||||
ui.visuals().text_color(),
|
||||
);
|
||||
|
||||
@@ -1248,8 +1303,7 @@ impl TitleBar {
|
||||
// Paint separator between title and content:
|
||||
let content_rect = content_response.rect;
|
||||
if false {
|
||||
ui.ctx()
|
||||
.debug_painter()
|
||||
ui.debug_painter()
|
||||
.debug_rect(content_rect, Color32::RED, "content_rect");
|
||||
}
|
||||
let y = title_inner_rect.bottom() + window_frame.stroke.width / 2.0;
|
||||
@@ -1263,11 +1317,8 @@ impl TitleBar {
|
||||
let double_click_rect = title_inner_rect.shrink2(vec2(32.0, 0.0));
|
||||
|
||||
if false {
|
||||
ui.ctx().debug_painter().debug_rect(
|
||||
double_click_rect,
|
||||
Color32::GREEN,
|
||||
"double_click_rect",
|
||||
);
|
||||
ui.debug_painter()
|
||||
.debug_rect(double_click_rect, Color32::GREEN, "double_click_rect");
|
||||
}
|
||||
|
||||
let id = ui.unique_id().with("__window_title_bar");
|
||||
|
||||
@@ -19,8 +19,8 @@ use crate::{
|
||||
ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory,
|
||||
ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText,
|
||||
SafeAreaInsets, ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui,
|
||||
ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet,
|
||||
ViewportOutput, Widget as _, WidgetRect, WidgetText,
|
||||
UiBuilder, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair,
|
||||
ViewportIdSet, ViewportOutput, Visuals, Widget as _, WidgetRect, WidgetText,
|
||||
animation_manager::AnimationManager,
|
||||
containers::{self, area::AreaState},
|
||||
data::output::PlatformOutput,
|
||||
@@ -34,8 +34,7 @@ use crate::{
|
||||
os::OperatingSystem,
|
||||
output::FullOutput,
|
||||
pass_state::PassState,
|
||||
plugin,
|
||||
plugin::TypedPluginHandle,
|
||||
plugin::{self, TypedPluginHandle},
|
||||
resize, response, scroll_area,
|
||||
util::IdTypeMap,
|
||||
viewport::ViewportClass,
|
||||
@@ -194,7 +193,7 @@ impl ContextImpl {
|
||||
pub struct ViewportState {
|
||||
/// The type of viewport.
|
||||
///
|
||||
/// This will never be [`ViewportClass::Embedded`],
|
||||
/// This will never be [`ViewportClass::EmbeddedWindow`],
|
||||
/// since those don't result in real viewports.
|
||||
pub class: ViewportClass,
|
||||
|
||||
@@ -564,7 +563,10 @@ impl ContextImpl {
|
||||
log::trace!("Adding new fonts");
|
||||
}
|
||||
|
||||
let text_alpha_from_coverage = self.memory.options.style().visuals.text_alpha_from_coverage;
|
||||
let Visuals {
|
||||
mut text_options, ..
|
||||
} = self.memory.options.style().visuals;
|
||||
text_options.max_texture_side = max_texture_side;
|
||||
|
||||
let mut is_new = false;
|
||||
|
||||
@@ -573,21 +575,17 @@ impl ContextImpl {
|
||||
|
||||
is_new = true;
|
||||
profiling::scope!("Fonts::new");
|
||||
Fonts::new(
|
||||
max_texture_side,
|
||||
text_alpha_from_coverage,
|
||||
self.font_definitions.clone(),
|
||||
)
|
||||
Fonts::new(text_options, self.font_definitions.clone())
|
||||
});
|
||||
|
||||
{
|
||||
profiling::scope!("Fonts::begin_pass");
|
||||
fonts.begin_pass(max_texture_side, text_alpha_from_coverage);
|
||||
fonts.begin_pass(text_options);
|
||||
}
|
||||
}
|
||||
|
||||
fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::Node {
|
||||
let state = self.viewport().this_pass.accesskit_state.as_mut().unwrap();
|
||||
fn accesskit_node_builder(&mut self, id: Id) -> Option<&mut accesskit::Node> {
|
||||
let state = self.viewport().this_pass.accesskit_state.as_mut()?;
|
||||
let builders = &mut state.nodes;
|
||||
|
||||
if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) {
|
||||
@@ -611,13 +609,13 @@ impl ContextImpl {
|
||||
}
|
||||
|
||||
let parent_id = find_accesskit_parent(&state.parent_map, builders, id)
|
||||
.unwrap_or(crate::accesskit_root_id());
|
||||
.unwrap_or_else(crate::accesskit_root_id);
|
||||
|
||||
let parent_builder = builders.get_mut(&parent_id).unwrap();
|
||||
let parent_builder = builders.get_mut(&parent_id)?;
|
||||
parent_builder.push_child(id.accesskit_id());
|
||||
}
|
||||
|
||||
builders.get_mut(&id).unwrap()
|
||||
builders.get_mut(&id)
|
||||
}
|
||||
|
||||
fn pixels_per_point(&mut self) -> f32 {
|
||||
@@ -758,6 +756,63 @@ impl Context {
|
||||
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| {
|
||||
let mut top_ui = Ui::new(
|
||||
ctx.clone(),
|
||||
Id::new((ctx.viewport_id(), "__top_ui")),
|
||||
UiBuilder::new()
|
||||
.layer_id(LayerId::background())
|
||||
.max_rect(ctx.available_rect().round_ui()),
|
||||
);
|
||||
|
||||
{
|
||||
plugins.on_begin_pass(&mut top_ui);
|
||||
run_ui(&mut top_ui);
|
||||
plugins.on_end_pass(&mut top_ui);
|
||||
}
|
||||
|
||||
// Inform ctx about what we actually used, so we can shrink the native window to fit.
|
||||
// TODO(emilk): make better use of this somehow
|
||||
ctx.pass_state_mut(|state| state.allocate_central_panel(top_ui.min_rect()));
|
||||
})
|
||||
}
|
||||
|
||||
/// Run the ui code for one frame.
|
||||
///
|
||||
/// At most [`Options::max_passes`] calls will be issued to `run_ui`,
|
||||
@@ -781,8 +836,17 @@ impl Context {
|
||||
/// });
|
||||
/// // handle full_output
|
||||
/// ```
|
||||
///
|
||||
/// ## See also
|
||||
/// * [`Self::run_ui`]
|
||||
#[must_use]
|
||||
pub fn run(&self, mut new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput {
|
||||
#[deprecated = "Call run_ui instead"]
|
||||
pub fn run(&self, new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput {
|
||||
self.run_dyn(new_input, &mut run_ui)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn run_dyn(&self, mut new_input: RawInput, run_ui: &mut dyn FnMut(&Self)) -> FullOutput {
|
||||
profiling::function_scope!();
|
||||
let viewport_id = new_input.viewport_id;
|
||||
let max_passes = self.write(|ctx| ctx.memory.options.max_passes.get());
|
||||
@@ -877,9 +941,6 @@ impl Context {
|
||||
plugins.on_input(&mut new_input);
|
||||
|
||||
self.write(|ctx| ctx.begin_pass(new_input));
|
||||
|
||||
// Plugins run just after the pass starts:
|
||||
plugins.on_begin_pass(self);
|
||||
}
|
||||
|
||||
/// See [`Self::begin_pass`].
|
||||
@@ -1101,7 +1162,7 @@ impl Context {
|
||||
let content_rect = self.content_rect();
|
||||
|
||||
let text = format!("🔥 {text}");
|
||||
let color = self.style().visuals.error_fg_color;
|
||||
let color = self.global_style().visuals.error_fg_color;
|
||||
let painter = self.debug_painter();
|
||||
painter.rect_stroke(widget_rect, 0.0, (1.0, color), StrokeKind::Outside);
|
||||
|
||||
@@ -1165,7 +1226,12 @@ impl Context {
|
||||
///
|
||||
/// `allow_focus` should usually be true, unless you call this function multiple times with the
|
||||
/// same widget, then `allow_focus` should only be true once (like in [`Ui::new`] (true) and [`Ui::remember_min_rect`] (false)).
|
||||
pub(crate) fn create_widget(&self, w: WidgetRect, allow_focus: bool) -> Response {
|
||||
pub(crate) fn create_widget(
|
||||
&self,
|
||||
w: WidgetRect,
|
||||
allow_focus: bool,
|
||||
options: crate::InteractOptions,
|
||||
) -> Response {
|
||||
let interested_in_focus = w.enabled
|
||||
&& w.sense.is_focusable()
|
||||
&& self.memory(|mem| mem.allows_interaction(w.layer_id));
|
||||
@@ -1177,7 +1243,7 @@ impl Context {
|
||||
// We add all widgets here, even non-interactive ones,
|
||||
// because we need this list not only for checking for blocking widgets,
|
||||
// but also to know when we have reached the widget we are checking for cover.
|
||||
viewport.this_pass.widgets.insert(w.layer_id, w);
|
||||
viewport.this_pass.widgets.insert(w.layer_id, w, options);
|
||||
|
||||
if allow_focus && interested_in_focus {
|
||||
ctx.memory.interested_in_focus(w.id, w.layer_id);
|
||||
@@ -1193,7 +1259,7 @@ impl Context {
|
||||
self.check_for_id_clash(w.id, w.rect, "widget");
|
||||
}
|
||||
|
||||
#[allow(clippy::let_and_return, clippy::allow_attributes)]
|
||||
#[allow(clippy::allow_attributes, clippy::let_and_return)]
|
||||
let res = self.get_response(w);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -1458,7 +1524,7 @@ impl Context {
|
||||
Painter::new(self.clone(), layer_id, content_rect)
|
||||
}
|
||||
|
||||
/// Paint on top of everything else
|
||||
/// Paint on top of _everything_ else (even on top of tooltips and popups).
|
||||
pub fn debug_painter(&self) -> Painter {
|
||||
Self::layer_painter(self, LayerId::debug())
|
||||
}
|
||||
@@ -1556,7 +1622,7 @@ impl Context {
|
||||
..
|
||||
} = ModifierNames::SYMBOLS;
|
||||
|
||||
let font_id = TextStyle::Body.resolve(&self.style());
|
||||
let font_id = TextStyle::Body.resolve(&self.global_style());
|
||||
self.fonts_mut(|f| {
|
||||
let mut font = f.fonts.font(&font_id.family);
|
||||
font.has_glyphs(alt)
|
||||
@@ -1898,7 +1964,7 @@ impl Context {
|
||||
pub fn add_plugin(&self, plugin: impl plugin::Plugin + 'static) {
|
||||
let handle = plugin::PluginHandle::new(plugin);
|
||||
|
||||
let added = self.write(|ctx| ctx.plugins.add(handle.clone()));
|
||||
let added = self.write(|ctx| ctx.plugins.add(Arc::clone(&handle)));
|
||||
|
||||
if added {
|
||||
handle.lock().dyn_plugin_mut().setup(self);
|
||||
@@ -1957,15 +2023,12 @@ impl Context {
|
||||
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
|
||||
profiling::function_scope!();
|
||||
|
||||
let mut update_fonts = true;
|
||||
|
||||
self.read(|ctx| {
|
||||
if let Some(current_fonts) = ctx.fonts.as_ref() {
|
||||
// NOTE: this comparison is expensive since it checks TTF data for equality
|
||||
if current_fonts.definitions() == &font_definitions {
|
||||
update_fonts = false; // no need to update
|
||||
}
|
||||
}
|
||||
let update_fonts = self.read(|ctx| {
|
||||
// NOTE: this comparison is expensive since it checks TTF data for equality
|
||||
// TODO(valadaptive): add_font only checks the *names* for equality. Change this?
|
||||
ctx.fonts
|
||||
.as_ref()
|
||||
.is_none_or(|fonts| fonts.definitions() != &font_definitions)
|
||||
});
|
||||
|
||||
if update_fonts {
|
||||
@@ -2008,13 +2071,13 @@ impl Context {
|
||||
}
|
||||
|
||||
/// The [`Theme`] used to select the appropriate [`Style`] (dark or light)
|
||||
/// used by all subsequent windows, panels etc.
|
||||
/// used by all subsequent popups, menus, etc.
|
||||
pub fn theme(&self) -> Theme {
|
||||
self.options(|opt| opt.theme())
|
||||
}
|
||||
|
||||
/// The [`Theme`] used to select between dark and light [`Self::style`]
|
||||
/// as the active style used by all subsequent windows, panels etc.
|
||||
/// as the active style used by all subsequent popups, menus, etc.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
@@ -2025,37 +2088,70 @@ impl Context {
|
||||
self.options_mut(|opt| opt.theme_preference = theme_preference.into());
|
||||
}
|
||||
|
||||
/// The currently active [`Style`] used by all subsequent windows, panels etc.
|
||||
pub fn style(&self) -> Arc<Style> {
|
||||
self.options(|opt| opt.style().clone())
|
||||
/// The currently active [`Style`] used by all subsequent popups, menus, etc.
|
||||
pub fn global_style(&self) -> Arc<Style> {
|
||||
self.options(|opt| Arc::clone(opt.style()))
|
||||
}
|
||||
|
||||
/// Mutate the currently active [`Style`] used by all subsequent windows, panels etc.
|
||||
/// The currently active [`Style`] used by all subsequent popups, menus, etc.
|
||||
#[deprecated = "Renamed to `global_style` to avoid confusion with `ui.style()`"]
|
||||
pub fn style(&self) -> Arc<Style> {
|
||||
self.options(|opt| Arc::clone(opt.style()))
|
||||
}
|
||||
|
||||
/// Mutate the currently active [`Style`] used by all subsequent popups, menus, etc.
|
||||
/// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # let mut ctx = egui::Context::default();
|
||||
/// ctx.style_mut(|style| {
|
||||
/// ctx.global_style_mut(|style| {
|
||||
/// style.spacing.item_spacing = egui::vec2(10.0, 20.0);
|
||||
/// });
|
||||
/// ```
|
||||
pub fn global_style_mut(&self, mutate_style: impl FnOnce(&mut Style)) {
|
||||
self.options_mut(|opt| mutate_style(Arc::make_mut(opt.style_mut())));
|
||||
}
|
||||
|
||||
/// Mutate the currently active [`Style`] used by all subsequent popups, menus, etc.
|
||||
/// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # let mut ctx = egui::Context::default();
|
||||
/// ctx.global_style_mut(|style| {
|
||||
/// style.spacing.item_spacing = egui::vec2(10.0, 20.0);
|
||||
/// });
|
||||
/// ```
|
||||
#[deprecated = "Renamed to `global_style_mut` to avoid confusion with `ui.style_mut()`"]
|
||||
pub fn style_mut(&self, mutate_style: impl FnOnce(&mut Style)) {
|
||||
self.options_mut(|opt| mutate_style(Arc::make_mut(opt.style_mut())));
|
||||
}
|
||||
|
||||
/// The currently active [`Style`] used by all new windows, panels etc.
|
||||
/// The currently active [`Style`] used by all new popups, menus, etc.
|
||||
///
|
||||
/// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
|
||||
///
|
||||
/// You can also change this using [`Self::global_style_mut`].
|
||||
///
|
||||
/// You can use [`Ui::style_mut`] to change the style of a single [`Ui`].
|
||||
pub fn set_global_style(&self, style: impl Into<Arc<Style>>) {
|
||||
self.options_mut(|opt| *opt.style_mut() = style.into());
|
||||
}
|
||||
|
||||
/// The currently active [`Style`] used by all new popups, menus, etc.
|
||||
///
|
||||
/// Use [`Self::all_styles_mut`] to mutate both dark and light mode styles.
|
||||
///
|
||||
/// You can also change this using [`Self::style_mut`].
|
||||
///
|
||||
/// You can use [`Ui::style_mut`] to change the style of a single [`Ui`].
|
||||
#[deprecated = "Renamed to `set_global_style` to avoid confusion with `ui.set_style()`"]
|
||||
pub fn set_style(&self, style: impl Into<Arc<Style>>) {
|
||||
self.options_mut(|opt| *opt.style_mut() = style.into());
|
||||
}
|
||||
|
||||
/// Mutate the [`Style`]s used by all subsequent windows, panels etc. in both dark and light mode.
|
||||
/// Mutate the [`Style`]s used by all subsequent popups, menus, etc. in both dark and light mode.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
@@ -2071,15 +2167,15 @@ impl Context {
|
||||
});
|
||||
}
|
||||
|
||||
/// The [`Style`] used by all subsequent windows, panels etc.
|
||||
/// The [`Style`] used by all subsequent popups, menus, etc.
|
||||
pub fn style_of(&self, theme: Theme) -> Arc<Style> {
|
||||
self.options(|opt| match theme {
|
||||
Theme::Dark => opt.dark_style.clone(),
|
||||
Theme::Light => opt.light_style.clone(),
|
||||
Theme::Dark => Arc::clone(&opt.dark_style),
|
||||
Theme::Light => Arc::clone(&opt.light_style),
|
||||
})
|
||||
}
|
||||
|
||||
/// Mutate the [`Style`] used by all subsequent windows, panels etc.
|
||||
/// Mutate the [`Style`] used by all subsequent popups, menus, etc.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
@@ -2095,7 +2191,7 @@ impl Context {
|
||||
});
|
||||
}
|
||||
|
||||
/// The [`Style`] used by all new windows, panels etc.
|
||||
/// The [`Style`] used by all new popups, menus, etc.
|
||||
/// Use [`Self::set_theme`] to choose between dark and light mode.
|
||||
///
|
||||
/// You can also change this using [`Self::style_mut_of`].
|
||||
@@ -2109,7 +2205,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`].
|
||||
///
|
||||
@@ -2122,7 +2218,7 @@ impl Context {
|
||||
self.style_mut_of(theme, |style| style.visuals = visuals);
|
||||
}
|
||||
|
||||
/// The [`crate::Visuals`] used by all subsequent windows, panels etc.
|
||||
/// The [`crate::Visuals`] used by all subsequent popups, menus, etc.
|
||||
///
|
||||
/// You can also use [`Ui::visuals_mut`] to change the visuals of a single [`Ui`].
|
||||
///
|
||||
@@ -2269,7 +2365,7 @@ impl Context {
|
||||
///
|
||||
/// You can show stats about the allocated textures using [`Self::texture_ui`].
|
||||
pub fn tex_manager(&self) -> Arc<RwLock<epaint::textures::TextureManager>> {
|
||||
self.read(|ctx| ctx.tex_manager.0.clone())
|
||||
self.read(|ctx| Arc::clone(&ctx.tex_manager.0))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -2301,15 +2397,14 @@ impl Context {
|
||||
crate::gui_zoom::zoom_with_keyboard(self);
|
||||
}
|
||||
|
||||
// Plugins run just before the pass ends.
|
||||
let plugins = self.read(|ctx| ctx.plugins.ordered_plugins());
|
||||
plugins.on_end_pass(self);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
self.debug_painting();
|
||||
|
||||
let mut output = self.write(|ctx| ctx.end_pass());
|
||||
|
||||
let plugins = self.read(|ctx| ctx.plugins.ordered_plugins());
|
||||
plugins.on_output(&mut output);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
@@ -2341,7 +2436,7 @@ impl Context {
|
||||
}
|
||||
};
|
||||
|
||||
if self.style().debug.show_interactive_widgets {
|
||||
if self.global_style().debug.show_interactive_widgets {
|
||||
// Show all interactive widgets:
|
||||
let rects = self.write(|ctx| ctx.viewport().this_pass.widgets.clone());
|
||||
for (layer_id, rects) in rects.layers() {
|
||||
@@ -2419,7 +2514,7 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
if self.style().debug.show_widget_hits {
|
||||
if self.global_style().debug.show_widget_hits {
|
||||
let hits = self.write(|ctx| ctx.viewport().hits.clone());
|
||||
let WidgetHits {
|
||||
close,
|
||||
@@ -2721,12 +2816,13 @@ impl Context {
|
||||
}
|
||||
|
||||
/// How much space is still available after panels have been added.
|
||||
#[deprecated = "Use content_rect (or viewport_rect) instead"]
|
||||
pub fn available_rect(&self) -> Rect {
|
||||
self.pass_state(|s| s.available_rect()).round_ui()
|
||||
}
|
||||
|
||||
/// How much space is used by panels and windows.
|
||||
pub fn used_rect(&self) -> Rect {
|
||||
/// How much space is used by windows and the top-level [`Ui`].
|
||||
pub fn globally_used_rect(&self) -> Rect {
|
||||
self.write(|ctx| {
|
||||
let mut used = ctx.viewport().this_pass.used_by_panels;
|
||||
for (_id, window) in ctx.memory.areas().visible_windows() {
|
||||
@@ -2736,17 +2832,24 @@ impl Context {
|
||||
})
|
||||
}
|
||||
|
||||
/// How much space is used by panels and windows.
|
||||
/// How much space is used by windows and the top-level [`Ui`].
|
||||
#[deprecated = "Renamed to globally_used_rect"]
|
||||
pub fn used_rect(&self) -> Rect {
|
||||
self.globally_used_rect()
|
||||
}
|
||||
|
||||
/// How much space is used by windows and the top-level [`Ui`].
|
||||
///
|
||||
/// You can shrink your egui area to this size and still fit all egui components.
|
||||
#[deprecated = "Use globally_used_rect instead"]
|
||||
pub fn used_size(&self) -> Vec2 {
|
||||
(self.used_rect().max - Pos2::ZERO).round_ui()
|
||||
(self.globally_used_rect().max - Pos2::ZERO).round_ui()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// Is the pointer (mouse/touch) over any egui area?
|
||||
pub fn is_pointer_over_area(&self) -> bool {
|
||||
pub fn is_pointer_over_egui(&self) -> bool {
|
||||
let pointer_pos = self.input(|i| i.pointer.interact_pos());
|
||||
if let Some(pointer_pos) = pointer_pos {
|
||||
if let Some(layer) = self.layer_id_at(pointer_pos) {
|
||||
@@ -2763,29 +2866,60 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the pointer (mouse/touch) over any egui area?
|
||||
#[deprecated = "Renamed to is_pointer_over_egui"]
|
||||
pub fn is_pointer_over_area(&self) -> bool {
|
||||
self.is_pointer_over_egui()
|
||||
}
|
||||
|
||||
/// True if egui is currently interested in the pointer (mouse or touch).
|
||||
///
|
||||
/// Could be the pointer is hovering over a [`crate::Window`] or the user is dragging a widget.
|
||||
/// If `false`, the pointer is outside of any egui area and so
|
||||
/// you may be interested in what it is doing (e.g. controlling your game).
|
||||
/// Returns `false` if a drag started outside of egui and then moved over an egui area.
|
||||
pub fn egui_wants_pointer_input(&self) -> bool {
|
||||
self.egui_is_using_pointer()
|
||||
|| (self.is_pointer_over_egui() && !self.input(|i| i.pointer.any_down()))
|
||||
}
|
||||
|
||||
/// True if egui is currently interested in the pointer (mouse or touch).
|
||||
///
|
||||
/// Could be the pointer is hovering over a [`crate::Window`] or the user is dragging a widget.
|
||||
/// If `false`, the pointer is outside of any egui area and so
|
||||
/// you may be interested in what it is doing (e.g. controlling your game).
|
||||
/// Returns `false` if a drag started outside of egui and then moved over an egui area.
|
||||
#[deprecated = "Renamed to egui_wants_pointer_input"]
|
||||
pub fn wants_pointer_input(&self) -> bool {
|
||||
self.is_using_pointer()
|
||||
|| (self.is_pointer_over_area() && !self.input(|i| i.pointer.any_down()))
|
||||
self.egui_wants_pointer_input()
|
||||
}
|
||||
|
||||
/// Is egui currently using the pointer position (e.g. dragging a slider)?
|
||||
///
|
||||
/// NOTE: this will return `false` if the pointer is just hovering over an egui area.
|
||||
pub fn is_using_pointer(&self) -> bool {
|
||||
pub fn egui_is_using_pointer(&self) -> bool {
|
||||
self.memory(|m| m.interaction().is_using_pointer())
|
||||
}
|
||||
|
||||
/// Is egui currently using the pointer position (e.g. dragging a slider)?
|
||||
///
|
||||
/// NOTE: this will return `false` if the pointer is just hovering over an egui area.
|
||||
#[deprecated = "Renamed to egui_is_using_pointer"]
|
||||
pub fn is_using_pointer(&self) -> bool {
|
||||
self.egui_is_using_pointer()
|
||||
}
|
||||
|
||||
/// If `true`, egui is currently listening on text input (e.g. typing text in a [`crate::TextEdit`]).
|
||||
pub fn wants_keyboard_input(&self) -> bool {
|
||||
pub fn egui_wants_keyboard_input(&self) -> bool {
|
||||
self.memory(|m| m.focused().is_some())
|
||||
}
|
||||
|
||||
/// If `true`, egui is currently listening on text input (e.g. typing text in a [`crate::TextEdit`]).
|
||||
#[deprecated = "Renamed to egui_wants_keyboard_input"]
|
||||
pub fn wants_keyboard_input(&self) -> bool {
|
||||
self.egui_wants_keyboard_input()
|
||||
}
|
||||
|
||||
/// Highlight this widget, to make it look like it is hovered, even if it isn't.
|
||||
///
|
||||
/// If you call this after the widget has been fully rendered,
|
||||
@@ -2800,7 +2934,7 @@ impl Context {
|
||||
///
|
||||
/// This only works with the old, deprecated [`crate::menu`] API.
|
||||
#[expect(deprecated)]
|
||||
#[deprecated = "Use `is_popup_open` instead"]
|
||||
#[deprecated = "Use `any_popup_open` instead"]
|
||||
pub fn is_context_menu_open(&self) -> bool {
|
||||
self.data(|d| {
|
||||
d.get_temp::<crate::menu::BarState>(crate::menu::CONTEXT_MENU_ID_STR.into())
|
||||
@@ -2811,6 +2945,18 @@ impl Context {
|
||||
/// Is a popup or (context) menu open?
|
||||
///
|
||||
/// Will return false for [`crate::Tooltip`]s (which are technically popups as well).
|
||||
pub fn any_popup_open(&self) -> bool {
|
||||
self.pass_state_mut(|fs| {
|
||||
fs.layers
|
||||
.values()
|
||||
.any(|layer| !layer.open_popups.is_empty())
|
||||
})
|
||||
}
|
||||
|
||||
/// Is a popup or (context) menu open?
|
||||
///
|
||||
/// Will return false for [`crate::Tooltip`]s (which are technically popups as well).
|
||||
#[deprecated = "Renamed to any_popup_open"]
|
||||
pub fn is_popup_open(&self) -> bool {
|
||||
self.pass_state_mut(|fs| {
|
||||
fs.layers
|
||||
@@ -2836,7 +2982,7 @@ impl Context {
|
||||
self.input(|i| i.pointer.hover_pos())
|
||||
}
|
||||
|
||||
/// If you detect a click or drag and wants to know where it happened, use this.
|
||||
/// If you detect a click or drag and want to know where it happened, use this.
|
||||
///
|
||||
/// Latest position of the mouse, but ignoring any [`crate::Event::PointerGone`]
|
||||
/// if there were interactions this pass.
|
||||
@@ -2909,7 +3055,7 @@ impl Context {
|
||||
|
||||
/// Moves the given area to the top in its [`Order`].
|
||||
///
|
||||
/// [`crate::Area`]:s and [`crate::Window`]:s also do this automatically when being clicked on or interacted with.
|
||||
/// [`crate::Area`]s and [`crate::Window`]s also do this automatically when being clicked on or interacted with.
|
||||
pub fn move_to_top(&self, layer_id: LayerId) {
|
||||
self.memory_mut(|mem| mem.areas_mut().move_to_top(layer_id));
|
||||
}
|
||||
@@ -2991,7 +3137,7 @@ impl Context {
|
||||
/// The animation time is taken from [`Style::animation_time`].
|
||||
#[track_caller] // To track repaint cause
|
||||
pub fn animate_bool(&self, id: Id, value: bool) -> f32 {
|
||||
let animation_time = self.style().animation_time;
|
||||
let animation_time = self.global_style().animation_time;
|
||||
self.animate_bool_with_time_and_easing(id, value, animation_time, emath::easing::linear)
|
||||
}
|
||||
|
||||
@@ -3007,7 +3153,7 @@ impl Context {
|
||||
/// Like [`Self::animate_bool`] but allows you to control the easing function.
|
||||
#[track_caller] // To track repaint cause
|
||||
pub fn animate_bool_with_easing(&self, id: Id, value: bool, easing: fn(f32) -> f32) -> f32 {
|
||||
let animation_time = self.style().animation_time;
|
||||
let animation_time = self.global_style().animation_time;
|
||||
self.animate_bool_with_time_and_easing(id, value, animation_time, easing)
|
||||
}
|
||||
|
||||
@@ -3028,7 +3174,7 @@ impl Context {
|
||||
/// for a responsive start and a slow end.
|
||||
///
|
||||
/// The easing function flips when `target_value` is `false`,
|
||||
/// so that when going back towards 0.0, we get
|
||||
/// so that when going back towards 0.0, we get the reverse behavior.
|
||||
#[track_caller] // To track repaint cause
|
||||
pub fn animate_bool_with_time_and_easing(
|
||||
&self,
|
||||
@@ -3140,16 +3286,16 @@ impl Context {
|
||||
|
||||
ui.label("Is using pointer")
|
||||
.on_hover_text("Is egui currently using the pointer actively (e.g. dragging a slider)?");
|
||||
ui.monospace(self.is_using_pointer().to_string());
|
||||
ui.monospace(self.egui_is_using_pointer().to_string());
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Wants pointer input")
|
||||
.on_hover_text("Is egui currently interested in the location of the pointer (either because it is in use, or because it is hovering over a window).");
|
||||
ui.monospace(self.wants_pointer_input().to_string());
|
||||
ui.monospace(self.egui_wants_pointer_input().to_string());
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Wants keyboard input").on_hover_text("Is egui currently listening for text input?");
|
||||
ui.monospace(self.wants_keyboard_input().to_string());
|
||||
ui.monospace(self.egui_wants_keyboard_input().to_string());
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Keyboard focus widget").on_hover_text("Is egui currently listening for text input?");
|
||||
@@ -3414,9 +3560,7 @@ impl Context {
|
||||
let response =
|
||||
ui.add(Label::new(RichText::new(text).monospace()).sense(Sense::click()));
|
||||
if response.hovered() && is_visible {
|
||||
ui.ctx()
|
||||
.debug_painter()
|
||||
.debug_rect(area.rect(), Color32::RED, "");
|
||||
ui.debug_painter().debug_rect(area.rect(), Color32::RED, "");
|
||||
}
|
||||
} else {
|
||||
ui.monospace(layer_id.short_debug_format());
|
||||
@@ -3489,7 +3633,7 @@ impl Context {
|
||||
/// If AccessKit support is active for the current frame, get or create
|
||||
/// a node builder with the specified ID and return a mutable reference to it.
|
||||
/// For newly created nodes, the parent is the parent [`Ui`]s ID.
|
||||
/// And an [`Ui`]s parent can be set with [`crate::UiBuilder::accessibility_parent`].
|
||||
/// And an [`Ui`]s parent can be set with [`UiBuilder::accessibility_parent`].
|
||||
///
|
||||
/// The `Context` lock is held while the given closure is called!
|
||||
///
|
||||
@@ -3500,14 +3644,7 @@ impl Context {
|
||||
id: Id,
|
||||
writer: impl FnOnce(&mut accesskit::Node) -> R,
|
||||
) -> Option<R> {
|
||||
self.write(|ctx| {
|
||||
ctx.viewport()
|
||||
.this_pass
|
||||
.accesskit_state
|
||||
.is_some()
|
||||
.then(|| ctx.accesskit_node_builder(id))
|
||||
.map(writer)
|
||||
})
|
||||
self.write(|ctx| ctx.accesskit_node_builder(id).map(writer))
|
||||
}
|
||||
|
||||
pub(crate) fn register_accesskit_parent(&self, id: Id, parent_id: Id) {
|
||||
@@ -3748,7 +3885,7 @@ impl Context {
|
||||
|
||||
/// The loaders of bytes, images, and textures.
|
||||
pub fn loaders(&self) -> Arc<Loaders> {
|
||||
self.read(|this| this.loaders.clone())
|
||||
self.read(|this| Arc::clone(&this.loaders))
|
||||
}
|
||||
|
||||
/// Returns `true` if any image is currently being loaded.
|
||||
@@ -3875,21 +4012,23 @@ impl Context {
|
||||
///
|
||||
/// If [`Context::embed_viewports`] is `true` (e.g. if the current egui
|
||||
/// backend does not support multiple viewports), the given callback
|
||||
/// will be called immediately, embedding the new viewport in the current one.
|
||||
/// You can check this with the [`ViewportClass`] given in the callback.
|
||||
/// If you find [`ViewportClass::Embedded`], you need to create a new [`crate::Window`] for you content.
|
||||
/// will be called immediately, embedding the new viewport in the current one,
|
||||
/// inside of a [`crate::Window`].
|
||||
/// You can know by checking for [`ViewportClass::EmbeddedWindow`].
|
||||
///
|
||||
/// See [`crate::viewport`] for more information about viewports.
|
||||
pub fn show_viewport_deferred(
|
||||
&self,
|
||||
new_viewport_id: ViewportId,
|
||||
viewport_builder: ViewportBuilder,
|
||||
viewport_ui_cb: impl Fn(&Self, ViewportClass) + Send + Sync + 'static,
|
||||
viewport_ui_cb: impl Fn(&mut Ui, ViewportClass) + Send + Sync + 'static,
|
||||
) {
|
||||
profiling::function_scope!();
|
||||
|
||||
if self.embed_viewports() {
|
||||
viewport_ui_cb(self, ViewportClass::Embedded);
|
||||
crate::Window::from_viewport(new_viewport_id, viewport_builder).show(self, |ui| {
|
||||
viewport_ui_cb(ui, ViewportClass::EmbeddedWindow);
|
||||
});
|
||||
} else {
|
||||
self.write(|ctx| {
|
||||
ctx.viewport_parents
|
||||
@@ -3899,8 +4038,8 @@ impl Context {
|
||||
viewport.class = ViewportClass::Deferred;
|
||||
viewport.builder = viewport_builder;
|
||||
viewport.used = true;
|
||||
viewport.viewport_ui_cb = Some(Arc::new(move |ctx| {
|
||||
(viewport_ui_cb)(ctx, ViewportClass::Deferred);
|
||||
viewport.viewport_ui_cb = Some(Arc::new(move |ui| {
|
||||
(viewport_ui_cb)(ui, ViewportClass::Deferred);
|
||||
}));
|
||||
});
|
||||
}
|
||||
@@ -3909,7 +4048,7 @@ impl Context {
|
||||
/// Show an immediate viewport, creating a new native window, if possible.
|
||||
///
|
||||
/// This is the easier type of viewport to use, but it is less performant
|
||||
/// at it requires both parent and child to repaint if any one of them needs repainting,
|
||||
/// as it requires both parent and child to repaint if any one of them needs repainting,
|
||||
/// which effectively produce double work for two viewports, and triple work for three viewports, etc.
|
||||
/// To avoid this, use [`Self::show_viewport_deferred`] instead.
|
||||
///
|
||||
@@ -3927,28 +4066,32 @@ impl Context {
|
||||
///
|
||||
/// If [`Context::embed_viewports`] is `true` (e.g. if the current egui
|
||||
/// backend does not support multiple viewports), the given callback
|
||||
/// will be called immediately, embedding the new viewport in the current one.
|
||||
/// You can check this with the [`ViewportClass`] given in the callback.
|
||||
/// If you find [`ViewportClass::Embedded`], you need to create a new [`crate::Window`] for you content.
|
||||
/// will be called immediately, embedding the new viewport in the current one,
|
||||
/// inside of a [`crate::Window`].
|
||||
/// You can know by checking for [`ViewportClass::EmbeddedWindow`].
|
||||
///
|
||||
/// See [`crate::viewport`] for more information about viewports.
|
||||
pub fn show_viewport_immediate<T>(
|
||||
&self,
|
||||
new_viewport_id: ViewportId,
|
||||
builder: ViewportBuilder,
|
||||
mut viewport_ui_cb: impl FnMut(&Self, ViewportClass) -> T,
|
||||
mut viewport_ui_cb: impl FnMut(&mut Ui, ViewportClass) -> T,
|
||||
) -> T {
|
||||
profiling::function_scope!();
|
||||
|
||||
if self.embed_viewports() {
|
||||
return viewport_ui_cb(self, ViewportClass::Embedded);
|
||||
return self.show_embedded_viewport(new_viewport_id, builder, |ui| {
|
||||
viewport_ui_cb(ui, ViewportClass::EmbeddedWindow)
|
||||
});
|
||||
}
|
||||
|
||||
IMMEDIATE_VIEWPORT_RENDERER.with(|immediate_viewport_renderer| {
|
||||
let immediate_viewport_renderer = immediate_viewport_renderer.borrow();
|
||||
let Some(immediate_viewport_renderer) = immediate_viewport_renderer.as_ref() else {
|
||||
// This egui backend does not support multiple viewports.
|
||||
return viewport_ui_cb(self, ViewportClass::Embedded);
|
||||
return self.show_embedded_viewport(new_viewport_id, builder, |ui| {
|
||||
viewport_ui_cb(ui, ViewportClass::EmbeddedWindow)
|
||||
});
|
||||
};
|
||||
|
||||
let ids = self.write(|ctx| {
|
||||
@@ -3972,8 +4115,8 @@ impl Context {
|
||||
let viewport = ImmediateViewport {
|
||||
ids,
|
||||
builder,
|
||||
viewport_ui_cb: Box::new(move |context| {
|
||||
*out = Some(viewport_ui_cb(context, ViewportClass::Immediate));
|
||||
viewport_ui_cb: Box::new(move |ui| {
|
||||
*out = Some((viewport_ui_cb)(ui, ViewportClass::Immediate));
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -3985,6 +4128,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
|
||||
@@ -4008,7 +4165,7 @@ impl Context {
|
||||
/// Is this specific widget being dragged?
|
||||
///
|
||||
/// A widget that sense both clicks and drags is only marked as "dragged"
|
||||
/// when the mouse has moved a bit
|
||||
/// when the mouse has moved a bit.
|
||||
///
|
||||
/// See also: [`crate::Response::dragged`].
|
||||
pub fn is_being_dragged(&self, id: Id) -> bool {
|
||||
@@ -4022,7 +4179,7 @@ impl Context {
|
||||
self.interaction_snapshot(|i| i.drag_started)
|
||||
}
|
||||
|
||||
/// This widget was being dragged, but was released this pass
|
||||
/// This widget was being dragged, but was released this pass.
|
||||
pub fn drag_stopped_id(&self) -> Option<Id> {
|
||||
self.interaction_snapshot(|i| i.drag_stopped)
|
||||
}
|
||||
@@ -4084,11 +4241,11 @@ mod test {
|
||||
// A single call, no request to discard:
|
||||
{
|
||||
let mut num_calls = 0;
|
||||
let output = ctx.run(Default::default(), |ctx| {
|
||||
let output = ctx.run_ui(Default::default(), |ui| {
|
||||
num_calls += 1;
|
||||
assert_eq!(ctx.output(|o| o.num_completed_passes), 0);
|
||||
assert!(!ctx.output(|o| o.requested_discard()));
|
||||
assert!(!ctx.will_discard());
|
||||
assert_eq!(ui.output(|o| o.num_completed_passes), 0);
|
||||
assert!(!ui.output(|o| o.requested_discard()));
|
||||
assert!(!ui.will_discard());
|
||||
});
|
||||
assert_eq!(num_calls, 1);
|
||||
assert_eq!(output.platform_output.num_completed_passes, 1);
|
||||
@@ -4098,10 +4255,10 @@ mod test {
|
||||
// A single call, with a denied request to discard:
|
||||
{
|
||||
let mut num_calls = 0;
|
||||
let output = ctx.run(Default::default(), |ctx| {
|
||||
let output = ctx.run_ui(Default::default(), |ui| {
|
||||
num_calls += 1;
|
||||
ctx.request_discard("test");
|
||||
assert!(!ctx.will_discard(), "The request should have been denied");
|
||||
ui.request_discard("test");
|
||||
assert!(!ui.will_discard(), "The request should have been denied");
|
||||
});
|
||||
assert_eq!(num_calls, 1);
|
||||
assert_eq!(output.platform_output.num_completed_passes, 1);
|
||||
@@ -4129,10 +4286,10 @@ mod test {
|
||||
// Normal single pass:
|
||||
{
|
||||
let mut num_calls = 0;
|
||||
let output = ctx.run(Default::default(), |ctx| {
|
||||
assert_eq!(ctx.output(|o| o.num_completed_passes), 0);
|
||||
assert!(!ctx.output(|o| o.requested_discard()));
|
||||
assert!(!ctx.will_discard());
|
||||
let output = ctx.run_ui(Default::default(), |ui| {
|
||||
assert_eq!(ui.output(|o| o.num_completed_passes), 0);
|
||||
assert!(!ui.output(|o| o.requested_discard()));
|
||||
assert!(!ui.will_discard());
|
||||
num_calls += 1;
|
||||
});
|
||||
assert_eq!(num_calls, 1);
|
||||
@@ -4143,13 +4300,13 @@ mod test {
|
||||
// Request discard once:
|
||||
{
|
||||
let mut num_calls = 0;
|
||||
let output = ctx.run(Default::default(), |ctx| {
|
||||
assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls);
|
||||
let output = ctx.run_ui(Default::default(), |ui| {
|
||||
assert_eq!(ui.output(|o| o.num_completed_passes), num_calls);
|
||||
|
||||
assert!(!ctx.will_discard());
|
||||
assert!(!ui.will_discard());
|
||||
if num_calls == 0 {
|
||||
ctx.request_discard("test");
|
||||
assert!(ctx.will_discard());
|
||||
ui.request_discard("test");
|
||||
assert!(ui.will_discard());
|
||||
}
|
||||
|
||||
num_calls += 1;
|
||||
@@ -4165,15 +4322,15 @@ mod test {
|
||||
// Request discard twice:
|
||||
{
|
||||
let mut num_calls = 0;
|
||||
let output = ctx.run(Default::default(), |ctx| {
|
||||
assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls);
|
||||
let output = ctx.run_ui(Default::default(), |ui| {
|
||||
assert_eq!(ui.output(|o| o.num_completed_passes), num_calls);
|
||||
|
||||
assert!(!ctx.will_discard());
|
||||
ctx.request_discard("test");
|
||||
assert!(!ui.will_discard());
|
||||
ui.request_discard("test");
|
||||
if num_calls == 0 {
|
||||
assert!(ctx.will_discard(), "First request granted");
|
||||
assert!(ui.will_discard(), "First request granted");
|
||||
} else {
|
||||
assert!(!ctx.will_discard(), "Second request should be denied");
|
||||
assert!(!ui.will_discard(), "Second request should be denied");
|
||||
}
|
||||
|
||||
num_calls += 1;
|
||||
@@ -4195,13 +4352,13 @@ mod test {
|
||||
// Request discard three times:
|
||||
{
|
||||
let mut num_calls = 0;
|
||||
let output = ctx.run(Default::default(), |ctx| {
|
||||
assert_eq!(ctx.output(|o| o.num_completed_passes), num_calls);
|
||||
let output = ctx.run_ui(Default::default(), |ui| {
|
||||
assert_eq!(ui.output(|o| o.num_completed_passes), num_calls);
|
||||
|
||||
assert!(!ctx.will_discard());
|
||||
assert!(!ui.will_discard());
|
||||
if num_calls <= 2 {
|
||||
ctx.request_discard("test");
|
||||
assert!(ctx.will_discard());
|
||||
ui.request_discard("test");
|
||||
assert!(ui.will_discard());
|
||||
}
|
||||
|
||||
num_calls += 1;
|
||||
|
||||
@@ -679,6 +679,8 @@ impl WidgetInfo {
|
||||
WidgetType::Panel => "panel",
|
||||
WidgetType::ProgressIndicator => "progress indicator",
|
||||
WidgetType::Window => "window",
|
||||
WidgetType::ScrollBar => "scroll bar",
|
||||
WidgetType::ResizeHandle => "resize handle",
|
||||
WidgetType::Label | WidgetType::Other => "",
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! to get callbacks on certain events ([`Plugin::on_begin_pass`], [`Plugin::on_end_pass`]).
|
||||
|
||||
use crate::{
|
||||
Align, Align2, Color32, Context, FontFamily, FontId, Plugin, Rect, Shape, Vec2, WidgetText,
|
||||
Align, Align2, Color32, Context, FontFamily, FontId, Plugin, Rect, Shape, Ui, Vec2, WidgetText,
|
||||
text,
|
||||
};
|
||||
|
||||
@@ -57,9 +57,9 @@ impl Plugin for DebugTextPlugin {
|
||||
"DebugTextPlugin"
|
||||
}
|
||||
|
||||
fn on_end_pass(&mut self, ctx: &Context) {
|
||||
fn on_end_pass(&mut self, ui: &mut Ui) {
|
||||
let entries = std::mem::take(&mut self.entries);
|
||||
Self::paint_entries(ctx, entries);
|
||||
Self::paint_entries(ui, entries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ impl DebugTextPlugin {
|
||||
let available_width = ctx.content_rect().max.x - pos.x;
|
||||
let galley = text.into_galley_impl(
|
||||
ctx,
|
||||
&ctx.style(),
|
||||
&ctx.global_style(),
|
||||
text::TextWrapping::wrap_at_width(available_width),
|
||||
font_id.clone().into(),
|
||||
Align::TOP,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use crate::{Context, CursorIcon, Plugin};
|
||||
use crate::{Context, CursorIcon, Plugin, Ui};
|
||||
|
||||
/// Plugin for tracking drag-and-drop payload.
|
||||
///
|
||||
@@ -32,12 +32,12 @@ impl Plugin for DragAndDrop {
|
||||
/// Interrupt drag-and-drop if the user presses the escape key.
|
||||
///
|
||||
/// This needs to happen at frame start so we can properly capture the escape key.
|
||||
fn on_begin_pass(&mut self, ctx: &Context) {
|
||||
fn on_begin_pass(&mut self, ui: &mut Ui) {
|
||||
let has_any_payload = self.payload.is_some();
|
||||
|
||||
if has_any_payload {
|
||||
let abort_dnd_due_to_escape_key =
|
||||
ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
|
||||
ui.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape));
|
||||
|
||||
if abort_dnd_due_to_escape_key {
|
||||
self.payload = None;
|
||||
@@ -50,18 +50,18 @@ impl Plugin for DragAndDrop {
|
||||
/// This is a catch-all safety net in case user code doesn't capture the drag payload itself.
|
||||
/// This must happen at end-of-frame such that we don't shadow the mouse release event from user
|
||||
/// code.
|
||||
fn on_end_pass(&mut self, ctx: &Context) {
|
||||
fn on_end_pass(&mut self, ui: &mut Ui) {
|
||||
let has_any_payload = self.payload.is_some();
|
||||
|
||||
if has_any_payload {
|
||||
let abort_dnd_due_to_mouse_release = ctx.input_mut(|i| i.pointer.any_released());
|
||||
let abort_dnd_due_to_mouse_release = ui.input_mut(|i| i.pointer.any_released());
|
||||
|
||||
if abort_dnd_due_to_mouse_release {
|
||||
self.payload = None;
|
||||
} else {
|
||||
// We set the cursor icon only if its default, as the user code might have
|
||||
// explicitly set it already.
|
||||
ctx.output_mut(|o| {
|
||||
ui.output_mut(|o| {
|
||||
if o.cursor_icon == CursorIcon::Default {
|
||||
o.cursor_icon = CursorIcon::Grabbing;
|
||||
}
|
||||
@@ -97,11 +97,7 @@ impl DragAndDrop {
|
||||
where
|
||||
Payload: Any + Send + Sync,
|
||||
{
|
||||
ctx.plugin::<Self>()
|
||||
.lock()
|
||||
.payload
|
||||
.as_ref()?
|
||||
.clone()
|
||||
Arc::clone(ctx.plugin::<Self>().lock().payload.as_ref()?)
|
||||
.downcast()
|
||||
.ok()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use emath::GuiRounding as _;
|
||||
|
||||
use crate::{
|
||||
@@ -102,7 +104,7 @@ impl GridLayout {
|
||||
|
||||
Self {
|
||||
ctx: ui.ctx().clone(),
|
||||
style: ui.style().clone(),
|
||||
style: Arc::clone(ui.style()),
|
||||
id,
|
||||
is_first_frame,
|
||||
prev_state,
|
||||
@@ -449,7 +451,7 @@ impl Grid {
|
||||
|
||||
if ui.is_visible() {
|
||||
// Try to cover up the glitchy initial frame:
|
||||
ui.ctx().request_discard("new Grid");
|
||||
ui.request_discard("new Grid");
|
||||
}
|
||||
|
||||
// Hide the ui this frame, and make things as narrow as possible:
|
||||
|
||||
@@ -2,7 +2,7 @@ use ahash::HashMap;
|
||||
|
||||
use emath::TSTransform;
|
||||
|
||||
use crate::{LayerId, Pos2, Rect, Sense, WidgetRect, WidgetRects, ahash, emath, id::IdSet};
|
||||
use crate::{LayerId, Pos2, Sense, WidgetRect, WidgetRects, ahash, emath, id::IdSet};
|
||||
|
||||
/// Result of a hit-test against [`WidgetRects`].
|
||||
///
|
||||
@@ -201,7 +201,7 @@ fn contains_circle(interact_rect: emath::Rect, pos: Pos2, radius: f32) -> bool {
|
||||
}
|
||||
|
||||
fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
|
||||
#![allow(clippy::collapsible_else_if)]
|
||||
#![expect(clippy::collapsible_else_if)]
|
||||
|
||||
// First find the best direct hits:
|
||||
let hit_click = find_closest_within(
|
||||
@@ -361,7 +361,10 @@ fn hit_test_on_close(close: &[WidgetRect], pos: Pos2) -> WidgetHits {
|
||||
|
||||
(Some(hit_click), Some(hit_drag)) => {
|
||||
// We have a perfect hit on both click and drag. Which is the topmost?
|
||||
#[expect(clippy::unwrap_used)]
|
||||
let click_idx = close.iter().position(|w| *w == hit_click).unwrap();
|
||||
|
||||
#[expect(clippy::unwrap_used)]
|
||||
let drag_idx = close.iter().position(|w| *w == hit_drag).unwrap();
|
||||
|
||||
let click_is_on_top_of_drag = drag_idx < click_idx;
|
||||
@@ -424,16 +427,6 @@ fn find_closest_within(
|
||||
|
||||
let dist_sq = widget.interact_rect.distance_sq_to_pos(pos);
|
||||
|
||||
if let Some(closest) = closest
|
||||
&& dist_sq == closest_dist_sq
|
||||
{
|
||||
// It's a tie! Pick the thin candidate over the thick one.
|
||||
// This makes it easier to hit a thin resize-handle, for instance:
|
||||
if should_prioritize_hits_on_back(closest.interact_rect, widget.interact_rect) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// In case of a tie, take the last one = the one on top.
|
||||
if dist_sq <= closest_dist_sq {
|
||||
closest_dist_sq = dist_sq;
|
||||
@@ -444,27 +437,6 @@ fn find_closest_within(
|
||||
closest
|
||||
}
|
||||
|
||||
/// Should we prioritize hits on `back` over those on `front`?
|
||||
///
|
||||
/// `back` should be behind the `front` widget.
|
||||
///
|
||||
/// Returns true if `back` is a small hit-target and `front` is not.
|
||||
fn should_prioritize_hits_on_back(back: Rect, front: Rect) -> bool {
|
||||
if front.contains_rect(back) {
|
||||
return false; // back widget is fully occluded; no way to hit it
|
||||
}
|
||||
|
||||
// Reduce each rect to its width or height, whichever is smaller:
|
||||
let back = back.width().min(back.height());
|
||||
let front = front.width().min(front.height());
|
||||
|
||||
// These are hard-coded heuristics that could surely be improved.
|
||||
let back_is_much_thinner = back <= 0.5 * front;
|
||||
let back_is_thin = back <= 16.0;
|
||||
|
||||
back_is_much_thinner && back_is_thin
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![expect(clippy::print_stdout)]
|
||||
|
||||
@@ -631,8 +631,10 @@ impl InputState {
|
||||
/// A positive Y-value indicates the content is being moved down, as when swiping down on a touch-screen or track-pad with natural scrolling.
|
||||
#[inline(always)]
|
||||
pub fn translation_delta(&self) -> Vec2 {
|
||||
self.multi_touch()
|
||||
.map_or(self.smooth_scroll_delta(), |touch| touch.translation_delta)
|
||||
self.multi_touch().map_or_else(
|
||||
|| self.smooth_scroll_delta(),
|
||||
|touch| touch.translation_delta,
|
||||
)
|
||||
}
|
||||
|
||||
/// True if there is an active scroll action that might scroll more when using [`Self::smooth_scroll_delta`].
|
||||
@@ -1549,11 +1551,9 @@ impl InputState {
|
||||
options: _,
|
||||
} = self;
|
||||
|
||||
ui.style_mut()
|
||||
.text_styles
|
||||
.get_mut(&crate::TextStyle::Body)
|
||||
.unwrap()
|
||||
.family = crate::FontFamily::Monospace;
|
||||
if let Some(style) = ui.style_mut().text_styles.get_mut(&crate::TextStyle::Body) {
|
||||
style.family = crate::FontFamily::Monospace;
|
||||
}
|
||||
|
||||
ui.collapsing("Raw Input", |ui| raw.ui(ui));
|
||||
|
||||
|
||||
@@ -288,6 +288,7 @@ impl TouchState {
|
||||
// touch individually, and then calculate the average of all individual changes in
|
||||
// direction. But this approach cannot be implemented locally in this method, making
|
||||
// everything a bit more complicated.
|
||||
#[expect(clippy::unwrap_used)] // guarded against already
|
||||
let first_touch = self.active_touches.values().next().unwrap();
|
||||
state.heading = (state.avg_pos - first_touch.pos).angle();
|
||||
|
||||
@@ -323,13 +324,14 @@ enum PinchType {
|
||||
|
||||
impl PinchType {
|
||||
fn classify(touches: &BTreeMap<TouchId, ActiveTouch>) -> Self {
|
||||
#![expect(clippy::unwrap_used)]
|
||||
|
||||
// For non-proportional 2d zooming:
|
||||
// If the user is pinching with two fingers that have roughly the same Y coord,
|
||||
// then the Y zoom is unstable and should be 1.
|
||||
// Similarly, if the fingers are directly above/below each other,
|
||||
// we should only zoom on the Y axis.
|
||||
// If the fingers are roughly on a diagonal, we revert to the proportional zooming.
|
||||
|
||||
if touches.len() == 2 {
|
||||
let mut touches = touches.values();
|
||||
let t0 = touches.next().unwrap().pos;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! Try the live web demo: <https://www.egui.rs/#demo>. Read more about egui at <https://github.com/emilk/egui>.
|
||||
//!
|
||||
//! `egui` is in heavy development, with each new version having breaking changes.
|
||||
//! You need to have rust 1.88.0 or later to use `egui`.
|
||||
//! You need to have rust 1.92.0 or later to use `egui`.
|
||||
//!
|
||||
//! To quickly get started with egui, you can take a look at [`eframe_template`](https://github.com/emilk/eframe_template)
|
||||
//! which uses [`eframe`](https://docs.rs/eframe).
|
||||
@@ -43,23 +43,6 @@
|
||||
//! In some GUI frameworks this would require defining multiple types and functions with callbacks or message handlers,
|
||||
//! but thanks to `egui` being immediate mode everything is one self-contained function!
|
||||
//!
|
||||
//! ### Getting a [`Ui`]
|
||||
//!
|
||||
//! Use one of [`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
|
||||
//!
|
||||
//! ```
|
||||
@@ -195,7 +178,7 @@
|
||||
//! * lays out the letters `click me` in order to figure out the size of the button
|
||||
//! * decides where on screen to place the button
|
||||
//! * check if the mouse is hovering or clicking that location
|
||||
//! * chose button colors based on if it is being hovered or clicked
|
||||
//! * choose button colors based on if it is being hovered or clicked
|
||||
//! * add a [`Shape::Rect`] and [`Shape::Text`] to the list of shapes to be painted later this frame
|
||||
//! * return a [`Response`] with the [`clicked`](`Response::clicked`) member so the user can check for interactions
|
||||
//!
|
||||
@@ -402,8 +385,8 @@
|
||||
//! egui apps can run significantly (~20%) faster by using a custom allocator, like [mimalloc](https://crates.io/crates/mimalloc) or [talc](https://crates.io/crates/talc).
|
||||
//!
|
||||
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
#![expect(clippy::float_cmp)]
|
||||
#![expect(clippy::manual_range_contains)]
|
||||
|
||||
mod animation_manager;
|
||||
mod atomics;
|
||||
@@ -511,7 +494,7 @@ pub use self::{
|
||||
ui_builder::UiBuilder,
|
||||
ui_stack::*,
|
||||
viewport::*,
|
||||
widget_rect::{WidgetRect, WidgetRects},
|
||||
widget_rect::{InteractOptions, WidgetRect, WidgetRects},
|
||||
widget_text::{RichText, WidgetText},
|
||||
widgets::*,
|
||||
};
|
||||
@@ -680,6 +663,10 @@ pub enum WidgetType {
|
||||
|
||||
Window,
|
||||
|
||||
ResizeHandle,
|
||||
|
||||
ScrollBar,
|
||||
|
||||
/// If you cannot fit any of the above slots.
|
||||
///
|
||||
/// If this is something you think should be added, file an issue.
|
||||
@@ -692,8 +679,8 @@ pub enum WidgetType {
|
||||
pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) {
|
||||
let ctx = Context::default();
|
||||
ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time)
|
||||
let _ = ctx.run(Default::default(), |ctx| {
|
||||
run_ui(ctx);
|
||||
let _ = ctx.run_ui(Default::default(), |ui| {
|
||||
run_ui(ui.ctx());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -701,10 +688,8 @@ pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) {
|
||||
pub fn __run_test_ui(add_contents: impl Fn(&mut Ui)) {
|
||||
let ctx = Context::default();
|
||||
ctx.set_fonts(FontDefinitions::empty()); // prevent fonts from being loaded (save CPU time)
|
||||
let _ = ctx.run(Default::default(), |ctx| {
|
||||
crate::CentralPanel::default().show(ctx, |ui| {
|
||||
add_contents(ui);
|
||||
});
|
||||
let _ = ctx.run_ui(Default::default(), |ui| {
|
||||
add_contents(ui);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -609,7 +609,7 @@ impl Default for Loaders {
|
||||
fn default() -> Self {
|
||||
let include = Arc::new(DefaultBytesLoader::default());
|
||||
Self {
|
||||
bytes: Mutex::new(vec![include.clone()]),
|
||||
bytes: Mutex::new(vec![Arc::clone(&include) as _]),
|
||||
image: Mutex::new(Vec::new()),
|
||||
// By default we only include `DefaultTextureLoader`.
|
||||
texture: Mutex::new(vec![Arc::new(DefaultTextureLoader::default())]),
|
||||
|
||||
@@ -21,7 +21,7 @@ pub use theme::{Theme, ThemePreference};
|
||||
/// how far the user has scrolled in a [`ScrollArea`](crate::ScrollArea) etc.
|
||||
///
|
||||
/// If you want this to persist when closing your app, you should serialize [`Memory`] and store it.
|
||||
/// For this you need to enable the `persistence`.
|
||||
/// For this you need to enable the `persistence` feature.
|
||||
///
|
||||
/// If you want to store data for your widgets, you should look at [`Memory::data`]
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -306,6 +306,8 @@ impl Default for Options {
|
||||
zoom_with_keyboard: true,
|
||||
tessellation_options: Default::default(),
|
||||
repaint_on_widget_change: false,
|
||||
|
||||
#[expect(clippy::unwrap_used)]
|
||||
max_passes: NonZeroUsize::new(2).unwrap(),
|
||||
screen_reader: false,
|
||||
warn_on_id_clash: cfg!(debug_assertions),
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Theme {
|
||||
/// This is not the best design as it doesn't allow switching back to "follow system".
|
||||
#[must_use]
|
||||
pub(crate) fn small_toggle_button(self, ui: &mut crate::Ui) -> Option<Self> {
|
||||
#![allow(clippy::collapsible_else_if)]
|
||||
#![expect(clippy::collapsible_else_if)]
|
||||
if self == Self::Dark {
|
||||
if ui
|
||||
.add(Button::new("☀").frame(false))
|
||||
@@ -89,7 +89,7 @@ impl ThemePreference {
|
||||
/// Show radio-buttons to switch between light mode, dark mode and following the system theme.
|
||||
pub fn radio_buttons(&mut self, ui: &mut crate::Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
let system_theme = ui.ctx().input(|i| i.raw.system_theme);
|
||||
let system_theme = ui.input(|i| i.raw.system_theme);
|
||||
|
||||
ui.selectable_value(self, Self::System, "💻 System")
|
||||
.on_hover_ui(|ui| {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![allow(deprecated)]
|
||||
#![expect(deprecated)]
|
||||
//! Deprecated menu API - Use [`crate::containers::menu`] instead.
|
||||
//!
|
||||
//! Usage:
|
||||
@@ -186,7 +186,7 @@ fn menu_popup<'c, R>(
|
||||
.kind(UiKind::Menu)
|
||||
.order(Order::Foreground)
|
||||
.fixed_pos(pos)
|
||||
.default_width(ctx.style().spacing.menu_width)
|
||||
.default_width(ctx.global_style().spacing.menu_width)
|
||||
.sense(Sense::hover());
|
||||
|
||||
let mut sizing_pass = false;
|
||||
@@ -198,7 +198,7 @@ fn menu_popup<'c, R>(
|
||||
|
||||
Frame::menu(ui.style())
|
||||
.show(ui, |ui| {
|
||||
ui.set_menu_state(Some(menu_state_arc.clone()));
|
||||
ui.set_menu_state(Some(Arc::clone(menu_state_arc)));
|
||||
ui.with_layout(Layout::top_down_justified(Align::LEFT), add_contents)
|
||||
.inner
|
||||
})
|
||||
@@ -395,9 +395,9 @@ impl MenuRoot {
|
||||
// or button hovered while other menu is open
|
||||
let mut pos = button.rect.left_bottom();
|
||||
|
||||
let menu_frame = Frame::menu(&button.ctx.style());
|
||||
let menu_frame = Frame::menu(&button.ctx.global_style());
|
||||
pos.x -= menu_frame.total_margin().left; // Make fist button in menu align with the parent button
|
||||
pos.y += button.ctx.style().spacing.menu_spacing;
|
||||
pos.y += button.ctx.global_style().spacing.menu_spacing;
|
||||
|
||||
if let Some(root) = root.inner.as_mut() {
|
||||
let menu_rect = root.menu_state.read().rect;
|
||||
@@ -701,7 +701,7 @@ impl MenuState {
|
||||
if self.moving_towards_current_submenu(&pointer) {
|
||||
// We don't close the submenu if the pointer is on its way to hover it.
|
||||
// ensure to repaint once even when pointer is not moving
|
||||
ui.ctx().request_repaint();
|
||||
ui.request_repaint();
|
||||
} else if !open && button.hovered() {
|
||||
// TODO(emilk): open menu to the left if there isn't enough space to the right
|
||||
let mut pos = button.rect.right_top();
|
||||
|
||||
@@ -322,7 +322,7 @@ impl Painter {
|
||||
}
|
||||
|
||||
pub fn error(&self, pos: Pos2, text: impl std::fmt::Display) -> Rect {
|
||||
let color = self.ctx.style().visuals.error_fg_color;
|
||||
let color = self.ctx.global_style().visuals.error_fg_color;
|
||||
self.debug_text(pos, Align2::LEFT_TOP, color, format!("🔥 {text}"))
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ impl DebugRect {
|
||||
// Paint rectangle around widget:
|
||||
{
|
||||
// Print width and height:
|
||||
let text_color = if ctx.style().visuals.dark_mode {
|
||||
let text_color = if ctx.global_style().visuals.dark_mode {
|
||||
Color32::WHITE
|
||||
} else {
|
||||
Color32::BLACK
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{Context, FullOutput, RawInput};
|
||||
use crate::{Context, FullOutput, RawInput, Ui};
|
||||
use ahash::HashMap;
|
||||
use epaint::mutex::{Mutex, MutexGuard};
|
||||
use std::sync::Arc;
|
||||
@@ -23,13 +23,13 @@ pub trait Plugin: Send + Sync + std::any::Any + 'static {
|
||||
|
||||
/// Called at the start of each pass.
|
||||
///
|
||||
/// Can be used to show ui, e.g. a [`crate::Window`] or [`crate::SidePanel`].
|
||||
fn on_begin_pass(&mut self, ctx: &Context) {}
|
||||
/// Can be used to show ui, e.g. a [`crate::Window`] or [`crate::Panel`].
|
||||
fn on_begin_pass(&mut self, ui: &mut Ui) {}
|
||||
|
||||
/// Called at the end of each pass.
|
||||
///
|
||||
/// Can be used to show ui, e.g. a [`crate::Window`].
|
||||
fn on_end_pass(&mut self, ctx: &Context) {}
|
||||
fn on_end_pass(&mut self, ui: &mut Ui) {}
|
||||
|
||||
/// Called just before the input is processed.
|
||||
///
|
||||
@@ -147,17 +147,17 @@ impl PluginsOrdered {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_begin_pass(&self, ctx: &Context) {
|
||||
pub fn on_begin_pass(&self, ui: &mut Ui) {
|
||||
profiling::scope!("plugins", "on_begin_pass");
|
||||
self.for_each_dyn(|p| {
|
||||
p.on_begin_pass(ctx);
|
||||
p.on_begin_pass(ui);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn on_end_pass(&self, ctx: &Context) {
|
||||
pub fn on_end_pass(&self, ui: &mut Ui) {
|
||||
profiling::scope!("plugins", "on_end_pass");
|
||||
self.for_each_dyn(|p| {
|
||||
p.on_end_pass(ctx);
|
||||
p.on_end_pass(ui);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ impl Plugins {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.plugins.insert(type_id, handle.clone());
|
||||
self.plugins.insert(type_id, Arc::clone(&handle));
|
||||
self.plugins_ordered.0.push(handle);
|
||||
|
||||
true
|
||||
@@ -214,7 +214,7 @@ impl Plugins {
|
||||
}
|
||||
|
||||
/// Generic event callback.
|
||||
pub type ContextCallback = Arc<dyn Fn(&Context) + Send + Sync>;
|
||||
pub type ContextCallback = Arc<dyn Fn(&mut Ui) + Send + Sync>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct CallbackPlugin {
|
||||
@@ -227,21 +227,21 @@ impl Plugin for CallbackPlugin {
|
||||
"CallbackPlugins"
|
||||
}
|
||||
|
||||
fn on_begin_pass(&mut self, ctx: &Context) {
|
||||
fn on_begin_pass(&mut self, ui: &mut Ui) {
|
||||
profiling::function_scope!();
|
||||
|
||||
for (_debug_name, cb) in &self.on_begin_plugins {
|
||||
profiling::scope!("on_begin_pass", *_debug_name);
|
||||
(cb)(ctx);
|
||||
(cb)(ui);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_end_pass(&mut self, ctx: &Context) {
|
||||
fn on_end_pass(&mut self, ui: &mut Ui) {
|
||||
profiling::function_scope!();
|
||||
|
||||
for (_debug_name, cb) in &self.on_end_plugins {
|
||||
profiling::scope!("on_end_pass", *_debug_name);
|
||||
(cb)(ctx);
|
||||
(cb)(ui);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::{
|
||||
/// It also lets you easily show a tooltip on hover.
|
||||
///
|
||||
/// Whenever something gets added to a [`Ui`], a [`Response`] object is returned.
|
||||
/// [`ui.add`] returns a [`Response`], as does [`ui.button`], and all similar shortcuts.
|
||||
/// [`Ui::add`] returns a [`Response`], as does [`Ui::button`], and all similar shortcuts.
|
||||
///
|
||||
/// ⚠️ The `Response` contains a clone of [`Context`], and many methods lock the `Context`.
|
||||
/// It can therefore be a deadlock to use `Context` from within a context-locking closures,
|
||||
@@ -433,7 +433,7 @@ impl Response {
|
||||
pub fn drag_motion(&self) -> Vec2 {
|
||||
if self.dragged() {
|
||||
self.ctx
|
||||
.input(|i| i.pointer.motion().unwrap_or(i.pointer.delta()))
|
||||
.input(|i| i.pointer.motion().unwrap_or_else(|| i.pointer.delta()))
|
||||
} else {
|
||||
Vec2::ZERO
|
||||
}
|
||||
@@ -472,7 +472,7 @@ impl Response {
|
||||
///
|
||||
/// Only returns something if [`Self::contains_pointer`] is true,
|
||||
/// the user is drag-dropping something of this type,
|
||||
/// and they released it this frame
|
||||
/// and they released it this frame.
|
||||
#[doc(alias = "drag and drop")]
|
||||
pub fn dnd_release_payload<Payload: Any + Send + Sync>(&self) -> Option<Arc<Payload>> {
|
||||
// NOTE: we use `response.contains_pointer` here instead of `hovered`, because
|
||||
@@ -738,6 +738,7 @@ impl Response {
|
||||
enabled: self.enabled(),
|
||||
},
|
||||
true,
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -761,7 +762,7 @@ impl Response {
|
||||
/// # });
|
||||
/// ```
|
||||
pub fn scroll_to_me(&self, align: Option<Align>) {
|
||||
self.scroll_to_me_animation(align, self.ctx.style().scroll_animation);
|
||||
self.scroll_to_me_animation(align, self.ctx.global_style().scroll_animation);
|
||||
}
|
||||
|
||||
/// Like [`Self::scroll_to_me`], but allows you to specify the [`crate::style::ScrollAnimation`].
|
||||
@@ -871,6 +872,10 @@ impl Response {
|
||||
WidgetType::Panel => Role::Pane,
|
||||
WidgetType::ProgressIndicator => Role::ProgressIndicator,
|
||||
WidgetType::Window => Role::Window,
|
||||
|
||||
WidgetType::ResizeHandle => Role::Splitter,
|
||||
WidgetType::ScrollBar => Role::ScrollBar,
|
||||
|
||||
WidgetType::Other => Role::Unknown,
|
||||
});
|
||||
if !info.enabled {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
//! egui theme (spacing, colors, etc).
|
||||
|
||||
#![allow(clippy::if_same_then_else)]
|
||||
|
||||
use emath::Align;
|
||||
use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, text::FontTweak};
|
||||
use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, TextOptions, text::FontTweak};
|
||||
use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
@@ -948,8 +946,11 @@ pub struct Visuals {
|
||||
/// this is more to provide a convenient summary of the rest of the settings.
|
||||
pub dark_mode: bool,
|
||||
|
||||
/// ADVANCED: Controls how we render text.
|
||||
pub text_alpha_from_coverage: AlphaFromCoverage,
|
||||
/// Controls how we render text.
|
||||
///
|
||||
/// The [`TextOptions::max_texture_side`] is ignored and overruled by
|
||||
/// [`crate::RawInput::max_texture_side`].
|
||||
pub text_options: TextOptions,
|
||||
|
||||
/// Override default text color for all text.
|
||||
///
|
||||
@@ -1233,6 +1234,11 @@ pub struct WidgetVisuals {
|
||||
pub fg_stroke: Stroke,
|
||||
|
||||
/// Make the frame this much larger.
|
||||
///
|
||||
/// The problem with "expanding" widgets is that they now want to paint outside their own bounds,
|
||||
/// which then requires all parent UIs to have proper margins.
|
||||
///
|
||||
/// It also means hovered things are no longer properly aligned with every other widget.
|
||||
pub expansion: f32,
|
||||
}
|
||||
|
||||
@@ -1391,7 +1397,7 @@ impl Default for Interaction {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
interact_radius: 5.0,
|
||||
resize_grab_radius_side: 5.0,
|
||||
resize_grab_radius_side: 3.0,
|
||||
resize_grab_radius_corner: 10.0,
|
||||
show_tooltips_only_when_still: true,
|
||||
tooltip_delay: 0.5,
|
||||
@@ -1407,7 +1413,10 @@ impl Visuals {
|
||||
pub fn dark() -> Self {
|
||||
Self {
|
||||
dark_mode: true,
|
||||
text_alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
|
||||
text_options: TextOptions {
|
||||
alpha_from_coverage: AlphaFromCoverage::DARK_MODE_DEFAULT,
|
||||
..Default::default()
|
||||
},
|
||||
override_text_color: None,
|
||||
weak_text_alpha: 0.6,
|
||||
weak_text_color: None,
|
||||
@@ -1470,7 +1479,10 @@ impl Visuals {
|
||||
pub fn light() -> Self {
|
||||
Self {
|
||||
dark_mode: false,
|
||||
text_alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
|
||||
text_options: TextOptions {
|
||||
alpha_from_coverage: AlphaFromCoverage::LIGHT_MODE_DEFAULT,
|
||||
..Default::default()
|
||||
},
|
||||
widgets: Widgets::light(),
|
||||
selection: Selection::light(),
|
||||
hyperlink_color: Color32::from_rgb(0, 155, 255),
|
||||
@@ -1561,7 +1573,7 @@ impl Widgets {
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button
|
||||
fg_stroke: Stroke::new(1.5, Color32::from_gray(240)),
|
||||
corner_radius: CornerRadius::same(3),
|
||||
expansion: 1.0,
|
||||
expansion: 0.0,
|
||||
},
|
||||
active: WidgetVisuals {
|
||||
weak_bg_fill: Color32::from_gray(55),
|
||||
@@ -1569,7 +1581,7 @@ impl Widgets {
|
||||
bg_stroke: Stroke::new(1.0, Color32::WHITE),
|
||||
fg_stroke: Stroke::new(2.0, Color32::WHITE),
|
||||
corner_radius: CornerRadius::same(2),
|
||||
expansion: 1.0,
|
||||
expansion: 0.0,
|
||||
},
|
||||
open: WidgetVisuals {
|
||||
weak_bg_fill: Color32::from_gray(45),
|
||||
@@ -1606,7 +1618,7 @@ impl Widgets {
|
||||
bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), // e.g. hover over window edge or button
|
||||
fg_stroke: Stroke::new(1.5, Color32::BLACK),
|
||||
corner_radius: CornerRadius::same(3),
|
||||
expansion: 1.0,
|
||||
expansion: 0.0,
|
||||
},
|
||||
active: WidgetVisuals {
|
||||
weak_bg_fill: Color32::from_gray(165),
|
||||
@@ -1614,7 +1626,7 @@ impl Widgets {
|
||||
bg_stroke: Stroke::new(1.0, Color32::BLACK),
|
||||
fg_stroke: Stroke::new(2.0, Color32::BLACK),
|
||||
corner_radius: CornerRadius::same(2),
|
||||
expansion: 1.0,
|
||||
expansion: 0.0,
|
||||
},
|
||||
open: WidgetVisuals {
|
||||
weak_bg_fill: Color32::from_gray(220),
|
||||
@@ -2107,7 +2119,7 @@ impl Visuals {
|
||||
pub fn ui(&mut self, ui: &mut crate::Ui) {
|
||||
let Self {
|
||||
dark_mode,
|
||||
text_alpha_from_coverage,
|
||||
text_options,
|
||||
override_text_color: _,
|
||||
weak_text_alpha,
|
||||
weak_text_color,
|
||||
@@ -2207,7 +2219,7 @@ impl Visuals {
|
||||
});
|
||||
});
|
||||
|
||||
ui.collapsing("Text color", |ui| {
|
||||
ui.collapsing("Text rendering", |ui| {
|
||||
fn ui_text_color(ui: &mut Ui, color: &mut Color32, label: impl Into<RichText>) {
|
||||
ui.label(label.into().color(*color));
|
||||
ui.color_edit_button_srgba(color);
|
||||
@@ -2259,7 +2271,15 @@ impl Visuals {
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
text_alpha_from_coverage_ui(ui, text_alpha_from_coverage);
|
||||
let TextOptions {
|
||||
max_texture_side: _,
|
||||
alpha_from_coverage,
|
||||
font_hinting,
|
||||
} = text_options;
|
||||
|
||||
text_alpha_from_coverage_ui(ui, alpha_from_coverage);
|
||||
|
||||
ui.checkbox(font_hinting, "Enable font hinting");
|
||||
});
|
||||
|
||||
ui.collapsing("Text cursor", |ui| {
|
||||
@@ -2370,9 +2390,9 @@ impl Visuals {
|
||||
}
|
||||
}
|
||||
|
||||
fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut AlphaFromCoverage) {
|
||||
fn text_alpha_from_coverage_ui(ui: &mut Ui, alpha_from_coverage: &mut AlphaFromCoverage) {
|
||||
let mut dark_mode_special =
|
||||
*text_alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
|
||||
*alpha_from_coverage == AlphaFromCoverage::TwoCoverageMinusCoverageSq;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Text rendering:");
|
||||
@@ -2380,9 +2400,9 @@ fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut Alpha
|
||||
ui.checkbox(&mut dark_mode_special, "Dark-mode special");
|
||||
|
||||
if dark_mode_special {
|
||||
*text_alpha_from_coverage = AlphaFromCoverage::TwoCoverageMinusCoverageSq;
|
||||
*alpha_from_coverage = AlphaFromCoverage::DARK_MODE_DEFAULT;
|
||||
} else {
|
||||
let mut gamma = match text_alpha_from_coverage {
|
||||
let mut gamma = match alpha_from_coverage {
|
||||
AlphaFromCoverage::Linear => 1.0,
|
||||
AlphaFromCoverage::Gamma(gamma) => *gamma,
|
||||
AlphaFromCoverage::TwoCoverageMinusCoverageSq => 0.5, // approximately the same
|
||||
@@ -2396,9 +2416,9 @@ fn text_alpha_from_coverage_ui(ui: &mut Ui, text_alpha_from_coverage: &mut Alpha
|
||||
);
|
||||
|
||||
if gamma == 1.0 {
|
||||
*text_alpha_from_coverage = AlphaFromCoverage::Linear;
|
||||
*alpha_from_coverage = AlphaFromCoverage::Linear;
|
||||
} else {
|
||||
*text_alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
|
||||
*alpha_from_coverage = AlphaFromCoverage::Gamma(gamma);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -2812,6 +2832,7 @@ impl Widget for &mut FontTweak {
|
||||
scale,
|
||||
y_offset_factor,
|
||||
y_offset,
|
||||
hinting_override,
|
||||
} = self;
|
||||
|
||||
ui.label("Scale");
|
||||
@@ -2827,6 +2848,19 @@ impl Widget for &mut FontTweak {
|
||||
ui.add(DragValue::new(y_offset).speed(-0.02));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("hinting_override");
|
||||
ComboBox::from_id_salt("hinting_override")
|
||||
.selected_text(match hinting_override {
|
||||
None => "None",
|
||||
Some(true) => "Enable",
|
||||
Some(false) => "Disable",
|
||||
})
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(hinting_override, None, "None");
|
||||
ui.selectable_value(hinting_override, Some(true), "Enable");
|
||||
ui.selectable_value(hinting_override, Some(false), "Disable");
|
||||
});
|
||||
|
||||
if ui.button("Reset").clicked() {
|
||||
*self = Default::default();
|
||||
}
|
||||
|
||||
@@ -128,8 +128,8 @@ impl Plugin for LabelSelectionState {
|
||||
"LabelSelectionState"
|
||||
}
|
||||
|
||||
fn on_begin_pass(&mut self, ctx: &Context) {
|
||||
if ctx.input(|i| i.pointer.any_pressed() && !i.modifiers.shift) {
|
||||
fn on_begin_pass(&mut self, ui: &mut Ui) {
|
||||
if ui.input(|i| i.pointer.any_pressed() && !i.modifiers.shift) {
|
||||
// Maybe a new selection is about to begin, but the old one is over:
|
||||
// state.selection = None; // TODO(emilk): this makes sense, but doesn't work as expected.
|
||||
}
|
||||
@@ -145,9 +145,9 @@ impl Plugin for LabelSelectionState {
|
||||
self.painted_selections.clear();
|
||||
}
|
||||
|
||||
fn on_end_pass(&mut self, ctx: &Context) {
|
||||
fn on_end_pass(&mut self, ui: &mut Ui) {
|
||||
if self.is_dragging {
|
||||
ctx.set_cursor_icon(CursorIcon::Text);
|
||||
ui.set_cursor_icon(CursorIcon::Text);
|
||||
}
|
||||
|
||||
if !self.has_reached_primary || !self.has_reached_secondary {
|
||||
@@ -159,7 +159,7 @@ impl Plugin for LabelSelectionState {
|
||||
if let Some(selection) = prev_selection {
|
||||
// This was the first frame of glitch, so hide the
|
||||
// glitching by removing all painted selections:
|
||||
ctx.graphics_mut(|layers| {
|
||||
ui.graphics_mut(|layers| {
|
||||
if let Some(list) = layers.get_mut(selection.layer_id) {
|
||||
for (shape_idx, row_selections) in self.painted_selections.drain(..) {
|
||||
list.mutate_shape(shape_idx, |shape| {
|
||||
@@ -190,21 +190,21 @@ impl Plugin for LabelSelectionState {
|
||||
}
|
||||
}
|
||||
|
||||
let pressed_escape = ctx.input(|i| i.key_pressed(crate::Key::Escape));
|
||||
let clicked_something_else = ctx.input(|i| i.pointer.any_pressed()) && !self.any_hovered;
|
||||
let pressed_escape = ui.input(|i| i.key_pressed(crate::Key::Escape));
|
||||
let clicked_something_else = ui.input(|i| i.pointer.any_pressed()) && !self.any_hovered;
|
||||
let delected_everything = pressed_escape || clicked_something_else;
|
||||
|
||||
if delected_everything {
|
||||
self.selection = None;
|
||||
}
|
||||
|
||||
if ctx.input(|i| i.pointer.any_released()) {
|
||||
if ui.input(|i| i.pointer.any_released()) {
|
||||
self.is_dragging = false;
|
||||
}
|
||||
|
||||
let text_to_copy = std::mem::take(&mut self.text_to_copy);
|
||||
if !text_to_copy.is_empty() {
|
||||
ctx.copy_text(text_to_copy);
|
||||
ui.copy_text(text_to_copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,7 +499,7 @@ impl LabelSelectionState {
|
||||
let global_from_galley = global_from_layer * layer_from_galley;
|
||||
|
||||
if response.hovered() {
|
||||
ui.ctx().set_cursor_icon(CursorIcon::Text);
|
||||
ui.set_cursor_icon(CursorIcon::Text);
|
||||
}
|
||||
|
||||
self.any_hovered |= response.hovered();
|
||||
|
||||
@@ -208,25 +208,33 @@ fn ccursor_previous_line(text: &str, ccursor: CCursor) -> CCursor {
|
||||
}
|
||||
}
|
||||
|
||||
fn next_word_boundary_char_index(text: &str, index: usize) -> usize {
|
||||
for word in text.split_word_bound_indices() {
|
||||
fn next_word_boundary_char_index(text: &str, cursor_ci: usize) -> usize {
|
||||
for (word_byte_index, word) in text.split_word_bound_indices() {
|
||||
let word_ci = char_index_from_byte_index(text, word_byte_index);
|
||||
|
||||
// We consider `.` a word boundary.
|
||||
// At least that's how Mac works when navigating something like `www.example.com`.
|
||||
for (dot_ci_offset, chr) in word.chars().enumerate() {
|
||||
let dot_ci = word_ci + dot_ci_offset;
|
||||
if chr == '.' && cursor_ci < dot_ci {
|
||||
return dot_ci;
|
||||
}
|
||||
}
|
||||
|
||||
// Splitting considers contiguous whitespace as one word, such words must be skipped,
|
||||
// this handles cases for example ' abc' (a space and a word), the cursor is at the beginning
|
||||
// (before space) - this jumps at the end of 'abc' (this is consistent with text editors
|
||||
// or browsers)
|
||||
let ci = char_index_from_byte_index(text, word.0);
|
||||
if ci > index && !skip_word(word.1) {
|
||||
return ci;
|
||||
if cursor_ci < word_ci && !all_word_chars(word) {
|
||||
return word_ci;
|
||||
}
|
||||
}
|
||||
|
||||
char_index_from_byte_index(text, text.len())
|
||||
}
|
||||
|
||||
fn skip_word(text: &str) -> bool {
|
||||
// skip words that contain anything other than alphanumeric characters and underscore
|
||||
// (i.e. whitespace, dashes, etc.)
|
||||
!text.chars().any(|c| !is_word_char(c))
|
||||
fn all_word_chars(text: &str) -> bool {
|
||||
text.chars().all(is_word_char)
|
||||
}
|
||||
|
||||
fn next_line_boundary_char_index(it: impl Iterator<Item = char>, mut index: usize) -> usize {
|
||||
@@ -337,6 +345,12 @@ mod test {
|
||||
assert_eq!(next_word_boundary_char_index("", 0), 0);
|
||||
assert_eq!(next_word_boundary_char_index("", 1), 0);
|
||||
|
||||
// ASCII only
|
||||
let text = "abc.def.ghi";
|
||||
assert_eq!(next_word_boundary_char_index(text, 1), 3);
|
||||
assert_eq!(next_word_boundary_char_index(text, 3), 7);
|
||||
assert_eq!(next_word_boundary_char_index(text, 7), 11);
|
||||
|
||||
// Unicode graphemes, some of which consist of multiple Unicode characters,
|
||||
// !!! Unicode character is not always what is tranditionally considered a character,
|
||||
// the values below are correct despite not seeming that way on the first look,
|
||||
|
||||
@@ -172,7 +172,7 @@ pub fn paint_text_cursor(
|
||||
total_duration - time_in_cycle
|
||||
};
|
||||
|
||||
ui.ctx().request_repaint_after_secs(wake_in);
|
||||
ui.request_repaint_after_secs(wake_in);
|
||||
} else {
|
||||
paint_cursor_end(painter, ui.visuals(), primary_cursor_rect);
|
||||
}
|
||||
|
||||
@@ -1,34 +1,13 @@
|
||||
#![warn(missing_docs)] // Let's keep `Ui` well-documented.
|
||||
#![allow(clippy::use_self)]
|
||||
#![expect(clippy::use_self)]
|
||||
|
||||
use std::{any::Any, hash::Hash, ops::Deref, sync::Arc};
|
||||
|
||||
use emath::GuiRounding as _;
|
||||
use epaint::mutex::RwLock;
|
||||
use epaint::text::FontsView;
|
||||
use std::{any::Any, hash::Hash, sync::Arc};
|
||||
|
||||
use crate::ClosableTag;
|
||||
#[cfg(debug_assertions)]
|
||||
use crate::Stroke;
|
||||
use crate::containers::menu;
|
||||
use crate::{
|
||||
Align, Color32, Context, CursorIcon, DragAndDrop, Id, InnerResponse, InputState, IntoAtoms,
|
||||
LayerId, Memory, Order, Painter, PlatformOutput, Pos2, Rangef, Rect, Response, Rgba, RichText,
|
||||
Sense, Style, TextStyle, TextWrapMode, UiBuilder, UiKind, UiStack, UiStackInfo, Vec2,
|
||||
WidgetRect, WidgetText,
|
||||
containers::{CollapsingHeader, CollapsingResponse, Frame},
|
||||
ecolor::Hsva,
|
||||
emath, epaint, grid,
|
||||
layout::{Direction, Layout},
|
||||
pass_state,
|
||||
placer::Placer,
|
||||
pos2, style,
|
||||
util::IdTypeMap,
|
||||
vec2, widgets,
|
||||
widgets::{
|
||||
Button, Checkbox, DragValue, Hyperlink, Image, ImageSource, Label, Link, RadioButton,
|
||||
Separator, Spinner, TextEdit, Widget, color_picker,
|
||||
},
|
||||
};
|
||||
use crate::{containers::*, ecolor::*, layout::*, placer::Placer, widgets::*, *};
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// This is what you use to place widgets.
|
||||
@@ -74,14 +53,14 @@ pub struct Ui {
|
||||
/// This value is based on where in the hierarchy of widgets this Ui is in,
|
||||
/// and the value is increment with each added child widget.
|
||||
/// This works as an Id source only as long as new widgets aren't added or removed.
|
||||
/// They are therefore only good for Id:s that has no state.
|
||||
/// They are therefore only good for Id:s that have no state.
|
||||
next_auto_id_salt: u64,
|
||||
|
||||
/// Specifies paint layer, clip rectangle and a reference to [`Context`].
|
||||
painter: Painter,
|
||||
|
||||
/// The [`Style`] (visuals, spacing, etc) of this ui.
|
||||
/// Commonly many [`Ui`]:s share the same [`Style`].
|
||||
/// Commonly many [`Ui`]s share the same [`Style`].
|
||||
/// The [`Ui`] implements copy-on-write for this.
|
||||
style: Arc<Style>,
|
||||
|
||||
@@ -112,6 +91,16 @@ pub struct Ui {
|
||||
min_rect_already_remembered: bool,
|
||||
}
|
||||
|
||||
/// Allow using [`Ui`] like a [`Context`].
|
||||
impl Deref for Ui {
|
||||
type Target = Context;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.ctx()
|
||||
}
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
// ------------------------------------------------------------------------
|
||||
// Creation:
|
||||
@@ -136,7 +125,7 @@ impl Ui {
|
||||
accessibility_parent,
|
||||
} = ui_builder;
|
||||
|
||||
let layer_id = layer_id.unwrap_or(LayerId::background());
|
||||
let layer_id = layer_id.unwrap_or_else(LayerId::background);
|
||||
|
||||
debug_assert!(
|
||||
id_salt.is_none(),
|
||||
@@ -147,8 +136,8 @@ impl Ui {
|
||||
let clip_rect = max_rect;
|
||||
let layout = layout.unwrap_or_default();
|
||||
let disabled = disabled || invisible;
|
||||
let style = style.unwrap_or_else(|| ctx.style());
|
||||
let sense = sense.unwrap_or(Sense::hover());
|
||||
let style = style.unwrap_or_else(|| ctx.global_style());
|
||||
let sense = sense.unwrap_or_else(Sense::hover);
|
||||
|
||||
let placer = Placer::new(max_rect, layout);
|
||||
let ui_stack = UiStack {
|
||||
@@ -191,6 +180,7 @@ impl Ui {
|
||||
enabled: ui.enabled,
|
||||
},
|
||||
true,
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
if disabled {
|
||||
@@ -277,7 +267,7 @@ impl Ui {
|
||||
|
||||
let id_salt = id_salt.unwrap_or_else(|| Id::from("child"));
|
||||
let max_rect = max_rect.unwrap_or_else(|| self.available_rect_before_wrap());
|
||||
let mut layout = layout.unwrap_or(*self.layout());
|
||||
let mut layout = layout.unwrap_or_else(|| *self.layout());
|
||||
let enabled = self.enabled && !disabled && !invisible;
|
||||
if let Some(layer_id) = layer_id {
|
||||
painter.set_layer_id(layer_id);
|
||||
@@ -286,8 +276,8 @@ impl Ui {
|
||||
painter.set_invisible();
|
||||
}
|
||||
let sizing_pass = self.sizing_pass || sizing_pass;
|
||||
let style = style.unwrap_or_else(|| self.style.clone());
|
||||
let sense = sense.unwrap_or(Sense::hover());
|
||||
let style = style.unwrap_or_else(|| Arc::clone(&self.style));
|
||||
let sense = sense.unwrap_or_else(Sense::hover);
|
||||
|
||||
if sizing_pass {
|
||||
// During the sizing pass we want widgets to use up as little space as possible,
|
||||
@@ -316,7 +306,7 @@ impl Ui {
|
||||
id: unique_id,
|
||||
layout_direction: layout.main_dir,
|
||||
info: ui_stack_info,
|
||||
parent: Some(self.stack.clone()),
|
||||
parent: Some(Arc::clone(&self.stack)),
|
||||
min_rect: placer.min_rect(),
|
||||
max_rect: placer.max_rect(),
|
||||
};
|
||||
@@ -356,6 +346,7 @@ impl Ui {
|
||||
enabled: child_ui.enabled,
|
||||
},
|
||||
true,
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
child_ui
|
||||
@@ -428,7 +419,7 @@ impl Ui {
|
||||
/// Mutably borrow internal [`Style`].
|
||||
/// Changes apply to this [`Ui`] and its subsequent children.
|
||||
///
|
||||
/// To set the style of all [`Ui`]:s, use [`Context::set_style_of`].
|
||||
/// To set the style of all [`Ui`]s, use [`Context::set_style_of`].
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
@@ -442,14 +433,14 @@ impl Ui {
|
||||
|
||||
/// Changes apply to this [`Ui`] and its subsequent children.
|
||||
///
|
||||
/// To set the visuals of all [`Ui`]:s, use [`Context::set_visuals_of`].
|
||||
/// To set the style of all [`Ui`]s, use [`Context::set_style_of`].
|
||||
pub fn set_style(&mut self, style: impl Into<Arc<Style>>) {
|
||||
self.style = style.into();
|
||||
}
|
||||
|
||||
/// Reset to the default style set in [`Context`].
|
||||
pub fn reset_style(&mut self) {
|
||||
self.style = self.ctx().style();
|
||||
self.style = self.ctx().global_style();
|
||||
}
|
||||
|
||||
/// The current spacing options for this [`Ui`].
|
||||
@@ -459,7 +450,7 @@ impl Ui {
|
||||
&self.style.spacing
|
||||
}
|
||||
|
||||
/// Mutably borrow internal [`Spacing`](crate::style::Spacing).
|
||||
/// Mutably borrow internal [`Spacing`].
|
||||
/// Changes apply to this [`Ui`] and its subsequent children.
|
||||
///
|
||||
/// Example:
|
||||
@@ -482,7 +473,7 @@ impl Ui {
|
||||
/// Mutably borrow internal `visuals`.
|
||||
/// Changes apply to this [`Ui`] and its subsequent children.
|
||||
///
|
||||
/// To set the visuals of all [`Ui`]:s, use [`Context::set_visuals_of`].
|
||||
/// To set the visuals of all [`Ui`]s, use [`Context::set_visuals_of`].
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
@@ -794,93 +785,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
|
||||
@@ -1123,6 +1027,17 @@ impl Ui {
|
||||
impl Ui {
|
||||
/// Check for clicks, drags and/or hover on a specific region of this [`Ui`].
|
||||
pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> Response {
|
||||
self.interact_opt(rect, id, sense, Default::default())
|
||||
}
|
||||
|
||||
/// Check for clicks, drags and/or hover on a specific region of this [`Ui`].
|
||||
pub fn interact_opt(
|
||||
&self,
|
||||
rect: Rect,
|
||||
id: Id,
|
||||
sense: Sense,
|
||||
options: crate::InteractOptions,
|
||||
) -> Response {
|
||||
self.ctx().register_accesskit_parent(id, self.unique_id);
|
||||
|
||||
self.ctx().create_widget(
|
||||
@@ -1135,6 +1050,7 @@ impl Ui {
|
||||
enabled: self.enabled,
|
||||
},
|
||||
true,
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1150,8 +1066,8 @@ impl Ui {
|
||||
self.interact(rect, id, sense)
|
||||
}
|
||||
|
||||
/// Read the [`Ui`]s background [`Response`].
|
||||
/// It's [`Sense`] will be based on the [`UiBuilder::sense`] used to create this [`Ui`].
|
||||
/// Read the [`Ui`]'s background [`Response`].
|
||||
/// Its [`Sense`] will be based on the [`UiBuilder::sense`] used to create this [`Ui`].
|
||||
///
|
||||
/// The rectangle of the [`Response`] (and interactive area) will be [`Self::min_rect`]
|
||||
/// of the last pass.
|
||||
@@ -1203,6 +1119,7 @@ impl Ui {
|
||||
enabled: self.enabled,
|
||||
},
|
||||
false,
|
||||
Default::default(),
|
||||
);
|
||||
if self.should_close() {
|
||||
response.set_close();
|
||||
@@ -1251,7 +1168,7 @@ impl Ui {
|
||||
/// [`crate::Area`], [`crate::Window`], [`crate::CollapsingHeader`], etc.
|
||||
///
|
||||
/// What exactly happens when you close a container depends on the container implementation.
|
||||
/// [`crate::Area`] e.g. will return true from it's [`Response::should_close`] method.
|
||||
/// [`crate::Area`] e.g. will return true from its [`Response::should_close`] method.
|
||||
///
|
||||
/// If you want to close a specific kind of container, use [`Ui::close_kind`] instead.
|
||||
///
|
||||
@@ -1432,7 +1349,7 @@ impl Ui {
|
||||
crate::StrokeKind::Inside,
|
||||
);
|
||||
|
||||
let stroke = Stroke::new(2.5, Color32::from_rgb(200, 0, 0));
|
||||
let stroke = crate::Stroke::new(2.5, Color32::from_rgb(200, 0, 0));
|
||||
let paint_line_seg = |a, b| self.painter().line_segment([a, b], stroke);
|
||||
|
||||
if debug_expand_width && too_wide {
|
||||
@@ -2292,7 +2209,7 @@ impl Ui {
|
||||
/// Show an image available at the given `uri`.
|
||||
///
|
||||
/// ⚠ This will do nothing unless you install some image loaders first!
|
||||
/// The easiest way to do this is via [`egui_extras::install_image_loaders`](https://docs.rs/egui_extras/latest/egui_extras/fn.install_image_loaders.html).
|
||||
/// The easiest way to do this is via [`egui_extras::install_image_loaders`](https://docs.rs/egui_extras/latest/egui_extras/loaders/fn.install_image_loaders.html).
|
||||
///
|
||||
/// The loaders handle caching image data, sampled textures, etc. across frames, so calling this is immediate-mode safe.
|
||||
///
|
||||
@@ -2368,7 +2285,7 @@ impl Ui {
|
||||
///
|
||||
/// If the user clicks the button, a full color picker is shown.
|
||||
/// The given color is in `sRGBA` space without premultiplied alpha.
|
||||
/// If unsure, what "premultiplied alpha" is, then this is probably the function you want to use.
|
||||
/// If unsure what "premultiplied alpha" is, then this is probably the function you want to use.
|
||||
pub fn color_edit_button_srgba_unmultiplied(&mut self, srgba: &mut [u8; 4]) -> Response {
|
||||
let mut rgba = Rgba::from_srgba_unmultiplied(srgba[0], srgba[1], srgba[2], srgba[3]);
|
||||
let response =
|
||||
@@ -3004,7 +2921,7 @@ impl Ui {
|
||||
///
|
||||
/// Returns the dropped item, if it was released this frame.
|
||||
///
|
||||
/// The given frame is used for its margins, but it color is ignored.
|
||||
/// The given frame is used for its margins, but the color is ignored.
|
||||
#[doc(alias = "drag and drop")]
|
||||
pub fn dnd_drop_zone<Payload, R>(
|
||||
&mut self,
|
||||
@@ -3305,7 +3222,7 @@ fn register_rect(ui: &Ui, rect: Rect) {
|
||||
|
||||
// Use the debug-painter to avoid clip rect,
|
||||
// otherwise the content of the widget may cover what we paint here!
|
||||
let painter = ui.ctx().debug_painter();
|
||||
let painter = ui.debug_painter();
|
||||
|
||||
if debug.hover_shows_next {
|
||||
ui.placer.debug_paint_cursor(&painter, "next");
|
||||
|
||||
@@ -142,7 +142,9 @@ impl Element {
|
||||
Self::Value {
|
||||
value: Box::new(t),
|
||||
clone_fn: |x| {
|
||||
let x = x.downcast_ref::<T>().unwrap(); // This unwrap will never panic, because we always construct this type using this `new` function and because we return &mut reference only with this type `T`, so type cannot change.
|
||||
// This unwrap will never panic, because we always construct this type using this `new` function and because we return &mut reference only with this type `T`, so type cannot change.
|
||||
#[expect(clippy::unwrap_used)]
|
||||
let x = x.downcast_ref::<T>().unwrap();
|
||||
Box::new(x.clone())
|
||||
},
|
||||
#[cfg(feature = "persistence")]
|
||||
@@ -156,12 +158,16 @@ impl Element {
|
||||
Self::Value {
|
||||
value: Box::new(t),
|
||||
clone_fn: |x| {
|
||||
let x = x.downcast_ref::<T>().unwrap(); // This unwrap will never panic, because we always construct this type using this `new` function and because we return &mut reference only with this type `T`, so type cannot change.
|
||||
// This unwrap will never panic, because we always construct this type using this `new` function and because we return &mut reference only with this type `T`, so type cannot change.
|
||||
#[expect(clippy::unwrap_used)]
|
||||
let x = x.downcast_ref::<T>().unwrap();
|
||||
Box::new(x.clone())
|
||||
},
|
||||
#[cfg(feature = "persistence")]
|
||||
serialize_fn: Some(|x| {
|
||||
let x = x.downcast_ref::<T>().unwrap(); // This will never panic too, for same reason.
|
||||
// This will never panic too, for same reason.
|
||||
#[expect(clippy::unwrap_used)]
|
||||
let x = x.downcast_ref::<T>().unwrap();
|
||||
ron::to_string(x).ok()
|
||||
}),
|
||||
}
|
||||
@@ -209,7 +215,9 @@ impl Element {
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Value { value, .. } => value.downcast_mut().unwrap(), // This unwrap will never panic because we already converted object to required type
|
||||
// This unwrap will never panic because we already converted object to required type
|
||||
#[expect(clippy::unwrap_used)]
|
||||
Self::Value { value, .. } => value.downcast_mut().unwrap(),
|
||||
Self::Serialized(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -238,7 +246,9 @@ impl Element {
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Value { value, .. } => value.downcast_mut().unwrap(), // This unwrap will never panic because we already converted object to required type
|
||||
// This unwrap will never panic because we already converted object to required type
|
||||
#[expect(clippy::unwrap_used)]
|
||||
Self::Value { value, .. } => value.downcast_mut().unwrap(),
|
||||
Self::Serialized(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -436,10 +446,14 @@ impl IdTypeMap {
|
||||
let hash = hash(TypeId::of::<T>(), id);
|
||||
use std::collections::hash_map::Entry;
|
||||
match self.map.entry(hash) {
|
||||
Entry::Vacant(vacant) => vacant
|
||||
.insert(Element::new_temp(insert_with()))
|
||||
.get_mut_temp()
|
||||
.unwrap(), // this unwrap will never panic, because we insert correct type right now
|
||||
Entry::Vacant(vacant) => {
|
||||
// this unwrap will never panic, because we insert correct type right now
|
||||
#[expect(clippy::unwrap_used)]
|
||||
vacant
|
||||
.insert(Element::new_temp(insert_with()))
|
||||
.get_mut_temp()
|
||||
.unwrap()
|
||||
}
|
||||
Entry::Occupied(occupied) => {
|
||||
occupied.into_mut().get_temp_mut_or_insert_with(insert_with)
|
||||
}
|
||||
@@ -454,10 +468,14 @@ impl IdTypeMap {
|
||||
let hash = hash(TypeId::of::<T>(), id);
|
||||
use std::collections::hash_map::Entry;
|
||||
match self.map.entry(hash) {
|
||||
Entry::Vacant(vacant) => vacant
|
||||
.insert(Element::new_persisted(insert_with()))
|
||||
.get_mut_persisted()
|
||||
.unwrap(), // this unwrap will never panic, because we insert correct type right now
|
||||
Entry::Vacant(vacant) => {
|
||||
// this unwrap will never panic, because we insert correct type right now
|
||||
#[expect(clippy::unwrap_used)]
|
||||
vacant
|
||||
.insert(Element::new_persisted(insert_with()))
|
||||
.get_mut_persisted()
|
||||
.unwrap()
|
||||
}
|
||||
Entry::Occupied(occupied) => occupied
|
||||
.into_mut()
|
||||
.get_persisted_mut_or_insert_with(insert_with),
|
||||
@@ -466,7 +484,7 @@ impl IdTypeMap {
|
||||
|
||||
/// For tests
|
||||
#[cfg(feature = "persistence")]
|
||||
#[allow(unused, clippy::allow_attributes)]
|
||||
#[allow(clippy::allow_attributes, unused)]
|
||||
fn get_generation<T: SerializableAny>(&self, id: Id) -> Option<usize> {
|
||||
let element = self.map.get(&hash(TypeId::of::<T>(), id))?;
|
||||
match element {
|
||||
@@ -706,7 +724,7 @@ fn test_two_id_two_type() {
|
||||
|
||||
#[test]
|
||||
fn test_two_id_x_two_types() {
|
||||
#![allow(clippy::approx_constant)]
|
||||
#![expect(clippy::approx_constant)]
|
||||
|
||||
let a = Id::new("a");
|
||||
let b = Id::new("b");
|
||||
|
||||
@@ -135,6 +135,7 @@ where
|
||||
self.flux = None;
|
||||
|
||||
if self.undos.back() == Some(current_state) {
|
||||
#[expect(clippy::unwrap_used)] // we just checked that undos is not empty
|
||||
self.redos.push(self.undos.pop_back().unwrap());
|
||||
} else {
|
||||
self.redos.push(current_state.clone());
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
//! In short: immediate viewports are simpler to use, but can waste a lot of CPU time.
|
||||
//!
|
||||
//! ### Embedded viewports
|
||||
//! These are not real, independent viewports, but is a fallback mode for when the integration does not support real viewports. In your callback is called with [`ViewportClass::Embedded`] it means you need to create a [`crate::Window`] to wrap your ui in, which will then be embedded in the parent viewport, unable to escape it.
|
||||
//! These are not real, independent viewports, but is a fallback mode for when the integration does not support real viewports.
|
||||
//! In your callback is called with [`ViewportClass::EmbeddedWindow`] it means the viewport is embedded inside of
|
||||
//! a regular [`crate::Window`], trapped in the parent viewport.
|
||||
//!
|
||||
//!
|
||||
//! ## Using the viewports
|
||||
@@ -71,7 +73,7 @@ use std::sync::Arc;
|
||||
|
||||
use epaint::{Pos2, Vec2};
|
||||
|
||||
use crate::{Context, Id};
|
||||
use crate::{Context, Id, Ui};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -101,7 +103,10 @@ pub enum ViewportClass {
|
||||
|
||||
/// The fallback, when the egui integration doesn't support viewports,
|
||||
/// or [`crate::Context::embed_viewports`] is set to `true`.
|
||||
Embedded,
|
||||
///
|
||||
/// If you get this, it is because you are already wrapped in a [`crate::Window`]
|
||||
/// inside of the parent viewport.
|
||||
EmbeddedWindow,
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -258,7 +263,7 @@ impl ViewportIdPair {
|
||||
}
|
||||
|
||||
/// The user-code that shows the ui in the viewport, used for deferred viewports.
|
||||
pub type DeferredViewportUiCallback = dyn Fn(&Context) + Sync + Send;
|
||||
pub type DeferredViewportUiCallback = dyn Fn(&mut Ui) + Sync + Send;
|
||||
|
||||
/// Render the given viewport, calling the given ui callback.
|
||||
pub type ImmediateViewportRendererCallback = dyn for<'a> Fn(&Context, ImmediateViewport<'a>);
|
||||
@@ -782,7 +787,7 @@ impl ViewportBuilder {
|
||||
};
|
||||
|
||||
if is_new {
|
||||
commands.push(ViewportCommand::Icon(Some(new_icon.clone())));
|
||||
commands.push(ViewportCommand::Icon(Some(Arc::clone(&new_icon))));
|
||||
self.icon = Some(new_icon);
|
||||
}
|
||||
}
|
||||
@@ -1189,7 +1194,7 @@ pub struct ViewportOutput {
|
||||
|
||||
/// What type of viewport are we?
|
||||
///
|
||||
/// This will never be [`ViewportClass::Embedded`],
|
||||
/// This will never be [`ViewportClass::EmbeddedWindow`],
|
||||
/// since those don't result in real viewports.
|
||||
pub class: ViewportClass,
|
||||
|
||||
@@ -1246,5 +1251,5 @@ pub struct ImmediateViewport<'a> {
|
||||
pub builder: ViewportBuilder,
|
||||
|
||||
/// The user-code that shows the GUI.
|
||||
pub viewport_ui_cb: Box<dyn FnMut(&Context) + 'a>,
|
||||
pub viewport_ui_cb: Box<dyn FnMut(&mut Ui) + 'a>,
|
||||
}
|
||||
|
||||
@@ -63,6 +63,21 @@ impl WidgetRect {
|
||||
}
|
||||
}
|
||||
|
||||
/// How to handle multiple calls to [`crate::Response::interact`] and [`crate::Ui::interact_opt`].
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct InteractOptions {
|
||||
/// If we call interact on the same widget multiple times,
|
||||
/// should we move it to the top on subsequent calls?
|
||||
pub move_to_top: bool,
|
||||
}
|
||||
|
||||
#[expect(clippy::derivable_impls)] // Nice to be explicit
|
||||
impl Default for InteractOptions {
|
||||
fn default() -> Self {
|
||||
Self { move_to_top: false }
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the [`WidgetRect`]s of all widgets generated during a single egui update/frame.
|
||||
///
|
||||
/// All [`crate::Ui`]s have a [`WidgetRect`]. It is created in [`crate::Ui::new`] with [`Rect::NOTHING`]
|
||||
@@ -140,13 +155,15 @@ impl WidgetRects {
|
||||
}
|
||||
|
||||
/// Insert the given widget rect in the given layer.
|
||||
pub fn insert(&mut self, layer_id: LayerId, widget_rect: WidgetRect) {
|
||||
pub fn insert(&mut self, layer_id: LayerId, widget_rect: WidgetRect, options: InteractOptions) {
|
||||
let Self {
|
||||
by_layer,
|
||||
by_id,
|
||||
infos: _,
|
||||
} = self;
|
||||
|
||||
let InteractOptions { move_to_top } = options;
|
||||
|
||||
let layer_widgets = by_layer.entry(layer_id).or_default();
|
||||
|
||||
match by_id.entry(widget_rect.id) {
|
||||
@@ -161,14 +178,6 @@ impl WidgetRects {
|
||||
// e.g. calling `response.interact(…)` to add more interaction.
|
||||
let (idx_in_layer, existing) = entry.get_mut();
|
||||
|
||||
debug_assert!(
|
||||
existing.layer_id == widget_rect.layer_id,
|
||||
"Widget {:?} changed layer_id during the frame from {:?} to {:?}",
|
||||
widget_rect.id,
|
||||
existing.layer_id,
|
||||
widget_rect.layer_id
|
||||
);
|
||||
|
||||
// Update it:
|
||||
existing.rect = widget_rect.rect; // last wins
|
||||
existing.interact_rect = widget_rect.interact_rect; // last wins
|
||||
@@ -176,7 +185,18 @@ impl WidgetRects {
|
||||
existing.enabled |= widget_rect.enabled;
|
||||
|
||||
if existing.layer_id == widget_rect.layer_id {
|
||||
layer_widgets[*idx_in_layer] = *existing;
|
||||
if move_to_top {
|
||||
layer_widgets.remove(*idx_in_layer);
|
||||
*idx_in_layer = layer_widgets.len();
|
||||
layer_widgets.push(*existing);
|
||||
} else {
|
||||
layer_widgets[*idx_in_layer] = *existing;
|
||||
}
|
||||
} else if cfg!(debug_assertions) {
|
||||
panic!(
|
||||
"DEBUG ASSERT: Widget {:?} changed layer_id during the frame from {:?} to {:?}",
|
||||
widget_rect.id, existing.layer_id, widget_rect.layer_id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -709,7 +709,7 @@ impl WidgetText {
|
||||
default_valign,
|
||||
)),
|
||||
Self::LayoutJob(job) => job,
|
||||
Self::Galley(galley) => galley.job.clone(),
|
||||
Self::Galley(galley) => Arc::clone(&galley.job),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,6 @@ impl Widget for Checkbox<'_> {
|
||||
text_style,
|
||||
} = ui.style().checkbox_style(state);
|
||||
|
||||
// interact_size or size ?
|
||||
let mut min_size = Vec2::splat(ui.spacing().interact_size.y);
|
||||
min_size.y = min_size.y.at_least(checkbox_size);
|
||||
|
||||
@@ -121,9 +120,8 @@ impl Widget for Checkbox<'_> {
|
||||
);
|
||||
let small_icon_rect =
|
||||
Rect::from_center_size(big_icon_rect.center(), Vec2::splat(check_size));
|
||||
|
||||
ui.painter().add(epaint::RectShape::new(
|
||||
big_icon_rect,
|
||||
big_icon_rect.expand(checkbox_frame.inner_margin.left.into()),
|
||||
checkbox_frame.corner_radius,
|
||||
checkbox_frame.fill,
|
||||
checkbox_frame.stroke,
|
||||
|
||||
@@ -114,7 +114,7 @@ fn color_button(ui: &mut Ui, color: Color32, open: bool) -> Response {
|
||||
}
|
||||
|
||||
fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Color32) -> Response {
|
||||
#![allow(clippy::identity_op)]
|
||||
#![expect(clippy::identity_op)]
|
||||
|
||||
let desired_size = vec2(ui.spacing().slider_width, ui.spacing().interact_size.y);
|
||||
let (rect, response) = ui.allocate_at_least(desired_size, Sense::click_and_drag());
|
||||
@@ -378,7 +378,7 @@ fn color_picker_hsvag_2d(ui: &mut Ui, hsvag: &mut HsvaGamma, alpha: Alpha) {
|
||||
}
|
||||
|
||||
fn input_type_button_ui(ui: &mut Ui) {
|
||||
let mut input_type = ui.ctx().style().visuals.numeric_color_space;
|
||||
let mut input_type = ui.global_style().visuals.numeric_color_space;
|
||||
if input_type.toggle_button_ui(ui).changed() {
|
||||
ui.ctx().all_styles_mut(|s| {
|
||||
s.visuals.numeric_color_space = input_type;
|
||||
@@ -402,9 +402,9 @@ fn srgba_edit_ui(ui: &mut Ui, [r, g, b, a]: &mut [u8; 4], alpha: Alpha) -> bool
|
||||
.clicked()
|
||||
{
|
||||
if alpha == Alpha::Opaque {
|
||||
ui.ctx().copy_text(format!("{r}, {g}, {b}"));
|
||||
ui.copy_text(format!("{r}, {g}, {b}"));
|
||||
} else {
|
||||
ui.ctx().copy_text(format!("{r}, {g}, {b}, {a}"));
|
||||
ui.copy_text(format!("{r}, {g}, {b}, {a}"));
|
||||
}
|
||||
}
|
||||
edited |= DragValue::new(r).speed(0.5).prefix("R ").ui(ui).changed();
|
||||
@@ -443,10 +443,9 @@ fn rgba_edit_ui(ui: &mut Ui, [r, g, b, a]: &mut [f32; 4], alpha: Alpha) -> bool
|
||||
.clicked()
|
||||
{
|
||||
if alpha == Alpha::Opaque {
|
||||
ui.ctx().copy_text(format!("{r:.03}, {g:.03}, {b:.03}"));
|
||||
ui.copy_text(format!("{r:.03}, {g:.03}, {b:.03}"));
|
||||
} else {
|
||||
ui.ctx()
|
||||
.copy_text(format!("{r:.03}, {g:.03}, {b:.03}, {a:.03}"));
|
||||
ui.copy_text(format!("{r:.03}, {g:.03}, {b:.03}, {a:.03}"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![allow(clippy::needless_pass_by_value)] // False positives with `impl ToString`
|
||||
#![expect(clippy::needless_pass_by_value)] // False positives with `impl ToString`
|
||||
|
||||
use std::{cmp::Ordering, ops::RangeInclusive};
|
||||
|
||||
@@ -624,7 +624,7 @@ impl Widget for DragValue<'_> {
|
||||
ui.memory_mut(|mem| mem.request_focus(id));
|
||||
select_all_text(ui, id, response.id, &value_text);
|
||||
} else if response.dragged() {
|
||||
ui.ctx().set_cursor_icon(cursor_icon);
|
||||
ui.set_cursor_icon(cursor_icon);
|
||||
|
||||
let mdelta = response.drag_delta();
|
||||
let delta_points = mdelta.x - mdelta.y; // Increase to the right and up
|
||||
|
||||
@@ -65,7 +65,7 @@ impl Widget for Link {
|
||||
}
|
||||
|
||||
if response.hovered() {
|
||||
ui.ctx().set_cursor_icon(CursorIcon::PointingHand);
|
||||
ui.set_cursor_icon(CursorIcon::PointingHand);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,12 +130,12 @@ impl Widget for Hyperlink {
|
||||
let response = ui.add(Link::new(text));
|
||||
|
||||
if response.clicked_with_open_in_background() {
|
||||
ui.ctx().open_url(crate::OpenUrl {
|
||||
ui.open_url(crate::OpenUrl {
|
||||
url: url.clone(),
|
||||
new_tab: true,
|
||||
});
|
||||
} else if response.clicked() {
|
||||
ui.ctx().open_url(crate::OpenUrl {
|
||||
ui.open_url(crate::OpenUrl {
|
||||
url: url.clone(),
|
||||
new_tab,
|
||||
});
|
||||
|
||||
@@ -681,7 +681,7 @@ pub fn paint_texture_load_result(
|
||||
}
|
||||
Ok(TexturePoll::Pending { .. }) => {
|
||||
let show_loading_spinner =
|
||||
show_loading_spinner.unwrap_or(ui.visuals().image_loading_spinners);
|
||||
show_loading_spinner.unwrap_or_else(|| ui.visuals().image_loading_spinners);
|
||||
if show_loading_spinner {
|
||||
Spinner::new().paint_at(ui, rect);
|
||||
}
|
||||
|
||||
@@ -248,7 +248,9 @@ impl Label {
|
||||
layout_job.halign = Align::LEFT;
|
||||
layout_job.justify = false;
|
||||
} else {
|
||||
layout_job.halign = self.halign.unwrap_or(ui.layout().horizontal_placement());
|
||||
layout_job.halign = self
|
||||
.halign
|
||||
.unwrap_or_else(|| ui.layout().horizontal_placement());
|
||||
layout_job.justify = ui.layout().horizontal_justify();
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ pub fn global_theme_preference_switch(ui: &mut Ui) {
|
||||
|
||||
/// Show larger buttons for switching between light and dark mode (globally).
|
||||
pub fn global_theme_preference_buttons(ui: &mut Ui) {
|
||||
let mut theme_preference = ui.ctx().options(|opt| opt.theme_preference);
|
||||
let mut theme_preference = ui.options(|opt| opt.theme_preference);
|
||||
theme_preference.radio_buttons(ui);
|
||||
ui.ctx().set_theme(theme_preference);
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ impl Widget for ProgressBar {
|
||||
|
||||
let desired_width =
|
||||
desired_width.unwrap_or_else(|| ui.available_size_before_wrap().x.at_least(96.0));
|
||||
let height = desired_height.unwrap_or(ui.spacing().interact_size.y);
|
||||
let height = desired_height.unwrap_or_else(|| ui.spacing().interact_size.y);
|
||||
let (outer_rect, response) =
|
||||
ui.allocate_exact_size(vec2(desired_width, height), Sense::hover());
|
||||
|
||||
@@ -135,7 +135,7 @@ impl Widget for ProgressBar {
|
||||
|
||||
if ui.is_rect_visible(response.rect) {
|
||||
if animate {
|
||||
ui.ctx().request_repaint();
|
||||
ui.request_repaint();
|
||||
}
|
||||
|
||||
let visuals = ui.style().visuals.clone();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![allow(clippy::needless_pass_by_value)] // False positives with `impl ToString`
|
||||
#![expect(clippy::needless_pass_by_value)] // False positives with `impl ToString`
|
||||
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
@@ -687,7 +687,7 @@ impl Slider<'_> {
|
||||
let mut increment = 0usize;
|
||||
|
||||
if response.has_focus() {
|
||||
ui.ctx().memory_mut(|m| {
|
||||
ui.memory_mut(|m| {
|
||||
m.set_focus_lock_filter(
|
||||
response.id,
|
||||
EventFilter {
|
||||
|
||||
@@ -37,7 +37,7 @@ impl Spinner {
|
||||
/// Paint the spinner in the given rectangle.
|
||||
pub fn paint_at(&self, ui: &Ui, rect: Rect) {
|
||||
if ui.is_rect_visible(rect) {
|
||||
ui.ctx().request_repaint(); // because it is animated
|
||||
ui.request_repaint(); // because it is animated
|
||||
|
||||
let color = self
|
||||
.color
|
||||
|
||||
@@ -256,7 +256,7 @@ impl<'t> TextEdit<'t> {
|
||||
/// so it is strongly suggested that you cache the results of any syntax highlighter
|
||||
/// so as not to waste CPU highlighting the same string every frame.
|
||||
///
|
||||
/// The arguments is the enclosing [`Ui`] (so you can access e.g. [`Ui::fonts`]),
|
||||
/// The arguments is the enclosing [`Ui`] (so you can access e.g. [`Context::fonts`]),
|
||||
/// the text and the wrap width.
|
||||
///
|
||||
/// ```
|
||||
@@ -496,7 +496,7 @@ impl TextEdit<'_> {
|
||||
} = self;
|
||||
|
||||
let text_color = text_color
|
||||
.or(ui.visuals().override_text_color)
|
||||
.or_else(|| ui.visuals().override_text_color)
|
||||
// .unwrap_or_else(|| ui.style().interact(&response).text_color()); // too bright
|
||||
.unwrap_or_else(|| ui.visuals().widgets.inactive.text_color());
|
||||
|
||||
@@ -605,12 +605,12 @@ impl TextEdit<'_> {
|
||||
if did_interact || response.clicked() {
|
||||
ui.memory_mut(|mem| mem.request_focus(response.id));
|
||||
|
||||
state.last_interaction_time = ui.ctx().input(|i| i.time);
|
||||
state.last_interaction_time = ui.input(|i| i.time);
|
||||
}
|
||||
}
|
||||
|
||||
if interactive && response.hovered() {
|
||||
ui.ctx().set_cursor_icon(CursorIcon::Text);
|
||||
ui.set_cursor_icon(CursorIcon::Text);
|
||||
}
|
||||
|
||||
let mut cursor_range = None;
|
||||
@@ -691,7 +691,7 @@ impl TextEdit<'_> {
|
||||
if ui.is_rect_visible(rect) {
|
||||
if text.as_str().is_empty() && !hint_text.is_empty() {
|
||||
let hint_text_color = ui.visuals().weak_text_color();
|
||||
let hint_text_font_id = hint_text_font.unwrap_or(font_id.into());
|
||||
let hint_text_font_id = hint_text_font.unwrap_or_else(|| font_id.into());
|
||||
let galley = if multiline {
|
||||
hint_text.into_galley(
|
||||
ui,
|
||||
@@ -721,42 +721,45 @@ impl TextEdit<'_> {
|
||||
paint_text_selection(&mut galley, ui.visuals(), &cursor_range, None);
|
||||
}
|
||||
|
||||
if !clip_text {
|
||||
// Allocate additional space if edits were made this frame that changed the size. This is important so that,
|
||||
// if there's a ScrollArea, it can properly scroll to the cursor.
|
||||
// Condition `!clip_text` is important to avoid breaking layout for `TextEdit::singleline` (PR #5640)
|
||||
let extra_size = galley.size() - rect.size();
|
||||
if extra_size.x > 0.0 || extra_size.y > 0.0 {
|
||||
match ui.layout().main_dir() {
|
||||
crate::Direction::LeftToRight | crate::Direction::TopDown => {
|
||||
ui.allocate_rect(
|
||||
Rect::from_min_size(outer_rect.max, extra_size),
|
||||
Sense::hover(),
|
||||
);
|
||||
}
|
||||
crate::Direction::RightToLeft => {
|
||||
ui.allocate_rect(
|
||||
Rect::from_min_size(
|
||||
emath::pos2(outer_rect.min.x - extra_size.x, outer_rect.max.y),
|
||||
extra_size,
|
||||
),
|
||||
Sense::hover(),
|
||||
);
|
||||
}
|
||||
crate::Direction::BottomUp => {
|
||||
ui.allocate_rect(
|
||||
Rect::from_min_size(
|
||||
emath::pos2(outer_rect.min.x, outer_rect.max.y - extra_size.y),
|
||||
extra_size,
|
||||
),
|
||||
Sense::hover(),
|
||||
);
|
||||
}
|
||||
// Allocate additional space if edits were made this frame that changed the size. This is important so that,
|
||||
// if there's a ScrollArea, it can properly scroll to the cursor.
|
||||
// Condition `!clip_text` is important to avoid breaking layout for `TextEdit::singleline` (PR #5640)
|
||||
if !clip_text
|
||||
&& let extra_size = galley.size() - rect.size()
|
||||
&& (extra_size.x > 0.0 || extra_size.y > 0.0)
|
||||
{
|
||||
match ui.layout().main_dir() {
|
||||
crate::Direction::LeftToRight | crate::Direction::TopDown => {
|
||||
ui.allocate_rect(
|
||||
Rect::from_min_size(outer_rect.max, extra_size),
|
||||
Sense::hover(),
|
||||
);
|
||||
}
|
||||
crate::Direction::RightToLeft => {
|
||||
ui.allocate_rect(
|
||||
Rect::from_min_size(
|
||||
emath::pos2(outer_rect.min.x - extra_size.x, outer_rect.max.y),
|
||||
extra_size,
|
||||
),
|
||||
Sense::hover(),
|
||||
);
|
||||
}
|
||||
crate::Direction::BottomUp => {
|
||||
ui.allocate_rect(
|
||||
Rect::from_min_size(
|
||||
emath::pos2(outer_rect.min.x, outer_rect.max.y - extra_size.y),
|
||||
extra_size,
|
||||
),
|
||||
Sense::hover(),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Avoid an ID shift during this pass if the textedit grow
|
||||
ui.skip_ahead_auto_ids(1);
|
||||
}
|
||||
|
||||
painter.galley(galley_pos, galley.clone(), text_color);
|
||||
painter.galley(galley_pos, Arc::clone(&galley), text_color);
|
||||
|
||||
if has_focus && let Some(cursor_range) = state.cursor.range(&galley) {
|
||||
let primary_cursor_rect = cursor_rect(&galley, &cursor_range.primary, row_height)
|
||||
@@ -768,7 +771,7 @@ impl TextEdit<'_> {
|
||||
}
|
||||
|
||||
if text.is_mutable() && interactive {
|
||||
let now = ui.ctx().input(|i| i.time);
|
||||
let now = ui.input(|i| i.time);
|
||||
if response.changed() || selection_changed {
|
||||
state.last_interaction_time = now;
|
||||
}
|
||||
@@ -777,7 +780,7 @@ impl TextEdit<'_> {
|
||||
// This is for two reasons:
|
||||
// * Don't give the impression that the user can type into a window without focus
|
||||
// * Don't repaint the ui because of a blinking cursor in an app that is not in focus
|
||||
let viewport_has_focus = ui.ctx().input(|i| i.focused);
|
||||
let viewport_has_focus = ui.input(|i| i.focused);
|
||||
if viewport_has_focus {
|
||||
text_selection::visuals::paint_text_cursor(
|
||||
ui,
|
||||
@@ -824,8 +827,7 @@ impl TextEdit<'_> {
|
||||
hint_text_str.as_str(),
|
||||
)
|
||||
});
|
||||
} else if selection_changed {
|
||||
let cursor_range = cursor_range.unwrap();
|
||||
} else if selection_changed && let Some(cursor_range) = cursor_range {
|
||||
let char_range = cursor_range.primary.index..=cursor_range.secondary.index;
|
||||
let info = WidgetInfo::text_selection_changed(
|
||||
ui.is_enabled(),
|
||||
@@ -922,7 +924,7 @@ fn events(
|
||||
|
||||
let copy_if_not_password = |ui: &Ui, text: String| {
|
||||
if !password {
|
||||
ui.ctx().copy_text(text);
|
||||
ui.copy_text(text);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ use accesskit_consumer::{FilterResult, Node, Tree, TreeChangeHandler};
|
||||
|
||||
use eframe::epaint::text::TextWrapMode;
|
||||
use egui::{
|
||||
Button, Color32, Context, Event, Frame, FullOutput, Id, Key, KeyboardShortcut, Label,
|
||||
Modifiers, Panel, RawInput, RichText, ScrollArea, Ui, collapsing_header::CollapsingState,
|
||||
Button, Color32, Event, Frame, FullOutput, Id, Key, KeyboardShortcut, Label, Modifiers, Panel,
|
||||
RawInput, RichText, ScrollArea, Ui, collapsing_header::CollapsingState,
|
||||
};
|
||||
|
||||
/// This [`egui::Plugin`] adds an inspector Panel.
|
||||
/// This [`egui::Plugin`] adds an inspector panel.
|
||||
///
|
||||
/// It can be opened with the `(Cmd/Ctrl)+Alt+I`. It shows the current AccessKit tree and details
|
||||
/// for the selected node.
|
||||
@@ -71,8 +71,8 @@ impl egui::Plugin for AccessibilityInspectorPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_begin_pass(&mut self, ctx: &Context) {
|
||||
if ctx.input_mut(|i| {
|
||||
fn on_begin_pass(&mut self, ui: &mut Ui) {
|
||||
if ui.input_mut(|i| {
|
||||
i.consume_shortcut(&KeyboardShortcut::new(
|
||||
Modifiers::COMMAND | Modifiers::ALT,
|
||||
Key::I,
|
||||
@@ -85,9 +85,9 @@ impl egui::Plugin for AccessibilityInspectorPlugin {
|
||||
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");
|
||||
if let Some(selected_node) = self.selected_node {
|
||||
Panel::bottom(Self::id().with("details_panel"))
|
||||
@@ -122,7 +122,7 @@ impl AccessibilityInspectorPlugin {
|
||||
let node_response = ui.ctx().read_response(selected_node);
|
||||
|
||||
if let Some(widget_response) = node_response {
|
||||
ui.ctx().debug_painter().debug_rect(
|
||||
ui.debug_painter().debug_rect(
|
||||
widget_response.rect,
|
||||
ui.style_mut().visuals.selection.bg_fill,
|
||||
"",
|
||||
@@ -199,8 +199,8 @@ impl AccessibilityInspectorPlugin {
|
||||
}
|
||||
let label = node
|
||||
.label()
|
||||
.or(node.value())
|
||||
.unwrap_or(node.id().0.to_string());
|
||||
.or_else(|| node.value())
|
||||
.unwrap_or_else(|| node.id().0.to_string());
|
||||
let label = format!("({:?}) {}", node.role(), label);
|
||||
|
||||
// Safety: This is safe since the `accesskit::NodeId` was created from an `egui::Id`.
|
||||
@@ -233,8 +233,7 @@ impl AccessibilityInspectorPlugin {
|
||||
let widget_response = ui.ctx().read_response(egui_node_id);
|
||||
|
||||
if let Some(widget_response) = widget_response {
|
||||
ui.ctx()
|
||||
.debug_painter()
|
||||
ui.debug_painter()
|
||||
.debug_rect(widget_response.rect, Color32::RED, "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![allow(clippy::undocumented_unsafe_blocks)]
|
||||
#![expect(clippy::undocumented_unsafe_blocks)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -22,26 +22,27 @@ impl Custom3d {
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for Custom3d {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::ScrollArea::both()
|
||||
.auto_shrink(false)
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("The triangle is being painted using ");
|
||||
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
|
||||
ui.label(" (OpenGL).");
|
||||
});
|
||||
ui.label("It's not a very impressive demo, but it shows you can embed 3D inside of egui.");
|
||||
|
||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
self.custom_painting(ui);
|
||||
});
|
||||
ui.label("Drag to rotate!");
|
||||
ui.add(egui_demo_lib::egui_github_link_file!());
|
||||
impl crate::DemoApp for Custom3d {
|
||||
fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
// TODO(emilk): Use `ScrollArea::inner_margin`
|
||||
egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||
egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("The triangle is being painted using ");
|
||||
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
|
||||
ui.label(" (OpenGL).");
|
||||
});
|
||||
ui.label(
|
||||
"It's not a very impressive demo, but it shows you can embed 3D inside of egui.",
|
||||
);
|
||||
|
||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
self.custom_painting(ui);
|
||||
});
|
||||
ui.label("Drag to rotate!");
|
||||
ui.add(egui_demo_lib::egui_github_link_file!());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![expect(clippy::unwrap_used)] // TODO(emilk): avoid unwraps
|
||||
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
use eframe::{
|
||||
@@ -98,26 +100,27 @@ impl Custom3d {
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for Custom3d {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::ScrollArea::both()
|
||||
.auto_shrink(false)
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("The triangle is being painted using ");
|
||||
ui.hyperlink_to("WGPU", "https://wgpu.rs");
|
||||
ui.label(" (Portable Rust graphics API awesomeness)");
|
||||
});
|
||||
ui.label("It's not a very impressive demo, but it shows you can embed 3D inside of egui.");
|
||||
|
||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
self.custom_painting(ui);
|
||||
});
|
||||
ui.label("Drag to rotate!");
|
||||
ui.add(egui_demo_lib::egui_github_link_file!());
|
||||
impl crate::DemoApp for Custom3d {
|
||||
fn demo_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
// TODO(emilk): Use `ScrollArea::inner_margin`
|
||||
egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||
egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("The triangle is being painted using ");
|
||||
ui.hyperlink_to("WGPU", "https://wgpu.rs");
|
||||
ui.label(" (Portable Rust graphics API awesomeness)");
|
||||
});
|
||||
ui.label(
|
||||
"It's not a very impressive demo, but it shows you can embed 3D inside of egui.",
|
||||
);
|
||||
|
||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
self.custom_painting(ui);
|
||||
});
|
||||
ui.label("Drag to rotate!");
|
||||
ui.add(egui_demo_lib::egui_github_link_file!());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ impl FractalClock {
|
||||
pub fn ui(&mut self, ui: &mut Ui, seconds_since_midnight: Option<f64>) {
|
||||
if !self.paused {
|
||||
self.time = seconds_since_midnight.unwrap_or_else(|| ui.input(|i| i.time));
|
||||
ui.ctx().request_repaint();
|
||||
ui.request_repaint();
|
||||
}
|
||||
|
||||
let painter = Painter::new(
|
||||
|
||||
@@ -59,16 +59,16 @@ impl Default for HttpApp {
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for HttpApp {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::Panel::bottom("http_bottom").show(ctx, |ui| {
|
||||
impl crate::DemoApp for HttpApp {
|
||||
fn demo_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
egui::Panel::bottom("http_bottom").show_inside(ui, |ui| {
|
||||
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
||||
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {
|
||||
ui.add(egui_demo_lib::egui_github_link_file!())
|
||||
})
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||
let prev_url = self.url.clone();
|
||||
let trigger_fetch = ui_url(ui, frame, &mut self.url);
|
||||
|
||||
@@ -80,7 +80,7 @@ impl eframe::App for HttpApp {
|
||||
});
|
||||
|
||||
if trigger_fetch {
|
||||
let ctx = ctx.clone();
|
||||
let ctx = ui.ctx().clone();
|
||||
let (sender, promise) = Promise::new();
|
||||
let request = ehttp::Request::get(&self.url);
|
||||
ehttp::fetch(request, move |response| {
|
||||
@@ -195,7 +195,7 @@ fn ui_resource(ui: &mut egui::Ui, resource: &Resource) {
|
||||
if let Some(text) = &text {
|
||||
let tooltip = "Click to copy the response body";
|
||||
if ui.button("📋").on_hover_text(tooltip).clicked() {
|
||||
ui.ctx().copy_text(text.clone());
|
||||
ui.copy_text(text.clone());
|
||||
}
|
||||
ui.separator();
|
||||
}
|
||||
@@ -222,10 +222,10 @@ fn syntax_highlighting(
|
||||
) -> Option<ColoredText> {
|
||||
let extension_and_rest: Vec<&str> = response.url.rsplitn(2, '.').collect();
|
||||
let extension = extension_and_rest.first()?;
|
||||
let theme = egui_extras::syntax_highlighting::CodeTheme::from_style(&ctx.style());
|
||||
let theme = egui_extras::syntax_highlighting::CodeTheme::from_style(&ctx.global_style());
|
||||
Some(ColoredText(egui_extras::syntax_highlighting::highlight(
|
||||
ctx,
|
||||
&ctx.style(),
|
||||
&ctx.global_style(),
|
||||
&theme,
|
||||
text,
|
||||
extension,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user