Changed the way cursors are defined in bevy_feathers.
Originally, we re-used the `CursorIcon` component which was intended to be placed on the window, and expanded its use so that it could be placed on any hoverable entity. In this PR, we restore `CursorIcon` to its original usage, and instead introduce a new type `EntityCursor` intended to be placed on hoverable entities. Part of the motivation for this is that it will make it easier to support BSN templates without having to add new trait impls in bevy_winit.
This commit is contained in:
parent
f964ee1e3a
commit
20e7baa2ee
@ -22,6 +22,7 @@ bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_text = { path = "../bevy_text", version = "0.17.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev", features = [
|
||||
"bevy_ui_picking_backend",
|
||||
@ -34,6 +35,7 @@ accesskit = "0.19"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
custom_cursor = ["bevy_winit/custom_cursor"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@ -14,10 +14,10 @@ use bevy_ecs::{
|
||||
use bevy_input_focus::tab_navigation::TabIndex;
|
||||
use bevy_picking::{hover::Hovered, PickingSystems};
|
||||
use bevy_ui::{AlignItems, InteractionDisabled, JustifyContent, Node, Pressed, UiRect, Val};
|
||||
use bevy_winit::cursor::CursorIcon;
|
||||
|
||||
use crate::{
|
||||
constants::{fonts, size},
|
||||
cursor::EntityCursor,
|
||||
font_styles::InheritableFont,
|
||||
handle_or_path::HandleOrPath,
|
||||
rounded_corners::RoundedCorners,
|
||||
@ -73,7 +73,7 @@ pub fn button<C: SpawnableList<ChildOf> + Send + Sync + 'static, B: Bundle>(
|
||||
},
|
||||
props.variant,
|
||||
Hovered::default(),
|
||||
CursorIcon::System(bevy_window::SystemCursorIcon::Pointer),
|
||||
EntityCursor::System(bevy_window::SystemCursorIcon::Pointer),
|
||||
TabIndex(0),
|
||||
props.corners.to_border_radius(4.0),
|
||||
ThemeBackgroundColor(tokens::BUTTON_BG),
|
||||
|
||||
@ -20,10 +20,10 @@ use bevy_ui::{
|
||||
AlignItems, BorderRadius, Checked, Display, FlexDirection, InteractionDisabled, JustifyContent,
|
||||
Node, PositionType, UiRect, UiTransform, Val,
|
||||
};
|
||||
use bevy_winit::cursor::CursorIcon;
|
||||
|
||||
use crate::{
|
||||
constants::{fonts, size},
|
||||
cursor::EntityCursor,
|
||||
font_styles::InheritableFont,
|
||||
handle_or_path::HandleOrPath,
|
||||
theme::{ThemeBackgroundColor, ThemeBorderColor, ThemeFontColor},
|
||||
@ -74,7 +74,7 @@ pub fn checkbox<C: SpawnableList<ChildOf> + Send + Sync + 'static, B: Bundle>(
|
||||
},
|
||||
CheckboxFrame,
|
||||
Hovered::default(),
|
||||
CursorIcon::System(bevy_window::SystemCursorIcon::Pointer),
|
||||
EntityCursor::System(bevy_window::SystemCursorIcon::Pointer),
|
||||
TabIndex(0),
|
||||
ThemeFontColor(tokens::CHECKBOX_TEXT),
|
||||
InheritableFont {
|
||||
|
||||
@ -19,10 +19,10 @@ use bevy_ui::{
|
||||
AlignItems, BorderRadius, Checked, Display, FlexDirection, InteractionDisabled, JustifyContent,
|
||||
Node, UiRect, Val,
|
||||
};
|
||||
use bevy_winit::cursor::CursorIcon;
|
||||
|
||||
use crate::{
|
||||
constants::{fonts, size},
|
||||
cursor::EntityCursor,
|
||||
font_styles::InheritableFont,
|
||||
handle_or_path::HandleOrPath,
|
||||
theme::{ThemeBackgroundColor, ThemeBorderColor, ThemeFontColor},
|
||||
@ -58,7 +58,7 @@ pub fn radio<C: SpawnableList<ChildOf> + Send + Sync + 'static, B: Bundle>(
|
||||
},
|
||||
CoreRadio,
|
||||
Hovered::default(),
|
||||
CursorIcon::System(bevy_window::SystemCursorIcon::Pointer),
|
||||
EntityCursor::System(bevy_window::SystemCursorIcon::Pointer),
|
||||
TabIndex(0),
|
||||
ThemeFontColor(tokens::RADIO_TEXT),
|
||||
InheritableFont {
|
||||
|
||||
@ -22,10 +22,10 @@ use bevy_ui::{
|
||||
InteractionDisabled, InterpolationColorSpace, JustifyContent, LinearGradient, Node, UiRect,
|
||||
Val,
|
||||
};
|
||||
use bevy_winit::cursor::CursorIcon;
|
||||
|
||||
use crate::{
|
||||
constants::{fonts, size},
|
||||
cursor::EntityCursor,
|
||||
font_styles::InheritableFont,
|
||||
handle_or_path::HandleOrPath,
|
||||
rounded_corners::RoundedCorners,
|
||||
@ -87,7 +87,7 @@ pub fn slider<B: Bundle>(props: SliderProps, overrides: B) -> impl Bundle {
|
||||
SliderStyle,
|
||||
SliderValue(props.value),
|
||||
SliderRange::new(props.min, props.max),
|
||||
CursorIcon::System(bevy_window::SystemCursorIcon::EwResize),
|
||||
EntityCursor::System(bevy_window::SystemCursorIcon::EwResize),
|
||||
TabIndex(0),
|
||||
RoundedCorners::All.to_border_radius(6.0),
|
||||
// Use a gradient to draw the moving bar
|
||||
|
||||
@ -18,10 +18,10 @@ use bevy_ecs::{
|
||||
use bevy_input_focus::tab_navigation::TabIndex;
|
||||
use bevy_picking::{hover::Hovered, PickingSystems};
|
||||
use bevy_ui::{BorderRadius, Checked, InteractionDisabled, Node, PositionType, UiRect, Val};
|
||||
use bevy_winit::cursor::CursorIcon;
|
||||
|
||||
use crate::{
|
||||
constants::size,
|
||||
cursor::EntityCursor,
|
||||
theme::{ThemeBackgroundColor, ThemeBorderColor},
|
||||
tokens,
|
||||
};
|
||||
@ -63,7 +63,7 @@ pub fn toggle_switch<B: Bundle>(props: ToggleSwitchProps, overrides: B) -> impl
|
||||
ThemeBorderColor(tokens::SWITCH_BORDER),
|
||||
AccessibilityNode(accesskit::Node::new(Role::Switch)),
|
||||
Hovered::default(),
|
||||
CursorIcon::System(bevy_window::SystemCursorIcon::Pointer),
|
||||
EntityCursor::System(bevy_window::SystemCursorIcon::Pointer),
|
||||
TabIndex(0),
|
||||
overrides,
|
||||
children![(
|
||||
|
||||
@ -1,20 +1,68 @@
|
||||
//! Provides a way to automatically set the mouse cursor based on hovered entity.
|
||||
use bevy_app::{App, Plugin, PreUpdate};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
hierarchy::ChildOf,
|
||||
reflect::ReflectComponent,
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs,
|
||||
system::{Commands, Query, Res},
|
||||
};
|
||||
use bevy_picking::{hover::HoverMap, pointer::PointerId, PickingSystems};
|
||||
use bevy_window::Window;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_window::{SystemCursorIcon, Window};
|
||||
use bevy_winit::cursor::CursorIcon;
|
||||
|
||||
/// A component that specifies the cursor icon to be used when the mouse is not hovering over
|
||||
/// any other entity. This is used to set the default cursor icon for the window.
|
||||
#[derive(Resource, Debug, Clone, Default)]
|
||||
pub struct DefaultCursorIcon(pub CursorIcon);
|
||||
pub struct DefaultEntityCursor(pub EntityCursor);
|
||||
|
||||
/// A component that specifies the cursor shape to be used when the pointer hovers over an entity.
|
||||
/// This is copied to the windows's [`CursorIcon`] component.
|
||||
///
|
||||
/// This is effectively the same type as [`CustomCursor`] but with different methods, and used
|
||||
/// in different places.
|
||||
#[derive(Component, Debug, Clone, Reflect, PartialEq, Eq)]
|
||||
#[reflect(Component, Debug, Default, PartialEq, Clone)]
|
||||
pub enum EntityCursor {
|
||||
#[cfg(feature = "custom_cursor")]
|
||||
/// Custom cursor image.
|
||||
Custom(CustomCursor),
|
||||
/// System provided cursor icon.
|
||||
System(SystemCursorIcon),
|
||||
}
|
||||
|
||||
impl EntityCursor {
|
||||
/// Convert the [`EntityCursor`] to a [`CursorIcon`] so that it can be inserted into a
|
||||
/// window.
|
||||
pub fn as_cursor_icon(&self) -> CursorIcon {
|
||||
match self {
|
||||
#[cfg(feature = "custom_cursor")]
|
||||
EntityCursor::Custom(custom_cursor) => CursorIcon::Custom(custom_cursor),
|
||||
EntityCursor::System(icon) => CursorIcon::from(*icon),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compare the [`EntityCursor`] to a [`CursorIcon`] so that we can see whether or not
|
||||
/// the window cursor needs to be changed.
|
||||
pub fn eq_cursor_icon(&self, cursor_icon: &CursorIcon) -> bool {
|
||||
match (self, cursor_icon) {
|
||||
#[cfg(feature = "custom_cursor")]
|
||||
(EntityCursor::Custom(custom), CursorIcon::Custom(other)) => custom == other,
|
||||
(EntityCursor::System(system), cursor_icon) => {
|
||||
CursorIcon::from(*system) == *cursor_icon
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EntityCursor {
|
||||
fn default() -> Self {
|
||||
EntityCursor::System(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// System which updates the window cursor icon whenever the mouse hovers over an entity with
|
||||
/// a [`CursorIcon`] component. If no entity is hovered, the cursor icon is set to
|
||||
@ -23,9 +71,9 @@ pub(crate) fn update_cursor(
|
||||
mut commands: Commands,
|
||||
hover_map: Option<Res<HoverMap>>,
|
||||
parent_query: Query<&ChildOf>,
|
||||
cursor_query: Query<&CursorIcon>,
|
||||
cursor_query: Query<&EntityCursor>,
|
||||
mut q_windows: Query<(Entity, &mut Window, Option<&CursorIcon>)>,
|
||||
r_default_cursor: Res<DefaultCursorIcon>,
|
||||
r_default_cursor: Res<DefaultEntityCursor>,
|
||||
) {
|
||||
let cursor = hover_map.and_then(|hover_map| match hover_map.get(&PointerId::Mouse) {
|
||||
Some(hover_set) => hover_set.keys().find_map(|entity| {
|
||||
@ -41,7 +89,7 @@ pub(crate) fn update_cursor(
|
||||
let mut windows_to_change: Vec<Entity> = Vec::new();
|
||||
for (entity, _window, prev_cursor) in q_windows.iter_mut() {
|
||||
match (cursor, prev_cursor) {
|
||||
(Some(cursor), Some(prev_cursor)) if cursor == prev_cursor => continue,
|
||||
(Some(cursor), Some(prev_cursor)) if cursor.eq_cursor_icon(prev_cursor) => continue,
|
||||
(None, None) => continue,
|
||||
_ => {
|
||||
windows_to_change.push(entity);
|
||||
@ -50,9 +98,11 @@ pub(crate) fn update_cursor(
|
||||
}
|
||||
windows_to_change.iter().for_each(|entity| {
|
||||
if let Some(cursor) = cursor {
|
||||
commands.entity(*entity).insert(cursor.clone());
|
||||
commands.entity(*entity).insert(cursor.as_cursor_icon());
|
||||
} else {
|
||||
commands.entity(*entity).insert(r_default_cursor.0.clone());
|
||||
commands
|
||||
.entity(*entity)
|
||||
.insert(r_default_cursor.0.as_cursor_icon());
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -62,8 +112,8 @@ pub struct CursorIconPlugin;
|
||||
|
||||
impl Plugin for CursorIconPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
if app.world().get_resource::<DefaultCursorIcon>().is_none() {
|
||||
app.init_resource::<DefaultCursorIcon>();
|
||||
if app.world().get_resource::<DefaultEntityCursor>().is_none() {
|
||||
app.init_resource::<DefaultEntityCursor>();
|
||||
}
|
||||
app.add_systems(PreUpdate, update_cursor.in_set(PickingSystems::Last));
|
||||
}
|
||||
|
||||
@ -22,11 +22,10 @@ use bevy_app::{HierarchyPropagatePlugin, Plugin, PostUpdate};
|
||||
use bevy_asset::embedded_asset;
|
||||
use bevy_ecs::query::With;
|
||||
use bevy_text::{TextColor, TextFont};
|
||||
use bevy_winit::cursor::CursorIcon;
|
||||
|
||||
use crate::{
|
||||
controls::ControlsPlugin,
|
||||
cursor::{CursorIconPlugin, DefaultCursorIcon},
|
||||
cursor::{CursorIconPlugin, DefaultEntityCursor, EntityCursor},
|
||||
theme::{ThemedText, UiTheme},
|
||||
};
|
||||
|
||||
@ -61,7 +60,7 @@ impl Plugin for FeathersPlugin {
|
||||
HierarchyPropagatePlugin::<TextFont, With<ThemedText>>::default(),
|
||||
));
|
||||
|
||||
app.insert_resource(DefaultCursorIcon(CursorIcon::System(
|
||||
app.insert_resource(DefaultEntityCursor(EntityCursor::System(
|
||||
bevy_window::SystemCursorIcon::Default,
|
||||
)));
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user