Commit graph

742 commits

Author SHA1 Message Date
ickshonpe
99b9a2fcd7
box shadow (#15204)
# Objective

UI box shadow support

Adds a new component `BoxShadow`:

```rust
pub struct BoxShadow {
    /// The shadow's color
    pub color: Color,
    /// Horizontal offset
    pub x_offset: Val,
    /// Vertical offset
    pub y_offset: Val,
    /// Horizontal difference in size from the occluding uninode
    pub spread_radius: Val,
    /// Blurriness of the shadow
    pub blur_radius: Val,
}
```

To use `BoxShadow`, add the component to any Bevy UI node and a shadow
will be drawn beneath that node.
Also adds a resource `BoxShadowSamples` that can be used to adjust the
shadow quality.

#### Notes
* I'm not super happy with the field names. Maybe we need a `struct Size
{ width: Val, height: Val }` type or something.
* The shader isn't very optimised but I don't see that it's too
important for now as the number of shadows being rendered is not going
to be massive most of the time. I think it's more important to get the
API and geometry correct with this PR.
* I didn't implement an inset property, it's not essential and can
easily be added in a follow up.
* Shadows are only rendered for uinodes, not for images or text.
* Batching isn't supported, it would need out-of-the-scope-of-this-pr
changes to the way the UI handles z-ordering for it to be effective.

# Showcase

```cargo run --example box_shadow -- --samples 4```

<img width="391" alt="br" src="https://github.com/user-attachments/assets/4e8add96-dc93-46e0-9e35-d995eb0943ad">

```cargo run --example box_shadow -- --samples 10```

<img width="391" alt="s10"
src="https://github.com/user-attachments/assets/ecb384c9-4012-4cd6-9dea-5180904bf28e">
2024-10-08 16:26:17 +00:00
Peter Hayman
1c3dee4a6c
fix: register_type::<ScrollPosition> (#15721)
# Objective

- register types that derive reflect.
2024-10-08 16:07:09 +00:00
Tim
d454db8e58
Rename the Pickable component and fix incorrect documentation (#15707)
# Objective

- Rename `Pickable` to `PickingBehavior` to counter the easily-made
assumption that the component is required. It is optional
- Fix and clarify documentation
- The docs in `crates/bevy_ui/src/picking_backend.rs` were incorrect
about the necessity of `Pickable`
- Plus two minor code quality changes in this commit
(7c2e75f48d)

Closes #15632
2024-10-07 17:09:57 +00:00
Trashtalk217
d1bd46d45e
Deprecate get_or_spawn (#15652)
# Objective

After merging retained rendering world #15320, we now have a good way of
creating a link between worlds (*HIYAA intensifies*). This means that
`get_or_spawn` is no longer necessary for that function. Entity should
be opaque as the warning above `get_or_spawn` says. This is also part of
#15459.

I'm deprecating `get_or_spawn_batch` in a different PR in order to keep
the PR small in size.

## Solution

Deprecate `get_or_spawn` and replace it with `get_entity` in most
contexts. If it's possible to query `&RenderEntity`, then the entity is
synced and `render_entity.id()` is initialized in the render world.

## Migration Guide

If you are given an `Entity` and you want to do something with it, use
`Commands.entity(...)` or `World.entity(...)`. If instead you want to
spawn something use `Commands.spawn(...)` or `World.spawn(...)`. If you
are not sure if an entity exists, you can always use `get_entity` and
match on the `Option<...>` that is returned.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-10-07 16:08:22 +00:00
Joona Aalto
25bfa80e60
Migrate cameras to required components (#15641)
# Objective

Yet another PR for migrating stuff to required components. This time,
cameras!

## Solution

As per the [selected
proposal](https://hackmd.io/tsYID4CGRiWxzsgawzxG_g#Combined-Proposal-1-Selected),
deprecate `Camera2dBundle` and `Camera3dBundle` in favor of `Camera2d`
and `Camera3d`.

Adding a `Camera` without `Camera2d` or `Camera3d` now logs a warning,
as suggested by Cart [on
Discord](https://discord.com/channels/691052431525675048/1264881140007702558/1291506402832945273).
I would personally like cameras to work a bit differently and be split
into a few more components, to avoid some footguns and confusing
semantics, but that is more controversial, and shouldn't block this core
migration.

## Testing

I ran a few 2D and 3D examples, and tried cameras with and without
render graphs.

---

## Migration Guide

`Camera2dBundle` and `Camera3dBundle` have been deprecated in favor of
`Camera2d` and `Camera3d`. Inserting them will now also insert the other
components required by them automatically.
2024-10-05 01:59:52 +00:00
vero
0b9a461d5d
Invert the dependency between bevy_animation and bevy_ui (#15634)
# Objective

- Improve crate dependency graph

## Solution

- Invert a dependency

## Testing

- Tested ui and animation examples
2024-10-04 01:27:20 +00:00
Tim
20dbf790a6
Get rid of unnecessary mutable access in ui picking backend (#15630)
## Solution

Yeet

## Testing

Tested the `simple_picking` example
2024-10-03 21:30:52 +00:00
ickshonpe
9bb27e97c5
Fix entity leak in extract_uinode_borders (#15626)
# Objective

Fix for another leak, this time when extracting outlines.
2024-10-03 18:15:32 +00:00
rudderbucky
2da8d17a44
Add try_despawn methods to World/Commands (#15480)
# Objective

Fixes #14511.

`despawn` allows you to remove entities from the world. However, if the
entity does not exist, it emits a warning. This may not be intended
behavior for many users who have use cases where they need to call
`despawn` regardless of if the entity actually exists (see the issue),
or don't care in general if the entity already doesn't exist.

(Also trying to gauge interest on if this feature makes sense, I'd
personally love to have it, but I could see arguments that this might be
a footgun. Just trying to help here 😄 If there's no contention I could
also implement this for `despawn_recursive` and `despawn_descendants` in
the same PR)

## Solution

Add `try_despawn`, `try_despawn_recursive` and
`try_despawn_descendants`.

Modify `World::despawn_with_caller` to also take in a `warn` boolean
argument, which is then considered when logging the warning. Set
`log_warning` to `true` in the case of `despawn`, and `false` in the
case of `try_despawn`.

## Testing

Ran `cargo run -p ci` on macOS, it seemed fine.
2024-10-03 16:21:05 +00:00
Tim
461305b3d7
Revert "Have EntityCommands methods consume self for easier chaining" (#15523)
As discussed in #15521

- Partial revert of #14897, reverting the change to the methods to
consume `self`
- The `insert_if` method is kept

The migration guide of #14897 should be removed
Closes #15521

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-10-02 12:47:26 +00:00
Viktor Gustavsson
f86ee32576
Add UI GhostNode (#15341)
# Objective

- Fixes #14826 
- For context, see #15238

## Solution
Add a `GhostNode` component to `bevy_ui` and update all the relevant
systems to use it to traverse for UI children.

- [x] `ghost_hierarchy` module
  - [x] Add `GhostNode`
- [x] Add `UiRootNodes` system param for iterating (ghost-aware) UI root
nodes
- [x] Add `UiChildren` system param for iterating (ghost-aware) UI
children
- [x] Update `layout::ui_layout_system`
  - [x] Use ghost-aware root nodes for camera updates
  - [x] Update and remove children in taffy
    - [x] Initial spawn
    - [x] Detect changes on nested UI children
- [x] Use ghost-aware children traversal in
`update_uinode_geometry_recursive`
- [x] Update the rest of the UI systems to use the ghost hierarchy
  - [x] `stack::ui_stack_system`
  - [x] `update::`
    - [x] `update_clipping_system`
    - [x] `update_target_camera_system`
  - [x] `accessibility::calc_name`

## Testing
- [x] Added a new example `ghost_nodes` that can be used as a testbed.
- [x] Added unit tests for _some_ of the traversal utilities in
`ghost_hierarchy`
- [x] Ensure this fulfills the needs for currently known use cases
  - [x] Reactivity libraries (test with `bevy_reactor`)
- [ ] Text spans (mentioned by koe [on
discord](https://discord.com/channels/691052431525675048/1285371432460881991/1285377442998915246))
  
---
## Performance
[See comment
below](https://github.com/bevyengine/bevy/pull/15341#issuecomment-2385456820)

## Migration guide
Any code that previously relied on `Parent`/`Children` to iterate UI
children may now want to use `bevy_ui::UiChildren` to ensure ghost nodes
are skipped, and their first descendant Nodes included.

UI root nodes may now be children of ghost nodes, which means
`Without<Parent>` might not query all root nodes. Use
`bevy_ui::UiRootNodes` where needed to iterate root nodes instead.

## Potential future work
- Benchmarking/optimizations of hierarchies containing lots of ghost
nodes
- Further exploration of UI hierarchies and markers for root nodes/leaf
nodes to create better ergonomics for things like `UiLayer` (world-space
ui)

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
2024-10-02 00:24:28 +00:00
UkoeHB
ead84e0e3d
Rename BreakLineOn to LineBreak (#15583)
# Objective

- Improve code quality in preparation for
https://github.com/bevyengine/bevy/discussions/15014

## Solution

- Rename BreakLineOn to LineBreak.

## Migration Guide

`BreakLineOn` was renamed to `LineBreak`, and paramters named
`linebreak_behavior` were renamed to `linebreak`.
2024-10-01 22:30:50 +00:00
ickshonpe
f53af2846c
Update the UI texture slice pipeline to work with the retained render world changes (#15578)
# Objective

Update the UI's texture slice extraction to work with the changes from
the retained render world PR (#15320).
2024-10-01 22:02:02 +00:00
Trashtalk217
56f8e526dd
The Cooler 'Retain Rendering World' (#15320)
- Adopted from #14449
- Still fixes #12144.

## Migration Guide

The retained render world is a complex change: migrating might take one
of a few different forms depending on the patterns you're using.

For every example, we specify in which world the code is run. Most of
the changes affect render world code, so for the average Bevy user who's
using Bevy's high-level rendering APIs, these changes are unlikely to
affect your code.

### Spawning entities in the render world

Previously, if you spawned an entity with `world.spawn(...)`,
`commands.spawn(...)` or some other method in the rendering world, it
would be despawned at the end of each frame. In 0.15, this is no longer
the case and so your old code could leak entities. This can be mitigated
by either re-architecting your code to no longer continuously spawn
entities (like you're used to in the main world), or by adding the
`bevy_render::world_sync::TemporaryRenderEntity` component to the entity
you're spawning. Entities tagged with `TemporaryRenderEntity` will be
removed at the end of each frame (like before).

### Extract components with `ExtractComponentPlugin`

```
// main world
app.add_plugins(ExtractComponentPlugin::<ComponentToExtract>::default());
```

`ExtractComponentPlugin` has been changed to only work with synced
entities. Entities are automatically synced if `ComponentToExtract` is
added to them. However, entities are not "unsynced" if any given
`ComponentToExtract` is removed, because an entity may have multiple
components to extract. This would cause the other components to no
longer get extracted because the entity is not synced.

So be careful when only removing extracted components from entities in
the render world, because it might leave an entity behind in the render
world. The solution here is to avoid only removing extracted components
and instead despawn the entire entity.

### Manual extraction using `Extract<Query<(Entity, ...)>>`

```rust
// in render world, inspired by bevy_pbr/src/cluster/mod.rs
pub fn extract_clusters(
    mut commands: Commands,
    views: Extract<Query<(Entity, &Clusters, &Camera)>>,
) {
    for (entity, clusters, camera) in &views {
        // some code
        commands.get_or_spawn(entity).insert(...);
    }
}
```
One of the primary consequences of the retained rendering world is that
there's no longer a one-to-one mapping from entity IDs in the main world
to entity IDs in the render world. Unlike in Bevy 0.14, Entity 42 in the
main world doesn't necessarily map to entity 42 in the render world.

Previous code which called `get_or_spawn(main_world_entity)` in the
render world (`Extract<Query<(Entity, ...)>>` returns main world
entities). Instead, you should use `&RenderEntity` and
`render_entity.id()` to get the correct entity in the render world. Note
that this entity does need to be synced first in order to have a
`RenderEntity`.

When performing manual abstraction, this won't happen automatically
(like with `ExtractComponentPlugin`) so add a `SyncToRenderWorld` marker
component to the entities you want to extract.

This results in the following code:
```rust
// in render world, inspired by bevy_pbr/src/cluster/mod.rs
pub fn extract_clusters(
    mut commands: Commands,
    views: Extract<Query<(&RenderEntity, &Clusters, &Camera)>>,
) {
    for (render_entity, clusters, camera) in &views {
        // some code
        commands.get_or_spawn(render_entity.id()).insert(...);
    }
}

// in main world, when spawning
world.spawn(Clusters::default(), Camera::default(), SyncToRenderWorld)
```

### Looking up `Entity` ids in the render world

As previously stated, there's now no correspondence between main world
and render world `Entity` identifiers.

Querying for `Entity` in the render world will return the `Entity` id in
the render world: query for `MainEntity` (and use its `id()` method) to
get the corresponding entity in the main world.

This is also a good way to tell the difference between synced and
unsynced entities in the render world, because unsynced entities won't
have a `MainEntity` component.

---------

Co-authored-by: re0312 <re0312@outlook.com>
Co-authored-by: re0312 <45868716+re0312@users.noreply.github.com>
Co-authored-by: Periwink <charlesbour@gmail.com>
Co-authored-by: Anselmo Sampietro <ans.samp@gmail.com>
Co-authored-by: Emerson Coskey <56370779+ecoskey@users.noreply.github.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Christian Hughes <9044780+ItsDoot@users.noreply.github.com>
2024-09-30 18:51:43 +00:00
ickshonpe
c5742ff43e
Simplified ui_stack_system (#9889)
# Objective

`ui_stack_system` generates a tree of `StackingContexts` which it then
flattens to get the `UiStack`.

But there's no need to construct a new tree. We can query for nodes with
a global `ZIndex`, add those nodes to the root nodes list and then build
the `UiStack` from a walk of the existing layout tree, ignoring any
branches that have a global `Zindex`.

Fixes #9877

## Solution

Split the `ZIndex` enum into two separate components, `ZIndex` and
`GlobalZIndex`

Query for nodes with a `GlobalZIndex`, add those nodes to the root nodes
list and then build the `UiStack` from a walk of the existing layout
tree, filtering branches by `Without<GlobalZIndex>` so we don't revisit
nodes.

```
cargo run --profile stress-test --features trace_tracy --example many_buttons
```

<img width="672" alt="ui-stack-system-walk-split-enum"
src="https://github.com/bevyengine/bevy/assets/27962798/11e357a5-477f-4804-8ada-c4527c009421">

(Yellow is this PR, red is main)

---

## Changelog
`Zindex`
* The `ZIndex` enum has been split into two separate components `ZIndex`
(which replaces `ZIndex::Local`) and `GlobalZIndex` (which replaces
`ZIndex::Global`). An entity can have both a `ZIndex` and
`GlobalZIndex`, in comparisons `ZIndex` breaks ties if two
`GlobalZIndex` values are equal.

`ui_stack_system`
* Instead of generating a tree of `StackingContexts`, query for nodes
with a `GlobalZIndex`, add those nodes to the root nodes list and then
build the `UiStack` from a walk of the existing layout tree, filtering
branches by `Without<GlobalZIndex` so we don't revisit nodes.

## Migration Guide

The `ZIndex` enum has been split into two separate components `ZIndex`
(which replaces `ZIndex::Local`) and `GlobalZIndex` (which replaces
`ZIndex::Global`). An entity can have both a `ZIndex` and
`GlobalZIndex`, in comparisons `ZIndex` breaks ties if two
`GlobalZindex` values are equal.

---------

Co-authored-by: Gabriel Bourgeois <gabriel.bourgeoisv4si@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
2024-09-30 18:43:57 +00:00
MiniaczQ
5289e18e0b
System param validation for observers, system registry and run once (#15526)
# Objective

Fixes #15394

## Solution

Observers now validate params.

System registry has a new error variant for when system running fails
due to invalid parameters.

Run once now returns a `Result<Out, RunOnceError>` instead of `Out`.
This is more inline with system registry, which also returns a result.

I'll address warning messages in #15500.

## Testing

Added one test for each case.

---

## Migration Guide

- `RunSystemOnce::run_system_once` and
`RunSystemOnce::run_system_once_with` now return a `Result<Out>` instead
of just `Out`

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Zachary Harrold <zac@harrold.com.au>
2024-09-30 01:00:39 +00:00
charlotte
df23b937cc
Make CosmicFontSystem and SwashCache pub resources. (#15479)
# Objective

In nannou, we'd like to be able to access the [outline
commands](https://docs.rs/cosmic-text/latest/cosmic_text/struct.SwashCache.html#method.get_outline_commands)
from swash, while still benefit from Bevy's management of font assets.

## Solution

Make `CosmicFontSystem` and  `SwashCache` pub resources.

## Testing

Ran some examples.
2024-09-28 00:00:27 +00:00
Zachary Harrold
d70595b667
Add core and alloc over std Lints (#15281)
# Objective

- Fixes #6370
- Closes #6581

## Solution

- Added the following lints to the workspace:
  - `std_instead_of_core`
  - `std_instead_of_alloc`
  - `alloc_instead_of_core`
- Used `cargo +nightly fmt` with [item level use
formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Item%5C%3A)
to split all `use` statements into single items.
- Used `cargo clippy --workspace --all-targets --all-features --fix
--allow-dirty` to _attempt_ to resolve the new linting issues, and
intervened where the lint was unable to resolve the issue automatically
(usually due to needing an `extern crate alloc;` statement in a crate
root).
- Manually removed certain uses of `std` where negative feature gating
prevented `--all-features` from finding the offending uses.
- Used `cargo +nightly fmt` with [crate level use
formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Crate%5C%3A)
to re-merge all `use` statements matching Bevy's previous styling.
- Manually fixed cases where the `fmt` tool could not re-merge `use`
statements due to conditional compilation attributes.

## Testing

- Ran CI locally

## Migration Guide

The MSRV is now 1.81. Please update to this version or higher.

## Notes

- This is a _massive_ change to try and push through, which is why I've
outlined the semi-automatic steps I used to create this PR, in case this
fails and someone else tries again in the future.
- Making this change has no impact on user code, but does mean Bevy
contributors will be warned to use `core` and `alloc` instead of `std`
where possible.
- This lint is a critical first step towards investigating `no_std`
options for Bevy.

---------

Co-authored-by: François Mockers <francois.mockers@vleue.com>
2024-09-27 00:59:59 +00:00
Mohamed Osama
4e7801388c
Rename UiPickingBackend to UiPickingBackendPlugin (#15462)
solves #15450
2024-09-27 00:50:06 +00:00
ickshonpe
0fe33c3bba
use precomputed border values (#15163)
# Objective

Fixes #15142

## Solution

* Moved all the UI border geometry calculations that were scattered
through the UI extraction functions into `ui_layout_system`.
* Added a `border: BorderRect` field to `Node` to store the border size
computed by `ui_layout_system`.
* Use the border values returned from Taffy rather than calculate them
ourselves during extraction.
* Removed the `logical_rect` and `physical_rect` methods from `Node` the
descriptions and namings are deceptive, it's better to create the rects
manually instead.
* Added a method `outline_radius` to `Node` that calculates the border
radius of outlines.
* For border values `ExtractedUiNode` takes `BorderRect` and
`ResolvedBorderRadius` now instead of raw `[f32; 4]` values and converts
them in `prepare_uinodes`.
* Removed some unnecessary scaling and clamping of border values
(#15142).
* Added a `BorderRect::ZERO` constant.
* Added an `outlined_node_size` method to `Node`.

## Testing

Added some non-uniform borders to the border example. Everything seems
to be in order:

<img width="626" alt="nub"
src="https://github.com/user-attachments/assets/258ed8b5-1a9e-4ac5-99c2-6bf25c0ef31c">

## Migration Guide

The `logical_rect` and `physical_rect` methods have been removed from
`Node`. Use `Rect::from_center_size` with the translation and node size
instead.

The types of the fields border and border_radius of `ExtractedUiNode`
have been changed to `BorderRect` and `ResolvedBorderRadius`
respectively.

---------

Co-authored-by: UkoeHB <37489173+UkoeHB@users.noreply.github.com>
Co-authored-by: akimakinai <105044389+akimakinai@users.noreply.github.com>
2024-09-26 23:10:35 +00:00
Clar Fon
efda7f3f9c
Simpler lint fixes: makes ci lints work but disables a lint for now (#15376)
Takes the first two commits from #15375 and adds suggestions from this
comment:
https://github.com/bevyengine/bevy/pull/15375#issuecomment-2366968300

See #15375 for more reasoning/motivation.

## Rebasing (rerunning)

```rust
git switch simpler-lint-fixes
git reset --hard main
cargo fmt --all -- --unstable-features --config normalize_comments=true,imports_granularity=Crate
cargo fmt --all
git add --update
git commit --message "rustfmt"
cargo clippy --workspace --all-targets --all-features --fix
cargo fmt --all -- --unstable-features --config normalize_comments=true,imports_granularity=Crate
cargo fmt --all
git add --update
git commit --message "clippy"
git cherry-pick e6c0b94f6795222310fb812fa5c4512661fc7887
```
2024-09-24 11:42:59 +00:00
Marco Buono
8e3db957c5
Add the ability to control font smoothing (#15368)
# Objective

- Fixes #10720
- Adds the ability to control font smoothing of rendered text

## Solution

- Introduce the `FontSmoothing` enum, with two possible variants
(`FontSmoothing::None` and `FontSmoothing::AntiAliased`):
- This is based on `-webkit-font-smoothing`, in line with our practice
of adopting CSS-like properties/names for UI;
- I could have gone instead for the [`font-smooth`
property](https://developer.mozilla.org/en-US/docs/Web/CSS/font-smooth)
that's also supported by browsers, but didn't since it's also
non-standard, has an uglier name, and doesn't allow controlling the type
of antialias applied.
- Having an enum instead of e.g. a boolean, leaves the path open for
adding `FontSmoothing::SubpixelAntiAliased` in the future, without a
breaking change;
- Add all the necessary plumbing to get the `FontSmoothing` information
to where we rasterize the glyphs and store them in the atlas;
- Change the font atlas key to also take into account the smoothing
setting, not only font and font size;
- Since COSMIC Text [doesn't support controlling font
smoothing](https://github.com/pop-os/cosmic-text/issues/279), we roll
out our own threshold-based “implementation”:
- This has the downside of **looking ugly for “regular” vector fonts**
⚠️, since it doesn't properly take the hinting information into account
like a proper implementation on the rasterizer side would.
- However, **for fonts that have been specifically authored to be pixel
fonts, (a common use case in games!) this is not as big of a problem**,
since all lines are vertical/horizontal, and close to the final pixel
boundaries (as long as the font is used at a multiple of the size
originally intended by the author)
- Once COSMIC exposes this functionality, we can switch to using it
directly, and get better results;
- Use a nearest neighbor sampler for atlases with font smoothing
disabled, so that you can scale the text via transform and still get the
pixelated look;
- Add a convenience method to `Text` for setting the font smoothing;
- Add a demonstration of using the `FontSmoothing` property to the
`text2d` example.

## Testing

- Did you test these changes? If so, how?
  - Yes. Via the `text2d`example, and also in my game.
- Are there any parts that need more testing?
- I'd like help from someone for testing this on devices/OSs with
fractional scaling (Android/Windows)
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
- Both via the `text2d` example and also by using it directly on your
projects.
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?
  - macOS

---

## Showcase

```rust
commands.spawn(Text2dBundle {
    text: Text::from_section("Hello, World!", default())
        .with_font_smoothing(FontSmoothing::None),
    ..default()
});
```
![Screenshot 2024-09-22 at 12 33
39](https://github.com/user-attachments/assets/93e19672-b8c0-4cba-a8a3-4525fe2ae1cb)

<img width="740" alt="image"
src="https://github.com/user-attachments/assets/b881b02c-4e43-410b-902f-6985c25140fc">

## Migration Guide

- `Text` now contains a `font_smoothing: FontSmoothing` property, make
sure to include it or add `..default()` when using the struct directly;
- `FontSizeKey` has been renamed to `FontAtlasKey`, and now also
contains the `FontSmoothing` setting;
- The following methods now take an extra `font_smoothing:
FontSmoothing` argument:
  - `FontAtlas::new()`
  - `FontAtlasSet::add_glyph_to_atlas()`
  - `FontAtlasSet::get_glyph_atlas_info()`
  - `FontAtlasSet::get_outlined_glyph_texture()`
2024-09-23 17:28:25 +00:00
Piefayth
55dddaf72e
UI Scrolling (#15291)
# Objective

- Fixes #8074 
- Adopts / Supersedes #8104

## Solution

Adapted from #8104 and affords the same benefits.

**Additions**
- [x] Update scrolling on relayout (height of node or contents may have
changed)
- [x] Make ScrollPosition component optional for ui nodes to avoid
checking every node on scroll
- [x] Nested scrollviews

**Omissions**
- Removed input handling for scrolling from `bevy_ui`. Users should
update `ScrollPosition` directly.

### Implementation

Adds a new `ScrollPosition` component. Updating this component on a
`Node` with an overflow axis set to `OverflowAxis::Scroll` will
reposition its children by that amount when calculating node transforms.
As before, no impact on the underlying Taffy layout.

Calculating this correctly is trickier than it was in #8104 due to
`"Update scrolling on relayout"`.

**Background**

When `ScrollPosition` is updated directly by the user, it can be
trivially handled in-engine by adding the parent's scroll position to
the final location of each child node. However, _other layout actions_
may result in a situation where `ScrollPosition` needs to be updated.
Consider a 1000 pixel tall vertically scrolling list of 100 elements,
each 100 pixels tall. Scrolled to the bottom, the
`ScrollPosition.offset_y` is 9000, just enough to display the last
element in the list. When removing an element from that list, the new
desired `ScrollPosition.offset_y` is 8900, but, critically, that is not
known until after the sizes and positions of the children of the
scrollable node are resolved.

All user scrolling code today handles this by delaying the resolution by
one frame. One notable disadvantage of this is the inability to support
`WinitSettings::desktop_app()`, since there would need to be an input
AFTER the layout change that caused the scroll position to update for
the results of the scroll position update to render visually.

I propose the alternative in this PR, which allows for same-frame
resolution of scrolling layout.

**Resolution**

_Edit: Below resolution is outdated, and replaced with the simpler usage
of taffy's `Layout::content_size`._

When recursively iterating the children of a node, each child now
returns a `Vec2` representing the location of their own bottom right
corner. Then, `[[0,0, [x,y]]` represents a bounding box containing the
scrollable area filled by that child. Scrollable parents aggregate those
areas into the bounding box of _all_ children, then consider that result
against `ScrollPosition` to ensure its validity.

In the event that resolution of the layout of the children invalidates
the `ScrollPosition` (e.g. scrolled further than there were children to
scroll to), _all_ children of that node must be recursively
repositioned. The position of each child must change as a result of the
change in scroll position.

Therefore, this implementation takes care to only spend the cost of the
"second layout pass" when a specific node actually had a
`ScrollPosition` forcibly updated by the layout of its children.


## Testing

Examples in `ui/scroll.rs`. There may be more complex node/style
interactions that were unconsidered.

---

## Showcase



![scroll](https://github.com/user-attachments/assets/1331138f-93aa-4a8f-959c-6be18a04ff03)

## Alternatives

- `bevy_ui` doesn't support scrolling.
- `bevy_ui` implements scrolling with a one-frame delay on reactions to
layout changes.
2024-09-23 17:17:58 +00:00
Benjamin Brienen
9386bd0114
feature gate picking backends (#15369)
# Objective

Fixes #15306

## Solution

- Add feature gate on the module and the place where each one is used
- Declare the features and make them default

## Testing

- CI
2024-09-22 19:35:15 +00:00
ickshonpe
48f2bd410b
RenderUiSystem::ExtractTextureSlice (#15332)
# Objective

Fixes #15330

## Solution
1. Add an `ExtractTextureSlice` variant to `RenderUiSystem`.
2. Add `RenderUiSystem::ExtractTextureSlice` to the `ExtractSchedule`
between `ExtractImages` and `ExtractBorders`.
3. Add `extract_ui_texture_slices` to the new `ExtractTextureSlice`
system set.

Which results in texture slice nodes being extracted before borders. No
more z-fighting, borders will always be drawn on top of texture-sliced
images.
2024-09-20 23:55:11 +00:00
Rich Churcher
fd329c0426
Allow to expect (adopted) (#15301)
# Objective

> Rust 1.81 released the #[expect(...)] attribute, which works like
#[allow(...)] but throws a warning if the lint isn't raised. This is
preferred to #[allow(...)] because it tells us when it can be removed.

- Adopts the parts of #15118 that are complete, and updates the branch
so it can be merged.
- There were a few conflicts, let me know if I misjudged any of 'em.

Alice's
[recommendation](https://github.com/bevyengine/bevy/issues/15059#issuecomment-2349263900)
seems well-taken, let's do this crate by crate now that @BD103 has done
the lion's share of this!

(Relates to, but doesn't yet completely finish #15059.)

Crates this _doesn't_ cover:

- bevy_input
- bevy_gilrs
- bevy_window
- bevy_winit
- bevy_state
- bevy_render
- bevy_picking
- bevy_core_pipeline
- bevy_sprite
- bevy_text
- bevy_pbr
- bevy_ui
- bevy_gltf
- bevy_gizmos
- bevy_dev_tools
- bevy_internal
- bevy_dylib

---------

Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com>
Co-authored-by: Ben Frankel <ben.frankel7@gmail.com>
Co-authored-by: Antony <antony.m.3012@gmail.com>
2024-09-20 19:16:42 +00:00
patrickariel
3efef59d83
Enable/disable UI anti-aliasing (#15170)
# Objective

Currently, UI is always rendered with anti-aliasing. This makes bevy's
UI completely unsuitable for art-styles that demands hard pixelated
edges, such as retro-style games.

## Solution

Add a component for disabling anti-aliasing in UI.

## Testing

In
[`examples/ui/button.rs`](15e246eff8/examples/ui/button.rs),
add the component to the camera like this:

```rust
use bevy::{prelude::*, ui::prelude::*};

commands.spawn((Camera2dBundle::default(), UiAntiAlias::Off));
```

The rounded button will now render without anti-aliasing.

## Showcase

An example of a rounded UI node rendered without anti-aliasing, with and
without borders:


![image](https://github.com/user-attachments/assets/ea797e40-bdaa-4ede-a0d3-c9a7eab95b6e)
2024-09-16 23:06:23 +00:00
Blazepaws
cb6ab16c97
Reflect derived traits on all components and resources: bevy_ui (#15231)
Solves https://github.com/bevyengine/bevy/issues/15187 for bevy_ui
2024-09-15 17:52:38 +00:00
ickshonpe
1b1105e327
Remove border radius scaling (#15173)
# Objective

Fixes #15142

Split this off from #15163 as it's a very simple fix.

## Solution

UiScale was applied twice to border radius, remove the second
application.

## Testing

You can use this modified button example from the issue for testing:

```
use bevy::{color::palettes::basic::*, prelude::*, winit::WinitSettings};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        // Only run the app when there is user input. This will significantly reduce CPU/GPU use.
        .insert_resource(UiScale(2.))
        .insert_resource(WinitSettings::desktop_app())
        .add_systems(Startup, setup)
        .add_systems(Update, button_system)
        .run();
}

const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);

