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

[Merged by Bors] - Relative cursor position #7199

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
71056a3
made resource_scope work for non-send resources
Pietrek14 Sep 27, 2022
d607e6b
Merge branch 'bevyengine:main' into main
Pietrek14 Sep 27, 2022
83f2417
fixed the non-send resource scope tests
Pietrek14 Sep 27, 2022
6da0e41
formatting
Pietrek14 Sep 27, 2022
3500039
simplified panic in a non-send resource scope test
Pietrek14 Sep 28, 2022
d823b1b
Merge branch 'bevyengine:main' into main
Pietrek14 Sep 28, 2022
f68eb33
changed the name of non-send struct used for testing
Pietrek14 Sep 28, 2022
6ed303e
Merge branch 'bevyengine:main' into main
Pietrek14 Oct 6, 2022
23a22ae
Merge branch 'bevyengine:main' into main
Pietrek14 Jan 7, 2023
a27f8b1
Merge branch 'main' of https://github.com/bevyengine/bevy into main
Pietrek14 Jan 14, 2023
8513317
added relative cursor position
Pietrek14 Jan 14, 2023
a81cb5a
added a relative cursor position example
Pietrek14 Jan 14, 2023
0921e75
updated example pages
Pietrek14 Jan 14, 2023
42699d1
comment clarification
Pietrek14 Jan 15, 2023
ae57369
line break after doc comment
Pietrek14 Jan 15, 2023
3ddd0c0
made relative cursor position an option
Pietrek14 Jan 15, 2023
2b64fa6
Merge branch 'relative-cursor-position' of https://github.com/Pietrek…
Pietrek14 Jan 15, 2023
9713cc8
clarification
Pietrek14 Jan 15, 2023
32cde9a
added a helper method to check if mouse is over a node
Pietrek14 Jan 15, 2023
b52f950
doc comment for RelativeCursorPosition::normalized
Pietrek14 Jan 15, 2023
6a00611
comment for example system
Pietrek14 Jan 15, 2023
b5d915e
Merge branch 'bevyengine:main' into relative-cursor-position
Pietrek14 Jan 15, 2023
2462874
changed upper-left to top-left
Pietrek14 Jan 16, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1455,6 +1455,16 @@ description = "Illustrates how FontAtlases are populated (used to optimize text
category = "UI (User Interface)"
wasm = true

[[example]]
name = "relative_cursor_position"
path = "examples/ui/relative_cursor_position.rs"

[package.metadata.example.relative_cursor_position]
name = "Relative Cursor Position"
description = "Showcases the RelativeCursorPosition component"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "text"
path = "examples/ui/text.rs"
Expand Down
65 changes: 57 additions & 8 deletions crates/bevy_ui/src/focus.rs
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,
Expand Down Expand Up @@ -52,6 +53,39 @@ 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.
Copy link
Contributor

Choose a reason for hiding this comment

The 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)]
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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 top-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 {
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ Example | Description
--- | ---
[Button](../examples/ui/button.rs) | Illustrates creating and updating a button
[Font Atlas Debug](../examples/ui/font_atlas_debug.rs) | Illustrates how FontAtlases are populated (used to optimize text rendering internally)
[Relative Cursor Position](../examples/ui/relative_cursor_position.rs) | Showcases the RelativeCursorPosition component
[Text](../examples/ui/text.rs) | Illustrates creating and updating text
[Text Debug](../examples/ui/text_debug.rs) | An example for debugging text layout
[Transparency UI](../examples/ui/transparency_ui.rs) | Demonstrates transparency for UI
Expand Down
80 changes: 80 additions & 0 deletions examples/ui/relative_cursor_position.rs
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());
Copy link
Contributor

Choose a reason for hiding this comment

The 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 ButtonBundle at the very least. @alice-i-cecile, thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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)
};
}