11380 Commits

Author SHA1 Message Date
Antony 4a0924e553 feat: expose MouseWheel::touch_phase (#23897)
# Objective

- `winit::event::WindowEvent::MouseWheel::phase` is not exposed
through`bevy_input::mouse::MouseWheel`.
- I hit this when mapping bevy mouse wheel events to `egui`, which
expects a touch phase parameter.

## Solution

- Add `bevy_input::mouse::MouseWheel::phase`.
- For completeness, also add
`bevy_picking::pointer;:PointerAction::Scroll::phase` and
`bevy_picking::events::Scroll::phase`.

## Testing

- `cargo run --example mouse_input_events` with a touch pad and without.
- Observed the `Started`, `Moved` and `Ended` phases showing up
properly.
---

## Future Work

The gesture types should also have `TouchPhase` exposed on them, but
doing so turns this PR quite a bit larger, so I've left them out.
2026-04-20 20:01:36 +00:00
Jordan Halase 9eed7a39a1 Fix Feathers radio groups, add radio_self_update() (#23890)
# Objective

No helper nor example code exists for handling multiple radio button
groups. Currently if a user tries to use existing feathers_gallery code
to create multiple radio groups, radio buttons from all other groups
will become unchecked.

## Solution

- Within queries, only update the radio buttons inside their own group
- Add `radio_self_update()` convenience function to encapsulate this
desired behavior

## Testing

Manually tested inside feathers_gallery example
2026-04-20 05:08:41 +00:00
Patrick Walton 035d8ae5bd Only calculate the world-space mesh center for an object when we know that object is transparent. (#23711)
PR #22041 made us use the center of the mesh AABB as the pivot point for
transparent sorting. This is fine in and of itself, but the way it was
implemented involved adding a matrix multiplication to
`collect_meshes_for_gpu_building` for all meshes, not just transparent
meshes. It also added several fields to `RenderMeshInstance` and related
instances for each mesh, even opaque ones. Since most meshes are opaque,
and `collect_meshes_for_gpu_building` is a performance-critical system,
this doesn't strike me as the right tradeoff.

This PR moves the calculation of the mesh center to
`queue_material_meshes`, to take place only after a mesh has been deemed
to be transparent. Not only does this make
`collect_meshes_for_gpu_building` faster, but it also allows us to
remove the various `center` fields, which stored redundant information.
Note that this comes with two tradeoffs:

1. The transparent sorting no longer takes a custom `Aabb` component on
the mesh into account. I doubt anybody was relying on this behavior.

2. We do have to calculate the AABB for the mesh when importing it to
the render world for the first time.

On `bevy_city --size 90 --no-cpu-culling`, this reduces the time spent
in `collect_meshes_for_gpu_building` after the loading screen from mean
84.85 ms, median 73.4 ms to mean 70.62 ms, median 72.5 ms.

Before this PR:
<img width="2756" height="1800" alt="Screenshot 2026-04-07 173043"
src="https://github.com/user-attachments/assets/3157b58c-b4f1-43db-8157-390e5c9c6ff0"
/>

After this PR:
<img width="2756" height="1800" alt="Screenshot 2026-04-07 172952"
src="https://github.com/user-attachments/assets/fda7100b-7695-4226-99e6-71b4c168f980"
/>
2026-04-20 02:23:22 +00:00
Carter Anderson 6e9522c5af Asset Value Templates via asset_value() and HandleTemplate::Value (#23839)
# Objective

Temporary simple / suboptimal solution to #23822

It would be very nice to be able to define assets inline in BSN.

## Solution

- Add a new `HandleTemplate::Value` variant, which contains an
`Arc<Mutex<AssetOrHandle<T>>>`
- This template, when first applied, will lock the mutex, insert the
asset value as a new asset, cache the handle in the
`Arc<Mutex<HandleOrTemplate>` and return the handle. Future calls will
lock the mutex and return the cached handle.
- Add `asset_value(SOME_ASSET)` function to improve ergonomics.
- Port `3d_scene` to illustrate

Doing a lock on every spawn is obviously suboptimal. The long term plan
for "inline assets in BSN" is defined in #23822 and will not use this
locking approach.
2026-04-20 00:52:10 +00:00
ickshonpe 8b8a432ab4 EditableText change detection using parley::Generation (#23785)
# Objective

Improved change detection for `EditableText`.

Fixes #23793

## Solution

* Remove the `text_edited` field from `EditableText` that was used for
manual change detection.
* Use the `PlainEditor`'s `Generation` to track changes.
* New component `EditableTextGeneration`. Newtypes `parley::Generation`.
Stores the generation from the last `TextLayoutInfo` update.
* If `EditableText::editor`'s and `EditableTextGeneration`'s generation
values aren't equal, reupdate `TextLayoutInfo`.
* Added support for `TextLayout::justify`.
* Split up `editable_text_system` into two systems
`update_editable_text_styles` and `update_editable_text_layout`. The
text input's style values need to be updated before layout, so the
measure func returns the correct size for the text layout.
* New `EditableText` `testbed_ui` scene.
* Added two numeric inputs to `multiline_text_input` that allow you to
set the height of the multiline input and its fontsize.
* Update selection rects on all changes, not just when a text input is
focused.

## Testing

```
cargo run --example multiline_text_input
```

The cursor appears to be missing for the numeric inputs in the example
but it isn't. The cursor gets clipped because it's at the end of the
right aligned input value text. If you press left it comes into view.
Cursor and scrolling behaviour needs some adjustments but that's out of
the scope of this PR.
2026-04-19 18:38:47 +00:00
Carter Anderson aa5322ed0f BSN: scene.spawn() system ergonomics (#23868)
# Objective

Spawning a scene on startup is a common pattern. Lets make it easier to
do so!

## Solution

- Add `SpawnSystem` and `SpawnListSystem` traits that are implemented
for functions that return scenes / scene lists, and return a system that
spawns the scene / handles errors.

### Before
```rust
fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .run();
}

fn setup(world: &mut World) -> Result {
    world.spawn_scene_list(bsn_list![Camera2d, ui()])?;
    Ok(())
}
```

### After
```rust
fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, scene.spawn())
        .run();
}

fn scene() -> impl SceneList {
    bsn_list![Camera2d, ui()]
}
```

This cuts out some boilerplate. It also further encourages people to
define standalone "scene functions" rather than embedding them in code,
which is generally a good pattern.
2026-04-19 17:25:21 +00:00
shunkie c847aafdbb Rename MeshPipelineSet to MeshPipelineSystems (#23823)
# Objective

According to #18900 , rename `MeshPipelineSet` to `MeshPipelineSystems`
for a consistent naming convention.
`MeshPipelineSet` was introduced in this cycle, so no migration guide
required.

## Testing

```
cargo run --example specialized_mesh_pipeline
```
2026-04-19 17:06:23 +00:00
JMS55 01804ded1c Solari: Minor bugfix (#23872)
Co-authored-by: François Mockers <francois.mockers@vleue.com>
2026-04-19 17:06:17 +00:00
ickshonpe 42aeb763c1 ComputedStackIndex component (#23878)
# Objective

Split off the stack index field from `ComputedNode` into its own
specialized component.
* Makes for a cleaner UI schedule and a clearer division of
responsibilities.
* More fine-grained change detection, might allow for some new
optimisations.

Fixes #23862

## Solution

* Remove the `stack_index` field and its accessor from `ComputedNode`.
* New component `ComputedStackIndex`, newtyping a `u32`.
* Require `ComputedStackIndex` on `ComputedNode`. 
* In `ui_stack_system`, query for and update `ComputedStackIndex`
instead of `ComputedNode`.
* In rendering add queries for `ComputedStackIndex`.
* Removed the ambiguity supression for `ui_stack_system`, as no longer
needed.

Notes
* `ui_stack_system` should probably replace its `With<Node>` query
filters with `With<ComputedStackIndex>`. It uses the ghost hierarchy
navigation params though, which complicates things, so I left them alone
here.
* In the future it might make sense to make the value optional, with
`None` indicating the UI node was removed from the layout via
`Display::None`.

---------

Co-authored-by: François Mockers <francois.mockers@vleue.com>
2026-04-19 17:06:11 +00:00
Jordan Halase 6b2ce7cb8c Add style tokens to Feathers Slider (#23883)
# Objective

Continuation of https://github.com/bevyengine/bevy/pull/23820,
https://github.com/bevyengine/bevy/pull/23830,
https://github.com/bevyengine/bevy/pull/23869

This adds style tokens to the slider, and adds Pressed events to do so.

## Showcase

No visual change aside from adding hover and pressed style tokens.

<img width="373" height="35" alt="Screenshot 2026-04-18 at 8 05 17 PM"
src="https://github.com/user-attachments/assets/d9fb38f2-10d9-437d-b776-e6d74fdc3756"
/>
2026-04-19 17:06:04 +00:00
Jordan Halase 6af4a0329c Fix toggle behavior and add styling to Feathers Radio Buttons (#23869)
# Objective

Continuation of https://github.com/bevyengine/bevy/pull/23820 and
https://github.com/bevyengine/bevy/pull/23830

This fixes the clicking behavior to toggle on mouse-release, or on
mouse-press with the `ActivateOnPress` component. More styling tokens
are also added for transition states.

---

## Showcase

### Before

<img width="91" height="91" alt="Screenshot 2026-04-17 at 11 32 39 PM"
src="https://github.com/user-attachments/assets/b1b8832d-7aaa-45ee-a641-4c100d44a232"
/>

### After

<img width="91" height="91" alt="Screenshot 2026-04-17 at 11 13 33 PM"
src="https://github.com/user-attachments/assets/d3cf5bf6-1dfe-4d88-8dd1-fc719449e7b5"
/>

(Not pictured: new intermediate styling for mouse press)

---------

Co-authored-by: François Mockers <francois.mockers@vleue.com>
2026-04-19 15:56:24 +00:00
Mark Old 334c242c96 Enable dynamic triggers for observers (#23870)
# Objective

Finish the bevy_ecs dynamic story. Currently you can register observers
with dynamic triggers and runners, but there's no way to actually
trigger these observers dynamically.

## Solution

Adds three new unsafe `World` functions:
- `trigger_dynamic()`
- `trigger_dynamic_targets()`
- `trigger_dynamic_targets_components()`

These enable observers to be triggered with untyped events and trigger
data. Their implementations are just wiring up some existing internal
structure. Structurally, they are based on their non-dynamic
counterparts.

Also exposes `EventKey::new()` and `EventKey::component_id()` for
constructing event keys from dynamic `ComponentId`s.

## Testing

Several new tests

## Showcase

See updated example
2026-04-19 15:55:59 +00:00
andriyDev 15a6b5eec5 Handle the case where there is no resume time. (#23885)
# Objective

- Fix the case where the UpdateMode::reactive is set to Duration::MAX.

## Solution

- When `StartCause::WaitCancelled::requested_resume` is `None`, always
report this case as not having elapsed the wait time.

## Testing

- Ran the desktop_request_redraw with
`UpdateMode::reactive(Duration::MAX)` and now it updates only when a
redraw happens or a window even happens!
2026-04-19 08:21:40 +00:00
François Mockers ec2b9dba62 can have observers through BRP (#23800)
# Objective

- Observe events through BRP

## Solution

- Add a observe + watch method to observe events
- remote observer systems are limited to only the event as a parameter,
processing it will have to happen on the client side
- depends on #23797

## Testing

- CI with new test

---------

Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com>
2026-04-19 08:21:18 +00:00
Rostyslav Toch 60a860d605 Disable VALIDATION_INDIRECT_CALL by default in release builds for wgpu (#23879)
**Objective**

Improves performance in release builds by disabling
`VALIDATION_INDIRECT_CALL` wgpu instance default flag. Currently, even
release builds have this validation enabled
([link](https://github.com/gfx-rs/wgpu/blob/ff438f3cadb8a25e0f36409566a6fb50f6707e02/wgpu-types/src/instance.rs#L253-L264)),
causing unnecessary compute passes that add sometimes `~13%` CPU
overhead in stress tests.

## Solution
Modified `crates/bevy_render/src/settings.rs` to explicitly remove
`VALIDATION_INDIRECT_CALL` in release builds. Users can still apply
environment variable overrides by using `WGPU_VALIDATION_INDIRECT_CALL`
([link](https://github.com/gfx-rs/wgpu/blob/ff438f3cadb8a25e0f36409566a6fb50f6707e02/wgpu-types/src/instance.rs#L313-L315)).

This ensures:
- **Debug builds**: Unchanged, indirect validation is enabled.
- **Release builds**: Indirect validation is disabled by default.
- **User override**: Users can re-enable in release build by using
`WGPU_VALIDATION_INDIRECT_CALL=1` environment variable.

## Testing

Tested on macOS with stress test scene:
- **Command**: `cargo run --profile profiling --example many_materials`
and `cargo run --profile profiling --example many_shadow_lights -- -m 50
-l 50`
- **Without fix** (env var off): ~132 FPS on
[many_materials](https://github.com/bevyengine/bevy/blob/main/examples/stress_tests/many_materials.rs)
and ~13FPS on
[many_shadow_lights](https://github.com/CrazyRoka/bevy/blob/many_shadow_lights/examples/stress_tests/many_shadow_lights.rs)
- **With fix** (default): ~148 FPS on
[many_materials](https://github.com/bevyengine/bevy/blob/main/examples/stress_tests/many_materials.rs)
and ~18FPS on
[many_shadow_lights](https://github.com/CrazyRoka/bevy/blob/many_shadow_lights/examples/stress_tests/many_shadow_lights.rs)
- **With WGPU_VALIDATION_INDIRECT_CALL=1**: produces same result as
"without fix" - 132FPS and 13FPS

The improvement is visible when comparing release build with vs without
`WGPU_VALIDATION_INDIRECT_CALL=1`. The fix aligns the default with the
expected behavior - validation enabled in debug, disabled in release.

**Tested on**: 

```
SystemInfo { os: "macOS 15.3.1 Sequoia", kernel: "24.3.0", cpu: "Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz", core_count: "6", memory: "16.0 GiB" }
AdapterInfo { name: "AMD Radeon Pro 5300M", vendor: 0, device: 0, device_type: DiscreteGpu, device_pci_bus_id: "", driver: "", driver_info: "", backend: Metal, subgroup_min_size: 4, subgroup_max_size: 64, transient_saves_memory: false }
```
---

### Profiling results before fix

It's clearly visible that `inject_validation_pass takes` ~13% CPU time.

<img width="1427" height="499" alt="image"
src="https://github.com/user-attachments/assets/734a93a7-8988-4a4a-a295-bef64a00ff68"
/>

Co-authored-by: François Mockers <francois.mockers@vleue.com>
2026-04-19 08:18:08 +00:00
andriyDev 155ad034e6 Resolve "strict ambiguities" between font systems. (#23856)
# Objective

- Resolve 2 "strict ambiguities" for #23843.
- TL;DR the systems update_text_2d_layout, editable_text_system, and
update_editable_text_content_size are ambiguous, but these ambiguities
are "accidentally" solved by automatically inserted apply_deferred
stages.

## Solution

- Make `editable_text_system` and `update_editable_text_content_size`
being ambiguous with `update_text2d_layout`.
    - This is the same as the status quo,
- In practice, we should never really have an entity that is both a
Text2D, and also a UI node. It's not exactly clear what the behavior
should even be in that case.

## Testing

- These remove the 2 ambiguities.
- We're just marking these as ambiguous, so there is nothing to really
test.

Co-authored-by: François Mockers <francois.mockers@vleue.com>
2026-04-19 08:14:04 +00:00
François Mockers ebe1117a3c MSRV CI job fail to use cargo rust-version dependency resolution (#23881)
# Objective

- CI fails because of a wrong MSRV issue

## Solution

- ~`cargo metadata` creates a lock file with the current stable version,
which is then used by the MSRV~ turns out `cargo metadata --no-deps`
doesn't lock the lock file unlike `cargo metadata`
- ~clear the stable lock file~
- also update the resolver to v3:
https://doc.rust-lang.org/edition-guide/rust-2024/cargo-resolver.html

## Testing

- CI
2026-04-19 03:35:59 +00:00
Rob Parrett d4379d437d Revert unintended example changes (#23877)
# Objective

Fixes #23873

## Solution

Partial git revert

## Testing

I did not test
2026-04-18 17:19:09 +00:00
ickshonpe bb1d51623b Fix UI accessibility transforms and update ordering (#23859)
# Objective

* Bevy UI's accessibility module doesn't set the transform on
accessiblity nodes, ignoring scaling and rotation.
* UI accessibility nodes should be updated before `bevy_winit` updates
the accesskit adaptors, otherwise there will be a frames delay.


## Solution

Replaced the `calc_bounds` systems with a new system
`sync_bounds_and_transforms`.

Each accesskit `Node` corresponding to an `AccessibleNode` UI entity is
now given object-centered coordinates for its bounding rect (instead of
window coordinates) and a transform.

Accesskit uses local transforms so if an accessible node also has an
accessible parent, its transform has to be recomputed relative to its
parent.

## Testing

I modified the button example so that accesskit integration is enabled
by default and the button is drawn at a 45 degrees angle.

```
cargo run --example button
```

Screen readers should only react when the pointer is directly over the
rotated button if the changes are working.

The example changes should be reverted before merging.
2026-04-18 04:35:53 +00:00
François Mockers 7a42034ffa make TypeIdMap iteration order respect its comment (#23864)
# Objective

- Fixes #23840
- Make `TypeIdMap` iteration order depends only on insertion/removals

## Solution

- Replace the backing `HashMap` by an `IndexMap`

## Testing

- CI
2026-04-18 04:35:29 +00:00
Dylan Sechet 5927c47b8d Use a LUT for F_AB (#23737)
# Objective

`F_AB` currently uses a polynomial approximation. This replaces it it
with a more precise LUT.

## Solution

Add a LUT computed using Monte-Carlo sampling. Generation script is
available
[here](https://gist.github.com/dylansechet/6086e924266caf8dd975c127f73f1e45).

The minimum value was clipped to `1e-6`, which modifies 0.4% of the
values that were below that but should help prevent bugs like #22833.

Open questions:
- Do we want to feature-gate this with the polynomial version as a
fallback?
## Testing

- White furnace still looks white
- Solari example runs
- Put a camera at grazing angles in the atmosphere example to make sure
we weren't reintroducing #22833
---

## Showcase

### F_AB tables

Current PR uses the Monte-Carlo based LUT, bevy currently uses the
polynomial approximation.

<img width="600" height="600" alt="f_ab_monte_carlo"
src="https://github.com/user-attachments/assets/f5f47c57-94a5-4a3b-886c-ae1cac72d3d5"
/>
<img width="600" height="600" alt="f_ab_polynomial_approx"
src="https://github.com/user-attachments/assets/f946a3a1-7206-4de2-952b-b28d588d0e11"
/>
<img width="600" height="600" alt="mae"
src="https://github.com/user-attachments/assets/70679caf-a9ec-4389-8bc8-d7b8fc24b3b1"
/>

### Solari white furnace
This new approximation significantly improves the solari white furnace.

Main:
<img width="1280" height="720" alt="solari_wf_main"
src="https://github.com/user-attachments/assets/3a5aed1b-8449-491f-bedf-1d73614b202d"
/>

With this PR:
<img width="1280" height="720" alt="solari_wf_fablut"
src="https://github.com/user-attachments/assets/e2d2c585-b201-4194-8c94-5376d8f24706"
/>


### LUT size vs error
I ended up going with 64x64.
<img width="2500" height="600" alt="size_error"
src="https://github.com/user-attachments/assets/3d94c2bf-1768-4a04-bc02-e621bf6ad3a9"
/>
2026-04-18 03:34:40 +00:00
charlotte 🌸 9f17c2e9b1 Make ClearColorConfig::None work correctly (esp in the presence of MSAA) (#23844)
Two separate issues:

1. Because we were re-creating the atomics used for tracking which main
texture is "active", post-process writes could be lost across frames
because the atomic was being reset. This is a bug even without MSAA.
2. We need to force writeback when the user opts into `Load` semantics
on their camera and MSAA is enabled, otherwise we'll throw away the main
target texture incorrectly.
2026-04-17 22:19:35 +00:00
andriyDev 7c5a1c316b Fix some UI widgets accidentally being ambiguous with recomputing the ComputedNode::stack_index, which is not used. (#23854)
# Objective

- Resolves 2 "strict ambiguities" from #23843.

## Solution

- Make these two widget systems ambiguous with the Stack stage.
- This only contains the ui_stack_system which only updates the
stack_index of the `ComputedNode`, which is unused by these systems. So
we don't care about order here.

## Testing

- Fixes the ambiguities in #23846.
2026-04-17 17:50:25 +00:00
ickshonpe 4c7e99cc56 feathers_gallery infinite update loop fix (#23827)
# Objective

In the `feathers_gallery` example, on a `TextEditChange`
`handle_hex_color_change` updates `DemoWidgetStates` which causes
`update_colors` to update the hex input triggering another
`TextEditChange`.

## Solution

Only update `DemoWidgetStates` in `handle_hex_color_change` if the new
color is different.
2026-04-17 15:57:02 +00:00
DaoLendaye 911820f5b7 FromTemplate for TilemapChunkTileData (#23852)
### In Tilemap usage, TilemapChunk implements FromTemplate, so it can be
used directly. However, TilemapChunkTileData cannot; it must be wrapped
in a template before use.

```rust
bsn! { 
    TilemapChunk { 
        chunk_size: chunk_size, 
        tile_display_size: tile_display_size, 
        tileset: { tileset.clone() }, 
        alpha_mode: AlphaMode2d::default(), 
    }
    template(|_| TilemapChunkTileData(tile_data.clone())) 
}
```
### Similar to the previous FontSize PR
(https://github.com/bevyengine/bevy/pull/23724), it can be used after
modification
```rust
bsn! { 
    TilemapChunk { 
        chunk_size: chunk_size, 
        tile_display_size: tile_display_size, 
        tileset: { tileset.clone() }, 
        alpha_mode: AlphaMode2d::default(), 
    } 
    TilemapChunkTileData({ tile_data.clone() }) 
}
```
2026-04-17 15:55:29 +00:00
Mat 068b5083be Update settings release notes author and PRs (#23857)
# Objective

Fixes https://github.com/bevyengine/bevy/issues/23814

It isn't clear whether we should be detailing what is now possible with
bevy settings (can use enums and tuple structs) in the release notes so
i've left it as is but happy to collate code examples and the toml they
result in and add this.
2026-04-17 15:51:15 +00:00
andriyDev 774e1fd32e Fix light bounding spheres being ambiguous with TransformSystems::Propagate. (#23847)
# Objective

- Resolves a "strict ambiguity" from #23843.
- TL;DR, adding systems before this schedule with `Commands` could cause
these systems being ambiguous.
- A step for #23846.

## Solution

- Explicitly order these lighting bounds updates after the transform
propagation.

## Testing

- This removes a "strict ambiguity" from #23846.
2026-04-17 15:49:35 +00:00
andriyDev 724bc10be6 Remove unused &Camera in update_gizmo_meshes. (#23853)
# Objective

- Removes an entirely unused component in `update_gizmo_meshes`.
- Resolve a "strict ambiguity" for #23846.

## Solution

- Remove the arg! Replace with `With<Camera>`.

## Testing

- Ran the `3d_gizmos` example. It still works!
2026-04-17 15:49:05 +00:00
Jordan Halase 6c867fa44a Fix and Polish Feathers Toggle Switch (#23830)
# Objective

This is a continuation of https://github.com/bevyengine/bevy/pull/23820
for toggle switches to fix their clicking behavior and add more style
tokens. Again, adding the `ActivateOnPress` component will change its
state instantly on mouse-press rather than mouse-release.

Styling is also changed for visibility and disambiguation.

---

## Showcase

### Before

<img width="120" height="31" alt="Screenshot 2026-04-16 at 1 53 12 PM"
src="https://github.com/user-attachments/assets/22fd0374-76cb-40bb-9428-cbd7c69f2e50"
/>

### After

<img width="159" height="31" alt="Screenshot 2026-04-16 at 3 32 21 PM"
src="https://github.com/user-attachments/assets/ecb5bee6-0dbd-4598-8487-cdb333575f78"
/>

(Not pictured: new intermediate styling for mouse press)
2026-04-17 02:37:18 +00:00
Carter Anderson 9c27c26805 Fix QueuedScenes not existing when spawning queued scenes (#23821)
# Objective

Fix #23731

## Solution

1. Slim down QueuedScenes to the minimum by breaking out "waiting
scenes" into its own resource
2. Store two copies of QueuedScene (one in the system and one in the
resource) and mem::swap them when the system runs

## Testing

Added a test, derived from the repro in #23731
2026-04-17 01:08:54 +00:00
François Mockers 16dc750aab fix freezes on AppExit on macos (#23838)
# Objective

- Fix #23313
- On macOS, sending `AppExit` sometimes freezes the app

## Solution

- This was caused by a race condition between the main thread and the
render thread both holding an `Arc` to the window
- Make sure more things drop on the main thread in the correct order

## Testing

- Ran the repro a few times, it used to freezes before, it doesn't after

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com>
2026-04-17 00:47:28 +00:00
ickshonpe 3d04eeb2b5 Allow Outline's to have negative offset values (#23835)
# Objective

Allow negative outline offset values

## Solution

Instead of clamping outline offsets to equal or above zero, clamp them
to equal or above the length of the node's shorter side.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2026-04-16 22:48:05 +00:00
Carter Anderson dc70b2e0fd Insert ResolvedScenes as dynamic bundles (#23808)
# Objective

Currently BSN inserts each component one-by-one. This is incredibly
expensive, as it forces an archetype move after every insert.

## Solution

Scene templates now write their outputs to reusable bundle scratch space
(which uses a bump allocator). The final bundle is written after all
components (including inherited components) are written to the bundle.

- Introduce a `BundleWriter`, which enables defining a dynamic bundle
using scratch space in a bump allocator. Currently this only supports
writing individual components to the `BundleWriter`, because supporting
arbitrary bundles is much harder (ex: dynamic bundle effects). This
sadly means that we are temporarily constraining both `bsn! { Node }`
and `bsn! { @SomeTemplate }` "template patches" to _require_ a component
output. Custom scene impls can still push arbitrary bundles, which are
inserted before the final dynamic bundle write. In practice I believe
this will cover the relevant use cases in the short term.
- We now skip duplicate insertions of components when spawning inherited
scenes.
- We now write empty RelationshipTarget collections, pre-allocated to
the correct size to the dynamic bundle, which both avoids another
archetype move and ensures we only allocate the inner relationship
target collection once.
- We now write the Relationship component to the dynamic bundle,
avoiding an archetype move
- I added a new "loaded scene inheritance" test variant, just to make
sure that case still works
- `ErasedTemplate` has been moved to `bevy_scene`, as it is now
"opinionated", more specific to `bevy_scene`, and less safe to use in a
general context

<img width="795" height="274" alt="image"
src="https://github.com/user-attachments/assets/68ea02c7-6b7e-4f7c-9474-603188fe6d0b"
/>

These are benchmarks that produce the same UI scene hierarchy through
different means (the benchmarks have been updated to have a few "matrix
wrapper" components to show the cost of archetype moves):
- `immediate_function_scene`: a test of going through the whole "scene
building" process, then spawning. this is the cost of ad-hoc scenes that
don't reuse work from inherited scenes
- `immediate_loaded_scene`: a test where we instantiate a bunch of
inherited scene instances, where the inherited scene has already been
computed / cached.
- `raw_bundle_no_scene`: just spawning the raw bundle directly
2026-04-16 22:34:28 +00:00
Alice Cecile 85212f763e Fix CI errors for Rust 1.95 (#23829)
# Objective

CI is
[broken](https://github.com/bevyengine/bevy/actions/runs/24511176705/job/71642514168?pr=23785)

## Solution

Look at the CI errors. Fix them.

## Testing

Let's see if CI is green!
2026-04-16 17:46:57 +00:00
Talin 6b4a003428 Disclosure toggle widget. (#23817)
# Objective

Part of #19236 

The "disclosure toggle" is a small checkbox-like widget which displays a
chevron that toggles between pointing right and pointing down.

## Testing

Manual testing
2026-04-16 04:39:26 +00:00
François Mockers cb3cba9c9b can reflect observers (#23797)
# Objective

- Reflect observers *so that I can observe my reflection*

## Solution

- Reflect observers

## Testing

- CI (and followup PR)
2026-04-16 02:13:01 +00:00
Jordan Halase e677babe59 Fix and Polish Feathers Checkbox (#23820)
# Objective

* Fix toggle behavior
* Add more style tokens and polish appearance

The original intention for the Feathers Checkbox was to toggle the
change immediately on mouse press, but currently only toggles on mouse
release, and lacks styling. Both behaviors are desirable depending on
context, such as being able to cancel an expensive checkmark click that
vastly changes render settings, so this pull request allows both, with
the mouse-press behavior enabled via the `ActivateOnPress` component.

In addition to adding styling, there will no longer a visible outline
for the checkbox in the checked state in the default theme. Having a
border and check symbol made it visually busy, especially at small
sizes.

---

## Showcase

### Before

<img width="161" height="83" alt="Screenshot 2026-04-15 at 7 58 09 PM"
src="https://github.com/user-attachments/assets/9cae7bcf-0b2a-46db-954a-b09332a7b06f"
/>

### After

<img width="161" height="108" alt="Screenshot 2026-04-15 at 8 34 57 PM"
src="https://github.com/user-attachments/assets/72d5083f-20db-45cd-8c1b-1ee711fcedf3"
/>

(Not pictured: new intermediate styling for mouse press)
2026-04-16 02:12:07 +00:00
Hive Mind 5802ab0a5a Fixed typo in EntityValidButNotSpawnedError description (#23819)
# Objective

- Fix a typo I found in Error's description

## Solution

- Fixed a typo in documentation

## Testing

- Its just a typo fix, checked if IDE displays correct text
2026-04-16 00:39:03 +00:00
JMS55 b9c385b01d Solari: Improve ReSTIR DI performance (#23809)
Remove a couple of redundant loads from global memory to improve perf.

Was like ~0.8ms -> ~0.5ms when testing solari's many lights on an RTX
5080.
2026-04-15 18:35:12 +00:00
andriyDev 61127f6d01 Replace all different load variants in AssetServer with a builder. (#23663)
# Objective

- In 0.18, we had 10 different functions that load assets (I'm not even
counting `load_folder`).
- In 0.19, we've even added `load_erased` - but it unfortunately doesn't
support all the features that the other variants support.
- We apparently needed `load_acquire_with_settings_override` which 1)
loads the asset, 2) uses the settings provided, 3) allows reading
unapproved asset paths, and 4) drops a guard once the load completes.
- That's fine if that's necessary. But we needed to create an explicit
variant for that.
- We need fewer load paths!

## Solution

- Create a builder.
- Store all these options dynamically instead of statically handling
each case.
- Have the caller choose a particular "kind" of load when they are
ready: `load`, `load_erased`, `load_untyped`, or `load_untyped_async`.
- I intentionally didn't provide a `load_async` or `load_erased_async`,
since those can be replicated using `load`/`load_erased` +
`AssetServer::wait_for_asset_id` to get the exact same effect.

I am also intentionally leaving `NestedLoader` untouched in this PR, but
a followup will duplicate this API for `NestedLoader`, which should make
it easier to understand.

Unlike the `NestedLoader` API, we aren't doing any type-state craziness,
so the docs are much more clear: users don't need to understand how
type-state stuff works, they just call the handful of methods on the
type. The "cost" here is we now need to be careful about including the
cross product of loads between static asset type, runtime asset type, or
dynamic asset type, crossed with deferred or async. In theory, if we
added more kinds on either side, we would need to expand this cross
product a lot. In practice though, it seems unlikely there will be any
more variants there. (maybe there could be a blocking variant? I don't
think this is a popular opinion though).

A big con here is some somewhat common calls are now more verbose.
Specifically, `asset_server.load_with_settings()` has become
`asset_server.load_builder().with_settings().load()`. I am not really
concerned about this though, since it really isn't that painful.

## Testing

- Tests all pass!

---

## Showcase

Now instead of:

```rust
asset_server.load_acquire_with_settings_override("some_path", |settings: &mut GltfLoaderSettings| { ... }, my_lock_guard);
```

You can instead do:

```rust
asset_server.load_builder()
    .with_guard(my_lock_guard)
    .with_settings(|settings: &mut GltfLoaderSettings| { ... })
    .override_unapproved()
    .load("some_path");
```

We also now cover more variants! For example, you can now load an asset
untyped with a guard, or with override_unapproved, etc.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2026-04-15 18:32:25 +00:00
ickshonpe 250a7c0e50 EditableText::visible_width (#23717)
# Objective

Horizontal equivalent of the `visible_lines` option.

Allow users to set the width of a text input widget by number of glyphs.
Or for proportional fonts, where the glyph widths are non-uniform, it
would use a multiple of the advance width for "0".

## Solution

* New `visible_width: Option<f32>` field on `EditableText`.
* `TextInputMeasure` now has both a width and height field and both are
`Option<f32>`.
* `update_editable_text_content_size` calculates the width if
`visible_width` is `Some`.
* The width is calculated by multiplying the advance width of "0" by the
`visible_width` (if set) and the current scale factor.

## Testing

I altered the hex input in the `feathers` example to so it's sized to
fit 10 characters (includes space for the cursor).

```
cargo run --example feathers --features="experimental_bevy_feathers"
```

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2026-04-15 17:02:41 +00:00
Mat 695c8e0481 Bevy settings support tuple structs and non-unit like enums (#23812)
# Objective

Implements part of https://github.com/bevyengine/bevy/issues/23302

For background on bevy settings see the initial PR:
https://github.com/bevyengine/bevy/pull/23034

## Solution
As we invoke the serde toml serializer:
Single field tuple structs are serialized as single values:
```rust
#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug, Default)]
#[reflect(Resource, SettingsGroup, Default)]
struct SingleFieldTupleStruct(u8);
```
Results in:
```toml
[single_field_tuple_struct]
single_field_tuple_struct = 0
```

Multi-field tuple structs are serialized as arrays of values, with
nested structs serialized as inline tables:
```rust
#[derive(Reflect, PartialEq, Debug, Default)]
#[reflect(Default)]
struct NestedStruct {
    a: u8,
    b: u16,
}

#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug, Default)]
#[reflect(Resource, SettingsGroup, Default)]
struct MultiFieldTupleStruct(u8, NestedStruct);
```
Results in:
```toml
[multi_field_tuple_struct]
multi_field_tuple_struct = [0, { a = 0, b = 0 }]
```

Non-unit enums retain the "key" field with it defaulting the resources
name, with it being serialized into the group table:
```rust
#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
#[reflect(Resource, SettingsGroup, Default)]
enum EnumSingleTupleVariant {
    A(u8),
}

impl Default for EnumSingleTupleVariant {
    fn default() -> Self {
        EnumSingleTupleVariant::A(0)
    }
}
```
Results in:
```toml
[enum_single_tuple_variant.enum_single_tuple_variant]
A = 0
```

The same multi-field tuple serialization occurs in enums:
```rust
#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
#[reflect(Resource, SettingsGroup, Default)]
enum EnumMultiNewTypeVariant {
    A(SingleFieldTupleStruct, MultiFieldTupleStruct),
}

impl Default for EnumMultiNewTypeVariant {
    fn default() -> Self {
        EnumMultiNewTypeVariant::A(
            SingleFieldTupleStruct(0),
            MultiFieldTupleStruct(0, NestedStruct { a: 0, b: 0 }),
        )
    }
}
```
Results in:
```toml
[enum_multi_new_type_variant.enum_multi_new_type_variant]
A = [0, [0, { a = 0, b = 0 }]]
```

Enums variants with fields are serialised with field entries:
```rust
#[derive(Resource, SettingsGroup, Reflect, PartialEq, Debug)]
#[reflect(Resource, SettingsGroup, Default)]
enum EnumStructVariant {
    A { x: u8, y: u16 },
}

impl Default for EnumStructVariant {
    fn default() -> Self {
        EnumStructVariant::A { x: 0, y: 0 }
    }
}
```
Results in:
```toml
[enum_struct_variant.enum_struct_variant.A]
x = 0
y = 0
```

## Notes
- I'm not 100% sold on the `[group_name.key_name]` for non-unit like
enums, its easy enough to not include the key for these types but i'll
keep in for the sake of discussion.
- Still not supporting Unions.
https://github.com/bevyengine/bevy/issues/23302 did not mention them but
I can add support for these in this PR or we can just not support them
at all. Open to discussion.

## Testing

- I've added to the above examples to the bevy settings test suite.
- I've ran the `persisting_preferences` example and cross referenced the
toml output in the file.
2026-04-15 16:23:47 +00:00
Talin 574e9356cb Decorative widgets (#23804)
# Objective

Part of #19236 

## Solution

A bunch of new, decorative widgets - see release note.

## Testing

Manual testing

## Showcase

<img width="384" height="207" alt="panes"
src="https://github.com/user-attachments/assets/0b0bb2ee-d520-4280-938e-e08d5c4e49d1"
/>

---------

Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com>
2026-04-15 15:42:55 +00:00
JMS55 c0e8e2998f Solari: Fix self-intersection artifacts (#23813)
Fix self intersection artifacts by offsetting the ray origin along the
geometric normal (or shading normal when geo normal not available, e.g.
from gbuffer).



Fixes flickering in Solari's many\_lights test scene.
2026-04-15 15:42:17 +00:00
Luo Zhihao 1fa8f03949 Use EntityHashMap and EntityHashSet when possible (#23810)
# Objective

Replace `HashMap<Entity, T>` and `HashSet<Entity>` with `EntityHashMap`
and `EntityHashSet` for better performance.

## Solution

Replace them.

## Testing

CI
2026-04-15 15:41:05 +00:00
François Mockers 2d39c4f8e4 Reflect screenshots (#23799)
# Objective

- Taking screenshots is cool
- Now let's do it through reflection!

## Solution

- Reflect event for ScreenshotCaptured, register Screenshot and
ScreenshotCaptured
- Also set the usage of the image of a screenshot to the main world. it
doesn't change anything but it's less wrong

## Testing

- CI
2026-04-14 21:45:55 +00:00
François Mockers 64a9dbc292 images are serializable through SerializedImage (#23798)
# Objective

- Some types wrap an image and can be useful to serialise 

## Solution

- Implement serialize on Image through SerializedImage

## Testing

- CI
2026-04-14 21:45:31 +00:00
Talin f05ba5e794 Missing license files. (#23806)
# Objective

Several crates that I created were missing license files.

## Solution

Add the license files
2026-04-14 20:00:15 +00:00
Talin 4553e66916 Add menu_divider widget. (#23788)
# Objective

Menu dividers have been requested by the editor ui design folks.

## Solution

A new `menu_divider` bsn function

## Testing

Manual testing

## Showcase

<img width="103" height="120" alt="menu_divider"
src="https://github.com/user-attachments/assets/382eb57b-732d-454b-a0df-96ebcc39df97"
/>
2026-04-14 03:13:49 +00:00
Talin f1a6b878ab Change feathers bsn APIs to make captions an explicit parameter. (#23787)
# Objective

As discussed in [this discord
thread](https://discord.com/channels/691052431525675048/1492688648360165406),
using the `Children` relation to append caption elements to feathers
widgets is dodgy.

## Solution

Make `caption` an explicit parameter for button, radio, and checkbox
widgets.

Also fixed some erroneous docs.

## Testing

Manual testing of feathers and bevy_city examples.

Note that there's no migration guide because this only affects the bsn
functions, which are new.
2026-04-13 23:48:40 +00:00