fn button_system(
    mut interaction_query: Query<
        (
            &Interaction,
            &mut BackgroundColor,
            &mut BorderColor,
            &Children,
        ),
        (Changed<Interaction>, With<Button>),
    >,
    mut text_query: Query<&mut Text>,
) {
    for (interaction, mut color, mut border_color, children) in &mut interaction_query {
        let mut text = text_query.get_mut(children[0]).unwrap();
        match *interaction {
            Interaction::Pressed => {
                text.sections[0].value = "Press".to_string();
                *color = PRESSED_BUTTON.into();
                border_color.0 = RED.into();
            }
            Interaction::Hovered => {
                text.sections[0].value = "Hover".to_string();
                *color = HOVERED_BUTTON.into();
                border_color.0 = Color::WHITE;
            }
            Interaction::None => {
                text.sections[0].value = "Button".to_string();
                *color = NORMAL_BUTTON.into();
                border_color.0 = Color::BLACK;
            }
        }
    }
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    // ui camera
    commands.spawn(Camera2dBundle::default());
    commands
        .spawn(NodeBundle {
            style: Style {
                width: Val::Percent(100.0),
                height: Val::Percent(100.0),
                align_items: AlignItems::Center,
                justify_content: JustifyContent::Center,
                ..default()
            },
            ..default()
        })
        .with_children(|parent| {
            parent
                .spawn(ButtonBundle {
                    style: Style {
                        width: Val::Px(250.0),
                        height: Val::Px(100.0),
                        border: UiRect::all(Val::Px(25.0)),
                        // horizontally center child text
                        justify_content: JustifyContent::Center,
                        // vertically center child text
                        align_items: AlignItems::Center,
                        ..default()
                    },
                    border_color: BorderColor(Color::BLACK),
                    border_radius: BorderRadius::all(Val::Px(25.)),
                    background_color: NORMAL_BUTTON.into(),
                    ..default()
                })
                .with_child(TextBundle::from_section(
                    "Button",
                    TextStyle {
                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
                        font_size: 40.0,
                        color: Color::srgb(0.9, 0.9, 0.9),
                    },
                ));
            parent
                .spawn(NodeBundle {
                    style: Style {
                        width: Val::Px(150.0),
                        padding: UiRect::vertical(Val::Px(25.)),
                        height: Val::Px(100.0),
                        align_items: AlignItems::Stretch,
                        justify_content: JustifyContent::Stretch,
                        ..Default::default()
                    },
                    ..default()
                })
                .with_child(NodeBundle {
                    style: Style {
                        flex_basis: Val::Percent(100.),
                        ..Default::default()
                    },
                    background_color: RED.into(),
                    ..Default::default()
                });
        });
}
```

## Showcase

Using the modified button example

### main

<img alt="366023197-e6124f07-e522-4514-bd8e-7986ac32890c"
src="https://github.com/user-attachments/assets/b7b909ed-1184-4d9d-b50b-e30f4c1f76b2">


### this PR

![image](https://github.com/user-attachments/assets/89b89a2f-533f-41bd-b2cb-4743aec6519e)
2024-09-13 15:52:42 +00:00
ickshonpe
cacf3929db
fix spelling mistake (#15146)
# Objective

Fix spelling mistake: `interned_root_notes` -> `interned_root_nodes`
2024-09-10 20:21:40 +00:00
ickshonpe
8d143e3ed8
ui material node border calculations fix (#15119)
# Objective

Fixes  #15115

## Solution

Retrieve the size of the node's parent in a separate query and base
percentage border values on the parent node's width (or the width of the
viewport in the case of root nodes).
2024-09-09 22:35:29 +00:00
ickshonpe
4de9edeaa6
Retrieve the stack_index from Node in extract_ui_material_nodes instead of walking UiStack (#15104)
# Objective

 `ExtractedUiMaterialNode` is still walking the whole `UiStack`. 

more info: https://github.com/bevyengine/bevy/pull/9853

## Solution

Retrieve the `stack_index` from the `Node` component instead.
Also changed the `stack_index` field of `ExtractedUiMaterialNode` to
`u32`.
2024-09-09 16:18:37 +00:00
Marco Meijer
66b5128b6f
Add rect field to UI image (#15095)
# Objective

Fixes #14424 

## Solution

Add a rect field to UiImage, and update the extraction of ui images and
slices.

## Testing

I tested all possible combinations of having a rect, using a texture
atlas, setting image scale mode to sliced and image scale mode to tiled.
See the showcase section.

---

## Showcase

<img width="1279" alt="Screenshot 2024-09-08 at 16 23 05"
src="https://github.com/user-attachments/assets/183e53eb-f27c-4c8e-9fd5-4678825db3b6">

<details>
  <summary>Click to view showcase</summary>

```rust
use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()))
        .add_systems(Startup, create_ui)
        .run();
}

