# 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