mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Relative cursor position (#7199)
# Objective Add useful information about cursor position relative to a UI node. Fixes #7079. ## Solution - Added a new `RelativeCursorPosition` component --- ## Changelog - Added - `RelativeCursorPosition` - an example showcasing the new component Co-authored-by: Dawid Piotrowski <41804418+Pietrek14@users.noreply.github.com>
This commit is contained in:
parent
517deda215
commit
a792f37040
4 changed files with 148 additions and 8 deletions
10
Cargo.toml
10
Cargo.toml
|
@ -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"
|
||||
|
|
|
@ -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 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,
|
||||
)]
|
||||
#[reflect(Component, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RelativeCursorPosition {
|
||||
/// Cursor position relative to size and position of the Node.
|
||||
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))
|
||||
.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 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 {
|
||||
|
|
|
@ -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
|
||||
|
|
80
examples/ui/relative_cursor_position.rs
Normal file
80
examples/ui/relative_cursor_position.rs
Normal file
|
@ -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());
|
||||
|
||||
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(
|
||||
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)
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue