Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make clipped areas of UI nodes non-interactive #10454

Merged
merged 8 commits into from
Nov 22, 2023
Merged
46 changes: 19 additions & 27 deletions crates/bevy_ui/src/focus.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::{camera_config::UiCameraConfig, CalculatedClip, Node, UiScale, UiStack};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
change_detection::DetectChangesMut,
entity::Entity,
Expand All @@ -9,7 +8,7 @@ use bevy_ecs::{
system::{Local, Query, Res},
};
use bevy_input::{mouse::MouseButton, touch::Touches, Input};
use bevy_math::Vec2;
use bevy_math::{Rect, Vec2};
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::ViewVisibility};
use bevy_transform::components::GlobalTransform;
Expand Down Expand Up @@ -57,33 +56,24 @@ impl Default for Interaction {

/// A component storing the position of the mouse relative to the node, (0., 0.) being the top-left corner and (1., 1.) being the bottom-right
/// If the mouse is not over the node, the value will go beyond the range of (0., 0.) to (1., 1.)
/// A None value means that the cursor position is unknown.

///
/// It can be used alongside interaction to get the position of the press.
#[derive(
Component,
Deref,
DerefMut,
Copy,
Clone,
Default,
PartialEq,
Debug,
Reflect,
Serialize,
Deserialize,
)]
#[derive(Component, Copy, Clone, Default, PartialEq, Debug, Reflect, Serialize, Deserialize)]
#[reflect(Component, Serialize, Deserialize, PartialEq)]
pub struct RelativeCursorPosition {
/// Cursor position relative to size and position of the Node.
/// Visible area of the Node relative to the size of the entire Node.
pub normalized_visible_node_rect: Rect,
/// Cursor position relative to the size and position of the Node.
/// A None value indicates that the cursor position is unknown.
pub normalized: Option<Vec2>,
}

impl RelativeCursorPosition {
/// A helper function to check if the mouse is over the node
pub fn mouse_over(&self) -> bool {
self.normalized
.map(|position| (0.0..1.).contains(&position.x) && (0.0..1.).contains(&position.y))
.map(|position| self.normalized_visible_node_rect.contains(position))
.unwrap_or(false)
}
}
Expand Down Expand Up @@ -216,22 +206,24 @@ pub fn ui_focus_system(
}
}

let position = node.global_transform.translation();
let ui_position = position.truncate();
let extents = node.node.size() / 2.0;
let mut min = ui_position - extents;
if let Some(clip) = node.calculated_clip {
min = Vec2::max(min, clip.clip.min);
}
let node_rect = node.node.logical_rect(node.global_transform);

// Intersect with the calculated clip rect to find the bounds of the visible region of the node
let visible_rect = node
.calculated_clip
.map(|clip| node_rect.intersect(clip.clip))
.unwrap_or(node_rect);

// The mouse position relative to the node
// (0., 0.) is the top-left corner, (1., 1.) is the bottom-right corner
// Coordinates are relative to the entire node, not just the visible region.
let relative_cursor_position = cursor_position
.map(|cursor_position| (cursor_position - min) / node.node.size());
.map(|cursor_position| (cursor_position - node_rect.min) / node_rect.size());

// If the current cursor position is within the bounds of the node, consider it for
// If the current cursor position is within the bounds of the node's visible area, consider it for
// clicking
let relative_cursor_position_component = RelativeCursorPosition {
normalized_visible_node_rect: visible_rect.normalize(node_rect),
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
normalized: relative_cursor_position,
};

Expand Down
38 changes: 30 additions & 8 deletions examples/ui/overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ fn main() {
// Only run the app when there is user input. This will significantly reduce CPU/GPU use.
.insert_resource(WinitSettings::desktop_app())
.add_systems(Startup, setup)
.add_systems(Update, update_outlines)
.run();
}

Expand Down Expand Up @@ -86,18 +87,39 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..Default::default()
})
.with_children(|parent| {
parent.spawn(ImageBundle {
image: UiImage::new(image.clone()),
style: Style {
min_width: Val::Px(100.),
min_height: Val::Px(100.),
parent.spawn((
ImageBundle {
image: UiImage::new(image.clone()),
style: Style {
min_width: Val::Px(100.),
min_height: Val::Px(100.),
..Default::default()
},
background_color: Color::WHITE.into(),

..Default::default()
},
background_color: Color::WHITE.into(),
..Default::default()
});
Interaction::default(),
Outline {
width: Val::Px(2.),
offset: Val::Px(2.),
color: Color::NONE,
},
));
});
});
}
});
}

fn update_outlines(mut outlines_query: Query<(&mut Outline, Ref<Interaction>)>) {
for (mut outline, interaction) in outlines_query.iter_mut() {
if interaction.is_changed() {
outline.color = match *interaction {
Interaction::Pressed => Color::RED,
Interaction::Hovered => Color::WHITE,
Interaction::None => Color::NONE,
};
}
}
}