Commit Graph

47 Commits

Author SHA1 Message Date
ickshonpe
a949867a1c
UI z-ordering fix (#19691)
# Objective

During the migration to required components a lot of things were changed
around and somehow the draw order for some UI elements ended up
depending on the system ordering in `RenderSystems::Queue`, which can
sometimes result in the elements being drawn in the wrong order.

Fixes #19674

## Solution

* Added some more `stack_z_offsets` constants and used them to enforce
an explicit ordering.
* Removed the `stack_index: u32` field from `ExtractedUiNodes` and
replaced it with a `z_order: f32` field.

These changes should fix all the ordering problems. 

## Testing

I added a nine-patched bordered node with a navy background color to the
slice section of the `testbed_ui` example.
The border should always be drawn above the background color.
2025-07-01 19:20:07 +00:00
ickshonpe
5e8aa7986b
Newtyped ScrollPosition (#19881)
# Objective

Change `ScrollPosition` to newtype `Vec2`. It's easier to work with a
`Vec2` wrapper than individual fields.

I'm not sure why this wasn't newtyped to start with. Maybe the intent
was to support responsive coordinates eventually but that probably isn't
very useful or straightforward to implement. And even if we do want to
support responsive coords in the future, it can newtype `Val2`.

## Solution

Change `ScrollPosition` to newtype `Vec2`. 

Also added some extra details to the doc comments.

## Testing

Try the `scroll` example.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-07-01 17:41:48 +00:00
ickshonpe
45a3f3d138
Color interpolation in OKLab, OKLCH spaces for UI gradients (#19330)
# Objective

Add support for interpolation in OKLab and OKLCH color spaces for UI
gradients.

## Solution
* New `InterpolationColorSpace` enum with `OkLab`, `OkLch`, `OkLchLong`,
`Srgb` and `LinearRgb` variants.
  * Added a color space specialization to the gradients pipeline.
* Added support for interpolation in OkLCH and OkLAB color spaces to the
gradients shader. OKLCH interpolation supports both short and long hue
paths. This is mostly based on the conversion functions from
`bevy_color` except that interpolation in polar space uses radians.
  * Added `color_space` fields to each gradient type.

## Testing

The `gradients` example has been updated to demonstrate the different
color interpolation methods.
Press space to cycle through the different options.

---

## Showcase


![color_spaces](https://github.com/user-attachments/assets/e10f8342-c3c8-487e-b386-7acdf38d638f)
2025-06-21 15:06:35 +00:00
Joona Aalto
38c3423693
Event Split: Event, EntityEvent, and BufferedEvent (#19647)
# Objective

Closes #19564.

The current `Event` trait looks like this:

```rust
pub trait Event: Send + Sync + 'static {
    type Traversal: Traversal<Self>;
    const AUTO_PROPAGATE: bool = false;
    
    fn register_component_id(world: &mut World) -> ComponentId { ... }
    fn component_id(world: &World) -> Option<ComponentId> { ... }
}
```

The `Event` trait is used by both buffered events
(`EventReader`/`EventWriter`) and observer events. If they are observer
events, they can optionally be targeted at specific `Entity`s or
`ComponentId`s, and can even be propagated to other entities.

However, there has long been a desire to split the trait semantically
for a variety of reasons, see #14843, #14272, and #16031 for discussion.
Some reasons include:

- It's very uncommon to use a single event type as both a buffered event
and targeted observer event. They are used differently and tend to have
distinct semantics.
- A common footgun is using buffered events with observers or event
readers with observer events, as there is no type-level error that
prevents this kind of misuse.
- #19440 made `Trigger::target` return an `Option<Entity>`. This
*seriously* hurts ergonomics for the general case of entity observers,
as you need to `.unwrap()` each time. If we could statically determine
whether the event is expected to have an entity target, this would be
unnecessary.

There's really two main ways that we can categorize events: push vs.
pull (i.e. "observer event" vs. "buffered event") and global vs.
targeted:

|              | Push            | Pull                        |
| ------------ | --------------- | --------------------------- |
| **Global**   | Global observer | `EventReader`/`EventWriter` |
| **Targeted** | Entity observer | -                           |

There are many ways to approach this, each with their tradeoffs.
Ultimately, we kind of want to split events both ways:

- A type-level distinction between observer events and buffered events,
to prevent people from using the wrong kind of event in APIs
- A statically designated entity target for observer events to avoid
accidentally using untargeted events for targeted APIs

This PR achieves these goals by splitting event traits into `Event`,
`EntityEvent`, and `BufferedEvent`, with `Event` being the shared trait
implemented by all events.

## `Event`, `EntityEvent`, and `BufferedEvent`

`Event` is now a very simple trait shared by all events.

```rust
pub trait Event: Send + Sync + 'static {
    // Required for observer APIs
    fn register_component_id(world: &mut World) -> ComponentId { ... }
    fn component_id(world: &World) -> Option<ComponentId> { ... }
}
```

You can call `trigger` for *any* event, and use a global observer for
listening to the event.

```rust
#[derive(Event)]
struct Speak {
    message: String,
}

// ...

app.add_observer(|trigger: On<Speak>| {
    println!("{}", trigger.message);
});

// ...

commands.trigger(Speak {
    message: "Y'all like these reworked events?".to_string(),
});
```

To allow an event to be targeted at entities and even propagated
further, you can additionally implement the `EntityEvent` trait:

```rust
pub trait EntityEvent: Event {
    type Traversal: Traversal<Self>;
    const AUTO_PROPAGATE: bool = false;
}
```

This lets you call `trigger_targets`, and to use targeted observer APIs
like `EntityCommands::observe`:

```rust
#[derive(Event, EntityEvent)]
#[entity_event(traversal = &'static ChildOf, auto_propagate)]
struct Damage {
    amount: f32,
}

// ...

let enemy = commands.spawn((Enemy, Health(100.0))).id();

// Spawn some armor as a child of the enemy entity.
// When the armor takes damage, it will bubble the event up to the enemy.
let armor_piece = commands
    .spawn((ArmorPiece, Health(25.0), ChildOf(enemy)))
    .observe(|trigger: On<Damage>, mut query: Query<&mut Health>| {
        // Note: `On::target` only exists because this is an `EntityEvent`.
        let mut health = query.get(trigger.target()).unwrap();
        health.0 -= trigger.amount();
    });

commands.trigger_targets(Damage { amount: 10.0 }, armor_piece);
```

> [!NOTE]
> You *can* still also trigger an `EntityEvent` without targets using
`trigger`. We probably *could* make this an either-or thing, but I'm not
sure that's actually desirable.

To allow an event to be used with the buffered API, you can implement
`BufferedEvent`:

```rust
pub trait BufferedEvent: Event {}
```

The event can then be used with `EventReader`/`EventWriter`:

```rust
#[derive(Event, BufferedEvent)]
struct Message(String);

fn write_hello(mut writer: EventWriter<Message>) {
    writer.write(Message("I hope these examples are alright".to_string()));
}

fn read_messages(mut reader: EventReader<Message>) {
    // Process all buffered events of type `Message`.
    for Message(message) in reader.read() {
        println!("{message}");
    }
}
```

In summary:

- Need a basic event you can trigger and observe? Derive `Event`!
- Need the event to be targeted at an entity? Derive `EntityEvent`!
- Need the event to be buffered and support the
`EventReader`/`EventWriter` API? Derive `BufferedEvent`!

## Alternatives

I'll now cover some of the alternative approaches I have considered and
briefly explored. I made this section collapsible since it ended up
being quite long :P

<details>

<summary>Expand this to see alternatives</summary>

### 1. Unified `Event` Trait

One option is not to have *three* separate traits (`Event`,
`EntityEvent`, `BufferedEvent`), and to instead just use associated
constants on `Event` to determine whether an event supports targeting
and buffering or not:

```rust
pub trait Event: Send + Sync + 'static {
    type Traversal: Traversal<Self>;
    const AUTO_PROPAGATE: bool = false;
    const TARGETED: bool = false;
    const BUFFERED: bool = false;
    
    fn register_component_id(world: &mut World) -> ComponentId { ... }
    fn component_id(world: &World) -> Option<ComponentId> { ... }
}
```

Methods can then use bounds like `where E: Event<TARGETED = true>` or
`where E: Event<BUFFERED = true>` to limit APIs to specific kinds of
events.

This would keep everything under one `Event` trait, but I don't think
it's necessarily a good idea. It makes APIs harder to read, and docs
can't easily refer to specific types of events. You can also create
weird invariants: what if you specify `TARGETED = false`, but have
`Traversal` and/or `AUTO_PROPAGATE` enabled?

### 2. `Event` and `Trigger`

Another option is to only split the traits between buffered events and
observer events, since that is the main thing people have been asking
for, and they have the largest API difference.

If we did this, I think we would need to make the terms *clearly*
separate. We can't really use `Event` and `BufferedEvent` as the names,
since it would be strange that `BufferedEvent` doesn't implement
`Event`. Something like `ObserverEvent` and `BufferedEvent` could work,
but it'd be more verbose.

For this approach, I would instead keep `Event` for the current
`EventReader`/`EventWriter` API, and call the observer event a
`Trigger`, since the "trigger" terminology is already used in the
observer context within Bevy (both as a noun and a verb). This is also
what a long [bikeshed on
Discord](https://discord.com/channels/691052431525675048/749335865876021248/1298057661878898791)
seemed to land on at the end of last year.

```rust
// For `EventReader`/`EventWriter`
pub trait Event: Send + Sync + 'static {}

// For observers
pub trait Trigger: Send + Sync + 'static {
    type Traversal: Traversal<Self>;
    const AUTO_PROPAGATE: bool = false;
    const TARGETED: bool = false;
    
    fn register_component_id(world: &mut World) -> ComponentId { ... }
    fn component_id(world: &World) -> Option<ComponentId> { ... }
}
```

The problem is that "event" is just a really good term for something
that "happens". Observers are rapidly becoming the more prominent API,
so it'd be weird to give them the `Trigger` name and leave the good
`Event` name for the less common API.

So, even though a split like this seems neat on the surface, I think it
ultimately wouldn't really work. We want to keep the `Event` name for
observer events, and there is no good alternative for the buffered
variant. (`Message` was suggested, but saying stuff like "sends a
collision message" is weird.)

### 3. `GlobalEvent` + `TargetedEvent`

What if instead of focusing on the buffered vs. observed split, we
*only* make a distinction between global and targeted events?

```rust
// A shared event trait to allow global observers to work
pub trait Event: Send + Sync + 'static {
    fn register_component_id(world: &mut World) -> ComponentId { ... }
    fn component_id(world: &World) -> Option<ComponentId> { ... }
}

// For buffered events and non-targeted observer events
pub trait GlobalEvent: Event {}

// For targeted observer events
pub trait TargetedEvent: Event {
    type Traversal: Traversal<Self>;
    const AUTO_PROPAGATE: bool = false;
}
```

This is actually the first approach I implemented, and it has the neat
characteristic that you can only use non-targeted APIs like `trigger`
with a `GlobalEvent` and targeted APIs like `trigger_targets` with a
`TargetedEvent`. You have full control over whether the entity should or
should not have a target, as they are fully distinct at the type-level.

However, there's a few problems:

- There is no type-level indication of whether a `GlobalEvent` supports
buffered events or just non-targeted observer events
- An `Event` on its own does literally nothing, it's just a shared trait
required to make global observers accept both non-targeted and targeted
events
- If an event is both a `GlobalEvent` and `TargetedEvent`, global
observers again have ambiguity on whether an event has a target or not,
undermining some of the benefits
- The names are not ideal

### 4. `Event` and `EntityEvent`

We can fix some of the problems of Alternative 3 by accepting that
targeted events can also be used in non-targeted contexts, and simply
having the `Event` and `EntityEvent` traits:

```rust
// For buffered events and non-targeted observer events
pub trait Event: Send + Sync + 'static {
    fn register_component_id(world: &mut World) -> ComponentId { ... }
    fn component_id(world: &World) -> Option<ComponentId> { ... }
}

// For targeted observer events
pub trait EntityEvent: Event {
    type Traversal: Traversal<Self>;
    const AUTO_PROPAGATE: bool = false;
}
```

This is essentially identical to this PR, just without a dedicated
`BufferedEvent`. The remaining major "problem" is that there is still
zero type-level indication of whether an `Event` event *actually*
supports the buffered API. This leads us to the solution proposed in
this PR, using `Event`, `EntityEvent`, and `BufferedEvent`.

</details>

## Conclusion

The `Event` + `EntityEvent` + `BufferedEvent` split proposed in this PR
aims to solve all the common problems with Bevy's current event model
while keeping the "weirdness" factor minimal. It splits in terms of both
the push vs. pull *and* global vs. targeted aspects, while maintaining a
shared concept for an "event".

### Why I Like This

- The term "event" remains as a single concept for all the different
kinds of events in Bevy.
- Despite all event types being "events", they use fundamentally
different APIs. Instead of assuming that you can use an event type with
any pattern (when only one is typically supported), you explicitly opt
in to each one with dedicated traits.
- Using separate traits for each type of event helps with documentation
and clearer function signatures.
- I can safely make assumptions on expected usage.
- If I see that an event is an `EntityEvent`, I can assume that I can
use `observe` on it and get targeted events.
- If I see that an event is a `BufferedEvent`, I can assume that I can
use `EventReader` to read events.
- If I see both `EntityEvent` and `BufferedEvent`, I can assume that
both APIs are supported.

In summary: This allows for a unified concept for events, while limiting
the different ways to use them with opt-in traits. No more guess-work
involved when using APIs.

### Problems?

- Because `BufferedEvent` implements `Event` (for more consistent
semantics etc.), you can still use all buffered events for non-targeted
observers. I think this is fine/good. The important part is that if you
see that an event implements `BufferedEvent`, you know that the
`EventReader`/`EventWriter` API should be supported. Whether it *also*
supports other APIs is secondary.
- I currently only support `trigger_targets` for an `EntityEvent`.
However, you can technically target components too, without targeting
any entities. I consider that such a niche and advanced use case that
it's not a huge problem to only support it for `EntityEvent`s, but we
could also split `trigger_targets` into `trigger_entities` and
`trigger_components` if we wanted to (or implement components as
entities :P).
- You can still trigger an `EntityEvent` *without* targets. I consider
this correct, since `Event` implements the non-targeted behavior, and
it'd be weird if implementing another trait *removed* behavior. However,
it does mean that global observers for entity events can technically
return `Entity::PLACEHOLDER` again (since I got rid of the
`Option<Entity>` added in #19440 for ergonomics). I think that's enough
of an edge case that it's not a huge problem, but it is worth keeping in
mind.
- ~~Deriving both `EntityEvent` and `BufferedEvent` for the same type
currently duplicates the `Event` implementation, so you instead need to
manually implement one of them.~~ Changed to always requiring `Event` to
be derived.

## Related Work

There are plans to implement multi-event support for observers,
especially for UI contexts. [Cart's
example](https://github.com/bevyengine/bevy/issues/14649#issuecomment-2960402508)
API looked like this:

```rust
// Truncated for brevity
trigger: Trigger<(
    OnAdd<Pressed>,
    OnRemove<Pressed>,
    OnAdd<InteractionDisabled>,
    OnRemove<InteractionDisabled>,
    OnInsert<Hovered>,
)>,
```

I believe this shouldn't be in conflict with this PR. If anything, this
PR might *help* achieve the multi-event pattern for entity observers
with fewer footguns: by statically enforcing that all of these events
are `EntityEvent`s in the context of `EntityCommands::observe`, we can
avoid misuse or weird cases where *some* events inside the trigger are
targeted while others are not.
2025-06-15 16:46:34 +00:00
Joona Aalto
e5dc177b4b
Rename Trigger to On (#19596)
# Objective

Currently, the observer API looks like this:

```rust
app.add_observer(|trigger: Trigger<Explode>| {
    info!("Entity {} exploded!", trigger.target());
});
```

Future plans for observers also include "multi-event observers" with a
trigger that looks like this (see [Cart's
example](https://github.com/bevyengine/bevy/issues/14649#issuecomment-2960402508)):

```rust
trigger: Trigger<(
    OnAdd<Pressed>,
    OnRemove<Pressed>,
    OnAdd<InteractionDisabled>,
    OnRemove<InteractionDisabled>,
    OnInsert<Hovered>,
)>,
```

In scenarios like this, there is a lot of repetition of `On`. These are
expected to be very high-traffic APIs especially in UI contexts, so
ergonomics and readability are critical.

By renaming `Trigger` to `On`, we can make these APIs read more cleanly
and get rid of the repetition:

```rust
app.add_observer(|trigger: On<Explode>| {
    info!("Entity {} exploded!", trigger.target());
});
```

```rust
trigger: On<(
    Add<Pressed>,
    Remove<Pressed>,
    Add<InteractionDisabled>,
    Remove<InteractionDisabled>,
    Insert<Hovered>,
)>,
```

Names like `On<Add<Pressed>>` emphasize the actual event listener nature
more than `Trigger<OnAdd<Pressed>>`, and look cleaner. This *also* frees
up the `Trigger` name if we want to use it for the observer event type,
splitting them out from buffered events (bikeshedding this is out of
scope for this PR though).

For prior art:
[`bevy_eventlistener`](https://github.com/aevyrie/bevy_eventlistener)
used
[`On`](https://docs.rs/bevy_eventlistener/latest/bevy_eventlistener/event_listener/struct.On.html)
for its event listener type. Though in our case, the observer is the
event listener, and `On` is just a type containing information about the
triggered event.

## Solution

Steal from `bevy_event_listener` by @aevyrie and use `On`.

- Rename `Trigger` to `On`
- Rename `OnAdd` to `Add`
- Rename `OnInsert` to `Insert`
- Rename `OnReplace` to `Replace`
- Rename `OnRemove` to `Remove`
- Rename `OnDespawn` to `Despawn`

## Discussion

### Naming Conflicts??

Using a name like `Add` might initially feel like a very bad idea, since
it risks conflict with `core::ops::Add`. However, I don't expect this to
be a big problem in practice.

- You rarely need to actually implement the `Add` trait, especially in
modules that would use the Bevy ECS.
- In the rare cases where you *do* get a conflict, it is very easy to
fix by just disambiguating, for example using `ops::Add`.
- The `Add` event is a struct while the `Add` trait is a trait (duh), so
the compiler error should be very obvious.

For the record, renaming `OnAdd` to `Add`, I got exactly *zero* errors
or conflicts within Bevy itself. But this is of course not entirely
representative of actual projects *using* Bevy.

You might then wonder, why not use `Added`? This would conflict with the
`Added` query filter, so it wouldn't work. Additionally, the current
naming convention for observer events does not use past tense.

### Documentation

This does make documentation slightly more awkward when referring to
`On` or its methods. Previous docs often referred to `Trigger::target`
or "sends a `Trigger`" (which is... a bit strange anyway), which would
now be `On::target` and "sends an observer `Event`".

You can see the diff in this PR to see some of the effects. I think it
should be fine though, we may just need to reword more documentation to
read better.
2025-06-12 18:22:33 +00:00
ickshonpe
02fa833be1
Rename JustifyText to Justify (#19522)
# Objective

Rename `JustifyText`:

* The name `JustifyText` is just ugly.
* It's inconsistent since no other `bevy_text` types have a `Text-`
suffix, only prefix.
* It's inconsistent with the other text layout enum `Linebreak` which
doesn't have a prefix or suffix.

Fixes #19521.

## Solution

Rename `JustifyText` to `Justify`.

Without other context, it's natural to assume the name `Justify` refers
to text justification.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-06-09 19:59:48 +00:00
Eagster
064e5e48b4
Remove entity placeholder from observers (#19440)
# Objective

`Entity::PLACEHOLDER` acts as a magic number that will *probably* never
really exist, but it certainly could. And, `Entity` has a niche, so the
only reason to use `PLACEHOLDER` is as an alternative to `MaybeUninit`
that trades safety risks for logic risks.

As a result, bevy has generally advised against using `PLACEHOLDER`, but
we still use if for a lot internally. This pr starts removing internal
uses of it, starting from observers.

## Solution

Change all trigger target related types from `Entity` to
`Option<Entity>`

Small migration guide to come.

## Testing

CI

## Future Work

This turned a lot of code from 

```rust
trigger.target()
```

to 

```rust
trigger.target().unwrap()
```

The extra panic is no worse than before; it's just earlier than
panicking after passing the placeholder to something else.

But this is kinda annoying. 

I would like to add a `TriggerMode` or something to `Event` that would
restrict what kinds of targets can be used for that event. Many events
like `Removed` etc, are always triggered with a target. We can make
those have a way to assume Some, etc. But I wanted to save that for a
future pr.
2025-06-09 19:37:56 +00:00
ickshonpe
4836c7868c
Specialized UI transform (#16615)
# Objective

Add specialized UI transform `Component`s and fix some related problems:
* Animating UI elements by modifying the `Transform` component of UI
nodes doesn't work very well because `ui_layout_system` overwrites the
translations each frame. The `overflow_debug` example uses a horrible
hack where it copies the transform into the position that'll likely
cause a panic if any users naively copy it.
* Picking ignores rotation and scaling and assumes UI nodes are always
axis aligned.
* The clipping geometry stored in `CalculatedClip` is wrong for rotated
and scaled elements.
* Transform propagation is unnecessary for the UI, the transforms can be
updated during layout updates.
* The UI internals use both object-centered and top-left-corner-based
coordinates systems for UI nodes. Depending on the context you have to
add or subtract the half-size sometimes before transforming between
coordinate spaces. We should just use one system consistantly so that
the transform can always be directly applied.
* `Transform` doesn't support responsive coordinates.

## Solution

* Unrequire `Transform` from `Node`.
* New components `UiTransform`, `UiGlobalTransform`:
- `Node` requires `UiTransform`, `UiTransform` requires
`UiGlobalTransform`
- `UiTransform` is a 2d-only equivalent of `Transform` with a
translation in `Val`s.
- `UiGlobalTransform` newtypes `Affine2` and is updated in
`ui_layout_system`.
* New helper functions on `ComputedNode` for mapping between viewport
and local node space.
* The cursor position is transformed to local node space during picking
so that it respects rotations and scalings.
* To check if the cursor hovers a node recursively walk up the tree to
the root checking if any of the ancestor nodes clip the point at the
cursor. If the point is clipped the interaction is ignored.
* Use object-centered coordinates for UI nodes.
* `RelativeCursorPosition`'s coordinates are now object-centered with
(0,0) at the the center of the node and the corners at (±0.5, ±0.5).
* Replaced the `normalized_visible_node_rect: Rect` field of
`RelativeCursorPosition` with `cursor_over: bool`, which is set to true
when the cursor is over an unclipped point on the node. The visible area
of the node is not necessarily a rectangle, so the previous
implementation didn't work.

This should fix all the logical bugs with non-axis aligned interactions
and clipping. Rendering still needs changes but they are far outside the
scope of this PR.

Tried and abandoned two other approaches:
* New `transform` field on `Node`, require `GlobalTransform` on `Node`,
and unrequire `Transform` on `Node`. Unrequiring `Transform` opts out of
transform propagation so there is then no conflict with updating the
`GlobalTransform` in `ui_layout_system`. This was a nice change in its
simplicity but potentially confusing for users I think, all the
`GlobalTransform` docs mention `Transform` and having special rules for
how it's updated just for the UI is unpleasently surprising.
* New `transform` field on `Node`. Unrequire `Transform` on `Node`. New
`transform: Affine2` field on `ComputedNode`.
This was okay but I think most users want a separate specialized UI
transform components. The fat `ComputedNode` doesn't work well with
change detection.

Fixes #18929, #18930

## Testing

There is an example you can look at: 
```
cargo run --example ui_transform
```

Sometimes in the example if you press the rotate button couple of times
the first glyph from the top label disappears , I'm not sure what's
causing it yet but I don't think it's related to this PR.

##  Migration Guide
New specialized 2D UI transform components `UiTransform` and
`UiGlobalTransform`. `UiTransform` is a 2d-only equivalent of
`Transform` with a translation in `Val`s. `UiGlobalTransform` newtypes
`Affine2` and is updated in `ui_layout_system`.
`Node` now requires `UiTransform` instead of `Transform`. `UiTransform`
requires `UiGlobalTransform`.

In previous versions of Bevy `ui_layout_system` would overwrite UI
node's `Transform::translation` each frame. `UiTransform`s aren't
overwritten and there is no longer any need for systems that cache and
rewrite the transform for translated UI elements.

`RelativeCursorPosition`'s coordinates are now object-centered with
(0,0) at the the center of the node and the corners at (±0.5, ±0.5). Its
`normalized_visible_node_rect` field has been removed and replaced with
a new `cursor_over: bool` field which is set to true when the cursor is
hovering an unclipped area of the UI node.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2025-06-09 19:05:49 +00:00
Daniel Skates
922ee480d2
Rename Position to UiPosition in bevy_ui (#19422)
# Objective

- Fixes #19418

## Solution

- Rename Position to UiPosition in bevy_ui

## Testing

- `cargo build`
- `cargo run --example gradients`
- `cargo run --example stacked_gradients`
2025-05-29 14:52:44 +00:00
Rob Parrett
a575502886
Move radial_gradients example to UI testbed (#19390)
# Objective

Fixes #19385

Note: this has shader errors due to #19383 and should probably be merged
after #19384

## Solution

- Move the example to the UI testbed
- Adjust label contents and cell size so that every test case fits on
the screen
- Minor tidying, slightly less harsh colors while preserving the
intentional debug coloring

## Testing

`cargo run --example testbed_ui`

![Screenshot 2025-05-27 at 8 53
43 AM](https://github.com/user-attachments/assets/97ea20ee-d265-45f6-8b99-bcd5f6030e30)

---------

Co-authored-by: François Mockers <mockersf@gmail.com>
2025-05-27 22:06:19 +00:00
François Mockers
8a223be651
Enable state scoped entities by default (#19354)
# Objective

- Enable state scoped entities by default
- Provide a way to disable it when needed

---------

Co-authored-by: Ben Frankel <ben.frankel7@gmail.com>
2025-05-26 20:26:41 +00:00
Emerson Coskey
7ab00ca185
Split Camera.hdr out into a new component (#18873)
# Objective

- Simplify `Camera` initialization
- allow effects to require HDR

## Solution

- Split out `Camera.hdr` into a marker `Hdr` component

## Testing

- ran `bloom_3d` example

---

## Showcase

```rs
// before
commands.spawn((
  Camera3d
  Camera {
    hdr: true
    ..Default::default()
  }
))

// after
commands.spawn((Camera3d, Hdr));

// other rendering components can require that the camera enables hdr!
// currently implemented for Bloom, AutoExposure, and Atmosphere.
#[require(Hdr)]
pub struct Bloom;
```
2025-05-26 19:24:45 +00:00
robtfm
b641aa0ecf
separate border colors (#18682)
# Objective

allow specifying the left/top/right/bottom border colors separately for
ui elements

fixes #14773

## Solution

- change `BorderColor` to 
```rs
pub struct BorderColor {
    pub left: Color,
    pub top: Color,
    pub right: Color,
    pub bottom: Color,
}
```
- generate one ui node per distinct border color, set flags for the
active borders
- render only the active borders

i chose to do this rather than adding multiple colors to the
ExtractedUiNode in order to minimize the impact for the common case
where all border colors are the same.

## Testing

modified the `borders` example to use separate colors:


![image](https://github.com/user-attachments/assets/5d9a4492-429a-4ee1-9656-215511886164)

the behaviour is a bit weird but it mirrors html/css border behaviour.

---

## Migration:

To keep the existing behaviour, just change `BorderColor(color)` into
`BorderColor::all(color)`.

---------

Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
2025-05-26 16:57:13 +00:00
ickshonpe
2b59ab8e2d
Fix Anchor component inconsistancies (#18393)
## Objective

Fix the misleading 2d anchor API where `Anchor` is a component and
required by `Text2d` but is stored on a field for sprites.

Fixes #18367

## Solution

Remove the `anchor` field from `Sprite` and require `Anchor` instead.

## Migration Guide

The `anchor` field has been removed from `Sprite`. Instead the `Anchor`
component is now a required component on `Sprite`.
2025-05-21 15:32:04 +00:00
ickshonpe
bf20c630a8
UI Node Gradients (#18139)
# Objective

Allowing drawing of UI nodes with a gradient instead of a flat color.

## Solution

The are three gradient structs corresponding to the three types of
gradients supported: `LinearGradient`, `ConicGradient` and
`RadialGradient`. These are then wrapped in a `Gradient` enum
discriminator which has `Linear`, `Conic` and `Radial` variants.

Each gradient type consists of the geometric properties for that
gradient and a list of color stops.
Color stops consist of a color, a position or angle and an optional
hint. If no position is specified for a stop, it's evenly spaced between
the previous and following stops. Color stop positions are absolute, if
you specify a list of stops:
```vec![vec![ColorStop::new(RED, Val::Percent(90.), ColorStop::new(Color::GREEN, Val::Percent(10.))```
the colors will be reordered and the gradient will transition from green at 10% to red at 90%. 

Colors are interpolated between the stops in SRGB space. The hint is a normalized value that can be used to shift the mid-point where the colors are mixed 50-50.  between the stop with the hint and the following stop.

For sharp stops with no interpolated transition, place two stops at the same position.

`ConicGradient`s and RadialGradient`s have a center which is set using the new `Position` type. `Position` consists of a normalized (relative to the UI node) `Vec2` anchor point and a responsive x, y offset.

To draw a UI node with a gradient you insert the components `BackgroundGradient` and `BorderGradient`, which both newtype a vector of `Gradient`s. If you set a background color, the background color is drawn first and the gradient(s) are drawn on top.

The implementation is deliberately simple and self contained. The shader draws the gradient in multiple passes which is quite inefficient for gradients with a very large number of color stops. It's simple though and there won't be any compatibility issues. We could make gradients a specialization for `UiPipeline` but I used a separate pipeline plugin for now to ensure that these changes don't break anything. 

#### Not supported in this PR
* Interpolation in other color spaces besides SRGB. 
* Images and text: This would need some breaking changes like a `UiColor` enum type with `Color` and `Gradient` variants, to enable `BorderColor`, `TextColor`, `BackgroundColor` and `ImageNode::color` to take either a `Color` or a gradient.
* Repeating gradients

## Testing

Includes three examples that can be used for testing:
```
cargo run --example linear_gradients
cargo run --example stacked_gradients
cargo run --example radial_gradients
```

Most of the code except the components API is contained within the `bevy_ui/src/render/linear_gradients` module.
There are no changes to any existing systems or plugins except for the addition of the gradients rendering systems to the render world schedule and the `Val` changes from #18164 . 

## Showcase

![gradients](https://github.com/user-attachments/assets/a09c5bb2-f9dc-4bc5-9d17-21a6338519d3)
![stacked](https://github.com/user-attachments/assets/7a1ad28e-8ae0-41d5-85b2-aa62647aef03)
![rad](https://github.com/user-attachments/assets/48609cf1-52aa-453c-afba-3b4845f3ddec)

Conic gradients can be used to draw simple pie charts like in CSS:
![PIE](https://github.com/user-attachments/assets/4594b96f-52ab-4974-911a-16d065d213bc)
2025-05-20 14:45:22 +00:00
mgi388
7a1fcb7fe7
Rename StateScoped to DespawnOnExitState and add DespawnOnEnterState (#18818)
# Objective

- Alternative to and builds on top of #16284.
- Fixes #15849.

## Solution

- Rename component `StateScoped` to `DespawnOnExitState`.
- Rename system `clear_state_scoped_entities` to
`despawn_entities_on_exit_state`.
- Add `DespawnOnEnterState` and `despawn_entities_on_enter_state` which
is the `OnEnter` equivalent.

> [!NOTE]
> Compared to #16284, the main change is that I did the rename in such a
way as to keep the terms `OnExit` and `OnEnter` together. In my own
game, I was adding `VisibleOnEnterState` and `HiddenOnExitState` and
when naming those, I kept the `OnExit` and `OnEnter` together. When I
checked #16284 it stood out to me that the naming was a bit awkward.
Putting the `State` in the middle and breaking up `OnEnter` and `OnExit`
also breaks searching for those terms.

## Open questions

1. Should we split `enable_state_scoped_entities` into two functions,
one for the `OnEnter` and one for the `OnExit`? I personally have zero
need thus far for the `OnEnter` version, so I'd be interested in not
having this enabled unless I ask for it.
2. If yes to 1., should we follow my lead in my `Visibility` state
components (see below) and name these
`app.enable_despawn_entities_on_enter_state()` and
`app.enable_despawn_entities_on_exit_state()`, which IMO says what it
does on the tin?

## Testing

Ran all changed examples.

## Side note: `VisibleOnEnterState` and `HiddenOnExitState`

For reference to anyone else and to help with the open questions, I'm
including the code I wrote for controlling entity visibility when a
state is entered/exited.

<details>
<summary>visibility.rs</summary>

```rust
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_reflect::prelude::*;
use bevy_render::prelude::*;
use bevy_state::{prelude::*, state::StateTransitionSteps};
use tracing::*;

pub trait AppExtStates {
    fn enable_visible_entities_on_enter_state<S: States>(&mut self) -> &mut Self;

    fn enable_hidden_entities_on_exit_state<S: States>(&mut self) -> &mut Self;
}

impl AppExtStates for App {
    fn enable_visible_entities_on_enter_state<S: States>(&mut self) -> &mut Self {
        self.main_mut()
            .enable_visible_entities_on_enter_state::<S>();
        self
    }

    fn enable_hidden_entities_on_exit_state<S: States>(&mut self) -> &mut Self {
        self.main_mut().enable_hidden_entities_on_exit_state::<S>();
        self
    }
}

impl AppExtStates for SubApp {
    fn enable_visible_entities_on_enter_state<S: States>(&mut self) -> &mut Self {
        if !self
            .world()
            .contains_resource::<Events<StateTransitionEvent<S>>>()
        {
            let name = core::any::type_name::<S>();
            warn!("Visible entities on enter state are enabled for state `{}`, but the state isn't installed in the app!", name);
        }
        // We work with [`StateTransition`] in set
        // [`StateTransitionSteps::ExitSchedules`] as opposed to [`OnExit`],
        // because [`OnExit`] only runs for one specific variant of the state.
        self.add_systems(
            StateTransition,
            update_to_visible_on_enter_state::<S>.in_set(StateTransitionSteps::ExitSchedules),
        )
    }

    fn enable_hidden_entities_on_exit_state<S: States>(&mut self) -> &mut Self {
        if !self
            .world()
            .contains_resource::<Events<StateTransitionEvent<S>>>()
        {
            let name = core::any::type_name::<S>();
            warn!("Hidden entities on exit state are enabled for state `{}`, but the state isn't installed in the app!", name);
        }
        // We work with [`StateTransition`] in set
        // [`StateTransitionSteps::ExitSchedules`] as opposed to [`OnExit`],
        // because [`OnExit`] only runs for one specific variant of the state.
        self.add_systems(
            StateTransition,
            update_to_hidden_on_exit_state::<S>.in_set(StateTransitionSteps::ExitSchedules),
        )
    }
}

#[derive(Clone, Component, Debug, Reflect)]
#[reflect(Component, Debug)]
pub struct VisibleOnEnterState<S: States>(pub S);

#[derive(Clone, Component, Debug, Reflect)]
#[reflect(Component, Debug)]
pub struct HiddenOnExitState<S: States>(pub S);

/// Makes entities marked with [`VisibleOnEnterState<S>`] visible when the state
/// `S` is entered.
pub fn update_to_visible_on_enter_state<S: States>(
    mut transitions: EventReader<StateTransitionEvent<S>>,
    mut query: Query<(&VisibleOnEnterState<S>, &mut Visibility)>,
) {
    // We use the latest event, because state machine internals generate at most
    // 1 transition event (per type) each frame. No event means no change
    // happened and we skip iterating all entities.
    let Some(transition) = transitions.read().last() else {
        return;
    };
    if transition.entered == transition.exited {
        return;
    }
    let Some(entered) = &transition.entered else {
        return;
    };
    for (binding, mut visibility) in query.iter_mut() {
        if binding.0 == *entered {
            visibility.set_if_neq(Visibility::Visible);
        }
    }
}

/// Makes entities marked with [`HiddenOnExitState<S>`] invisible when the state
/// `S` is exited.
pub fn update_to_hidden_on_exit_state<S: States>(
    mut transitions: EventReader<StateTransitionEvent<S>>,
    mut query: Query<(&HiddenOnExitState<S>, &mut Visibility)>,
) {
    // We use the latest event, because state machine internals generate at most
    // 1 transition event (per type) each frame. No event means no change
    // happened and we skip iterating all entities.
    let Some(transition) = transitions.read().last() else {
        return;
    };
    if transition.entered == transition.exited {
        return;
    }
    let Some(exited) = &transition.exited else {
        return;
    };
    for (binding, mut visibility) in query.iter_mut() {
        if binding.0 == *exited {
            visibility.set_if_neq(Visibility::Hidden);
        }
    }
}
```

</details>

---------

Co-authored-by: Benjamin Brienen <Benjamin.Brienen@outlook.com>
Co-authored-by: Ben Frankel <ben.frankel7@gmail.com>
2025-05-06 00:37:04 +00:00
Carter Anderson
e9a0ef49f9
Rename bevy_platform_support to bevy_platform (#18813)
# Objective

The goal of `bevy_platform_support` is to provide a set of platform
agnostic APIs, alongside platform-specific functionality. This is a high
traffic crate (providing things like HashMap and Instant). Especially in
light of https://github.com/bevyengine/bevy/discussions/18799, it
deserves a friendlier / shorter name.

Given that it hasn't had a full release yet, getting this change in
before Bevy 0.16 makes sense.

## Solution

- Rename `bevy_platform_support` to `bevy_platform`.
2025-04-11 23:13:28 +00:00
ickshonpe
f74abb1b89
Add sprite flipping to testbed_2d's sprite scene (#18537)
# Objective

Add sprite flipping to `testbed_2d`'s sprite scene

## Solution

Draw the sprite flipped in each axis and both axes.

Changed the sprite to the rectangular bevy banner with text and made the
images different colors.

## Testing
```
cargo run --example testbed_2d
```


![image](https://github.com/user-attachments/assets/dcfe687b-2f40-4417-bb20-6c892b425228)

---------

Co-authored-by: François Mockers <mockersf@gmail.com>
2025-03-25 21:14:17 +00:00
krunchington
a090e2fd47
Update shader_prepass, testbed_2d, and first_person_view_model examples to use children! macro (#18270)
# Objective

Contributes to #18238 
Updates the `shader_prepass`, `testbed_2d` and `first_person_view_model`
examples to use the `children!` macro. I wanted to keep the PR small but
chose to do 3 examples since they were all limited in scope

## Solution

Updates examples to use the Improved Spawning API merged in
https://github.com/bevyengine/bevy/pull/17521

## Testing

- Did you test these changes? If so, how?
- Opened the examples before and after and verified the same behavior
was observed. I did this on Ubuntu 24.04.2 LTS using `--features
wayland`.
- Are there any parts that need more testing?
- Other OS's and features can't hurt, but this is such a small change it
shouldn't be a problem.
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
  - Run the examples yourself with and without these changes.
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?
  - see above

---

## Showcase

n/a

## Migration Guide

n/a
2025-03-22 22:35:20 +00:00
ickshonpe
84b09b9398
Newtype Anchor (#18439)
# Objective

The `Anchor` component doesn't need to be a enum. The variants are just
mapped to `Vec2`s so it could be changed to a newtype with associated
const values, saving the space needed for the discriminator by the enum.

Also there was no benefit I think in hiding the underlying `Vec2`
representation of `Anchor`s.

Suggested by @atlv24.

Fixes #18459
Fixes #18460

## Solution

Change `Anchor` to a struct newtyping a `Vec2`, and its variants into
associated constants.

## Migration Guide

The anchor component has been changed from an enum to a struct newtyping
a `Vec2`. The `Custom` variant has been removed, instead to construct a
custom `Anchor` use its tuple constructor:
```rust
Sprite {
     anchor: Anchor(Vec2::new(0.25, 0.4)),
     ..default()
}
```
The other enum variants have been replaced with corresponding constants:
* `Anchor::BottomLeft` to `Anchor::BOTTOM_LEFT`
* `Anchor::Center` to `Anchor::CENTER`
* `Anchor::TopRight` to `Anchor::TOP_RIGHT`
* .. and so on for the remaining variants
2025-03-21 22:27:11 +00:00
krunchington
9ae7aa4399
Update testbed_ui to use Improved Spawning API (#18329)
# Objective

Contributes to #18238 
Updates the `text2d`, example to use the `children!` macro.

~~The SpawnIter usage in this example is maybe not the best. Very open
to opinions. I even left one `with_children` that I thought was just
much better than any alternative.~~

## Solution

Updates examples to use the Improved Spawning API merged in
https://github.com/bevyengine/bevy/pull/17521

## Testing

- Did you test these changes? If so, how?
- Opened the examples before and after and verified the same behavior
was observed. I did this on Ubuntu 24.04.2 LTS using `--features
wayland`.
- Are there any parts that need more testing?
- Other OS's and features can't hurt, but this is such a small change it
shouldn't be a problem.
- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
  - Run the examples yourself with and without these changes.
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?
  - see above

---

## Showcase

n/a

## Migration Guide

n/a
2025-03-21 19:32:12 +00:00
François Mockers
4b457cc2ce
Revert "don't use bevy_pbr for base bevy_gizmos plugin" (#18327)
# Objective

- #17581 broke gizmos
- Fixes #18325

## Solution

- Revert #17581 
- Add gizmos to testbed

## Testing

- Run any example with gizmos, it renders correctly
2025-03-17 22:23:42 +00:00
Rob Parrett
4a41525cd0
Add more to the 2d testbed for text (#18240)
# Objective

`Text2d` testing hasn't been as thorough as text in the UI, and it
suffered a bunch of bugs / perf issues in recent cycles.

## Solution

Add some more `Text2d` scenarios to the 2d testbed to catch bugs,
testing bounded and unbounded text with various justification.

## Testing

`cargo run --example testbed_2d` (press space a few times)

<img width="1280" alt="Screenshot 2025-03-10 at 1 02 03 PM"
src="https://github.com/user-attachments/assets/1e4ee39c-809b-4cc6-81bd-68e67b9625b5"
/>

---------

Co-authored-by: Ben Frankel <ben.frankel7@gmail.com>
2025-03-10 22:04:14 +00:00
François Mockers
019a6fde25
testbed for UI (#18091)
# Objective

- have a testbed for UI

## Solution

- move previous `ui` example to `full_ui`
- add a testbed ui with several scenes
- `ui_layout_rounding` is one of those scenes, so remove it as a
standalone example

the previous `ui` / new `full_ui` is I think still useful as it has some
things like scroll, debug ui that are not shown anywhere else
2025-03-04 11:02:55 +00:00
Rob Parrett
0cb3eaef67
Fix validation errors in Fox.glb (#17801)
# Objective

Fix gltf validation errors in `Fox.glb`.

Inspired by #8099, but that issue doesn't appear to describe a real bug
to fix, as far as I can tell.

## Solution

Use the latest version of the Fox from
[glTF-Sample-Assets](https://github.com/KhronosGroup/glTF-Sample-Assets/blob/main/Models/Fox/glTF-Binary/Fox.glb).

## Testing

Dropped both versions in https://github.khronos.org/glTF-Validator/

`cargo run --example animated_mesh` seems to still look fine.

Before:

```
The asset contains errors.
"numErrors": 126,
"numWarnings": 4184,
```

After:

```
The asset is valid.
"numErrors": 0,
"numWarnings": 0,
```

## Discussion

The 3d testbed was panicking with
```
thread 'main' panicked at examples/testbed/3d.rs:288:60:
called `Result::unwrap()` on an `Err` value: QueryDoesNotMatch(35v1 with components Transform, GlobalTransform, Visibility, InheritedVisibility, ViewVisibility, ChildOf, Children, Name)
```
Which is bizarre. I think this might be related to #17720, or maybe the
structure of the gltf changed.

I fixed it by using updating the testbed to use a more robust method of
finding the correct entity as is done in `animated_mesh`.
2025-02-11 22:19:24 +00:00
François Mockers
7d141829be
run example in CI on windows using static dxc (#17783)
# Objective

- Run more things on windows

## Solution

- With the update of wgpu and the statically linked dxc, examples now
run on windows in CI
2025-02-10 22:35:41 +00:00
François Mockers
e57f73207e
Smarter testbeds (#17573)
# Objective

- Improve CI when testing rendering by having smarter testbeds

## Solution

- CI testing no longer need a config file and will run with a default
config if not found
- It is now possible to give a name to a screenshot instead of just a
frame number
- 2d and 3d testbeds are now driven from code
  - a new system in testbed will watch for state changed
- on state changed, trigger a screenshot 100 frames after (so that the
scene has time to render) with the name of the scene
- when the screenshot is taken (`Captured` component has been removed),
switch scene
- this means less setup to run a testbed (no need for a config file),
screenshots have better names, and it's faster as we don't wait 100
frames for the screenshot to be taken

## Testing

- `cargo run --example testbed_2d --features bevy_ci_testing`
2025-01-31 22:38:39 +00:00
Carter Anderson
ba5e71f53d
Parent -> ChildOf (#17427)
Fixes #17412

## Objective

`Parent` uses the "has a X" naming convention. There is increasing
sentiment that we should use the "is a X" naming convention for
relationships (following #17398). This leaves `Children` as-is because
there is prevailing sentiment that `Children` is clearer than `ParentOf`
in many cases (especially when treating it like a collection).

This renames `Parent` to `ChildOf`.

This is just the implementation PR. To discuss the path forward, do so
in #17412.

## Migration Guide

- The `Parent` component has been renamed to `ChildOf`.
2025-01-20 22:13:29 +00:00
Antony
02bb151889
Rename PickingBehavior to Pickable (#17266)
# Objective

PR #17225 allowed for sprite picking to be opt-in. After some
discussion, it was agreed that `PickingBehavior` should be used to
opt-in to sprite picking behavior for entities. This leads to
`PickingBehavior` having two purposes: mark an entity for use in a
backend, and describe how it should be picked. Discussion led to the
name `Pickable`making more sense (also: this is what the component was
named before upstreaming).

A follow-up pass will be made after this PR to unify backends.

## Solution

Replace all instances of `PickingBehavior` and `picking_behavior` with
`Pickable` and `pickable`, respectively.

## Testing

CI

## Migration Guide

Change all instances of `PickingBehavior` to `Pickable`.
2025-01-12 05:36:52 +00:00
ickshonpe
1a18c9f87b
UI Debug Overlay show_hidden and show_clipped options (#17097)
# Objective

The UI debug overlay draws an outline for every UI node even if it is
invisible or clipped.
Disable debug outlines for hidden and clipped nodes by default and add
options to renable them if needed.

## Solution

* Add `show_hidden` and `show_clipped` fields to `UiDebugOptions`:
```rust
    /// Show outlines for non-visible UI nodes
    pub show_hidden: bool,
    /// Show outlines for clipped sections of UI nodes
    pub show_clipped: bool,
```

* Only extract debug outlines for hidden and clipped UI nodes if the
respective field in `UiDebugOptions` is set to `true`.

## Testing

Also added some extra features to the `testbed_ui` example that
demonstrate the new options:

cargo run --example testbed_ui --features "bevy_ui_debug"

<img width="641" alt="show-hidden-and-clipped"
src="https://github.com/user-attachments/assets/16a68600-170c-469e-a3c7-f7dae411dc40"
/>
2025-01-02 18:43:14 +00:00
Alice Cecile
48fe2a6e21
Rename "focus" in bevy_picking to "hover" (#16872)
# Objective

With the introduction of bevy_input_focus, the uses of "focus" in
bevy_picking are quite confusing and make searching hard.

Users will intuitively think these concepts are related, but they
actually aren't.

## Solution

Rename / rephrase all uses of "focus" in bevy_picking to refer to
"hover", since this is ultimately related to creating the `HoverMap`.

## Migration Guide

Various terms related to "focus" in `bevy_picking` have been renamed to
refer to "hover" to avoid confusion with `bevy_input_focus`. In
particular:

- The `update_focus` system has been renamed to `generate_hovermap`
- `PickSet::Focus` and `PostFocus` have been renamed to `Hover` and
`PostHover`
- The `bevy_picking::focus` module has been renamed to
`bevy_picking::hover`
- The `is_focus_enabled` field on `PickingPlugin` has been renamed to
`is_hover_enabled`
- The `focus_should_run` run condition has been renamed to
`hover_should_run`
2024-12-24 06:22:13 +00:00
Alice Cecile
6fd6ce1367
Feature flag testbed_3d code correctly (#16866)
# Objective

Rust-Analyzer was reporting problems with dead code in the 3d testbed
scene.

## Solution

These scenes don't work in CI on the Windows runner (because they're too
weak).

Mirror the feature flags from above onto the offending modules.

## Testing

RA no longer complains.
2024-12-17 21:34:11 +00:00
ickshonpe
9098973fb9
Draw the UI debug overlay using the UI renderer (#16693)
# Objective

Draw the UI debug overlay using the UI renderer.

Significantly simpler and easier to use than
`bevy_dev_tools::ui_debug_overlay` which uses `bevy_gizmos`.
* Supports multiple windows and UI rendered to texture.
* Draws rounded debug rects for rounded UI nodes. 

Fixes #16666

## Solution

Removed the `ui_debug_overlay` module from `bevy_dev_tools`.

Added a `bevy_ui_debug` feature gate.

Draw the UI debug overlay using the UI renderer.
Adds a new module `bevy_ui::render::debug_overlay`. 

The debug overlay extraction function queries for the existing UI layout
and then adds a border around each UI node with `u32::MAX / 2` added to
each stack index so it's drawn on top.

There is a `UiDebugOptions` resource that can be used to enable or
disable the debug overlay and set the line width.

## Testing

The `testbed_ui` example has been changed to use the new debug overlay:

```
cargo run --example testbed_ui --features bevy_ui_debug
```

Press Space to toggle the debug overlay on and off.

---

## Showcase

<img width="961" alt="testbed-ui-new-debug"
src="https://github.com/user-attachments/assets/e9523d18-39ae-46a8-adbe-7d3f3ab8e951">

## Migration Guide

The `ui_debug_overlay` module has been removed from `bevy_dev_tools`.
There is a new debug overlay implemented using the `bevy_ui` renderer.
To use it, enable the `bevy_ui_debug` feature and set the `enable` field
of the `UiDebugOptions` resource to `true`.
2024-12-11 00:49:47 +00:00
Aevyrie
61b98ec80f
Rename trigger.entity() to trigger.target() (#16716)
# Objective

- A `Trigger` has multiple associated `Entity`s - the entity observing
the event, and the entity that was targeted by the event.
- The field `entity: Entity` encodes no semantic information about what
the entity is used for, you can already tell that it's an `Entity` by
the type signature!

## Solution

- Rename `trigger.entity()` to `trigger.target()`

---

## Changelog

- `Trigger`s are associated with multiple entities. `Trigger::entity()`
has been renamed to `Trigger::target()` to reflect the semantics of the
entity being returned.

## Migration Guide

- Rename `Trigger::entity()` to `Trigger::target()`.
- Rename `ObserverTrigger::entity` to `ObserverTrigger::target`
2024-12-08 21:55:09 +00:00
ickshonpe
dd49dc71d2
Rename UiBoxShadowSamples to BoxShadowSamples. (#16505)
# Objective

The `UiBoxShadowSamples` resource should be renamed to
`BoxShadowSamples` so it matches the `BoxShadow` component.

## Migration Guide

`UiBoxShadowSamples` has been renamed to `BoxShadowSamples`
2024-12-03 19:43:26 +00:00
ickshonpe
56d5591028
Multiple box shadow support (#16502)
# Objective

Add support for multiple box shadows on a single `Node`.

## Solution

* Rename `BoxShadow` to `ShadowStyle` and remove its `Component` derive.
* Create a new `BoxShadow` component that newtypes a `Vec<ShadowStyle>`.
* Add a `new` constructor method to `BoxShadow` for single shadows.
* Change `extract_shadows` to iterate through a list of shadows per
node.

Render order is determined implicitly from the order of the shadows
stored in the `BoxShadow` component, back-to-front.
Might be more efficient to use a `SmallVec<[ShadowStyle; 1]>` for the
list of shadows but not sure if the extra friction is worth it.

## Testing

Added a node with four differently coloured shadows to the `box_shadow`
example.

---

## Showcase

```
cargo run --example box_shadow
```

<img width="460" alt="four-shadow"
src="https://github.com/user-attachments/assets/2f728c47-33b4-42e1-96ba-28a774b94b24">

## Migration Guide

Bevy UI now supports multiple shadows per node. A new struct
`ShadowStyle` is used to set the style for each shadow. And the
`BoxShadow` component is changed to a tuple struct wrapping a vector
containing a list of `ShadowStyle`s. To spawn a node with a single
shadow you can use the `new` constructor function:
```rust
commands.spawn((
    Node::default(),
    BoxShadow::new(
        Color::BLACK.with_alpha(0.8),
        Val::Percent(offset.x),
        Val::Percent(offset.y),
        Val::Percent(spread),
        Val::Px(blur),
    )
));
```

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-12-01 21:30:08 +00:00
François Mockers
fcfb685821
enable_state_scoped_entities() as a derive attribute (#16180)
# Objective

- I got tired of calling `enable_state_scoped_entities`, and though it
would make more sense to define that at the place where the state is
defined

## Solution

- add a derive attribute `#[states(scoped_entities)]` when derive
`States` or `SubStates` that enables it automatically when adding the
state

## Testing

- Ran the examples using it, they still work
2024-12-01 20:09:36 +00:00
Lomírus
740e8f74dd
fix: scroll list is non-scrollable (#16540)
# Objective

Run `testbed_ui` example:

```
cargo run --example testbed_ui
```

The scroll list is non-scrollable because it's blocked by the front
four-icon node.

## Solution

Add `PickingBehavior::IGNORE` for the front node

## Testing

- Did you test these changes? If so, how?

Yes.

- Are there any parts that need more testing?

No, I guess.

- How can other people (reviewers) test your changes? Is there anything
specific they need to know?
```
cargo run --example testbed_ui
```
- If relevant, what platforms did you test these changes on, and are
there any important ones you can't test?

macOS.
2024-11-28 17:27:58 +00:00
François Mockers
689c21d315
Add screenshot check on UI (#16486)
# Objective

- Progress towards #15918 
- Add test on UI

## Solution

- Get a single screenshot from the UI testbed example
- Remove older examples from runs in CI as they're covered by the
testbed to reduce CI duration
2024-11-23 18:38:24 +00:00
ickshonpe
aab36f3951
UI anti-aliasing fix (#16181)
# Objective

UI Anti-aliasing is incorrectly implemented. It always uses an edge
radius of 0.25 logical pixels, and ignores the physical resolution. For
low dpi screens 0.25 is is too low and on higher dpi screens the
physical edge radius is much too large, resulting in visual artifacts.

## Solution

Multiply the distance by the scale factor in the `antialias` function so
that the edge radius stays constant in physical pixels.

## Testing

To see the problem really clearly run the button example with `UiScale`
set really high. With `UiScale(25.)` on main if you examine the button's
border you can see a thick gradient fading away from the edges:

<img width="127" alt="edgg"
src="https://github.com/user-attachments/assets/7c852030-c0e8-4aef-8d3e-768cb2464cab">

With this PR the edges are sharp and smooth at all scale factors: 

<img width="127" alt="edge"
src="https://github.com/user-attachments/assets/b3231140-1bbc-4a4f-a1d3-dde21f287988">
2024-11-13 21:41:02 +00:00
Derick M
0ac495f7f4
Remove accesskit re-export from bevy_a11y (#16257)
# Objective

- Fixes #16235 

## Solution

- Both Bevy and AccessKit export a `Node` struct, to reduce confusion
Bevy will no longer re-export `AccessKit` from `bevy_a11y`

## Testing

- Tested locally

## Migration Guide

```diff
# main.rs
--    use bevy_a11y::{
--        accesskit::{Node, Rect, Role},
--        AccessibilityNode,
--    };
++    use bevy_a11y::AccessibilityNode;
++    use accesskit::{Node, Rect, Role};

# Cargo.toml
++    accesskit = "0.17"
```

- Users will need to add `accesskit = "0.17"` to the dependencies
section of their `Cargo.toml` file and update their `accesskit` use
statements to come directly from the external crate instead of
`bevy_a11y`.
- Make sure to keep the versions of `accesskit` aligned with the
versions Bevy uses.
2024-11-08 21:01:16 +00:00
Carter Anderson
f754cecb49
UiImage -> ImageNode, UiImageSize -> ImageNodeSize (#16271)
# Objective

Align `UiImage` with the new `XNode` naming convention.

## Solution

- Rename `UiImage` to `ImageNode`
- Rename `UiImageSize` to `ImageNodeSize`

---

## Migration Guide

Before:
```rust
commands.spawn(UiImage::new(image));
````

After:
```rust
commands.spawn(ImageNode::new(image));
```
2024-11-07 21:52:58 +00:00
Rich Churcher
cdc18ee886
Move UI example to testbed (#16241)
# Objective

UI example is quite extensive, probably not the best teaching example
anymore.

Closes #16230.
2024-11-07 20:57:45 +00:00
ickshonpe
3d72f494a2
Layout rounding debug example (#16096)
# Objective

Simple example for debugging layout rounding errors.

<img width="1039" height="752" alt="layout_rounding_debug"
src="https://github.com/user-attachments/assets/12673000-e267-467e-b25b-3f8001c1347c">

Any white lines are gaps in the layout caused by coordinate rounding
errors.
2024-10-27 20:08:51 +00:00
François Mockers
ec268420f7
Check examples screenshots on windows (#16010)
# Objective

- Checks screenshots on Windows
- Progress towards #15918 

## Solution

- Checks screenshots on Windows
- Also disable the helmet gltf scene in windows ci as it doesn't work
2024-10-20 14:58:35 +00:00
François Mockers
74dedb2841
Testbed for 3d (#15993)
# Objective

- Progress towards #15918 
- Add tests for 3d

## Solution

- Add tests that cover lights, bloom, gltf and animation
- Removed examples `contributors` and `load_gltf` as they don't
contribute additional checks to CI

## Testing

- `CI_TESTING_CONFIG=.github/example-run/testbed_3d.ron cargo run
--example testbed_3d --features "bevy_ci_testing"`
2024-10-19 19:32:03 +00:00
François Mockers
e1b9f545fb
Introduce testbed examples starting with 2d (#15954)
# Objective

- Make progress for #15918 
- Start with 2d

## Solution

- Remove screenshots for existing examples as they're not deterministic
- Create new "testbed" example category, with a 2d one to start

## Testing

- Run `CI_TESTING_CONFIG=.github/example-run/testbed_2d.ron cargo run
--example testbed_2d --features "bevy_ci_testing"`
- ???
- Check the screenshots
2024-10-16 17:37:47 +00:00