├── .github ├── dependabot.yml └── workflows │ ├── check.yml │ └── deploy_examples.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── icon.png └── icon_inverted.png ├── bevy_egui.png ├── examples ├── absorb_input.rs ├── color_test.rs ├── file_browse.rs ├── paint_callback.rs ├── paint_callback.wgsl ├── render_egui_to_image.rs ├── render_to_image_widget.rs ├── run_manually.rs ├── side_panel_2d.rs ├── side_panel_3d.rs ├── simple.rs ├── two_windows.rs └── ui.rs └── src ├── egui.wgsl ├── egui_node.rs ├── helpers.rs ├── input.rs ├── lib.rs ├── output.rs ├── render_systems.rs ├── text_agent.rs └── web_clipboard.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | branches: [main] 6 | 7 | name: CI 8 | 9 | jobs: 10 | fmt: 11 | name: Formatter check 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: dtolnay/rust-toolchain@master 16 | with: 17 | toolchain: nightly 18 | components: rustfmt 19 | - run: cargo fmt --all -- --check 20 | 21 | clippy: 22 | name: Clippy check 23 | runs-on: ${{ matrix.os }} 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | os: [ubuntu-latest, macos-14, windows-latest] 28 | features: [ 29 | "", 30 | "immutable_ctx", 31 | "manage_clipboard", 32 | "open_url", 33 | "render", 34 | "manage_clipboard,open_url,render", 35 | ] 36 | steps: 37 | - uses: actions/checkout@v3 38 | - uses: dtolnay/rust-toolchain@master 39 | with: 40 | toolchain: stable 41 | components: clippy 42 | - uses: Swatinem/rust-cache@v2 43 | - name: Install dependencies 44 | if: runner.os == 'Linux' 45 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libglib2.0-dev libgtk-3-dev 46 | - run: cargo clippy --no-default-features --all-targets --features=${{ matrix.features }} -- -D warnings 47 | 48 | clippy_wasm32: 49 | name: Clippy check (wasm32) 50 | runs-on: ubuntu-latest 51 | strategy: 52 | fail-fast: false 53 | matrix: 54 | features: 55 | [ 56 | "", 57 | "immutable_ctx", 58 | "manage_clipboard", 59 | "open_url", 60 | "render", 61 | "manage_clipboard,open_url,render", 62 | ] 63 | steps: 64 | - uses: actions/checkout@v3 65 | - uses: dtolnay/rust-toolchain@master 66 | with: 67 | toolchain: stable 68 | components: clippy 69 | targets: wasm32-unknown-unknown 70 | - uses: Swatinem/rust-cache@v2 71 | - run: cargo clippy --no-default-features --target=wasm32-unknown-unknown --all-targets --features=${{ matrix.features }} -- -D warnings 72 | 73 | clippy_android: 74 | name: Clippy check (android) 75 | runs-on: ubuntu-latest 76 | strategy: 77 | fail-fast: false 78 | matrix: 79 | features: 80 | [ 81 | "", 82 | "immutable_ctx", 83 | "manage_clipboard", 84 | "open_url", 85 | "render", 86 | "manage_clipboard,open_url,render", 87 | ] 88 | steps: 89 | - uses: actions/checkout@v3 90 | - uses: dtolnay/rust-toolchain@master 91 | with: 92 | toolchain: stable 93 | components: clippy 94 | targets: aarch64-linux-android 95 | - uses: Swatinem/rust-cache@v2 96 | - run: | 97 | AR=$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar \ 98 | CC=$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android34-clang \ 99 | CXX=$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android34-clang++ \ 100 | cargo clippy --target=aarch64-linux-android --no-default-features --all-targets --features=${{ matrix.features }} -- -D warnings 101 | 102 | clippy_ios: 103 | name: Clippy check (ios) 104 | runs-on: macos-latest 105 | strategy: 106 | fail-fast: false 107 | matrix: 108 | features: 109 | [ 110 | "", 111 | "immutable_ctx", 112 | "manage_clipboard", 113 | "open_url", 114 | "render", 115 | "manage_clipboard,open_url,render", 116 | ] 117 | steps: 118 | - uses: actions/checkout@v3 119 | - uses: dtolnay/rust-toolchain@master 120 | with: 121 | toolchain: stable 122 | components: clippy 123 | targets: aarch64-apple-ios 124 | - uses: Swatinem/rust-cache@v2 125 | - run: cargo clippy --target=aarch64-apple-ios --no-default-features --all-targets --features=${{ matrix.features }} -- -D warnings 126 | 127 | doc: 128 | name: Check documentation 129 | runs-on: ubuntu-latest 130 | steps: 131 | - uses: actions/checkout@v3 132 | - uses: dtolnay/rust-toolchain@master 133 | with: 134 | toolchain: stable 135 | - uses: Swatinem/rust-cache@v2 136 | - run: cargo doc --all --features "bevy_winit/x11" 137 | env: 138 | RUSTDOCFLAGS: -D warnings 139 | 140 | test: 141 | name: Run tests 142 | runs-on: ubuntu-latest 143 | steps: 144 | - uses: actions/checkout@v3 145 | - uses: dtolnay/rust-toolchain@master 146 | with: 147 | toolchain: stable 148 | - uses: Swatinem/rust-cache@v2 149 | - name: Install dependencies 150 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libglib2.0-dev libgtk-3-dev 151 | - run: cargo test --all 152 | -------------------------------------------------------------------------------- /.github/workflows/deploy_examples.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | name: Build and deploy examples 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Reset to the latest tag 17 | run: git reset --hard $(git describe --tags --abbrev=0) 18 | 19 | - uses: dtolnay/rust-toolchain@master 20 | with: 21 | toolchain: stable 22 | targets: wasm32-unknown-unknown 23 | 24 | - uses: Swatinem/rust-cache@v2 25 | with: 26 | cache-on-failure: true 27 | 28 | - name: Install dependencies 29 | run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev 30 | 31 | - name: Install bevy_cli 32 | run: cargo install --git https://github.com/TheBevyFlock/bevy_cli bevy_cli --force 33 | 34 | - name: Build absorb_input 35 | # The first run may prompt to install dependencies, hence the `--yes` flag. 36 | run: bevy build --yes --example absorb_input web --bundle 37 | - name: Build color_test 38 | run: bevy build --example color_test web --bundle 39 | - name: Build paint_callback 40 | run: bevy build --example paint_callback web --bundle 41 | - name: Build render_egui_to_image 42 | run: bevy build --example render_egui_to_image --features="render bevy/bevy_gizmos" web --bundle 43 | - name: Build render_to_image_widget 44 | run: bevy build --example render_to_image_widget web --bundle 45 | - name: Build side_panel_2d 46 | run: bevy build --example side_panel_2d web --bundle 47 | - name: Build side_panel_3d 48 | run: bevy build --example side_panel_3d web --bundle 49 | - name: Build simple 50 | run: bevy build --example simple web --bundle 51 | - name: Build run_manually 52 | run: bevy build --example run_manually web --bundle 53 | # We skip the two_windows example as it's not supported in WASM 54 | - name: Build ui 55 | run: bevy build --example ui web --bundle 56 | 57 | - name: Upload the artifacts 58 | uses: actions/upload-pages-artifact@v3 59 | with: 60 | path: 'target/bevy_web/web' 61 | 62 | deploy: 63 | needs: [ build ] 64 | permissions: 65 | pages: write 66 | id-token: write 67 | environment: 68 | name: github-pages 69 | url: ${{steps.deployment.outputs.page_url}} 70 | runs-on: ubuntu-latest 71 | steps: 72 | - name: Deploy to GitHub Pages 73 | id: deployment 74 | uses: actions/deploy-pages@v4 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust 2 | /target 3 | Cargo.lock 4 | 5 | # JetBrains 6 | .idea 7 | 8 | # MacOS 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity="Crate" 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.34.1] - 26-Apr-2025 9 | 10 | ### Fixed 11 | 12 | - Fix panic if `bevy_egui/picking` is enabled, but `bevy/bevy_picking` is not (or `PickingPlugin` is not added). 13 | 14 | ## [0.34.0] - 25-Apr-2025 15 | 16 | ### Added 17 | 18 | - Implement multi-pass support ([#372](https://github.com/vladbat00/bevy_egui/pull/372)). 19 | - **Breaking change:** this adds a new `enable_multipass_for_primary_context` field to `EguiPlugin`, see its documentation for the migration guide. 20 | - Implement absorbing inputs and Egui input run conditions ([#369](https://github.com/vladbat00/bevy_egui/pull/369), [#373](https://github.com/vladbat00/bevy_egui/pull/373)). 21 | - (Disabled until the next Egui release) Integrate AccessKit ([#238](https://github.com/vladbat00/bevy_egui/pull/238) by @ndarilek). 22 | 23 | ### Changed 24 | 25 | - Update Bevy to 0.16 ([#367](https://github.com/vladbat00/bevy_egui/pull/367) by @Friz64). 26 | - Feature-gate `bevy_picking` support behind the `picking` feature ([#363](https://github.com/vladbat00/bevy_egui/pull/363) by @jakkos-net). 27 | - Updated the minimum required version of wasm-binggen to `0.2.93` to match the actual version needed to compile `bevy_egui`. 28 | 29 | ### Fixed 30 | 31 | - Fixed missing `EguiOutput` updates ([#371](https://github.com/vladbat00/bevy_egui/pull/371)). 32 | - Fixed non-latin hotkeys ([#374](https://github.com/vladbat00/bevy_egui/pull/374) by @VinTarZ). 33 | - Fixed multiple windows when a window is spawned later ([#332](https://github.com/vladbat00/bevy_egui/pull/332) by @jabuwu). 34 | 35 | ### Misc 36 | 37 | - Added a side_panel example for 2D ([#253](https://github.com/vladbat00/bevy_egui/pull/253) by @weberja). 38 | 39 | ## [0.33.0] - 16-Feb-2025 40 | 41 | ### Changed 42 | 43 | - Update Egui to 0.31 ([#357](https://github.com/vladbat00/bevy_egui/pull/357) by @tomara-x). 44 | - Implement the new `egui::OutputCommand::CopyImage` command support ([#361](https://github.com/vladbat00/bevy_egui/pull/361)). 45 | - Change `EguiRenderOutput` internals to improve performance, and other optimizations ([#359](https://github.com/vladbat00/bevy_egui/pull/359) by @PPakalns). 46 | 47 | ## [0.32.0] - 6-Jan-2025 48 | 49 | ### Added 50 | 51 | - Basic `bevy_picking` support to prevent picking through Egui windows ([#338](https://github.com/vladbat00/bevy_egui/pull/338), [#331](https://github.com/vladbat00/bevy_egui/pull/331) by @globin and @aevyrie). 52 | - Helpers for converting between Egui and Bevy types ([#345](https://github.com/vladbat00/bevy_egui/pull/345), [488ac6a](https://github.com/vladbat00/bevy_egui/commit/488ac6a9d8fc3e1be98b494aed83751415beb845)). 53 | 54 | ### Changed 55 | 56 | - Update Egui to 0.30 ([#340](https://github.com/vladbat00/bevy_egui/pull/340) by @PPakalns). 57 | - Reuse IDs of removed textures ([#344](https://github.com/vladbat00/bevy_egui/pull/344)). 58 | - Input handling refactor ([#345](https://github.com/vladbat00/bevy_egui/pull/345)). 59 | - This brings us closer to diegetic (world space) UI support, the `HoveredNonWindowEguiContext` 60 | and `FocusedNonWindowEguiContext` resources can now be used to redirect input 61 | events to other contexts. 62 | - Input handling was split into separate systems for each event type, which are now also disableable ([#346](https://github.com/vladbat00/bevy_egui/pull/346)), 63 | see the `EguiGlobalSettings` resource. 64 | - The `EguiInputEvent` event type was added, which wraps all the events that are sent to Egui. 65 | It can also be used for custom input events logic, in case you want to mock inputs or handle inputs yourself. 66 | - The `EguiSettings` component was renamed to `EguiContextSettings`. 67 | - `EguiSet` was split into the `EguiPreUpdateSet` and `EguiPostUpdateSet` sets. 68 | - `EguiInputSet` was also added (as `EguiPreUpdateSet::ProcessInput` subsets). 69 | - Egui contexts are now immediately initialised for entities with a `EguiRenderToImage` component 70 | (as opposed to being initialised with a delay by a separate system), 71 | thanks to Bevy [required components](https://docs.rs/bevy/0.15.1/bevy/prelude/trait.Component.html#required-components). 72 | 73 | ### Fixed 74 | 75 | - Fix clipboard error handling ([#347](https://github.com/vladbat00/bevy_egui/pull/347)). 76 | 77 | ## [0.31.1] - 30-Nov-2024 78 | 79 | ### Fixed 80 | 81 | - Fix docs.rs build. 82 | 83 | ## [0.31.0] - 30-Nov-2024 84 | 85 | ### Changed 86 | 87 | - Update to Bevy 0.15 ([#309](https://github.com/vladbat00/bevy_egui/pull/309) by @Vrixyz). 88 | 89 | ### Fixed 90 | 91 | - Don't add `EguiContext` to every entity when the `render` feature is disabled ([#321](https://github.com/vladbat00/bevy_egui/pull/321) by @DGriffin91). 92 | 93 | ## [0.30.1] - 10-Nov-2024 94 | 95 | ### Changed 96 | 97 | - Depend on bevy subcrates for compile time improvement ([#319](https://github.com/vladbat00/bevy_egui/pull/319) by @aevyrie). 98 | 99 | ## [0.30.0] - 4-Oct-2024 100 | 101 | ### Added 102 | 103 | - `prepare_render` step support for `EguiBevyPaintCallbackImpl` ([#306](https://github.com/vladbat00/bevy_egui/pull/306) by @PPakalns). 104 | - Mobile virtual keyboard support in web ([#279](https://github.com/vladbat00/bevy_egui/pull/279) by @v-kat). 105 | - Requires `Window::prevent_default_event_handling` being set to `false`. 106 | - IME support (#[204](https://github.com/vladbat00/bevy_egui/pull/204) by @EReeves). 107 | 108 | ### Changed 109 | 110 | - Update Egui to 0.29 ([#313](https://github.com/vladbat00/bevy_egui/pull/313) by @PPakalns). 111 | 112 | ### Additional notes on breaking changes 113 | 114 | - `EguiSettings` is now a component. 115 | - `EguiSet::BeginFrame` has been renamed to `EguiSet::BeginPass`. 116 | 117 | ## [0.29.0] - 18-Aug-2024 118 | 119 | ### Added 120 | 121 | - Initial worldspace UI support ([#304](https://github.com/vladbat00/bevy_egui/pull/304) by @TheButlah, @Schmarni-Dev). 122 | - Paint callback support ([#303](https://github.com/vladbat00/bevy_egui/pull/303) by @PPakalns). 123 | 124 | ### Changed 125 | 126 | - Adapt to `web-sys` clipboard api change ([#301](https://github.com/vladbat00/bevy_egui/pull/301) by @no-materials). 127 | 128 | ### Fixed 129 | 130 | - Clear modifier state when focus is lost ([#298](https://github.com/vladbat00/bevy_egui/pull/298) by @SludgePhD). 131 | - Fix redraws ([#293](https://github.com/vladbat00/bevy_egui/pull/293)). 132 | 133 | ## [0.28.0] - 6-Jul-2024 134 | 135 | ### Changed 136 | 137 | - Update Bevy to 0.14 ([#284](https://github.com/vladbat00/bevy_egui/pull/284) by @Friz64). 138 | - Update Egui to 0.28 ([#290](https://github.com/vladbat00/bevy_egui/pull/290) by @Swoorup). 139 | - Update webbrowser to 1.0.1 140 | 141 | ## [0.27.1] - 2-Jun-2024 142 | 143 | ### Changed 144 | 145 | - Request Redraw only if really needed ([#278](https://github.com/vladbat00/bevy_egui/pull/278) by @Maximetinu). 146 | - Fix light in the `render_to_image_wideget` example ([#282](https://github.com/vladbat00/bevy_egui/pull/282) by @rlidwka). 147 | 148 | ## [0.27.0] - 18-Apr-2024 149 | 150 | ### Added 151 | 152 | - Fallible variants of primary window getters for `EguiContexts`. 153 | 154 | ### Changed 155 | 156 | - Update Egui to 0.27 ([#271](https://github.com/vladbat00/bevy_egui/pull/271) by @jakobhellermann). 157 | - Improve compilation errors when missing `web_sys_unstable_apis` ([#270](https://github.com/vladbat00/bevy_egui/pull/270) by @Vrixyz). 158 | 159 | ### Fixed 160 | 161 | - Rework reading window ids for events (fixes edge-cases with ignoring events, [#273](https://github.com/vladbat00/bevy_egui/pull/273)). 162 | 163 | ### Removed 164 | 165 | - Unused `RenderGraphConfig`. 166 | 167 | ## [0.26.0] - 18-Mar-2024 168 | 169 | ### Added 170 | 171 | - Add web clipboard support ([#267](https://github.com/vladbat00/bevy_egui/pull/267), [#178](https://github.com/vladbat00/bevy_egui/pull/178) by @Vrixyz). 172 | 173 | ### Fixed 174 | 175 | - Respect `egui::TextureOptions` for managed textures ([#264](https://github.com/vladbat00/bevy_egui/pull/264) by @TheRawMeatball). 176 | - Fix keybind modifiers ([#265](https://github.com/vladbat00/bevy_egui/pull/265) by @eero-lehtinen). 177 | 178 | ## [0.25.0] - 19-Feb-2024 179 | 180 | ### Added 181 | 182 | - Add `render` feature which can be disabled for applications with a custom renderer ([#240](https://github.com/vladbat00/bevy_egui/pull/240) by @BeastLe9enD). 183 | 184 | ### Changed 185 | 186 | - Update Bevy to 0.13 ([#236](https://github.com/vladbat00/bevy_egui/pull/236) by @eri). 187 | - Update Egui to 0.26. 188 | 189 | ### Fixed 190 | 191 | - Retrieve user agent for better platform detection on WASM ([#256](https://github.com/vladbat00/bevy_egui/pull/256) by @Vrixyz). 192 | - Remove unused `once_cell` dev-dependency ([#258](https://github.com/vladbat00/bevy_egui/pull/258) by @frewsxcv). 193 | - Make fields inside `WindowSize` pub ([#251](https://github.com/vladbat00/bevy_egui/pull/251) by @BeastLe9enD). 194 | - Fix requested repaints not causing Bevy to redraw ([#240](https://github.com/vladbat00/bevy_egui/pull/240) by @andriyDev). 195 | - Fix build on Android with default features ([#241](https://github.com/vladbat00/bevy_egui/pull/241) by @Hellzbellz123). 196 | 197 | ## [0.24.0] - 11-Dec-2023 198 | 199 | ### Changed 200 | 201 | - Update Egui to 0.24 ([#234](https://github.com/vladbat00/bevy_egui/pull/234) by @naomijub, @frewsxcv). 202 | 203 | ### Fixed 204 | 205 | - Handle giving time input to egui correctly ([#226](https://github.com/vladbat00/bevy_egui/pull/226) by @TheRawMeatball). 206 | 207 | ## [0.23.0] - 5-Nov-2023 208 | 209 | ### Changed 210 | 211 | - Update Bevy to 0.12 ([#221](https://github.com/vladbat00/bevy_egui/pull/221) by @raffaeleragni). 212 | 213 | ### Fixed 214 | 215 | - Fix color attachments in WASM (WebGPU) ([#220](https://github.com/vladbat00/bevy_egui/pull/220) by @seabassjh, @frewsxcv). 216 | 217 | ## [0.22.0] - 7-Oct-2023 218 | 219 | ### Added 220 | 221 | - Add `#[derive(Reflect)]` ([#195](https://github.com/vladbat00/bevy_egui/pull/195) by @SludgePhD). 222 | 223 | ### Changed 224 | 225 | - Update Egui to 0.23 ([#217](https://github.com/vladbat00/bevy_egui/pull/217) by @zicklag). 226 | - Refactor components and resources extraction ([#210](https://github.com/vladbat00/bevy_egui/pull/210), [#211](https://github.com/vladbat00/bevy_egui/pull/211) by @TheButlah). 227 | 228 | ## [0.21.0] - 10-Jul-2023 229 | 230 | ### Added 231 | 232 | - Add touch events support ([#180](https://github.com/vladbat00/bevy_egui/pull/180) by @oscrim). 233 | 234 | ### Changed 235 | 236 | - Update Bevy to 0.11 ([#188](https://github.com/vladbat00/bevy_egui/pull/188) by @Vrixyz). 237 | - Update Egui to 0.22 ([#184](https://github.com/vladbat00/bevy_egui/pull/184)). 238 | - Move sampler descriptor into `EguiSettings` ([#179](https://github.com/vladbat00/bevy_egui/pull/179) by @GlummixX). 239 | - Update GitHub Actions CI ([#183](https://github.com/vladbat00/bevy_egui/pull/183) by @striezel). 240 | 241 | ## [0.20.3] - 21-Apr-2023 242 | 243 | ### Fixed 244 | 245 | - Accept NumpadEnter as Enter ([#171](https://github.com/vladbat00/bevy_egui/pull/171) by @dimvoly). 246 | 247 | ## [0.20.2] - 27-Mar-2023 248 | 249 | ### Changed 250 | 251 | - Move `bevy_core_pipeline` to dev-dependencies ([#166](https://github.com/vladbat00/bevy_egui/pull/166) by @jakobhellermann). 252 | 253 | ### Fixed 254 | 255 | - Fix incorrect bounds check for set_scissor_rect ([#167](https://github.com/vladbat00/bevy_egui/pull/167) by @Gorialis). 256 | - Fix panic messages for uninitialised contexts. 257 | 258 | ## [0.20.1] - 12-Mar-2023 259 | 260 | ### Fixed 261 | 262 | - Fix recreation of `EguiContext` on startup ([#162](https://github.com/vladbat00/bevy_egui/pull/162) by @encounter). 263 | - Set image sampler address modes to `ClampToEdge` ([#158](https://github.com/vladbat00/bevy_egui/pull/158) by @galop1n). 264 | 265 | ## [0.20.0] - 8-Mar-2023 266 | 267 | ### Added 268 | 269 | - Add `altgr` support for Windows ([#149](https://github.com/vladbat00/bevy_egui/pull/149) by @Vrixyz). 270 | - Add `serde` feature ([#154](https://github.com/vladbat00/bevy_egui/pull/154) by @AlanRace). 271 | 272 | ### Changed 273 | 274 | - Update Bevy to 0.10 ([#159](https://github.com/vladbat00/bevy_egui/pull/159), thanks to @DGriffin91). 275 | - Update Egui to 0.21 ([#152](https://github.com/vladbat00/bevy_egui/pull/152) by @paul-hansen). 276 | - Implement better multi-window support ([#147](https://github.com/vladbat00/bevy_egui/pull/147) by @TheRawMeatball). 277 | 278 | ### Fixed 279 | 280 | - Pass raw Bevy time to Egui to fix UI animations ([#155](https://github.com/vladbat00/bevy_egui/pull/155) by @jakobhellermann). 281 | 282 | ## [0.19.0] - 15-Jan-2023 283 | 284 | ### Changed 285 | 286 | - Update the `arboard` dependency ([#142](https://github.com/vladbat00/bevy_egui/pull/142) by @jakobhellermann). 287 | 288 | ### Fixed 289 | 290 | - Fix panics due to missing swapchain textures ([#141](https://github.com/vladbat00/bevy_egui/pull/141) by @connerebbinghaus). 291 | 292 | ## [0.18.0] - 11-Dec-2022 293 | 294 | ### Changed 295 | 296 | - Update Egui to 0.20 ([#139](https://github.com/vladbat00/bevy_egui/pull/139) by @no-materials). 297 | 298 | ## [0.17.1] - 14-Nov-2022 299 | 300 | ### Fixed 301 | 302 | - Fix clearing event readers (missed events warnings). 303 | 304 | ## [0.17.0] - 13-Nov-2022 305 | 306 | ### Changed 307 | 308 | - Update to Bevy 0.9 ([#127](https://github.com/vladbat00/bevy_egui/pull/127), [#133](https://github.com/vladbat00/bevy_egui/pull/133), thanks to @terhechte and @jakobhellermann). 309 | 310 | ### Fixed 311 | 312 | - Fix window resizing on Windows ([#128](https://github.com/vladbat00/bevy_egui/pull/128) by @chronicl). 313 | 314 | ## [0.16.1] - 18-Sep-2022 315 | 316 | ### Fixed 317 | 318 | - Fix releasing buttons outside a window ([#123](https://github.com/vladbat00/bevy_egui/pull/123), thanks to @TheRawMeatball for flagging the issue in [#121](https://github.com/vladbat00/bevy_egui/pull/121)). 319 | 320 | ## [0.16.0] - 24-Aug-2022 321 | 322 | ### Changed 323 | 324 | - Update Egui to 0.19. 325 | 326 | ## [0.15.1] - 13-Aug-2022 327 | 328 | ### Fixed 329 | 330 | - Store image handles instead of ids to persist strong handles. 331 | 332 | ## [0.15.0] - 30-Jul-2022 333 | 334 | ### Added 335 | 336 | - Add a feature that can be disabled to replace default Egui fonts ([#110](https://github.com/vladbat00/bevy_egui/pull/110) by @iTitus). 337 | 338 | ### Changed 339 | 340 | - Update Bevy to 0.8 ([#111](https://github.com/vladbat00/bevy_egui/pull/111) by @DGriffin91). 341 | 342 | ## [0.14.0] - 1-May-2022 343 | 344 | ### Added 345 | 346 | - Add new_tab support for open_url ([#96](https://github.com/vladbat00/bevy_egui/pull/96) by @Azorlogh). 347 | - `EguiSettings` has also got the `default_open_url_target` parameter to make the default behaviour on left mouse click configurable. 348 | - Update Egui to 0.18 ([#99](https://github.com/vladbat00/bevy_egui/pull/99)). 349 | 350 | ### Changed 351 | 352 | - The `multi_threaded` feature was renamed to `immutable_ctx`. 353 | 354 | ### Fixed 355 | 356 | - Improve wgsl readability and introduce minor optimisations ([#95](https://github.com/vladbat00/bevy_egui/pull/95) by @lain-dono). 357 | - Remove duplicate EguiPipeline resource initialization ([#98](https://github.com/vladbat00/bevy_egui/pull/98) by @lain-dono). 358 | - Fix color blending for user textures ([#100](https://github.com/vladbat00/bevy_egui/pull/100)). 359 | 360 | ## [0.13.0] - 16-Apr-2022 361 | 362 | ### Changed 363 | 364 | - Update Bevy to 0.7 ([#79](https://github.com/vladbat00/bevy_egui/pull/79) by @aevyrie and @forbjok). 365 | - Return egui::TextureId on removal ([#81](https://github.com/vladbat00/bevy_egui/pull/81) by @Shatur). 366 | - Add `must_use` attributes to methods ([#82](https://github.com/vladbat00/bevy_egui/pull/82)). 367 | 368 | ### Fixed 369 | 370 | - Remove unnecessary image clone allocation ([#84](https://github.com/vladbat00/bevy_egui/pull/84) by @frewsxcv). 371 | - Avoid allocations by utilizing `HashMap::iter_mut` ([#83](https://github.com/vladbat00/bevy_egui/pull/83) by @frewsxcv). 372 | - Remove unnecessary swap texture clone ([#85](https://github.com/vladbat00/bevy_egui/pull/85) by @frewsxcv). 373 | 374 | ## [0.12.1] - 13-Mar-2022 375 | 376 | ### Added 377 | 378 | - Add a function to get image id ([#80](https://github.com/vladbat00/bevy_egui/pull/80) by @Shatur). 379 | 380 | ## [0.12.0] - 12-Mar-2022 381 | 382 | ### Added 383 | 384 | - Add side panel example ([#73](https://github.com/vladbat00/bevy_egui/pull/73)). 385 | 386 | ### Changed 387 | 388 | - Update Egui to 0.17 ([#78](https://github.com/vladbat00/bevy_egui/pull/78) by @emilk). 389 | 390 | ### Fixed 391 | 392 | - User texture ids are now tracked internally ([#71](https://github.com/vladbat00/bevy_egui/pull/71)). 393 | - Instead of using `set_egui_texture`, you can now use `add_image` which returns a texture id itself 394 | (see the updated [ui](https://github.com/vladbat00/bevy_egui/blob/c611671603a70e5956ba06f77bb94851c7ced659/examples/ui.rs) example). 395 | - Switch to `arboard` for managing clipboard ([#72](https://github.com/vladbat00/bevy_egui/pull/72)). 396 | 397 | ## [0.11.1] - 4-Feb-2022 398 | 399 | ### Added 400 | 401 | - Add `ctx_for_windows_mut` and `try_ctx_for_windows_mut` for accessing multiple contexts without the `multi_threaded` feature. 402 | 403 | ## [0.11.0] - 4-Feb-2022 404 | 405 | ### Changed 406 | 407 | - Introduce mutable getters for EguiContext, feature gate immutable ones ([#64](https://github.com/vladbat00/bevy_egui/pull/63)). 408 | - If you used `bevy_egui` without the `multi_threaded` feature, you'll need to change every `ctx` call to `ctx_mut`. 409 | 410 | ## [0.10.3] - 29-Jan-2022 411 | 412 | ### Added 413 | 414 | - Feature `multi_threaded`, to avoid using `egui/multi_threaded` ([#63](https://github.com/vladbat00/bevy_egui/pull/63) by @ndarilek). 415 | 416 | ### Fixed 417 | 418 | - WGPU crash on minimizing a window ([#62](https://github.com/vladbat00/bevy_egui/pull/62) by @aevyrie). 419 | 420 | ## [0.10.2] - 23-Jan-2022 421 | 422 | ### Added 423 | 424 | - Horizontal scroll support (Shift + Mouse Wheel). 425 | - Zoom support (Ctrl/Cmd + Mouse Wheel). 426 | 427 | ### Fixed 428 | 429 | - Change points delta from 24 to 50 for `MouseScrollUnit::Line` event. 430 | - Fix handling of mouse button events for Safari (inputs are no longer ignored). 431 | - Scroll is no longer applied to every Bevy window. 432 | 433 | ## [0.10.1] - 16-Jan-2022 434 | 435 | ### Added 436 | 437 | - Headless mode support ([#51](https://github.com/vladbat00/bevy_egui/pull/51) by @Shatur). 438 | 439 | ### Fixed 440 | 441 | - Egui pass now runs after `bevy_ui` ([#53](https://github.com/vladbat00/bevy_egui/pull/53) by @jakobhellermann). 442 | 443 | ## [0.10.0] - 8-Jan-2022 444 | 445 | ### Changed 446 | 447 | - Update Bevy to 0.6 ([#25](https://github.com/vladbat00/bevy_egui/pull/25) by @jakobhellermann). 448 | 449 | ## [0.9.0] - 1-Jan-2022 450 | 451 | ### Changed 452 | 453 | - Update Egui to 0.16 ([#49](https://github.com/vladbat00/bevy_egui/pull/49) by @Meshiest). 454 | 455 | ## [0.8.0] - 27-Nov-2021 456 | 457 | ### Changed 458 | 459 | - Update Egui to 0.15.0 ([#45](https://github.com/vladbat00/bevy_egui/pull/45)). 460 | 461 | ## [0.7.1] - 06-Oct-2021 462 | 463 | ### Added 464 | 465 | - Add `EguiStartupSystem` system labels. 466 | 467 | ### Fixed 468 | 469 | - Initialize egui contexts during startup (fixes [#41](https://github.com/vladbat00/bevy_egui/issues/41)). 470 | 471 | ## [0.7.0] - 05-Sep-2021 472 | 473 | ### Changed 474 | 475 | - Update Egui to 0.14.0 ([#38](https://github.com/vladbat00/bevy_egui/pull/38)). 476 | 477 | ## [0.6.2] - 15-Aug-2021 478 | 479 | ### Fixed 480 | 481 | - Fix receiving input when holding a button ([#37](https://github.com/vladbat00/bevy_egui/pull/37)). 482 | 483 | ## [0.6.1] - 20-Jul-2021 484 | 485 | ### Fixed 486 | 487 | - Fix more edge-cases related to invalid scissors. 488 | 489 | ## [0.6.0] - 29-Jun-2021 490 | 491 | ### Changed 492 | 493 | - Update Egui to 0.13.0. 494 | 495 | ## [0.5.0] - 22-May-2021 496 | 497 | ### Changed 498 | 499 | - Update Egui to 0.12.0. 500 | 501 | ## [0.4.2] - 03-May-2021 502 | 503 | ### Added 504 | 505 | - Better error message for a missing Egui context ([#24](https://github.com/vladbat00/bevy_egui/pull/24) by @jakobhellermann). 506 | - Add `try_ctx_for_window` function ([#20](https://github.com/vladbat00/bevy_egui/pull/20) by @jakobhellermann). 507 | 508 | ## [0.4.1] - 24-Apr-2021 509 | 510 | ### Fixed 511 | 512 | - Fix crashes related to invalid scissor or window size ([#18](https://github.com/vladbat00/bevy_egui/pull/18)). 513 | 514 | ## [0.4.0] - 10-Apr-2021 515 | 516 | Huge thanks to @jakobhellermann and @Weasy666 for contributing to this release! 517 | 518 | ### Added 519 | 520 | - Implement multiple windows support ([#14](https://github.com/vladbat00/bevy_egui/pull/14) by @jakobhellermann). 521 | 522 | ### Changed 523 | 524 | - Update Egui to 0.11.0 ([#12](https://github.com/vladbat00/bevy_egui/pull/12) by @Weasy666 and @jakobhellermann). 525 | 526 | ## [0.3.0] - 02-Mar-2021 527 | 528 | ### Changed 529 | 530 | - Update Egui to 0.10.0. 531 | 532 | ## [0.2.0] - 08-Feb-2021 533 | 534 | ### Changed 535 | 536 | - Update Egui to 0.9.0. 537 | 538 | ## [0.1.3] - 20-Jan-2021 539 | 540 | ### Fixed 541 | 542 | - Fix copying textures to take alignment into account. 543 | - Disable a documentation test. 544 | 545 | ## [0.1.2] - 18-Jan-2021 546 | 547 | ### Fixed 548 | 549 | - Disable default features for docs.rs to fix the build. 550 | 551 | ## [0.1.1] - 18-Jan-2021 552 | 553 | ### Fixed 554 | 555 | - Fix compilation errors when no features are set. 556 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_egui" 3 | version = "0.34.1" 4 | # Needed for LazyLock https://doc.rust-lang.org/stable/std/sync/struct.LazyLock.html 5 | rust-version = "1.80.0" 6 | authors = ["vladbat00 "] 7 | description = "A plugin for Egui integration into Bevy" 8 | license = "MIT" 9 | edition = "2021" 10 | repository = "https://github.com/vladbat00/bevy_egui" 11 | exclude = ["assets/**/*", ".github/**/*"] 12 | 13 | [package.metadata.docs.rs] 14 | features = ["bevy_winit/x11", "immutable_ctx"] 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | [features] 18 | default = ["manage_clipboard", "open_url", "default_fonts", "render", "picking"] 19 | accesskit_placeholder = ["egui/accesskit", "bevy_a11y"] # the feature won't compile until Egui updates its accesskit version, hence disabled by default 20 | immutable_ctx = [] 21 | manage_clipboard = ["arboard", "thread_local", "bytemuck"] 22 | open_url = ["webbrowser"] 23 | default_fonts = ["egui/default_fonts"] 24 | render = [ 25 | "bevy_asset", 26 | "bevy_image", 27 | "bevy_render", 28 | "encase", 29 | "bytemuck", 30 | "egui/bytemuck", 31 | "wgpu-types", 32 | ] 33 | picking = ["bevy_picking"] 34 | serde = ["egui/serde"] 35 | # The enabled logs will print with the info log level, to make it less cumbersome to debug in browsers. 36 | log_input_events = [] 37 | # The enabled logs will print with the info log level, to make it less cumbersome to debug in browsers. 38 | log_file_dnd_events = [] 39 | 40 | [[example]] 41 | name = "absorb_input" 42 | required-features = ["render"] 43 | [[example]] 44 | name = "color_test" 45 | required-features = ["render"] 46 | [[example]] 47 | name = "paint_callback" 48 | required-features = ["render"] 49 | [[example]] 50 | name = "render_to_image_widget" 51 | required-features = ["render"] 52 | [[example]] 53 | name = "run_manually" 54 | required-features = ["render"] 55 | [[example]] 56 | name = "side_panel_2d" 57 | required-features = ["render"] 58 | [[example]] 59 | name = "side_panel_3d" 60 | required-features = ["render"] 61 | [[example]] 62 | name = "simple" 63 | required-features = ["render"] 64 | [[example]] 65 | name = "two_windows" 66 | required-features = ["render"] 67 | [[example]] 68 | name = "ui" 69 | required-features = ["render"] 70 | [[example]] 71 | name = "render_egui_to_image" 72 | required-features = ["picking", "render", "bevy/bevy_gizmos"] 73 | [[example]] 74 | name = "file_browse" 75 | required-features = ["render"] 76 | 77 | [dependencies] 78 | egui = { version = "0.31", default-features = false } 79 | bevy_a11y = { version = "0.16.0", optional = true } 80 | bevy_app = "0.16.0" 81 | bevy_derive = "0.16.0" 82 | bevy_ecs = "0.16.0" 83 | bevy_input = "0.16.0" 84 | bevy_log = "0.16.0" 85 | bevy_math = "0.16.0" 86 | bevy_reflect = "0.16.0" 87 | bevy_time = "0.16.0" 88 | bevy_winit = "0.16.0" 89 | bevy_window = "0.16.0" 90 | bevy_platform = "0.16.0" 91 | 92 | # `open_url` feature 93 | webbrowser = { version = "1.0.1", optional = true } 94 | 95 | # `render` feature 96 | bytemuck = { version = "1", optional = true } 97 | bevy_asset = { version = "0.16.0", optional = true } 98 | bevy_image = { version = "0.16.0", optional = true } 99 | bevy_render = { version = "0.16.0", optional = true } 100 | encase = { version = "0.10", optional = true } 101 | wgpu-types = { version = "24.0", optional = true } 102 | 103 | # `picking` feature 104 | bevy_picking = { version = "0.16.0", optional = true } 105 | 106 | # `manage_clipboard` feature 107 | [target.'cfg(not(any(target_arch = "wasm32", target_os = "android")))'.dependencies] 108 | arboard = { version = "3.2.0", optional = true } 109 | thread_local = { version = "1.1.0", optional = true } 110 | 111 | [dev-dependencies] 112 | version-sync = "0.9.5" 113 | bevy = { version = "0.16.0", default-features = false, features = [ 114 | "accesskit_unix", 115 | "bevy_log", 116 | "bevy_asset", 117 | "bevy_core_pipeline", 118 | "bevy_pbr", 119 | "bevy_mesh_picking_backend", 120 | "bevy_sprite", 121 | "bevy_window", 122 | "bevy_winit", 123 | "android-game-activity", 124 | "png", 125 | "std", 126 | "tonemapping_luts", 127 | "webgl2", 128 | "x11", 129 | ] } 130 | egui = { version = "0.31", default-features = false, features = ["bytemuck"] } 131 | 132 | [target.'cfg(not(any(target_os = "android", target_os = "ios", target_arch = "wasm32")))'.dev-dependencies] 133 | rfd = "0.15" 134 | 135 | [target.'cfg(target_arch = "wasm32")'.dependencies] 136 | winit = "0.30" 137 | web-sys = { version = "0.3.74", features = [ 138 | "Clipboard", 139 | "ClipboardEvent", 140 | "ClipboardItem", 141 | "CompositionEvent", 142 | "DataTransfer", 143 | "Document", 144 | "EventTarget", 145 | "HtmlInputElement", 146 | "InputEvent", 147 | "KeyboardEvent", 148 | "Navigator", 149 | "TouchEvent", 150 | "Window", 151 | ] } 152 | image = { version = "0.25.5", default-features = false, features = ["png"] } # For copying images 153 | js-sys = "0.3.63" 154 | wasm-bindgen = "0.2.93" 155 | wasm-bindgen-futures = "0.4.36" 156 | crossbeam-channel = "0.5.8" 157 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Vladyslav Batyrenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://stand-with-ukraine.pp.ua) 2 | 3 | **Hey!** I'm the author of the crate, and I was born in Mariupol, Ukraine. When russians started the war in 2014, I moved to Kyiv. My parents, who had been staying in Mariupol till the start of the full-scale invasion, barely escaped the city alive. By the moment of writing (November 5th, 2023), we had [874 air raid alerts in Kyiv, and russians managed to bomb the city 132 times](https://air-alarms.in.ua/en/region/kyiv). 4 | 5 | **If you are using this crate, please consider donating to any of the listed funds (see the banner above), that will mean a lot to me.** 6 | 7 | # `bevy_egui` 8 | 9 | [![Crates.io](https://img.shields.io/crates/v/bevy_egui.svg)](https://crates.io/crates/bevy_egui) 10 | [![Documentation](https://docs.rs/bevy_egui/badge.svg)](https://docs.rs/bevy_egui) 11 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/bevyengine/bevy/blob/master/LICENSE) 12 | [![Downloads](https://img.shields.io/crates/d/bevy_egui.svg)](https://crates.io/crates/bevy_egui) 13 | [![CI](https://github.com/vladbat00/bevy_egui/actions/workflows/check.yml/badge.svg?branch=main)](https://github.com/vladbat00/bevy_egui/actions) 14 | 15 | This crate provides an [Egui](https://github.com/emilk/egui) integration for the [Bevy](https://github.com/bevyengine/bevy) game engine. 16 | 17 | **Trying out:** 18 | 19 | A basic WASM example is live at [vladbat00.github.io/bevy_egui/ui](https://vladbat00.github.io/bevy_egui/ui/). 20 | 21 | **Features:** 22 | - Desktop and web platforms support 23 | - Clipboard 24 | - Opening URLs 25 | - Multiple windows support (see [./examples/two_windows.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/two_windows.rs)) 26 | - Paint callback support (see [./examples/paint_callback.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/paint_callback.rs)) 27 | - Mobile web virtual keyboard (still rough around the edges and only works without `prevent_default_event_handling` set to `false` in the `WindowPlugin` settings) 28 | 29 | ![bevy_egui](bevy_egui.png) 30 | 31 | ## Dependencies 32 | 33 | On Linux, this crate requires certain parts of [XCB](https://xcb.freedesktop.org/) to be installed on your system. On Debian-based systems, these can be installed with the following command: 34 | 35 | ```bash 36 | sudo apt install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev 37 | ``` 38 | 39 | ## Usage 40 | 41 | Here's a minimal usage example: 42 | ```toml 43 | # Cargo.toml 44 | [dependencies] 45 | bevy = "0.16.0" 46 | bevy_egui = "0.34.1" 47 | ``` 48 | 49 | ```rust 50 | use bevy::prelude::*; 51 | use bevy_egui::{egui, EguiContexts, EguiPlugin, EguiContextPass}; 52 | 53 | fn main() { 54 | App::new() 55 | .add_plugins(DefaultPlugins) 56 | .add_plugins(EguiPlugin { enable_multipass_for_primary_context: true }) 57 | .add_systems(EguiContextPass, ui_example_system) 58 | .run(); 59 | } 60 | 61 | fn ui_example_system(mut contexts: EguiContexts) { 62 | egui::Window::new("Hello").show(contexts.ctx_mut(), |ui| { 63 | ui.label("world"); 64 | }); 65 | } 66 | 67 | ``` 68 | 69 | Note that this example uses Egui in the [multi-pass mode]((https://docs.rs/egui/0.31.1/egui/#multi-pass-immediate-mode)). 70 | If you don't want to be limited to the `EguiContextPass` schedule, you can use the single-pass mode, 71 | but it may get deprecated in the future. 72 | 73 | For more advanced examples, see the [examples](#Examples) section below. 74 | 75 | ### Note to developers of public plugins 76 | 77 | If your plugin depends on `bevy_egui`, here are some hints on how to implement the support of both single-pass and multi-pass modes 78 | (with respect to the `EguiPlugin::enable_multipass_for_primary_context` flag): 79 | - Don't initialize `EguiPlugin` for the user, i.e. DO NOT use `add_plugins(EguiPlugin { ... })` in your code, 80 | users should be able to opt in or opt out of the multi-pass mode on their own. 81 | - If you add UI systems, make sure they go into the `EguiContextPass` schedule - this will guarantee your plugin supports both the single-pass and multi-pass modes. 82 | 83 | Your plugin code might look like this: 84 | 85 | ```rust 86 | use bevy::prelude::*; 87 | use bevy_egui::{egui, EguiContexts, EguiPlugin, EguiContextPass}; 88 | 89 | pub struct MyPlugin; 90 | 91 | impl Plugin for MyPlugin { 92 | fn build(&self, app: &mut App) { 93 | // Don't add the plugin for users, let them chose the default mode themselves 94 | // and just make sure they initialize EguiPlugin before yours. 95 | assert!(app.is_plugin_added::()); 96 | 97 | app.add_systems(EguiContextPass, ui_system); 98 | } 99 | } 100 | 101 | fn ui_system(contexts: EguiContexts) { 102 | // ... 103 | } 104 | ``` 105 | 106 | ## Examples 107 | 108 | To run an example, use the following command (you may replace `ui` with a name of another example): 109 | 110 | ```bash 111 | cargo run --example ui 112 | ``` 113 | 114 | ### ui ([live page](https://vladbat00.github.io/bevy_egui/ui), source: [examples/ui.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/ui.rs)) 115 | 116 | Showcasing some more advanced UI, rendering images, hidpi scaling. 117 | 118 | ### absorb_input ([live page](https://vladbat00.github.io/bevy_egui/absorb_input), source: [examples/absorb_input.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/absorb_input.rs)) 119 | 120 | Demonstrating the available options for absorbing input when Egui is using pointer or keyboard. 121 | 122 | ### color_test ([live page](https://vladbat00.github.io/bevy_egui/color_test), source: [examples/color_test.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/color_test.rs)) 123 | 124 | Rendering test from [egui.rs](https://egui.rs). We don't fully pass it, help is wanted ([#291](https://github.com/vladbat00/bevy_egui/issues/291)). 125 | 126 | ### side_panel_2d ([live page](https://vladbat00.github.io/bevy_egui/side_panel_2d), source: [examples/side_panel_2d.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/side_panel_2d.rs)) 127 | 128 | Showing how to display an Egui side panel and transform a camera with a perspective projection to make rendering centered relative to the remaining screen area. 129 | 130 | ### side_panel_3d ([live page](https://vladbat00.github.io/bevy_egui/side_panel_3d), source: [examples/side_panel_3d.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/side_panel_3d.rs)) 131 | 132 | Showing how to display an Egui side panel and transform a camera with a orthographic projection to make rendering centered relative to the remaining screen area. 133 | 134 | ### render_egui_to_image ([live page](https://vladbat00.github.io/bevy_egui/render_egui_to_image), source: [examples/render_egui_to_image.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/render_egui_to_image.rs)) 135 | 136 | Rendering UI to an image (texture) and then using it as a mesh material texture. 137 | 138 | ### render_to_image_widget ([live page](https://vladbat00.github.io/bevy_egui/render_to_image_widget), source: [examples/render_to_image_widget.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/render_to_image_widget.rs)) 139 | 140 | Rendering to a texture with Bevy and showing it as an Egui image widget. 141 | 142 | ### two_windows (source: [examples/two_windows.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/two_windows.rs)) 143 | 144 | Setting up two windows with an Egui context for each. 145 | 146 | ### paint_callback ([live page](https://vladbat00.github.io/bevy_egui/paint_callback), source: [examples/paint_callback.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/paint_callback.rs)) 147 | 148 | Using Egui paint callbacks. 149 | 150 | ### simple ([live page](https://vladbat00.github.io/bevy_egui/simple), source: [examples/simple.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/simple.rs)) 151 | 152 | The minimal usage example from this readme. 153 | 154 | ### run_manually ([live page](https://vladbat00.github.io/bevy_egui/run_manually), source: [examples/run_manually.rs](https://github.com/vladbat00/bevy_egui/blob/v0.34.1/examples/run_manually.rs)) 155 | 156 | The same minimal example demonstrating running Egui passes manually. 157 | 158 | ## See also 159 | 160 | - [`jakobhellermann/bevy-inspector-egui`](https://github.com/jakobhellermann/bevy-inspector-egui) 161 | 162 | ## Bevy support table 163 | 164 | **Note:** if you're looking for a `bevy_egui` version that supports `main` branch of Bevy, check out [open PRs](https://github.com/vladbat00/bevy_egui/pulls), there's a great chance we've already started working on the future Bevy release support. 165 | 166 | | bevy | bevy_egui | 167 | |------|-----------| 168 | | 0.16 | 0.34 | 169 | | 0.15 | 0.31-0.33 | 170 | | 0.14 | 0.28-0.30 | 171 | | 0.13 | 0.25-0.27 | 172 | | 0.12 | 0.23-0.24 | 173 | | 0.11 | 0.21-0.22 | 174 | | 0.10 | 0.20 | 175 | | 0.9 | 0.17-0.19 | 176 | | 0.8 | 0.15-0.16 | 177 | | 0.7 | 0.13-0.14 | 178 | | 0.6 | 0.10-0.12 | 179 | | 0.5 | 0.4-0.9 | 180 | | 0.4 | 0.1-0.3 | 181 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbat00/bevy_egui/56a485bf94f1a6699d0506cceb72570eb29396c0/assets/icon.png -------------------------------------------------------------------------------- /assets/icon_inverted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbat00/bevy_egui/56a485bf94f1a6699d0506cceb72570eb29396c0/assets/icon_inverted.png -------------------------------------------------------------------------------- /bevy_egui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbat00/bevy_egui/56a485bf94f1a6699d0506cceb72570eb29396c0/bevy_egui.png -------------------------------------------------------------------------------- /examples/absorb_input.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | color::palettes::{basic::PURPLE, css::YELLOW}, 3 | input::{ 4 | keyboard::KeyboardInput, 5 | mouse::{MouseButtonInput, MouseWheel}, 6 | }, 7 | prelude::*, 8 | }; 9 | use bevy_egui::{ 10 | egui, 11 | input::{egui_wants_any_keyboard_input, egui_wants_any_pointer_input}, 12 | EguiContextPass, EguiContexts, EguiGlobalSettings, EguiPlugin, 13 | }; 14 | 15 | fn main() { 16 | App::new() 17 | .add_plugins(DefaultPlugins) 18 | .add_plugins(EguiPlugin::default()) 19 | .add_systems(Startup, setup_scene_system) 20 | .add_systems(EguiContextPass, ui_system) 21 | // You can wrap your systems with the `egui_wants_any_pointer_input`, `egui_wants_any_keyboard_input` run conditions if you 22 | // want to disable them while Egui is using input. 23 | // 24 | // As an alternative (a less safe one), you can set `EguiGlobalSettings::enable_absorb_bevy_input_system` 25 | // to true to let Egui absorb all input events (see `ui_system` for the usage example). 26 | .add_systems( 27 | Update, 28 | keyboard_input_system.run_if(not(egui_wants_any_keyboard_input)), 29 | ) 30 | .add_systems( 31 | Update, 32 | pointer_input_system.run_if(not(egui_wants_any_pointer_input)), 33 | ) 34 | .run(); 35 | } 36 | 37 | #[derive(Resource, Clone)] 38 | struct Materials { 39 | yellow: MeshMaterial2d, 40 | purple: MeshMaterial2d, 41 | } 42 | 43 | fn setup_scene_system( 44 | mut commands: Commands, 45 | mut meshes: ResMut>, 46 | mut color_materials: ResMut>, 47 | ) { 48 | let materials = Materials { 49 | yellow: MeshMaterial2d(color_materials.add(Color::from(YELLOW))), 50 | purple: MeshMaterial2d(color_materials.add(Color::from(PURPLE))), 51 | }; 52 | commands.insert_resource(materials.clone()); 53 | 54 | commands.spawn(Camera2d); 55 | commands.spawn(( 56 | Mesh2d(meshes.add(Rectangle::default())), 57 | materials.purple, 58 | Transform::default().with_scale(Vec3::splat(128.)), 59 | )); 60 | } 61 | 62 | struct LoremIpsum(String); 63 | 64 | impl Default for LoremIpsum { 65 | fn default() -> Self { 66 | Self("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.".to_string()) 67 | } 68 | } 69 | 70 | #[derive(Default)] 71 | struct LastEvents { 72 | keyboard_input: Option, 73 | mouse_button_input: Option, 74 | mouse_wheel: Option, 75 | } 76 | 77 | #[allow(clippy::too_many_arguments)] 78 | fn ui_system( 79 | mut contexts: EguiContexts, 80 | mut egui_global_settings: ResMut, 81 | mut text: Local, 82 | mut last_events: Local, 83 | mut keyboard_input_events: EventReader, 84 | mut mouse_button_input_events: EventReader, 85 | mut mouse_wheel_events: EventReader, 86 | ) { 87 | if let Some(ev) = keyboard_input_events.read().last() { 88 | last_events.keyboard_input = Some(ev.clone()); 89 | } 90 | if let Some(ev) = mouse_button_input_events.read().last() { 91 | last_events.mouse_button_input = Some(*ev); 92 | } 93 | if let Some(ev) = mouse_wheel_events.read().last() { 94 | last_events.mouse_wheel = Some(*ev); 95 | } 96 | 97 | egui::Window::new("Absorb Input") 98 | .max_size([300.0, 200.0]) 99 | .vscroll(true) 100 | .show(contexts.ctx_mut(), |ui| { 101 | ui.checkbox( 102 | &mut egui_global_settings.enable_absorb_bevy_input_system, 103 | "Absorb all input events", 104 | ); 105 | 106 | ui.separator(); 107 | 108 | ui.label(format!( 109 | "Last keyboard button event: {:?}", 110 | last_events.keyboard_input 111 | )); 112 | ui.label(format!( 113 | "Last mouse button event: {:?}", 114 | last_events.mouse_button_input 115 | )); 116 | ui.label(format!( 117 | "Last mouse wheel event: {:?}", 118 | last_events.mouse_wheel 119 | )); 120 | 121 | ui.separator(); 122 | 123 | ui.label("A text field to test absorbing keyboard events"); 124 | ui.text_edit_multiline(&mut text.0); 125 | }); 126 | } 127 | 128 | fn keyboard_input_system( 129 | mesh: Single<&mut Transform, Without>, 130 | keyboard_button_input: Res>, 131 | ) { 132 | let mut transform = mesh.into_inner(); 133 | 134 | if keyboard_button_input.pressed(KeyCode::KeyA) { 135 | transform.translation.x -= 5.0; 136 | } 137 | if keyboard_button_input.pressed(KeyCode::KeyD) { 138 | transform.translation.x += 5.0; 139 | } 140 | if keyboard_button_input.pressed(KeyCode::KeyS) { 141 | transform.translation.y -= 5.0; 142 | } 143 | if keyboard_button_input.pressed(KeyCode::KeyW) { 144 | transform.translation.y += 5.0; 145 | } 146 | } 147 | 148 | fn pointer_input_system( 149 | materials: Res, 150 | mesh: Single<(&mut Transform, &mut MeshMaterial2d), Without>, 151 | mouse_button_input: Res>, 152 | mut mouse_wheel_event_reader: EventReader, 153 | ) { 154 | let (mut transform, mut material) = mesh.into_inner(); 155 | 156 | if mouse_button_input.just_pressed(MouseButton::Left) { 157 | *material = if materials.yellow.0 == material.0 { 158 | materials.purple.clone() 159 | } else { 160 | materials.yellow.clone() 161 | } 162 | } 163 | 164 | for ev in mouse_wheel_event_reader.read() { 165 | transform.scale += ev.y; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /examples/file_browse.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_egui::{EguiContextPass, EguiPlugin}; 3 | 4 | fn main() { 5 | App::new() 6 | .add_plugins(DefaultPlugins) 7 | .add_plugins(EguiPlugin::default()) 8 | .add_systems(EguiContextPass, foo::ui_system) 9 | .run(); 10 | } 11 | 12 | #[cfg(not(any(target_os = "ios", target_os = "android", target_arch = "wasm32")))] 13 | mod foo { 14 | use bevy::{ 15 | prelude::*, 16 | tasks::{block_on, poll_once, AsyncComputeTaskPool, Task}, 17 | }; 18 | use bevy_egui::{egui, EguiContexts}; 19 | 20 | #[derive(Default)] 21 | pub struct MyState { 22 | dropped_files: Vec, 23 | picked_path: Option, 24 | } 25 | 26 | type DialogResponse = Option; 27 | 28 | // much of this ui is taken from https://github.com/emilk/egui/blob/c6bd30642a78d5ff244b064642c053f62967ef1b/examples/file_dialog/src/main.rs 29 | pub fn ui_system( 30 | mut contexts: EguiContexts, 31 | mut state: Local, 32 | mut file_dialog: Local>>, 33 | ) { 34 | let ctx = contexts.ctx_mut(); 35 | egui::CentralPanel::default().show(ctx, |ui| { 36 | ui.label("Drag-and-drop files onto the window!"); 37 | 38 | if let Some(file_response) = file_dialog 39 | .as_mut() 40 | .and_then(|task| block_on(poll_once(task))) 41 | { 42 | state.picked_path = file_response.map(|path| path.path().display().to_string()); 43 | *file_dialog = None; 44 | } 45 | 46 | if ui.button("Open file…").clicked() { 47 | *file_dialog = Some( 48 | AsyncComputeTaskPool::get().spawn(rfd::AsyncFileDialog::new().pick_file()), 49 | ); 50 | } 51 | 52 | if let Some(picked_path) = &state.picked_path { 53 | ui.horizontal(|ui| { 54 | ui.label("Picked file:"); 55 | ui.monospace(picked_path); 56 | }); 57 | } 58 | 59 | // Show dropped files (if any): 60 | if !state.dropped_files.is_empty() { 61 | ui.group(|ui| { 62 | ui.label("Dropped files:"); 63 | 64 | for file in &state.dropped_files { 65 | let mut info = if let Some(path) = &file.path { 66 | path.display().to_string() 67 | } else if !file.name.is_empty() { 68 | file.name.clone() 69 | } else { 70 | "???".to_owned() 71 | }; 72 | 73 | let mut additional_info = vec![]; 74 | if !file.mime.is_empty() { 75 | additional_info.push(format!("type: {}", file.mime)); 76 | } 77 | if let Some(bytes) = &file.bytes { 78 | additional_info.push(format!("{} bytes", bytes.len())); 79 | } 80 | if !additional_info.is_empty() { 81 | info += &format!(" ({})", additional_info.join(", ")); 82 | } 83 | 84 | ui.label(info); 85 | } 86 | }); 87 | } 88 | }); 89 | 90 | preview_files_being_dropped(ctx); 91 | 92 | // Collect dropped files: 93 | ctx.input(|i| { 94 | if !i.raw.dropped_files.is_empty() { 95 | state.dropped_files.clone_from(&i.raw.dropped_files); 96 | } 97 | }); 98 | 99 | ctx.input(|i| { 100 | if i.raw.modifiers.ctrl { 101 | info!("ctrl pressed"); 102 | } 103 | }) 104 | } 105 | 106 | fn preview_files_being_dropped(ctx: &egui::Context) { 107 | use egui::{Align2, Color32, Id, LayerId, Order, TextStyle}; 108 | use std::fmt::Write as _; 109 | 110 | if !ctx.input(|i| i.raw.hovered_files.is_empty()) { 111 | let text = ctx.input(|i| { 112 | let mut text = "Dropping files:\n".to_owned(); 113 | for file in &i.raw.hovered_files { 114 | if let Some(path) = &file.path { 115 | write!(text, "\n{}", path.display()).ok(); 116 | } else if !file.mime.is_empty() { 117 | write!(text, "\n{}", file.mime).ok(); 118 | } else { 119 | text += "\n???"; 120 | } 121 | } 122 | text 123 | }); 124 | 125 | let painter = 126 | ctx.layer_painter(LayerId::new(Order::Foreground, Id::new("file_drop_target"))); 127 | 128 | let screen_rect = ctx.screen_rect(); 129 | painter.rect_filled(screen_rect, 0.0, Color32::from_black_alpha(192)); 130 | painter.text( 131 | screen_rect.center(), 132 | Align2::CENTER_CENTER, 133 | text, 134 | TextStyle::Heading.resolve(&ctx.style()), 135 | Color32::WHITE, 136 | ); 137 | } 138 | } 139 | } 140 | 141 | // do nothing for wasm/ios/android :( 142 | #[cfg(any(target_os = "ios", target_os = "android", target_arch = "wasm32"))] 143 | mod foo { 144 | pub fn ui_system() {} 145 | } 146 | -------------------------------------------------------------------------------- /examples/paint_callback.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | asset::{embedded_asset, AssetPath}, 3 | ecs::schedule::ScheduleLabel, 4 | prelude::*, 5 | render::{ 6 | mesh::PrimitiveTopology, 7 | render_resource::{ 8 | BlendState, CachedRenderPipelineId, ColorTargetState, ColorWrites, FragmentState, 9 | MultisampleState, PipelineCache, PolygonMode, PrimitiveState, RenderPipelineDescriptor, 10 | SpecializedRenderPipeline, SpecializedRenderPipelines, 11 | }, 12 | sync_world::RenderEntity, 13 | RenderApp, 14 | }, 15 | }; 16 | use bevy_egui::{ 17 | egui_node::{EguiBevyPaintCallback, EguiBevyPaintCallbackImpl, EguiPipelineKey}, 18 | EguiContextPass, EguiContexts, EguiMultipassSchedule, EguiPlugin, EguiRenderToImage, 19 | }; 20 | use std::path::Path; 21 | use wgpu_types::{Extent3d, TextureUsages}; 22 | 23 | fn main() { 24 | App::new() 25 | .add_plugins((DefaultPlugins, EguiPlugin::default(), CustomPipelinePlugin)) 26 | .add_systems(Startup, setup_worldspace) 27 | .add_systems(EguiContextPass, ui_example_system) 28 | .add_systems(WorldspaceContextPass, ui_render_to_image_example_system) 29 | .run(); 30 | } 31 | 32 | #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] 33 | pub struct WorldspaceContextPass; 34 | 35 | struct CustomPipelinePlugin; 36 | 37 | impl Plugin for CustomPipelinePlugin { 38 | fn build(&self, app: &mut App) { 39 | embedded_asset!(app, "examples/", "paint_callback.wgsl"); 40 | app.get_sub_app_mut(RenderApp) 41 | .unwrap() 42 | .insert_resource(SpecializedRenderPipelines::::default()) 43 | .init_resource::(); 44 | } 45 | } 46 | 47 | struct CustomPaintCallback; 48 | 49 | #[derive(Component)] 50 | struct CustomPaintPipelineIdComp { 51 | pipeline_id: CachedRenderPipelineId, 52 | } 53 | 54 | impl EguiBevyPaintCallbackImpl for CustomPaintCallback { 55 | fn update( 56 | &self, 57 | _info: egui::PaintCallbackInfo, 58 | window_entity: RenderEntity, 59 | key: EguiPipelineKey, 60 | world: &mut World, 61 | ) { 62 | let pipeline_id = 63 | world.resource_scope( 64 | |world, 65 | mut specialized_custom_pipelines: Mut< 66 | SpecializedRenderPipelines, 67 | >| { 68 | let specialized_pipeline = world.get_resource().unwrap(); 69 | let pipeline_cache = world.get_resource().unwrap(); 70 | 71 | let pipeline_id = specialized_custom_pipelines.specialize( 72 | pipeline_cache, 73 | specialized_pipeline, 74 | key, 75 | ); 76 | 77 | world 78 | .entity_mut(window_entity.id()) 79 | .insert(CustomPaintPipelineIdComp { pipeline_id }); 80 | pipeline_id 81 | }, 82 | ); 83 | 84 | let mut pipeline_cache = world.get_resource_mut::().unwrap(); 85 | pipeline_cache.block_on_render_pipeline(pipeline_id); 86 | } 87 | 88 | fn render<'pass>( 89 | &self, 90 | _info: egui::PaintCallbackInfo, 91 | render_pass: &mut bevy::render::render_phase::TrackedRenderPass<'pass>, 92 | window_entity: RenderEntity, 93 | _key: EguiPipelineKey, 94 | world: &'pass World, 95 | ) { 96 | let Some(pipeline) = world 97 | .get_entity(window_entity.id()) 98 | .ok() 99 | .and_then(|entity| entity.get::()) 100 | .and_then(|comp| { 101 | world 102 | .get_resource::() 103 | .and_then(|cache| cache.get_render_pipeline(comp.pipeline_id)) 104 | }) 105 | else { 106 | return; 107 | }; 108 | 109 | render_pass.set_render_pipeline(pipeline); 110 | render_pass.draw(0..3, 0..1); 111 | } 112 | } 113 | 114 | #[derive(Debug, Resource)] 115 | struct CustomPipeline { 116 | shader: Handle, 117 | } 118 | 119 | impl FromWorld for CustomPipeline { 120 | fn from_world(world: &mut World) -> Self { 121 | let shader = world.resource::().load( 122 | AssetPath::from_path(Path::new("paint_callback/paint_callback.wgsl")) 123 | .with_source("embedded"), 124 | ); 125 | 126 | Self { shader } 127 | } 128 | } 129 | 130 | impl SpecializedRenderPipeline for CustomPipeline { 131 | type Key = EguiPipelineKey; 132 | 133 | fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { 134 | RenderPipelineDescriptor { 135 | label: Some("custom pipeline".into()), 136 | layout: vec![], 137 | push_constant_ranges: Vec::new(), 138 | vertex: bevy::render::render_resource::VertexState { 139 | shader: self.shader.clone(), 140 | shader_defs: vec![], 141 | entry_point: "vertex".into(), 142 | buffers: vec![], 143 | }, 144 | primitive: PrimitiveState { 145 | topology: PrimitiveTopology::TriangleStrip, 146 | strip_index_format: None, 147 | front_face: bevy::render::render_resource::FrontFace::Ccw, 148 | cull_mode: None, 149 | unclipped_depth: false, 150 | polygon_mode: PolygonMode::Fill, 151 | conservative: false, 152 | }, 153 | depth_stencil: None, 154 | multisample: MultisampleState::default(), 155 | fragment: Some(FragmentState { 156 | shader: self.shader.clone(), 157 | shader_defs: vec![], 158 | entry_point: "fragment".into(), 159 | targets: vec![Some(ColorTargetState { 160 | format: key.texture_format, 161 | blend: Some(BlendState::ALPHA_BLENDING), 162 | write_mask: ColorWrites::ALL, 163 | })], 164 | }), 165 | zero_initialize_workgroup_memory: false, 166 | } 167 | } 168 | } 169 | 170 | fn ui_example_system(mut ctx: EguiContexts) { 171 | for id in 0..4 { 172 | egui::Window::new(id.to_string()).show(ctx.ctx_mut(), |ui| { 173 | let (resp, painter) = 174 | ui.allocate_painter(egui::Vec2 { x: 200., y: 200. }, egui::Sense::hover()); 175 | 176 | painter.add(EguiBevyPaintCallback::new_paint_callback( 177 | resp.rect, 178 | CustomPaintCallback, 179 | )); 180 | }); 181 | } 182 | } 183 | 184 | // The following systems are used to render UI in world space to demonstrate that paint callbacks 185 | // work for them as well (they aren't needed to set up pain callbacks for regular screen-space UI, 186 | // so feel free to skip them): 187 | 188 | fn setup_worldspace( 189 | mut images: ResMut>, 190 | mut meshes: ResMut>, 191 | mut materials: ResMut>, 192 | mut commands: Commands, 193 | ) { 194 | let output_texture = images.add({ 195 | let size = Extent3d { 196 | width: 256, 197 | height: 256, 198 | depth_or_array_layers: 1, 199 | }; 200 | let mut output_texture = Image { 201 | // You should use `0` so that the pixels are transparent. 202 | data: Some(vec![0; (size.width * size.height * 4) as usize]), 203 | ..default() 204 | }; 205 | output_texture.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT; 206 | output_texture.texture_descriptor.size = size; 207 | output_texture 208 | }); 209 | 210 | commands.spawn(( 211 | Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0).mesh())), 212 | MeshMaterial3d(materials.add(StandardMaterial { 213 | base_color: Color::WHITE, 214 | base_color_texture: Some(output_texture.clone()), 215 | alpha_mode: AlphaMode::Blend, 216 | // Remove this if you want it to use the world's lighting. 217 | unlit: true, 218 | ..default() 219 | })), 220 | )); 221 | commands 222 | .spawn(EguiRenderToImage::new(output_texture.clone_weak())) 223 | .insert(EguiMultipassSchedule::new(WorldspaceContextPass)); 224 | commands.spawn(( 225 | Camera3d::default(), 226 | Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::new(0., 0., 0.), Vec3::Y), 227 | )); 228 | } 229 | 230 | fn ui_render_to_image_example_system( 231 | contexts: Single<&mut bevy_egui::EguiContext, With>, 232 | ) { 233 | let mut ctx = contexts.into_inner(); 234 | egui::Window::new("Worldspace UI").show(ctx.get_mut(), |ui| { 235 | let (resp, painter) = 236 | ui.allocate_painter(egui::Vec2 { x: 200., y: 200. }, egui::Sense::hover()); 237 | 238 | painter.add(EguiBevyPaintCallback::new_paint_callback( 239 | resp.rect, 240 | CustomPaintCallback, 241 | )); 242 | }); 243 | } 244 | -------------------------------------------------------------------------------- /examples/paint_callback.wgsl: -------------------------------------------------------------------------------- 1 | const POS = array( 2 | vec2f(0, -1) * 0.5, 3 | vec2f(1, 1) * 0.5, 4 | vec2f(-1, 1) * 0.5, 5 | ); 6 | 7 | @vertex 8 | fn vertex(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4f { 9 | if vertex_index == 0 { 10 | return vec4f(POS[0], 0, 1); 11 | } else if vertex_index == 1 { 12 | return vec4f(POS[1], 0, 1); 13 | } else { 14 | return vec4f(POS[2], 0, 1); 15 | } 16 | } 17 | 18 | @fragment 19 | fn fragment() -> @location(0) vec4f { 20 | return vec4f(1, 1, 0, 1); 21 | } 22 | -------------------------------------------------------------------------------- /examples/render_egui_to_image.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | ecs::schedule::ScheduleLabel, prelude::*, render::render_resource::LoadOp, 3 | window::PrimaryWindow, 4 | }; 5 | use bevy_egui::{ 6 | EguiContextPass, EguiContexts, EguiMultipassSchedule, EguiPlugin, EguiRenderToImage, 7 | }; 8 | use wgpu_types::{Extent3d, TextureUsages}; 9 | 10 | fn main() { 11 | let mut app = App::new(); 12 | app.add_plugins((DefaultPlugins, MeshPickingPlugin)); 13 | app.add_plugins(EguiPlugin::default()); 14 | app.add_systems(Startup, setup_worldspace_system); 15 | app.add_systems(Update, draw_gizmos_system); 16 | app.add_systems(EguiContextPass, update_screenspace_system); 17 | app.add_systems(WorldspaceContextPass, update_worldspace_system); 18 | app.run(); 19 | } 20 | 21 | #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] 22 | pub struct WorldspaceContextPass; 23 | 24 | struct Name(String); 25 | 26 | impl Default for Name { 27 | fn default() -> Self { 28 | Self("%username%".to_string()) 29 | } 30 | } 31 | 32 | fn update_screenspace_system(mut name: Local, mut contexts: EguiContexts) { 33 | egui::Window::new("Screenspace UI").show(contexts.ctx_mut(), |ui| { 34 | ui.horizontal(|ui| { 35 | ui.label("Your name:"); 36 | ui.text_edit_singleline(&mut name.0); 37 | }); 38 | ui.label(format!( 39 | "Hi {}, I'm rendering to an image in screenspace!", 40 | name.0 41 | )); 42 | }); 43 | } 44 | 45 | fn update_worldspace_system( 46 | mut name: Local, 47 | mut ctx: Single<&mut bevy_egui::EguiContext, With>, 48 | ) { 49 | egui::Window::new("Worldspace UI").show(ctx.get_mut(), |ui| { 50 | ui.horizontal(|ui| { 51 | ui.label("Your name:"); 52 | ui.text_edit_singleline(&mut name.0); 53 | }); 54 | ui.label(format!( 55 | "Hi {}, I'm rendering to an image in worldspace!", 56 | name.0 57 | )); 58 | }); 59 | } 60 | 61 | #[derive(Resource)] 62 | struct MaterialHandles { 63 | normal: Handle, 64 | hovered: Handle, 65 | } 66 | 67 | fn setup_worldspace_system( 68 | mut images: ResMut>, 69 | mut meshes: ResMut>, 70 | mut materials: ResMut>, 71 | mut commands: Commands, 72 | mut config_store: ResMut, 73 | ) { 74 | for (_, config, _) in config_store.iter_mut() { 75 | config.depth_bias = -1.0; 76 | } 77 | 78 | let image = images.add({ 79 | let size = Extent3d { 80 | width: 256, 81 | height: 256, 82 | depth_or_array_layers: 1, 83 | }; 84 | let mut image = Image { 85 | // You should use `0` so that the pixels are transparent. 86 | data: Some(vec![0; (size.width * size.height * 4) as usize]), 87 | ..default() 88 | }; 89 | image.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT; 90 | image.texture_descriptor.size = size; 91 | image 92 | }); 93 | 94 | let material_handles = MaterialHandles { 95 | normal: materials.add(StandardMaterial { 96 | base_color: Color::linear_rgb(0.4, 0.4, 0.4), 97 | ..default() 98 | }), 99 | hovered: materials.add(StandardMaterial { 100 | base_color: Color::linear_rgb(0.6, 0.6, 0.6), 101 | ..default() 102 | }), 103 | }; 104 | 105 | commands 106 | .spawn(( 107 | Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::splat(0.5)).mesh())), 108 | MeshMaterial3d(materials.add(StandardMaterial { 109 | base_color: Color::WHITE, 110 | base_color_texture: Some(Handle::clone(&image)), 111 | alpha_mode: AlphaMode::Blend, 112 | // Remove this if you want it to use the world's lighting. 113 | unlit: true, 114 | ..default() 115 | })), 116 | EguiRenderToImage { 117 | handle: image, 118 | load_op: LoadOp::Clear(Color::srgb_u8(43, 44, 47).to_linear().into()), 119 | }, 120 | // We want the "tablet" mesh behind to react to pointer inputs. 121 | Pickable { 122 | should_block_lower: false, 123 | is_hoverable: true, 124 | }, 125 | EguiMultipassSchedule::new(WorldspaceContextPass), 126 | )) 127 | .with_children(|commands| { 128 | // The "tablet" mesh, on top of which Egui is rendered. 129 | commands 130 | .spawn(( 131 | Mesh3d(meshes.add(Cuboid::new(1.1, 1.1, 0.1))), 132 | MeshMaterial3d(material_handles.normal.clone()), 133 | Transform::from_xyz(0.0, 0.0, -0.051), 134 | )) 135 | .observe(handle_over_system) 136 | .observe(handle_out_system) 137 | .observe(handle_drag_system); 138 | }); 139 | 140 | commands.spawn(( 141 | PointLight::default(), 142 | Transform::from_translation(Vec3::new(5.0, 3.0, 10.0)), 143 | )); 144 | 145 | let camera_transform = Transform::from_xyz(1.0, 1.5, 2.5).looking_at(Vec3::ZERO, Vec3::Y); 146 | commands.spawn((Camera3d::default(), camera_transform)); 147 | 148 | commands.insert_resource(material_handles); 149 | } 150 | 151 | fn draw_gizmos_system( 152 | mut gizmos: Gizmos, 153 | egui_mesh_query: Query<&Transform, With>, 154 | ) -> Result { 155 | let egui_mesh_transform = egui_mesh_query.single()?; 156 | gizmos.axes(*egui_mesh_transform, 0.1); 157 | 158 | Ok(()) 159 | } 160 | 161 | fn handle_over_system( 162 | over: Trigger>, 163 | mut mesh_material_query: Query<&mut MeshMaterial3d>, 164 | material_handles: Res, 165 | ) { 166 | let Ok(mut material) = mesh_material_query.get_mut(over.target) else { 167 | return; 168 | }; 169 | *material = MeshMaterial3d(material_handles.hovered.clone()); 170 | } 171 | 172 | fn handle_out_system( 173 | out: Trigger>, 174 | mut mesh_material_query: Query<&mut MeshMaterial3d>, 175 | material_handles: Res, 176 | ) { 177 | let Ok(mut material) = mesh_material_query.get_mut(out.target) else { 178 | return; 179 | }; 180 | *material = MeshMaterial3d(material_handles.normal.clone()); 181 | } 182 | 183 | fn handle_drag_system( 184 | drag: Trigger>, 185 | window: Single<&Window, With>, 186 | mut egui_mesh_transform: Single<&mut Transform, With>, 187 | // Need to specify `Without` for `camera_query` and `egui_mesh_query` to be disjoint. 188 | camera_transform: Single<&Transform, (With, Without)>, 189 | ) { 190 | let Some(delta_normalized) = Vec3::new(drag.delta.y, drag.delta.x, 0.0).try_normalize() else { 191 | return; 192 | }; 193 | 194 | let angle = Vec2::new( 195 | drag.delta.x / window.physical_width() as f32, 196 | drag.delta.y / window.physical_height() as f32, 197 | ) 198 | .length() 199 | * std::f32::consts::PI 200 | * 2.0; 201 | let frame_delta = Quat::from_axis_angle(delta_normalized, angle); 202 | 203 | let camera_rotation = camera_transform.rotation; 204 | egui_mesh_transform.rotation = 205 | camera_rotation * frame_delta * camera_rotation.conjugate() * egui_mesh_transform.rotation; 206 | } 207 | -------------------------------------------------------------------------------- /examples/render_to_image_widget.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | prelude::*, 3 | render::{ 4 | camera::RenderTarget, 5 | render_resource::{ 6 | Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, 7 | }, 8 | view::RenderLayers, 9 | }, 10 | }; 11 | use bevy_egui::{egui::Widget, EguiContextPass, EguiContexts, EguiPlugin, EguiUserTextures}; 12 | 13 | fn main() { 14 | App::new() 15 | .add_plugins(DefaultPlugins) 16 | .add_plugins(EguiPlugin::default()) 17 | .add_systems(Startup, setup) 18 | .add_systems( 19 | EguiContextPass, 20 | (rotator_system, render_to_image_example_system), 21 | ) 22 | .run(); 23 | } 24 | 25 | // Marks the preview pass cube. 26 | #[derive(Component)] 27 | struct PreviewPassCube; 28 | 29 | // Marks the main pass cube, to which the material is applied. 30 | #[derive(Component)] 31 | struct MainPassCube; 32 | 33 | #[derive(Deref, Resource)] 34 | struct CubePreviewImage(Handle); 35 | 36 | fn setup( 37 | mut egui_user_textures: ResMut, 38 | mut commands: Commands, 39 | mut meshes: ResMut>, 40 | mut materials: ResMut>, 41 | mut images: ResMut>, 42 | ) { 43 | let size = Extent3d { 44 | width: 512, 45 | height: 512, 46 | ..default() 47 | }; 48 | 49 | // This is the texture that will be rendered to. 50 | let mut image = Image { 51 | texture_descriptor: TextureDescriptor { 52 | label: None, 53 | size, 54 | dimension: TextureDimension::D2, 55 | format: TextureFormat::Bgra8UnormSrgb, 56 | mip_level_count: 1, 57 | sample_count: 1, 58 | usage: TextureUsages::TEXTURE_BINDING 59 | | TextureUsages::COPY_DST 60 | | TextureUsages::RENDER_ATTACHMENT, 61 | view_formats: &[], 62 | }, 63 | ..default() 64 | }; 65 | 66 | // fill image.data with zeroes 67 | image.resize(size); 68 | 69 | let image_handle = images.add(image); 70 | egui_user_textures.add_image(image_handle.clone()); 71 | commands.insert_resource(CubePreviewImage(image_handle.clone())); 72 | 73 | let cube_handle = meshes.add(Cuboid::new(4.0, 4.0, 4.0)); 74 | let default_material = StandardMaterial { 75 | base_color: Color::srgb(0.8, 0.7, 0.6), 76 | reflectance: 0.02, 77 | unlit: false, 78 | ..default() 79 | }; 80 | let preview_material_handle = materials.add(default_material.clone()); 81 | 82 | // This specifies the layer used for the preview pass, which will be attached to the preview pass camera and cube. 83 | let preview_pass_layer = RenderLayers::layer(1); 84 | 85 | // The cube that will be rendered to the texture. 86 | commands 87 | .spawn(( 88 | Mesh3d(cube_handle), 89 | MeshMaterial3d(preview_material_handle), 90 | Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)), 91 | )) 92 | .insert(PreviewPassCube) 93 | .insert(preview_pass_layer.clone()); 94 | 95 | // The same light is reused for both passes, 96 | // you can specify different lights for preview and main pass by setting appropriate RenderLayers. 97 | commands 98 | .spawn(( 99 | PointLight::default(), 100 | Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), 101 | )) 102 | .insert(RenderLayers::default().with(1)); 103 | 104 | commands 105 | .spawn(( 106 | Camera3d::default(), 107 | Camera { 108 | // render before the "main pass" camera 109 | order: -1, 110 | target: RenderTarget::Image(image_handle.into()), 111 | clear_color: ClearColorConfig::Custom(Color::srgba(1.0, 1.0, 1.0, 0.0)), 112 | ..default() 113 | }, 114 | Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) 115 | .looking_at(Vec3::default(), Vec3::Y), 116 | )) 117 | .insert(preview_pass_layer); 118 | 119 | let cube_size = 4.0; 120 | let cube_handle = meshes.add(Cuboid::new(cube_size, cube_size, cube_size)); 121 | 122 | let main_material_handle = materials.add(default_material); 123 | 124 | // Main pass cube. 125 | commands 126 | .spawn(( 127 | Mesh3d(cube_handle), 128 | MeshMaterial3d(main_material_handle), 129 | Transform { 130 | translation: Vec3::new(0.0, 0.0, 1.5), 131 | rotation: Quat::from_rotation_x(-std::f32::consts::PI / 5.0), 132 | ..default() 133 | }, 134 | )) 135 | .insert(MainPassCube); 136 | 137 | // The main pass camera. 138 | commands.spawn(( 139 | Camera3d::default(), 140 | Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)).looking_at(Vec3::default(), Vec3::Y), 141 | )); 142 | } 143 | 144 | fn render_to_image_example_system( 145 | cube_preview_image: Res, 146 | preview_cube_query: Query<&MeshMaterial3d, With>, 147 | main_cube_query: Query<&MeshMaterial3d, With>, 148 | mut materials: ResMut>, 149 | mut contexts: EguiContexts, 150 | ) -> Result { 151 | let cube_preview_texture_id = contexts.image_id(&cube_preview_image).unwrap(); 152 | let preview_material_handle = preview_cube_query.single()?; 153 | let preview_material = materials.get_mut(preview_material_handle).unwrap(); 154 | 155 | let ctx = contexts.ctx_mut(); 156 | let mut apply = false; 157 | egui::Window::new("Cube material preview").show(ctx, |ui| { 158 | ui.image(egui::load::SizedTexture::new( 159 | cube_preview_texture_id, 160 | egui::vec2(300., 300.), 161 | )); 162 | egui::Grid::new("preview").show(ui, |ui| { 163 | ui.label("Base color:"); 164 | color_picker_widget(ui, &mut preview_material.base_color); 165 | ui.end_row(); 166 | 167 | ui.label("Emissive:"); 168 | let mut emissive_color = Color::from(preview_material.emissive); 169 | color_picker_widget(ui, &mut emissive_color); 170 | preview_material.emissive = emissive_color.into(); 171 | ui.end_row(); 172 | 173 | ui.label("Perceptual roughness:"); 174 | egui::Slider::new(&mut preview_material.perceptual_roughness, 0.089..=1.0).ui(ui); 175 | ui.end_row(); 176 | 177 | ui.label("Reflectance:"); 178 | egui::Slider::new(&mut preview_material.reflectance, 0.0..=1.0).ui(ui); 179 | ui.end_row(); 180 | 181 | ui.label("Unlit:"); 182 | ui.checkbox(&mut preview_material.unlit, ""); 183 | ui.end_row(); 184 | }); 185 | 186 | apply = ui.button("Apply").clicked(); 187 | }); 188 | 189 | if apply { 190 | let material_clone = preview_material.clone(); 191 | 192 | let main_material_handle = main_cube_query.single()?; 193 | materials.insert(main_material_handle, material_clone); 194 | } 195 | 196 | Ok(()) 197 | } 198 | 199 | fn color_picker_widget(ui: &mut egui::Ui, color: &mut Color) -> egui::Response { 200 | let [r, g, b, a] = Srgba::from(*color).to_f32_array(); 201 | let mut egui_color: egui::Rgba = egui::Rgba::from_srgba_unmultiplied( 202 | (r * 255.0) as u8, 203 | (g * 255.0) as u8, 204 | (b * 255.0) as u8, 205 | (a * 255.0) as u8, 206 | ); 207 | let res = egui::widgets::color_picker::color_edit_button_rgba( 208 | ui, 209 | &mut egui_color, 210 | egui::color_picker::Alpha::Opaque, 211 | ); 212 | let [r, g, b, a] = egui_color.to_srgba_unmultiplied(); 213 | *color = Color::srgba( 214 | r as f32 / 255.0, 215 | g as f32 / 255.0, 216 | b as f32 / 255.0, 217 | a as f32 / 255.0, 218 | ); 219 | res 220 | } 221 | 222 | // Rotates the cubes. 223 | #[allow(clippy::type_complexity)] 224 | fn rotator_system( 225 | time: Res