Skip to content

Commit df5dfcd

Browse files
OverrideClip interaction fix (#20064)
# Objective Picking was changed in the UI transform PR to walk up the tree recursively to check if an interaction was on a clipped node. `OverrideClip` only affects a node's local clipping rect, so valid interactions can be ignored if a node has clipped ancestors. ## Solution Add a `Without<OverrideClip>` query filter to the picking systems' `child_of_query`s. ## Testing This modified `button` example can be used to test the change: ```rust //! This example illustrates how to create a button that changes color and text based on its //! interaction state. use bevy::{color::palettes::basic::*, input_focus::InputFocus, prelude::*, winit::WinitSettings}; fn main() { App::new() .add_plugins(DefaultPlugins) // Only run the app when there is user input. This will significantly reduce CPU/GPU use. .insert_resource(WinitSettings::desktop_app()) // `InputFocus` must be set for accessibility to recognize the button. .init_resource::<InputFocus>() .add_systems(Startup, setup) .add_systems(Update, button_system) .run(); } const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15); const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25); const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35); fn button_system( mut input_focus: ResMut<InputFocus>, mut interaction_query: Query< ( Entity, &Interaction, &mut BackgroundColor, &mut BorderColor, &mut Button, &Children, ), Changed<Interaction>, >, mut text_query: Query<&mut Text>, ) { for (entity, interaction, mut color, mut border_color, mut button, children) in &mut interaction_query { let mut text = text_query.get_mut(children[0]).unwrap(); match *interaction { Interaction::Pressed => { input_focus.set(entity); **text = "Press".to_string(); *color = PRESSED_BUTTON.into(); *border_color = BorderColor::all(RED.into()); // The accessibility system's only update the button's state when the `Button` component is marked as changed. button.set_changed(); } Interaction::Hovered => { input_focus.set(entity); **text = "Hover".to_string(); *color = HOVERED_BUTTON.into(); *border_color = BorderColor::all(Color::WHITE); button.set_changed(); } Interaction::None => { input_focus.clear(); **text = "Button".to_string(); *color = NORMAL_BUTTON.into(); *border_color = BorderColor::all(Color::BLACK); } } } } fn setup(mut commands: Commands, assets: Res<AssetServer>) { // ui camera commands.spawn(Camera2d); commands.spawn(button(&assets)); } fn button(asset_server: &AssetServer) -> impl Bundle + use<> { ( Node { width: Val::Percent(100.0), height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() }, children![( Node { width: Val::Px(0.), height: Val::Px(0.), overflow: Overflow::clip(), ..default() }, children![( //OverrideClip, Button, Node { position_type: PositionType::Absolute, width: Val::Px(150.0), height: Val::Px(65.0), border: UiRect::all(Val::Px(5.0)), // horizontally center child text justify_content: JustifyContent::Center, // vertically center child text align_items: AlignItems::Center, ..default() }, BorderColor::all(Color::WHITE), BorderRadius::MAX, BackgroundColor(Color::BLACK), children![( Text::new("Button"), TextFont { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 33.0, ..default() }, TextColor(Color::srgb(0.9, 0.9, 0.9)), TextShadow::default(), )] )], )], ) } ``` On main the button ignores interactions, with this PR it should respond correctly. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent 63dd8b6 commit df5dfcd

File tree

2 files changed

+8
-5
lines changed

2 files changed

+8
-5
lines changed

crates/bevy_ui/src/focus.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
use crate::{ui_transform::UiGlobalTransform, ComputedNode, ComputedNodeTarget, Node, UiStack};
1+
use crate::{
2+
ui_transform::UiGlobalTransform, ComputedNode, ComputedNodeTarget, Node, OverrideClip, UiStack,
3+
};
24
use bevy_ecs::{
35
change_detection::DetectChangesMut,
46
entity::{ContainsEntity, Entity},
57
hierarchy::ChildOf,
68
prelude::{Component, With},
7-
query::QueryData,
9+
query::{QueryData, Without},
810
reflect::ReflectComponent,
911
system::{Local, Query, Res},
1012
};
@@ -157,7 +159,7 @@ pub fn ui_focus_system(
157159
ui_stack: Res<UiStack>,
158160
mut node_query: Query<NodeQuery>,
159161
clipping_query: Query<(&ComputedNode, &UiGlobalTransform, &Node)>,
160-
child_of_query: Query<&ChildOf>,
162+
child_of_query: Query<&ChildOf, Without<OverrideClip>>,
161163
) {
162164
let primary_window = primary_window.iter().next();
163165

@@ -325,11 +327,12 @@ pub fn ui_focus_system(
325327
}
326328

327329
/// Walk up the tree child-to-parent checking that `point` is not clipped by any ancestor node.
330+
/// If `entity` has an [`OverrideClip`] component it ignores any inherited clipping and returns true.
328331
pub fn clip_check_recursive(
329332
point: Vec2,
330333
entity: Entity,
331334
clipping_query: &Query<'_, '_, (&ComputedNode, &UiGlobalTransform, &Node)>,
332-
child_of_query: &Query<&ChildOf>,
335+
child_of_query: &Query<&ChildOf, Without<OverrideClip>>,
333336
) -> bool {
334337
if let Ok(child_of) = child_of_query.get(entity) {
335338
let parent = child_of.0;

crates/bevy_ui/src/picking_backend.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ pub fn ui_picking(
109109
node_query: Query<NodeQuery>,
110110
mut output: EventWriter<PointerHits>,
111111
clipping_query: Query<(&ComputedNode, &UiGlobalTransform, &Node)>,
112-
child_of_query: Query<&ChildOf>,
112+
child_of_query: Query<&ChildOf, Without<OverrideClip>>,
113113
) {
114114
// For each camera, the pointer and its position
115115
let mut pointer_pos_by_camera = HashMap::<Entity, HashMap<PointerId, Vec2>>::default();

0 commit comments

Comments
 (0)