diff --git a/examples/every_option.rs b/examples/every_option.rs index 05b7473901c7fcacc3ad15f2e58473a9a4fc218a..96c86c3ece4a9fcab843b1538971a5c70c8c0863 100644 --- a/examples/every_option.rs +++ b/examples/every_option.rs @@ -26,6 +26,7 @@ fn setup(mut commands: Commands, mut font_system: ResMut<CosmicFontSystem>) { max_chars: MaxChars(15), max_lines: MaxLines(1), mode: CosmicWrap::Wrap, + hover_cursor: HoverCursor(CursorIcon::Pointer), // CosmicEdit draws to this spritebundle sprite_bundle: SpriteBundle { sprite: Sprite { diff --git a/examples/password.rs b/examples/password.rs index 75bfac024f3cd96cd5f746e1ef203c6c817d0e1c..290a2c7288cb9771ffe1ab1090906dfdf6325e09 100644 --- a/examples/password.rs +++ b/examples/password.rs @@ -29,10 +29,7 @@ fn setup(mut commands: Commands) { fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugins(CosmicEditPlugin { - change_cursor: CursorConfig::Default, - ..default() - }) + .add_plugins(CosmicEditPlugin { ..default() }) .add_systems(Startup, setup) .add_systems( Update, diff --git a/examples/sprite_and_ui_clickable.rs b/examples/sprite_and_ui_clickable.rs index 76c8cd70b36e677a23c6023d576119dd84e7957f..290edaf10e754ce4f841dbd6433a49b40e707f1e 100644 --- a/examples/sprite_and_ui_clickable.rs +++ b/examples/sprite_and_ui_clickable.rs @@ -68,10 +68,7 @@ fn ev_test( fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugins(CosmicEditPlugin { - change_cursor: CursorConfig::Default, - ..default() - }) + .add_plugins(CosmicEditPlugin { ..default() }) .add_systems(Startup, setup) .add_systems( Update, diff --git a/src/cosmic_edit.rs b/src/cosmic_edit.rs index 3ac21ed7258744bba7f148e293ff9c5db32c9104..d3f430d3266fb2191ab6c216d56a0f14c42e055b 100644 --- a/src/cosmic_edit.rs +++ b/src/cosmic_edit.rs @@ -187,6 +187,7 @@ pub struct CosmicEditBundle { pub text_position: CosmicTextAlign, pub padding: CosmicPadding, pub widget_size: CosmicWidgetSize, + pub hover_cursor: HoverCursor, } impl Default for CosmicEditBundle { @@ -213,6 +214,7 @@ impl Default for CosmicEditBundle { x_offset: Default::default(), padding: Default::default(), widget_size: Default::default(), + hover_cursor: Default::default(), } } } diff --git a/src/cursor.rs b/src/cursor.rs index 04bb4e03bc0e08838ee5bef7d950a6ceb48b5fad..f54fef69fdd8a5b2c728f236aa98bd98207e9089 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -8,51 +8,38 @@ use bevy::{input::mouse::MouseMotion, prelude::*, window::PrimaryWindow}; #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)] pub struct CursorSet; -/// Config enum for mouse cursor events. -#[derive(Default, Clone)] -pub enum CursorConfig { - /// Emit [`TextHoverIn`] and [`TextHoverOut`] events and change mouse cursor on hover - #[default] - Default, - /// Emit [`TextHoverIn`] and [`TextHoverOut`] events, but do not change the cursor - Events, - /// Ignore mouse events - None, -} - -pub(crate) struct CursorPlugin { - pub change_cursor: CursorConfig, -} +pub struct CursorPlugin; impl Plugin for CursorPlugin { fn build(&self, app: &mut App) { - match self.change_cursor { - CursorConfig::Default => { - app.add_systems(Update, (hover_sprites, hover_ui, change_cursor)) - .add_event::<TextHoverIn>() - .add_event::<TextHoverOut>(); - } - CursorConfig::Events => { - app.add_systems(Update, (hover_sprites, hover_ui)) - .add_event::<TextHoverIn>() - .add_event::<TextHoverOut>(); - } - CursorConfig::None => {} - } + app.add_systems(Update, ((hover_sprites, hover_ui), change_cursor).chain()) + .add_event::<TextHoverIn>() + .add_event::<TextHoverOut>(); } } -/// For use with custom cursor control; Event is emitted when cursor enters a text widget -#[derive(Event)] -pub struct TextHoverIn; +#[derive(Component, Deref)] +pub struct HoverCursor(pub CursorIcon); + +impl Default for HoverCursor { + fn default() -> Self { + Self(CursorIcon::Text) + } +} + +/// For use with custom cursor control +/// Event is emitted when cursor enters a text widget +/// Event contains the cursor from the buffer's [`HoverCursor`] +#[derive(Event, Deref)] +pub struct TextHoverIn(pub CursorIcon); -/// For use with custom cursor control; Event is emitted when cursor leaves a text widget +/// For use with custom cursor control +/// Event is emitted when cursor leaves a text widget #[derive(Event)] pub struct TextHoverOut; -/// Switches mouse cursor icon when hover events are received -pub fn change_cursor( - evr_hover_in: EventReader<TextHoverIn>, +pub(crate) fn change_cursor( + mut evr_hover_in: EventReader<TextHoverIn>, evr_hover_out: EventReader<TextHoverOut>, evr_text_changed: EventReader<CosmicTextChanged>, evr_mouse_motion: EventReader<MouseMotion>, @@ -63,15 +50,17 @@ pub fn change_cursor( return; } let mut window = windows.single_mut(); - if !evr_hover_in.is_empty() { - window.cursor.icon = CursorIcon::Text; - } - if !evr_hover_out.is_empty() { + + if let Some(ev) = evr_hover_in.read().last() { + window.cursor.icon = ev.0; + } else if !evr_hover_out.is_empty() { window.cursor.icon = CursorIcon::Default; } + if !evr_text_changed.is_empty() { window.cursor.visible = false; } + if mouse_buttons.get_just_pressed().len() != 0 || !evr_mouse_motion.is_empty() { window.cursor.visible = true; } @@ -84,11 +73,12 @@ type CameraQuery<'a, 'b, 'c, 'd> = #[cfg(not(feature = "multicam"))] type CameraQuery<'a, 'b, 'c, 'd> = Query<'a, 'b, (&'c Camera, &'d GlobalTransform)>; -/// Sprite widget mouse cursor hover detection system. Sends [`TextHoverIn`] and [`TextHoverOut`] -/// events. -pub fn hover_sprites( +pub(crate) fn hover_sprites( windows: Query<&Window, With<PrimaryWindow>>, - mut cosmic_edit_query: Query<(&mut Sprite, &Visibility, &GlobalTransform), With<CosmicBuffer>>, + mut cosmic_edit_query: Query< + (&mut Sprite, &Visibility, &GlobalTransform, &HoverCursor), + With<CosmicBuffer>, + >, camera_q: CameraQuery, mut hovered: Local<bool>, mut last_hovered: Local<bool>, @@ -101,7 +91,10 @@ pub fn hover_sprites( } let window = windows.single(); let (camera, camera_transform) = camera_q.single(); - for (sprite, visibility, node_transform) in &mut cosmic_edit_query.iter_mut() { + + let mut icon = CursorIcon::Default; + + for (sprite, visibility, node_transform, hover) in &mut cosmic_edit_query.iter_mut() { if visibility == Visibility::Hidden { continue; } @@ -115,6 +108,7 @@ pub fn hover_sprites( if let Some(pos) = camera.viewport_to_world_2d(camera_transform, pos) { if x_min < pos.x && pos.x < x_max && y_min < pos.y && pos.y < y_max { *hovered = true; + icon = hover.0; } } } @@ -122,7 +116,7 @@ pub fn hover_sprites( if *last_hovered != *hovered { if *hovered { - evw_hover_in.send(TextHoverIn); + evw_hover_in.send(TextHoverIn(icon)); } else { evw_hover_out.send(TextHoverOut); } @@ -131,20 +125,21 @@ pub fn hover_sprites( *last_hovered = *hovered; } -/// UI widget mouse cursor hover detection system. Sends [`TextHoverIn`] and [`TextHoverOut`] -/// events. -pub fn hover_ui( - mut interaction_query: Query<&Interaction, (Changed<Interaction>, With<CosmicSource>)>, +pub(crate) fn hover_ui( + interaction_query: Query<(&Interaction, &CosmicSource), Changed<Interaction>>, + cosmic_query: Query<&HoverCursor, With<CosmicBuffer>>, mut evw_hover_in: EventWriter<TextHoverIn>, mut evw_hover_out: EventWriter<TextHoverOut>, ) { - for interaction in interaction_query.iter_mut() { + for (interaction, source) in interaction_query.iter() { match interaction { Interaction::None => { evw_hover_out.send(TextHoverOut); } Interaction::Hovered => { - evw_hover_in.send(TextHoverIn); + if let Ok(hover) = cosmic_query.get(source.0) { + evw_hover_in.send(TextHoverIn(hover.0)); + } } _ => {} } diff --git a/src/lib.rs b/src/lib.rs index bec8d56a5160b54d63eae8f00718f5e56f587178..3c1331d414fd0ebf018f8530b6127a4db81ae0bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,7 +116,6 @@ pub use widget::*; #[derive(Default)] pub struct CosmicEditPlugin { pub font_config: CosmicFontConfig, - pub change_cursor: CursorConfig, } impl Plugin for CosmicEditPlugin { @@ -129,9 +128,7 @@ impl Plugin for CosmicEditPlugin { WidgetPlugin, InputPlugin, FocusPlugin, - CursorPlugin { - change_cursor: self.change_cursor.clone(), - }, + CursorPlugin, PlaceholderPlugin, PasswordPlugin, EventsPlugin,