-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
[Merged by Bors] - Relative cursor position #7199
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
Changes from 22 commits
71056a3
d607e6b
83f2417
6da0e41
3500039
d823b1b
f68eb33
6ed303e
23a22ae
a27f8b1
8513317
a81cb5a
0921e75
42699d1
ae57369
3ddd0c0
2b64fa6
9713cc8
32cde9a
b52f950
6a00611
b5d915e
2462874
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| use crate::{camera_config::UiCameraConfig, CalculatedClip, Node, UiStack}; | ||
| use bevy_derive::{Deref, DerefMut}; | ||
| use bevy_ecs::{ | ||
| change_detection::DetectChangesMut, | ||
| entity::Entity, | ||
|
|
@@ -52,6 +53,39 @@ impl Default for Interaction { | |
| } | ||
| } | ||
|
|
||
| /// A component storing the position of the mouse relative to the node, (0., 0.) being the upper-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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With this, we would have all the required building-blocks aside from rendering to produce the press-ripples sometime down the line: https://material.angular.io/components/ripple/overview |
||
| #[derive( | ||
| Component, | ||
| Deref, | ||
| DerefMut, | ||
| 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. | ||
| pub normalized: Option<Vec2>, | ||
Pietrek14 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| 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)) | ||
| .unwrap_or(false) | ||
| } | ||
| } | ||
|
|
||
| /// Describes whether the node should block interactions with lower nodes | ||
| #[derive(Component, Copy, Clone, Eq, PartialEq, Debug, Reflect, Serialize, Deserialize)] | ||
| #[reflect(Component, Serialize, Deserialize, PartialEq)] | ||
|
|
@@ -86,6 +120,7 @@ pub struct NodeQuery { | |
| node: &'static Node, | ||
| global_transform: &'static GlobalTransform, | ||
| interaction: Option<&'static mut Interaction>, | ||
| relative_cursor_position: Option<&'static mut RelativeCursorPosition>, | ||
| focus_policy: Option<&'static FocusPolicy>, | ||
| calculated_clip: Option<&'static CalculatedClip>, | ||
| computed_visibility: Option<&'static ComputedVisibility>, | ||
|
|
@@ -175,20 +210,34 @@ pub fn ui_focus_system( | |
| let ui_position = position.truncate(); | ||
| let extents = node.node.size() / 2.0; | ||
| let mut min = ui_position - extents; | ||
| let mut max = ui_position + extents; | ||
| if let Some(clip) = node.calculated_clip { | ||
| min = Vec2::max(min, clip.clip.min); | ||
| max = Vec2::min(max, clip.clip.max); | ||
| } | ||
| // if the current cursor position is within the bounds of the node, consider it for | ||
|
|
||
| // The mouse position relative to the node | ||
| // (0., 0.) is the upper-left corner, (1., 1.) is the bottom-right corner | ||
| let relative_cursor_position = cursor_position.map(|cursor_position| { | ||
| Vec2::new( | ||
| (cursor_position.x - min.x) / node.node.size().x, | ||
| (cursor_position.y - min.y) / node.node.size().y, | ||
| ) | ||
| }); | ||
|
|
||
| // If the current cursor position is within the bounds of the node, consider it for | ||
| // clicking | ||
| let contains_cursor = if let Some(cursor_position) = cursor_position { | ||
| (min.x..max.x).contains(&cursor_position.x) | ||
| && (min.y..max.y).contains(&cursor_position.y) | ||
| } else { | ||
| false | ||
| let relative_cursor_position_component = RelativeCursorPosition { | ||
| normalized: relative_cursor_position, | ||
| }; | ||
|
|
||
| let contains_cursor = relative_cursor_position_component.mouse_over(); | ||
|
|
||
| // Save the relative cursor position to the correct component | ||
| if let Some(mut node_relative_cursor_position_component) = | ||
| node.relative_cursor_position | ||
| { | ||
| *node_relative_cursor_position_component = relative_cursor_position_component; | ||
| } | ||
|
|
||
| if contains_cursor { | ||
| Some(*entity) | ||
| } else { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| //! Showcases the `RelativeCursorPosition` component, used to check the position of the cursor relative to a UI node. | ||
|
|
||
| use bevy::{prelude::*, ui::RelativeCursorPosition, 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()) | ||
| .add_startup_system(setup) | ||
| .add_system(relative_cursor_position_system) | ||
| .run(); | ||
| } | ||
|
|
||
| fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { | ||
| commands.spawn(Camera2dBundle::default()); | ||
|
|
||
| commands | ||
| .spawn(NodeBundle { | ||
| style: Style { | ||
| size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), | ||
| align_items: AlignItems::Center, | ||
| justify_content: JustifyContent::Center, | ||
| flex_direction: FlexDirection::Column, | ||
| ..default() | ||
| }, | ||
| ..default() | ||
| }) | ||
| .with_children(|parent| { | ||
| parent | ||
| .spawn(NodeBundle { | ||
| style: Style { | ||
| size: Size::new(Val::Px(250.0), Val::Px(250.0)), | ||
| margin: UiRect::new(Val::Px(0.), Val::Px(0.), Val::Px(0.), Val::Px(15.)), | ||
| ..default() | ||
| }, | ||
| background_color: Color::rgb(235., 35., 12.).into(), | ||
| ..default() | ||
| }) | ||
| .insert(RelativeCursorPosition::default()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should all node-bundles with some form of interaction have this component as default? I think it would make sense on the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that it's necessary. If someone needs this functionality they can always insert the component themselves, but I imagine most people won't use it.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, having thought about it a bit more, it only makes sense to add this to "default" bundles when we are building some more functionality on top of this and that is a requirement.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, we should only add components to bundles when they have a built-in use. |
||
|
|
||
| parent.spawn(TextBundle { | ||
| text: Text::from_section( | ||
| "(0.0, 0.0)", | ||
| TextStyle { | ||
| font: asset_server.load("fonts/FiraSans-Bold.ttf"), | ||
| font_size: 40.0, | ||
| color: Color::rgb(0.9, 0.9, 0.9), | ||
| }, | ||
| ), | ||
| ..default() | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| /// This systems polls the relative cursor position and displays its value in a text component. | ||
| fn relative_cursor_position_system( | ||
Pietrek14 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| relative_cursor_position_query: Query<&RelativeCursorPosition>, | ||
| mut output_query: Query<&mut Text>, | ||
| ) { | ||
| let relative_cursor_position = relative_cursor_position_query.single(); | ||
|
|
||
| let mut output = output_query.single_mut(); | ||
|
|
||
| output.sections[0].value = | ||
| if let Some(relative_cursor_position) = relative_cursor_position.normalized { | ||
| format!( | ||
| "({:.1}, {:.1})", | ||
| relative_cursor_position.x, relative_cursor_position.y | ||
| ) | ||
| } else { | ||
| "unknown".to_string() | ||
| }; | ||
|
|
||
| output.sections[0].style.color = if relative_cursor_position.mouse_over() { | ||
| Color::rgb(0.1, 0.9, 0.1) | ||
| } else { | ||
| Color::rgb(0.9, 0.1, 0.1) | ||
| }; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.