# Preoperative for `Interaction`
# Table of Contents
- [Subject, background, and context](#Subject-background-and-context")
- [The birth of `Interaction`](#The-birth-of-Interaction)
- [Issues and pulls](#Issues-and-pulls)
- [Summary of what the current `Interaction` type does](#Summary-of-what-the-current-Interaction-type-does)
- [Redundancy, Similarity, and Conflict](#Redundancy-Similarity-and-Conflict)
- [`Pointer`](#Pointer)
- [`bevy_feathers`](#bevy_feathers)
- [`bevy_ui_widgets`](#bevy_ui_widgets)
- [Problems](#Problems)
- [The roll-off problem](#The-roll-off-problem)
- [Accessibility](#accessibility)
- [What it would take to get rid of it](#What-it-would-take-to-get-rid-of-it)
- [Current practices without `Interaction`](#Current-practices-without-Interaction)
- [By `Pointer`](#By-Pointer)
- [By `bevy_feathers`](#By-bevy_feathers)
- [By `bevy_ui_widgets`](#By-bevy_ui_widgets)
- [Considerations](#Considerations)
- [`bevy_a11y`](#bevy_a11y)
- [Procedure](#Procedure)
## Subject, background, and context
[Interaction](https://github.com/bevyengine/bevy/blob/7958177744fc0e702b932affc9b852e3a3740026/crates/bevy_ui/src/focus.rs#L52)
> I would love a summary of exactly what the current Interaction type does, and what it would take to get rid of it. \- Alice
### The birth of `Interaction`
🛒 pushed [`3d2a4f6`](https://github.com/bevyengine/bevy/commit/3d2a4f6c394becf78dbce9e5af8a2ba24970003d) on Jul 28, 2020:
> ui: combine Click and Hover into Interaction
```diff
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
-pub enum Click {
- Released,
- Pressed,
-}
-
-impl Default for Click {
- fn default() -> Self {
- Click::Released
- }
-}
-
-#[derive(Copy, Clone, Eq, PartialEq, Debug)]
-pub enum Hover {
+pub enum Interaction {
+ Clicked,
Hovered,
- NotHovered,
+ None,
}
-impl Default for Hover {
+impl Default for Interaction {
fn default() -> Self {
- Hover::NotHovered
+ Interaction::None
}
}
```
### Issues and pulls
This section aggregates/summarizes discussions from all relevant issues and PRs on this topic. They tend to:
- Add something to `Interaction`
- Replace `Interaction` with something similar
#### Pull [420](https://github.com/bevyengine/bevy/pull/420)
<!-- on Sep 2, 2020 -->
Summary: Let's make the UI press/click to be a sequence of two input events (press & release).
Closed: time-based state transition for UI input? This feels a bit odd.
Note: it currently does something similar
```rs
if *interaction != Interaction::Pressed {
*interaction = Interaction::Pressed;
// if the mouse was simultaneously released, reset this Interaction in the next
// frame
if mouse_released {
state.entities_to_reset.push(node.entity);
}
}
```
#### Issue [1239](https://github.com/bevyengine/bevy/issues/1239)
<!-- on Jan 13, 2021 -->
Summary: Not only `MouseButton::Left` but also touch.
Closed: We'll go for it in `bevy_picking`
Note: it currently does something like that
```rs
pub fn ui_focus_system( /* ... */) {
let mouse_released =
mouse_button_input.just_released(MouseButton::Left)
|| touches_input.any_just_released();
// ...
}
```
#### Issue [2322](https://github.com/bevyengine/bevy/issues/2322)
<!-- on Jun 9, 2021 -->
Summary: Generalize `MouseButton::Left`
Closed: Will do in [2324](#Pull-2324)
#### Pull [2324](https://github.com/bevyengine/bevy/pull/2324)
<!-- on Jun 9, 2021 -->
```diff
- let mouse_clicked =
- mouse_button_input.just_pressed(MouseButton::Left) || touches_input.just_released(0);
+ let interacting_from = mouse_button_input
+ .get_just_pressed()
+ .last()
+ .map(|button| match button {
+ MouseButton::Left => ClickedWith::MouseLeftButton,
+ MouseButton::Middle => ClickedWith::MouseMiddleButton,
+ MouseButton::Right => ClickedWith::MouseRightButton,
+ MouseButton::Other(button) => ClickedWith::MouseOtherButton(*button),
+ })
+ .or_else(|| touches_input.just_released(0).then(|| ClickedWith::Touch));
```
Closed:
> If you "press left click", "press right click", then "release right click", it will be in the "hovered" state \- 🛒
~~generics~~
#### Issue [5769](https://github.com/bevyengine/bevy/issues/5769)
<!-- on Aug 22, 2022 -->
Summary: `Interaction::Released`
Closed: Will do in `bevy_picking`
#### Pull [7257](https://github.com/bevyengine/bevy/pull/7257)
<!-- on Jan 18, 2023 -->
Summary: Let's revert [this](#Summary-of-what-the-current-Interaction-type-does) and add something _more_.
Close: No _more_.
#### Pull [8157](https://github.com/bevyengine/bevy/pull/8157)
<!-- on Mar 22, 2023 -->
Summary: (Basically [5769](#Issue-5769)) `Interaction::Released`
Closed: Will do in [15597](#Pull-15597)
#### Pull [15597](https://github.com/bevyengine/bevy/pull/15597)
<!-- on Oct 2, 2024 -->
Summary: Turn `Interaction` into `Button`
```rs
// BEFORE crates/bevy_ui/src/focus.rs
pub enum Interaction {
Pressed,
Hovered,
None,
}
// AFTER crates/bevy_ui/src/widget/button.rs
pub struct Button {
pub pressed: bool, // you no more marker
pub hovered: bool,
}
```
Closed: Keep the marker; `Button` can be different from or more than just press/hover
#### Pull [10141](https://github.com/bevyengine/bevy/pull/10141)
<!-- on Jul 22, 2023 -->
Summary: `Clicked` event
```rs
// The old click (pre-Interaction)
pub enum Click {
Released,
Pressed,
}
// Cliked in 10141
#[derive(Event)]
pub struct Clicked(pub Entity);
```
Closed: narrow, web-unversed
#### Pull [9240](https://github.com/bevyengine/bevy/pull/9240)
<!-- on Jul 22, 2023 -->
Summary: (Basically [10141](#Pull-10141)) `Clicked` event
### The current state of `Interaction`
The [issue](https://github.com/bevyengine/bevy/issues/15550) has existed for two years
## Summary of what the current `Interaction` type does
```rs
pub enum Interaction {
Pressed,
Hovered,
None,
}
```
This effectively wrapped click and hover in `3d2a4f6`
```diff
.filter_map(|(entity, node, transform,
- click, hover,
+ interaction,
focus_policy)| {
```
This in speculation resolved the inconvenience of seemingly _NOT_ mutually exclusive nature of `Click` (renamed to `Pressed`) and `Hover`
```diff
fn button_system(
button_materials: Res<ButtonMaterials>,
- mut click_query: Query<(
+ mut interaction_query: Query<(
&Button,
- Mutated<Click>,
- Option<&Hover>,
- &mut Handle<ColorMaterial>,
- &Children,
- )>,
- mut hover_query: Query<(
- &Button,
- Mutated<Hover>,
- Option<&Click>,
+ Mutated<Interaction>,
&mut Handle<ColorMaterial>,
&Children,
)>,
text_query: Query<&mut Text>,
) {
```
Also, `Interaction` is intuitive when it's used with _just_ buttons
```rs
// crates/bevy_ui/src/widget/button.rs
#[reflect(Component, Default, Debug, PartialEq, Clone)]
#[require(Node, FocusPolicy::Block, Interaction)]
pub struct Button;
```
```rs
// some-user-code.rs
fn button(/* ... */) -> impl Bundle {
(
Node { /* ... */ },
Button,
)
}
fn do_something(
mut interaction_query: Query<&Interaction, (Changed<Interaction>, With<Button>)>,
) {
for interaction in &interaction_query {
if *interaction == Interaction::Pressed { /* ... */ }
/* ... */
}
}
```
### Redundancy, Similarity, and Conflict
### `InteractionDisabled`
> InteractionDisabled is not going away; although it has a similar name, it's not actually connected to Interaction. \- Talin
#### Pointer
~~`Po`+`Interaction`~~
```rs
pub enum PointerAction {
Press(PointerButton), // Redundant to `Interaction::Pressed`
Release(PointerButton),
Move {
delta: Vec2,
},
Scroll {
unit: MouseScrollUnit,
x: f32,
y: f32,
},
Cancel,
}
```
#### `ButtonState`
```rs
// crates/bevy_input/src/lib.rs
pub enum ButtonState {
Pressed, // Redundant to `Interaction::Pressed`
Released,
}
```
For practice on button interaction, see [here](#By-Pointer).
#### `bevy_feathers`
A UI crate exploring[^explore] a theme-based[^theme-based]/token-based styling framework, which comes with baseline features **including such button interactions**[^has-button-interaction].
Theming is achieved by mapping a set of `ThemeToken`s, whose values are [short](https://github.com/rust-lang/rust-analyzer/tree/master/lib/smol_str), dot-splittable strings.`"feathers.button.bg.hover"`, for example
```rs
// crates/bevy_feathers/src/tokens.rs
pub const BUTTON_BG_HOVER: ThemeToken = ThemeToken::new_static("feathers.button.bg.hover");
```
In the workflow, the user assigns values for the respective UI component[^ui-component] per token.
```rs
fn create_my_super_cool_theme() -> ThemeProps {
ThemeProps {
color: HashMap::from([
/* ... */
(tokens::BUTTON_BG_HOVER, palette::GRAY_3.lighter(0.42)), // or tailwind, ...
]),
/* here smells like something in the future: layout, ... */
}
}
```
For practice with button interaction, see [here](#By-bevy_feathers).
[^explore]: Experimental feature in Bevy 0.18 (2026); The crate is expected to be officialized with BSN migration.
[^theme-based]: For example, _Material UI_ : [AppTheme](https://gist.github.com/micttyoid/4b320f92d94e4f92732f60c3cb306b63), [themePrimitives](https://gist.github.com/micttyoid/6c403058a6d38ed068700c9b66e97943)
[^has-button-interaction]: For hover, `bevy_picking::hover::Hovered` is used.
[^ui-component]: Visual component represented by the entity, or a set of entities, not an ECS Component.
#### `bevy_ui_widgets`
For button interactions specifically, `UiWidgetsPlugins` includes `ButtonPlugin`.
```rs
// crates/bevy_ui_widgets/src/button.rs
impl Plugin for ButtonPlugin {
fn build(&self, app: &mut App) {
// They insert/remove `ButtonState`
app.add_observer(button_on_key_event)
.add_observer(button_on_pointer_down) // On<Pointer<Press>>
.add_observer(button_on_pointer_up) // On<Pointer<Up>>
.add_observer(button_on_pointer_click) // On<Pointer<Click>>
.add_observer(button_on_pointer_drag_end)
.add_observer(button_on_pointer_cancel);
}
}
```
For practice with button interaction, see [here](#By-bevy_ui_widgets).
### Problems
Design assumptions and their counterexamples.
#### Assumption 1: All widget are button-like
TODO
#### Assumption 2: Widgets only have a single hoverable part
TODO
#### The roll-off problem
<img src="https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExOWtpM244dWgwb3VobnRwcWdyMHZkbWpmYmhmbTM0N3FkZ2Q4NGgxdiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/d3OyMXApob81jHUsB5/giphy.gif" alt="Roll-off" width="100" alt="Alt Text"> [^roll-off-image]
The user is about to complete the click and changes its mind. So, the user "rolls the cursor off" the button area to cancel.
`Interaction` is not effective for such interaction since it cannot carry both press and hover action at the same time. [^both-at-the-same-time]
[^roll-off-image]: This circles around the cursor per click.
[^both-at-the-same-time]: In a single frame.
#### Accessibility
TODO
#### Ambiguities
"widgets"
- `crates/bevy_ui/src/widget`
- `crates/bevy_ui_widgets`
"button"
- `crates/bevy_ui/src/widget/button.rs`
- `crates/bevy_ui_widgets/src/button.rs`
## What it would take to get rid of it
Tasks
- Removal / Replacement of associated:
- code & types (except `examples/`)
- tests
- documentation (comment & web-deployed result)
- examples (`examples/`)
- Deprecation notice to users (the deprecated attribute)
- how long?
Decisions
- Whether or not breaking change is allowed.
### Commonly asked
#### Debug
> From game maker perspective- how to most easily get the "is this button just released" info?
- solution-1: observer
#### Focus
### Current practices without `Interaction`
Here we demonstrate how similar functionalities (press/hover) have been achieved without interaction.
#### By `Pointer`
```rs
fn apply_interaction_foo_on_click(
click: On<Pointer<Click>>,
mut foo_query: Query<&Foo>,
) {
let Ok(foo) = foo_query.get_mut(click.event_target()) else {
return;
};
// ...
}
```
See also:
- [bevy_ui_widgets/src/button.rs](https://github.com/bevyengine/bevy/blob/0962373d7c4a622120ed62d93214aa208abcc722/crates/bevy_ui_widgets/src/button.rs#L48)
- [bevy_new_2d](https://github.com/TheBevyFlock/bevy_new_2d/blob/435ece45f7e923713bef64cebc411cda1121b2c3/src/theme/widget.rs#L93)
- [A comment in 15597](https://github.com/bevyengine/bevy/pull/15597#issuecomment-2389459101)
#### By `bevy_feathers`
```rs
fn my_root() -> impl Bundle {
(
Node::default(),
children![
(
button(
ButtonProps {
..default()
},
(),
Spawn((Text::new("Foo"), ThemedText))
),
observe(|_activate: On<Activate>| {
info!("clicked!");
}),
),
],
)
}
```
See also
- The [full-length](https://gist.github.com/micttyoid/647e6a96ea3bf2ccd561f49de732776c)
- [examples/feathers/ui/widgets/feathers.rs](https://github.com/bevyengine/bevy/blob/main/examples/ui/widgets/feathers.rs)
#### By `bevy_ui_widgets`
```rs
// .add_observer(button_on_interaction::<Add, Pressed>)
// .add_observer(button_on_interaction::<Remove, Pressed>)
// .add_observer(button_on_interaction::<Insert, Hovered>)
fn button_on_interaction<E: EntityEvent, C: Component>(
event: On<E, C>,
mut buttons: Query<(&Hovered, Has<Pressed>, &mut BackgroundColor), With<MyButton>>,
) {
if let Ok((hovered, pressed, mut color)) = buttons.get_mut(event.event_target()) {
let hovered = hovered.get();
let pressed = pressed && !(E::is::<Remove>() && C::is::<Pressed>());
match (hovered, pressed) {
// Pressed (and hovered)
(true, true) => *color = PRESSED_BUTTON.into(),
// Hovered (and unpressed)
(true, false) => *color = HOVERED_BUTTON.into(),
// Unhovered
(false, _) => *color = NORMAL_BUTTON.into(),
}
}
}
```
See also
- The [full-length](https://gist.github.com/micttyoid/9fff5649face8e0d4b6a2f02402d7a20)
- [examples/ui/widgets/standard_widgets.rs](https://github.com/bevyengine/bevy/blob/main/examples/ui/widgets/standard_widgets.rs)
### Considerations
#### Bubbling
[Event bubbling](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/Event_bubbling), in context of button interaction, refers to the propagation of events between entities[^in-ecs]. which can be handled by [`InputDispatchPlugin`](https://github.com/bevyengine/bevy/blob/8b2dfb546483faddec0003a801fedb1d7d1d1b91/crates/bevy_input_focus/src/lib.rs#L224)
[^in-ecs]: In ECS.
#### `bevy_a11y`
TODO
### ??
#### Associated code & types
3 entries
```
crates/bevy_ui/src/focus.rs
crates/bevy_ui/src/lib.rs
crates/bevy_ui/src/widget/button.rs
```
#### Associated examples (`examples/`)
27 entries, directly using `Interaction`
```
examples/3d/clustered_decals.rs
examples/3d/color_grading.rs
examples/3d/light_textures.rs
examples/3d/mirror.rs
examples/3d/split_screen.rs
examples/animation/animation_graph.rs
examples/animation/animation_masks.rs
examples/games/game_menu.rs
examples/helpers/widgets.rs
examples/mobile/src/lib.rs
examples/state/computed_states.rs
examples/state/custom_transitions.rs
examples/state/states.rs
examples/state/sub_states.rs
examples/stress_tests/many_buttons.rs
examples/testbed/ui.rs
examples/ui/images/ui_texture_atlas_slice.rs
examples/ui/images/ui_texture_slice.rs
examples/ui/layout/display_and_visibility.rs
examples/ui/layout/ghost_nodes.rs
examples/ui/layout/size_constraints.rs
examples/ui/scroll_and_overflow/overflow.rs
examples/ui/styling/box_shadow.rs
examples/ui/ui_transform.rs
examples/ui/widgets/button.rs
examples/ui/widgets/tab_navigation.rs
examples/usage/cooldown.rs
```
8 entries, not directly using `Interaction` but has a mod using `Interaction`
```
examples/3d/clustered_decal_maps.rs
examples/3d/contact_shadows.rs
examples/3d/mixed_lighting.rs
examples/3d/pccm.rs
examples/3d/ssr.rs
examples/3d/pcss.rs
examples/3d/light_probe_blending.rs
examples/2d/dynamic_mip_generation.rs
```
#### Associated documentation
Web-deployed:
- [Interaction event](https://bevy.org/news/introducing-bevy/#interaction-events)
Comment-generated documentations (the comments)
TODO
### Deprecation notice
- for how long?
TODO
## Procedure
### Overview
```mermaid
flowchart LR
A["Update <code>DefaultPlugins</code>"] --> B["Update <code>examples/*</code>"] --> C["Remove <code>Interaction</code>"] --> D["Migrate"]
```
NOTE:
- Each step has to handle relavant/affected on-going PRs
- Each step has to inform / document / update migration guide / notify deprecation about the change if necessary
### Update `DefaultPlugins`
PR(s): [23346](https://github.com/bevyengine/bevy/pull/23346)
#### Scheduling
```rs
// System set `UiSystems`
pub enum UiSystems {
Focus,
Prepare, // ui_widgets::PopoverPlugin
Propagate,
Content,
Layout,
PostLayout,
Stack,
}
```
Component`UiGlobalTransform`
> `UiGlobalTransform`s are updated from `UiTransform` and `Node`
```rs
// UiPlugin
fn build(&self, app: &mut App) {
app // ...
.configure_sets(
PostUpdate,
(
CameraUpdateSystems,
UiSystems::Prepare.after(AnimationSystems), // PopoverPlugin
// ...
)
.chain(),
)
.add_systems(/*...*/);
// ...
}
```
Plugin `ui_widgets::PopoverPlugin`
Regarding scheduler, this uses: `ComputedNode`, `UiGlobalTransform`
### Update `examples/*`
PR(s): [23285](https://github.com/bevyengine/bevy/pull/23285)
### Remove `Interaction`
PR(s): TODO
### Migrate
PR(s): TODO