Skip to content

Changed the way cursors are defined in bevy_feathers. #20169

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions crates/bevy_feathers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -34,6 +35,7 @@ accesskit = "0.19"

[features]
default = []
custom_cursor = ["bevy_winit/custom_cursor"]

[lints]
workspace = true
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_feathers/src/controls/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_feathers/src/controls/checkbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_feathers/src/controls/radio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_feathers/src/controls/slider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_feathers/src/controls/toggle_switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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![(
Expand Down
75 changes: 64 additions & 11 deletions crates/bevy_feathers/src/cursor.rs
Original file line number Diff line number Diff line change
@@ -1,31 +1,82 @@
//! 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;
#[cfg(feature = "custom_cursor")]
use bevy_winit::cursor::CustomCursor;

/// A component that specifies the cursor icon to be used when the mouse is not hovering over
/// A resource 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 DefaultCursor(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 to_cursor_icon(&self) -> CursorIcon {
match self {
#[cfg(feature = "custom_cursor")]
EntityCursor::Custom(custom_cursor) => CursorIcon::Custom(custom_cursor.clone()),
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), CursorIcon::System(cursor_icon)) => {
*system == *cursor_icon
}
_ => false,
}
}
}

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
/// the cursor in the [`DefaultCursorIcon`] resource.
/// the cursor in the [`DefaultCursor`] resource.
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<DefaultCursor>,
) {
let cursor = hover_map.and_then(|hover_map| match hover_map.get(&PointerId::Mouse) {
Some(hover_set) => hover_set.keys().find_map(|entity| {
Expand All @@ -41,7 +92,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);
Expand All @@ -50,9 +101,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.to_cursor_icon());
} else {
commands.entity(*entity).insert(r_default_cursor.0.clone());
commands
.entity(*entity)
.insert(r_default_cursor.0.to_cursor_icon());
}
});
}
Expand All @@ -62,8 +115,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::<DefaultCursor>().is_none() {
app.init_resource::<DefaultCursor>();
}
app.add_systems(PreUpdate, update_cursor.in_set(PickingSystems::Last));
}
Expand Down
5 changes: 2 additions & 3 deletions crates/bevy_feathers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, DefaultCursor, EntityCursor},
theme::{ThemedText, UiTheme},
};

Expand Down Expand Up @@ -61,7 +60,7 @@ impl Plugin for FeathersPlugin {
HierarchyPropagatePlugin::<TextFont, With<ThemedText>>::default(),
));

app.insert_resource(DefaultCursorIcon(CursorIcon::System(
app.insert_resource(DefaultCursor(EntityCursor::System(
bevy_window::SystemCursorIcon::Default,
)));

Expand Down
2 changes: 1 addition & 1 deletion release-content/release-notes/feathers.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Bevy Feathers
authors: ["@viridia", "@Atlas16A"]
pull_requests: [19730, 19900, 19928]
pull_requests: [19730, 19900, 19928, 20169]
---

To make it easier for Bevy engine developers and third-party tool creators to make comfortable, visually cohesive tooling,
Expand Down
Loading