fn create_ui(
    mut commands: Commands,
    assets: Res<AssetServer>,
    mut texture_atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
) {
    let texture = assets.load("textures/fantasy_ui_borders/numbered_slices.png");
    let layout = TextureAtlasLayout::from_grid(UVec2::splat(16), 3, 3, None, None);
    let texture_atlas_layout = texture_atlas_layouts.add(layout);

    commands.spawn(Camera2dBundle::default());

    let style = Style {
        width: Val::Px(96.),
        height: Val::Px(96.),
        ..default()
    };

    commands
        .spawn(NodeBundle { ..default() })
        .with_children(|parent| {
            // nothing
            parent.spawn(ImageBundle {
                image: UiImage::new(texture.clone()),
                style: style.clone(),
                ..default()
            });

            // with rect
            parent.spawn(ImageBundle {
                image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 16., 16.)),
                style: style.clone(),
                ..default()
            });

            // with rect and texture atlas
            parent.spawn((
                ImageBundle {
                    image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 8., 8.)),
                    style: style.clone(),
                    ..default()
                },
                TextureAtlas {
                    layout: texture_atlas_layout.clone(),
                    index: 1,
                },
            ));

            // with texture atlas
            parent.spawn((
                ImageBundle {
                    image: UiImage::new(texture.clone()),
                    style: style.clone(),
                    ..default()
                },
                TextureAtlas {
                    layout: texture_atlas_layout.clone(),
                    index: 2,
                },
            ));

            // with texture slicer
            parent.spawn((
                ImageBundle {
                    image: UiImage::new(texture.clone()),
                    style: style.clone(),
                    ..default()
                },
                ImageScaleMode::Sliced(TextureSlicer {
                    border: BorderRect::square(16.),
                    center_scale_mode: SliceScaleMode::Stretch,
                    sides_scale_mode: SliceScaleMode::Stretch,
                    max_corner_scale: 1.,
                }),
            ));

            // with rect and texture slicer
            parent.spawn((
                ImageBundle {
                    image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 16., 16.)),
                    style: style.clone(),
                    ..default()
                },
                ImageScaleMode::Sliced(TextureSlicer {
                    border: BorderRect::square(2.),
                    center_scale_mode: SliceScaleMode::Stretch,
                    sides_scale_mode: SliceScaleMode::Stretch,
                    max_corner_scale: 1.,
                }),
            ));

            // with rect, texture atlas and texture slicer
            parent.spawn((
                ImageBundle {
                    image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 8., 8.)),
                    style: style.clone(),
                    ..default()
                },
                TextureAtlas {
                    layout: texture_atlas_layout.clone(),
                    index: 1,
                },
                ImageScaleMode::Sliced(TextureSlicer {
                    border: BorderRect::square(1.),
                    center_scale_mode: SliceScaleMode::Stretch,
                    sides_scale_mode: SliceScaleMode::Stretch,
                    max_corner_scale: 1.,
                }),
            ));

            // with texture atlas and texture slicer
            parent.spawn((
                ImageBundle {
                    image: UiImage::new(texture.clone()),
                    style: style.clone(),
                    ..default()
                },
                TextureAtlas {
                    layout: texture_atlas_layout.clone(),
                    index: 2,
                },
                ImageScaleMode::Sliced(TextureSlicer {
                    border: BorderRect::square(2.),
                    center_scale_mode: SliceScaleMode::Stretch,
                    sides_scale_mode: SliceScaleMode::Stretch,
                    max_corner_scale: 1.,
                }),
            ));

            // with tiled
            parent.spawn((
                ImageBundle {
                    image: UiImage::new(texture.clone()),
                    style: style.clone(),
                    ..default()
                },
                ImageScaleMode::Tiled {
                    tile_x: true,
                    tile_y: true,
                    stretch_value: 1.,
                },
            ));

            // with rect and tiled
            parent.spawn((
                ImageBundle {
                    image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 16., 16.)),
                    style: style.clone(),
                    ..default()
                },
                ImageScaleMode::Tiled {
                    tile_x: true,
                    tile_y: true,
                    stretch_value: 1.,
                },
            ));

            // with rect, texture atlas and tiled
            parent.spawn((
                ImageBundle {
                    image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 8., 8.)),
                    style: style.clone(),
                    ..default()
                },
                TextureAtlas {
                    layout: texture_atlas_layout.clone(),
                    index: 1,
                },
                ImageScaleMode::Tiled {
                    tile_x: true,
                    tile_y: true,
                    stretch_value: 1.,
                },
            ));

            // with texture atlas and tiled
            parent.spawn((
                ImageBundle {
                    image: UiImage::new(texture.clone()),
                    style: style.clone(),
                    ..default()
                },
                TextureAtlas {
                    layout: texture_atlas_layout.clone(),
                    index: 2,
                },
                ImageScaleMode::Tiled {
                    tile_x: true,
                    tile_y: true,
                    stretch_value: 1.,
                },
            ));
        });
}
```

</details>
2024-09-09 16:16:33 +00:00
CrazyboyQCD
bca228fdaa
Simplify pick_rounded_rect (#15065)
# Objective

Simplify `pick_rounded_rect` with multiple `if` statements to make it
more readable and efficient([Godbolt
link](https://godbolt.org/z/W5vPEvT5c)).

Co-authored-by: WX\shixi <shixi1@cnwxsoft.com>
2024-09-09 15:40:00 +00:00
Ben Frankel
7b217a976c
Remove deprecated SpriteSheetBundle and AtlasImageBundle (#15062)
# Objective

Remove bundles that were deprecated in 0.14.

## Testing

`rg SpriteSheetBundle` and `rg AtlasImageBundle` show no results.
2024-09-09 15:36:09 +00:00
BD103
6ec6a55645
Unify crate-level preludes (#15080)
# Objective

- Crate-level prelude modules, such as `bevy_ecs::prelude`, are plagued
with inconsistency! Let's fix it!

## Solution

Format all preludes based on the following rules:

1. All preludes should have brief documentation in the format of:
   > The _name_ prelude.
   >
> This includes the most common types in this crate, re-exported for
your convenience.
2. All documentation should be outer, not inner. (`///` instead of
`//!`.)
3. No prelude modules should be annotated with `#[doc(hidden)]`. (Items
within them may, though I'm not sure why this was done.)

## Testing

- I manually searched for the term `mod prelude` and updated all
occurrences by hand. 🫠

---------

Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
2024-09-08 17:10:57 +00:00
BigWingBeat
54aa45e62f
Fix bevy_ui compile error when bevy_picking feature is disabled (#15053)
# Objective

#14957 added the `pick_rounded_rect` function to `bevy_ui` in the
`picking_backend` module, which is gated behind the `bevy_picking`
feature. This function is used in that module, as well as in the `focus`
module. The latter usage is not gated behind the `bevy_picking` feature,
causing a compile error when the feature is disabled.

## Solution

Move the `pick_rounded_rect` function out of the `picking_backend`
module, as it does not depend on anything defined in that module. I put
it in `lib.rs` but it could reasonably be moved somewhere else instead.

## Testing

Encountered this compile error in a project and confirmed that this
patch fixes it.
2024-09-05 19:16:57 +00:00
ickshonpe
cb221d8852
Node::is_empty (#15050)
# Objective

Add a `Node::is_empty` method to replace the `uinode.size().x() <= 0. ||
uinode.size.y() <= 0.` checks.
2024-09-05 16:26:45 +00:00
ickshonpe
a0f5ea0d36
UI outlines radius (#15018)
# Objective

Fixes #13479

This also fixes the gaps you can sometimes observe in outlines
(screenshot from main, not this PR):

<img width="636" alt="outline-gaps"
src="https://github.com/user-attachments/assets/c11dae24-20f5-4aea-8ffc-1894ad2a2b79">

The outline around the last item in each section has vertical gaps. 

## Solution

Draw the outlines with corner radius using the existing border rendering
for uinodes. The outline radius is very simple to calculate. We just
take the computed border radius of the node, and if it's greater than
zero, add it to the distance from the edge of the node to the outer edge
of the node's outline.

---

## Showcase

<img width="634" alt="outlines-radius"
src="https://github.com/user-attachments/assets/1ecda26c-65c5-41ef-87e4-5d9171ddc3ae">

---------

Co-authored-by: Jan Hohenheim <jan@hohenheim.ch>
2024-09-04 22:30:16 +00:00
ickshonpe
8ac745ab10
UI texture slice texture flipping reimplementation (#15034)
# Objective

Fixes #15032

## Solution

Reimplement support for the `flip_x` and `flip_y` fields.
This doesn't flip the border geometry, I'm not really sure whether that
is desirable or not.
Also fixes a bug that was causing the side and center slices to tile
incorrectly.

### Testing

```
cargo run --example ui_texture_slice_flip_and_tile
```

## Showcase
<img width="787" alt="nearest"
src="https://github.com/user-attachments/assets/bc044bae-1748-42ba-92b5-0500c87264f6">
With tiling need to use nearest filtering to avoid bleeding between the
slices.

---------

Co-authored-by: Jan Hohenheim <jan@hohenheim.ch>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-09-04 19:31:41 +00:00
Alice Cecile
4ac2a63556
Remove all existing system order ambiguities in DefaultPlugins (#15031)
# Objective

As discussed in https://github.com/bevyengine/bevy/issues/7386, system
order ambiguities within `DefaultPlugins` are a source of bugs in the
engine and badly pollute diagnostic output for users.

We should eliminate them!

This PR is an alternative to #15027: with all external ambiguities
silenced, this should be much less prone to merge conflicts and the test
output should be much easier for authors to understand.

Note that system order ambiguities are still permitted in the
`RenderApp`: these need a bit of thought in terms of how to test them,
and will be fairly involved to fix. While these aren't *good*, they'll
generally only cause graphical bugs, not logic ones.

## Solution

All remaining system order ambiguities have been resolved.
Review this PR commit-by-commit to see how each of these problems were
fixed.

## Testing

`cargo run --example ambiguity_detection` passes with no panics or
logging!
2024-09-03 20:24:34 +00:00
Chris Juchem
c620eb7833
Return Results from Camera's world/viewport conversion methods (#14989)
# Objective

- Fixes https://github.com/bevyengine/bevy/issues/14593.

## Solution

- Add `ViewportConversionError` and return it from viewport conversion
methods on Camera.

## Testing

- I successfully compiled and ran all changed examples.

## Migration Guide

The following methods on `Camera` now return a `Result` instead of an
`Option` so that they can provide more information about failures:
 - `world_to_viewport`
 - `world_to_viewport_with_depth`
 - `viewport_to_world`
 - `viewport_to_world_2d`

Call `.ok()` on the `Result` to turn it back into an `Option`, or handle
the `Result` directly.

---------

Co-authored-by: Lixou <82600264+DasLixou@users.noreply.github.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Zachary Harrold <zac@harrold.com.au>
2024-09-03 19:45:15 +00:00
ickshonpe
4e9a62f094
Ignore clicks on uinodes outside of rounded corners (#14957)
# Objective

Fixes #14941

## Solution
1. Add a `resolved_border_radius` field to `Node` to hold the resolved
border radius values.
2. Remove the border radius calculations from the UI's extraction
functions.
4. Compute the border radius during UI relayouts in `ui_layout_system`
and store them in `Node`.
5. New `pick_rounded_rect` function based on the border radius SDF from
`ui.wgsl`.
6. Use `pick_rounded_rect` in `focus` and `picking_backend` to check if
the pointer is hovering UI nodes with rounded corners.
---

## Showcase

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


https://github.com/user-attachments/assets/ea951a64-17ef-455e-b5c9-a2e6f6360648

## Testing

Modified button example with buttons with different corner radius:

```
use bevy::{color::palettes::basic::*, prelude::*, winit::WinitSettings};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        // Only run the app when there is user input. This will significantly reduce CPU/GPU use.
        .insert_resource(WinitSettings::desktop_app())
        .add_systems(Startup, setup)
        .add_systems(Update, button_system)
        .run();
}

const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35);

fn button_system(
    mut interaction_query: Query<
        (
            &Interaction,
            &mut BackgroundColor,
            &mut BorderColor,
            &Children,
        ),
        (Changed<Interaction>, With<Button>),
    >,
    mut text_query: Query<&mut Text>,
) {
    for (interaction, mut color, mut border_color, children) in &mut interaction_query {
        let mut text = text_query.get_mut(children[0]).unwrap();
        match *interaction {
            Interaction::Pressed => {
                text.sections[0].value = "Press".to_string();
                *color = PRESSED_BUTTON.into();
                border_color.0 = RED.into();
            }
            Interaction::Hovered => {
                text.sections[0].value = "Hover".to_string();
                *color = HOVERED_BUTTON.into();
                border_color.0 = Color::WHITE;
            }
            Interaction::None => {
                text.sections[0].value = "Button".to_string();
                *color = NORMAL_BUTTON.into();
                border_color.0 = Color::BLACK;
            }
        }
    }
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    // ui camera
    commands.spawn(Camera2dBundle::default());
    commands
        .spawn(NodeBundle {
            style: Style {
                width: Val::Percent(100.0),
                height: Val::Percent(100.0),
                align_items: AlignItems::Center,
                justify_content: JustifyContent::Center,
                row_gap: Val::Px(10.),
                ..default()
            },
            ..default()
        })
        .with_children(|parent| {
            for border_radius in [
                BorderRadius {
                    top_left: Val::ZERO,
                    ..BorderRadius::MAX
                },
                BorderRadius {
                    top_right: Val::ZERO,
                    ..BorderRadius::MAX
                },
                BorderRadius {
                    bottom_right: Val::ZERO,
                    ..BorderRadius::MAX
                },
                BorderRadius {
                    bottom_left: Val::ZERO,
                    ..BorderRadius::MAX
                },
            ] {
                parent
                    .spawn(ButtonBundle {
                        style: Style {
                            width: Val::Px(150.0),
                            height: Val::Px(65.0),
                            border: UiRect::all(Val::Px(5.0)),
                            // horizontally center child text
                            justify_content: JustifyContent::Center,
                            // vertically center child text
                            align_items: AlignItems::Center,
                            ..default()
                        },
                        border_color: BorderColor(Color::BLACK),
                        border_radius,
                        background_color: NORMAL_BUTTON.into(),
                        ..default()
                    })
                    .with_child(TextBundle::from_section(
                        "Button",
                        TextStyle {
                            font: asset_server.load("fonts/FiraSans-Bold.ttf"),
                            font_size: 40.0,
                            color: Color::srgb(0.9, 0.9, 0.9),
                        },
                    ));
            }
        });
}
```

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Matty <weatherleymatthew@gmail.com>
2024-09-03 12:38:59 +00:00
ickshonpe
01a3b0e830
UI texture atlas slice shader (#14990)
# Objective

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

## Solution

Reimplement the UI texture atlas slicer using a shader. 

The problems with #14183 could be fixed more simply by hacking around
with the coordinates and scaling but that way is very fragile and might
get broken again the next time we make changes to the layout
calculations. A shader based solution is more robust, it's impossible
for gaps to appear between the image slices with these changes as we're
only drawing a single quad.

I've not tried any benchmarks yet but it should much more efficient as
well, in the worst cases even hundreds or thousands of times faster.

Maybe could have used the UiMaterialPipeline. I wrote the shader first
and used fat vertices and then realised it wouldn't work that way with a
UiMaterial. If it's rewritten it so it puts all the slice geometry in
uniform buffer, then it might work? Adding the uniform buffer would
probably make the shader more complicated though, so don't know if it's
even worth it. Instancing is another alternative.

## Testing
The examples are working and it seems to match the old API correctly but
I've not used the texture atlas slicing API for anything before, I
reviewed the PR but that was back in January.

Needs a review by someone who knows the rendering pipeline and wgsl
really well because I don't really have any idea what I'm doing.
2024-09-02 23:03:58 +00:00
ickshonpe
96942058f7
Extract borders without border radius (#15020)
# Objective

The `BorderRadius` component shouldn't be required to draw borders for
nodes with sharp corners.

## Solution

Make `BorderRadius` optional in `extract_uinode_borders`'s UI node
query.
2024-09-02 22:47:43 +00:00
UkoeHB
3fc02cb925
Reduce allocations in ui_layout_system (#15001)
# Objective

- Shave off some allocations from `ui_layout_system`.

## Solution

- Add a `Local` for allocation buffers.
2024-09-02 22:37:27 +00:00
UkoeHB
3227c3de36
Don't reallocate scale factors in measure_text_system (#14999)
# Objective

- Reuse allocation of `scale_factors` in `measure_text_system`.

## Solution

- Move it to a `Local`.
2024-09-02 17:01:59 +00:00
UkoeHB
2b94a108ae
Reuse TextLayoutInfo in queue_text (#14997)
# Objective

Don't reallocate `TextLayoutInfo` every time it needs to be updated.

## Solution

Reuse existing allocation.
2024-09-02 17:01:56 +00:00
ickshonpe
be100b8760
Resolve UI outlines using the correct target's viewport size (#14947)
# Objective
`resolve_outlines_system` wasn't updated when multi-window support was
added and it always uses the size of the primary window when resolving
viewport coords, regardless of the layout's camera target.

Fixes #14945

## Solution

It's awkward to get the viewport size of the target for an individual
node without walking the tree or adding extra fields to `Node`, so I
removed `resolve_outlines_system` and instead the outline values are
updated in `ui_layout_system`.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-09-02 16:56:58 +00:00
UkoeHB
41474226c3
Optimize UI text measurement (#15003)
# Objective

- Avoid cloning the `CosmicBuffer` every time you create a new text
measurement.

## Solution

- Inject a buffer query when calculating layout so existing buffers can
be reused.

## Testing

- I tested the `text`, `text_debug`, and `text_wrap_debug` examples.
- I did not do a performance test.
2024-09-01 11:50:54 +00:00