From 7f26311e88b2c7ef23729ef425c05d2e2df1c4e2 Mon Sep 17 00:00:00 2001
From: MrGVSV <gino.valente.code@gmail.com>
Date: Mon, 7 Feb 2022 11:08:32 -0800
Subject: [PATCH] Added ability to set cursor icon

---
 bevy_kayak_ui/src/cursor.rs        | 41 ++++++++++++++++++++++++++++
 bevy_kayak_ui/src/lib.rs           | 11 +++++++-
 kayak_core/src/context.rs          | 27 +++++++++++++++++-
 kayak_core/src/cursor_icon.rs      | 44 ++++++++++++++++++++++++++++++
 kayak_core/src/event_dispatcher.rs | 18 ++++++++++++
 kayak_core/src/lib.rs              |  2 ++
 kayak_core/src/styles.rs           |  4 ++-
 src/widgets/button.rs              |  2 ++
 src/widgets/text.rs                |  6 ++--
 src/widgets/text_box.rs            |  2 ++
 src/widgets/window.rs              |  9 ++++++
 11 files changed, 161 insertions(+), 5 deletions(-)
 create mode 100644 bevy_kayak_ui/src/cursor.rs
 create mode 100644 kayak_core/src/cursor_icon.rs

diff --git a/bevy_kayak_ui/src/cursor.rs b/bevy_kayak_ui/src/cursor.rs
new file mode 100644
index 0000000..338df80
--- /dev/null
+++ b/bevy_kayak_ui/src/cursor.rs
@@ -0,0 +1,41 @@
+use kayak_core::CursorIcon;
+
+pub fn convert_cursor_icon(cursor_icon: CursorIcon) -> bevy::prelude::CursorIcon {
+    match cursor_icon {
+        CursorIcon::Default => bevy::prelude::CursorIcon::Default,
+        CursorIcon::Crosshair => bevy::prelude::CursorIcon::Crosshair,
+        CursorIcon::Hand => bevy::prelude::CursorIcon::Hand,
+        CursorIcon::Arrow => bevy::prelude::CursorIcon::Arrow,
+        CursorIcon::Move => bevy::prelude::CursorIcon::Move,
+        CursorIcon::Text => bevy::prelude::CursorIcon::Text,
+        CursorIcon::Wait => bevy::prelude::CursorIcon::Wait,
+        CursorIcon::Help => bevy::prelude::CursorIcon::Help,
+        CursorIcon::Progress => bevy::prelude::CursorIcon::Progress,
+        CursorIcon::NotAllowed => bevy::prelude::CursorIcon::NotAllowed,
+        CursorIcon::ContextMenu => bevy::prelude::CursorIcon::ContextMenu,
+        CursorIcon::Cell => bevy::prelude::CursorIcon::Cell,
+        CursorIcon::VerticalText => bevy::prelude::CursorIcon::VerticalText,
+        CursorIcon::Alias => bevy::prelude::CursorIcon::Alias,
+        CursorIcon::Copy => bevy::prelude::CursorIcon::Copy,
+        CursorIcon::NoDrop => bevy::prelude::CursorIcon::NoDrop,
+        CursorIcon::Grab => bevy::prelude::CursorIcon::Grab,
+        CursorIcon::Grabbing => bevy::prelude::CursorIcon::Grabbing,
+        CursorIcon::AllScroll => bevy::prelude::CursorIcon::AllScroll,
+        CursorIcon::ZoomIn => bevy::prelude::CursorIcon::ZoomIn,
+        CursorIcon::ZoomOut => bevy::prelude::CursorIcon::ZoomOut,
+        CursorIcon::EResize => bevy::prelude::CursorIcon::EResize,
+        CursorIcon::NResize => bevy::prelude::CursorIcon::NResize,
+        CursorIcon::NeResize => bevy::prelude::CursorIcon::NeResize,
+        CursorIcon::NwResize => bevy::prelude::CursorIcon::NwResize,
+        CursorIcon::SResize => bevy::prelude::CursorIcon::SResize,
+        CursorIcon::SeResize => bevy::prelude::CursorIcon::SeResize,
+        CursorIcon::SwResize => bevy::prelude::CursorIcon::SwResize,
+        CursorIcon::WResize => bevy::prelude::CursorIcon::WResize,
+        CursorIcon::EwResize => bevy::prelude::CursorIcon::EwResize,
+        CursorIcon::NsResize => bevy::prelude::CursorIcon::NsResize,
+        CursorIcon::NeswResize => bevy::prelude::CursorIcon::NeswResize,
+        CursorIcon::NwseResize => bevy::prelude::CursorIcon::NwseResize,
+        CursorIcon::ColResize => bevy::prelude::CursorIcon::ColResize,
+        CursorIcon::RowResize => bevy::prelude::CursorIcon::RowResize,
+    }
+}
\ No newline at end of file
diff --git a/bevy_kayak_ui/src/lib.rs b/bevy_kayak_ui/src/lib.rs
index 86a10a0..2131333 100644
--- a/bevy_kayak_ui/src/lib.rs
+++ b/bevy_kayak_ui/src/lib.rs
@@ -10,12 +10,14 @@ mod bevy_context;
 mod camera;
 mod key;
 mod render;
+mod cursor;
 
 pub use bevy_context::BevyContext;
 pub use camera::*;
 use kayak_core::{bind, Binding, InputEvent, MutableBound};
 pub use render::unified::font::FontMapping;
 pub use render::unified::image::ImageManager;
+use crate::cursor::convert_cursor_icon;
 
 #[derive(Default)]
 pub struct BevyKayakUIPlugin;
@@ -40,8 +42,15 @@ pub fn update(world: &mut World) {
         if let Ok(mut context) = bevy_context.kayak_context.write() {
             context.set_global_state(std::mem::take(world));
             context.render();
-            *world = context.take_global_state::<World>().unwrap()
+            *world = context.take_global_state::<World>().unwrap();
+
+            if let Some(ref mut windows) = world.get_resource_mut::<Windows>() {
+                if let Some(window) = windows.get_primary_mut() {
+                    window.set_cursor_icon(convert_cursor_icon(context.cursor_icon()));
+                }
+            }
         }
+
         world.insert_resource(bevy_context);
     }
 }
diff --git a/kayak_core/src/context.rs b/kayak_core/src/context.rs
index 5e44724..fde229a 100644
--- a/kayak_core/src/context.rs
+++ b/kayak_core/src/context.rs
@@ -1,5 +1,5 @@
 use crate::assets::AssetStorage;
-use crate::{Binding, Changeable, KayakContextRef};
+use crate::{Binding, Changeable, CursorIcon, KayakContextRef};
 use std::collections::HashMap;
 use std::path::PathBuf;
 
@@ -27,6 +27,7 @@ pub struct KayakContext {
     widget_state_lifetimes:
         HashMap<crate::Index, HashMap<crate::flo_binding::Uuid, Box<dyn crate::Releasable>>>,
     widget_states: HashMap<crate::Index, resources::Resources>,
+    cursor_icon: CursorIcon
 }
 
 impl std::fmt::Debug for KayakContext {
@@ -42,6 +43,7 @@ impl KayakContext {
             assets: resources::Resources::default(),
             current_effect_index: 0,
             current_state_index: 0,
+            cursor_icon: CursorIcon::Default,
             event_dispatcher: EventDispatcher::new(),
             global_bindings: HashMap::new(),
             global_state: resources::Resources::default(),
@@ -394,6 +396,7 @@ impl KayakContext {
         // self.widget_manager.dirty_nodes.clear();
         self.widget_manager.render();
         self.widget_manager.calculate_layout();
+        self.update_cursor();
     }
 
     /// Processes the given input events
@@ -557,4 +560,26 @@ impl KayakContext {
     pub fn force_release_cursor(&mut self) -> Option<Index> {
         self.event_dispatcher.force_release_cursor()
     }
+
+    pub fn cursor_icon(&self) -> CursorIcon {
+        self.cursor_icon
+    }
+
+    pub(crate) fn set_cursor_icon(&mut self, icon: CursorIcon) {
+        self.cursor_icon = icon;
+    }
+
+    fn update_cursor(&mut self) {
+        if self.event_dispatcher.hovered.is_none() {
+            return;
+        }
+
+        let hovered = self.event_dispatcher.hovered.unwrap();
+        if let Some(node) = self.widget_manager.nodes.get(hovered) {
+            if let Some(node) = node {
+                    let icon = node.resolved_styles.cursor.resolve();
+                    self.cursor_icon = icon;
+            }
+        }
+    }
 }
diff --git a/kayak_core/src/cursor_icon.rs b/kayak_core/src/cursor_icon.rs
new file mode 100644
index 0000000..82b790e
--- /dev/null
+++ b/kayak_core/src/cursor_icon.rs
@@ -0,0 +1,44 @@
+#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
+pub enum CursorIcon {
+    Default,
+    Crosshair,
+    Hand,
+    Arrow,
+    Move,
+    Text,
+    Wait,
+    Help,
+    Progress,
+    NotAllowed,
+    ContextMenu,
+    Cell,
+    VerticalText,
+    Alias,
+    Copy,
+    NoDrop,
+    Grab,
+    Grabbing,
+    AllScroll,
+    ZoomIn,
+    ZoomOut,
+    EResize,
+    NResize,
+    NeResize,
+    NwResize,
+    SResize,
+    SeResize,
+    SwResize,
+    WResize,
+    EwResize,
+    NsResize,
+    NeswResize,
+    NwseResize,
+    ColResize,
+    RowResize,
+}
+
+impl Default for CursorIcon {
+    fn default() -> Self {
+        CursorIcon::Default
+    }
+}
\ No newline at end of file
diff --git a/kayak_core/src/event_dispatcher.rs b/kayak_core/src/event_dispatcher.rs
index 689ea42..2131b56 100644
--- a/kayak_core/src/event_dispatcher.rs
+++ b/kayak_core/src/event_dispatcher.rs
@@ -48,6 +48,7 @@ pub(crate) struct EventDispatcher {
     wants_cursor: Option<bool>,
     has_cursor: Option<Index>,
     pub cursor_capture: Option<Index>,
+    pub hovered: Option<Index>
 }
 
 impl EventDispatcher {
@@ -64,6 +65,7 @@ impl EventDispatcher {
             wants_cursor: None,
             has_cursor: None,
             cursor_capture: None,
+            hovered: None
         }
     }
 
@@ -144,6 +146,12 @@ impl EventDispatcher {
         self.has_cursor.is_some()
     }
 
+    /// The currently hovered node
+    #[allow(dead_code)]
+    pub fn hovered(&self) -> Option<Index> {
+        self.hovered
+    }
+
     /// Process and dispatch an [InputEvent](crate::InputEvent)
     #[allow(dead_code)]
     pub fn process_event(&mut self, input_event: InputEvent, context: &mut KayakContext) {
@@ -252,8 +260,10 @@ impl EventDispatcher {
         };
 
         // === Setup Cursor States === //
+        let old_hovered = self.hovered;
         let old_contains_cursor = self.contains_cursor;
         let old_wants_cursor = self.wants_cursor;
+        self.hovered = None;
         self.contains_cursor = None;
         self.wants_cursor = None;
         self.next_mouse_position = self.current_mouse_position;
@@ -375,6 +385,9 @@ impl EventDispatcher {
                         }
                         widget_manager.focus_tree.focus(node);
                     }
+                    EventType::Hover(..) => {
+                        self.hovered = Some(node);
+                    }
                     _ => {}
                 }
             }
@@ -393,6 +406,10 @@ impl EventDispatcher {
         self.current_mouse_position = self.next_mouse_position;
         self.is_mouse_pressed = self.next_mouse_pressed;
 
+        if self.hovered.is_none() {
+            // No change -> revert
+            self.hovered = old_hovered;
+        }
         if self.contains_cursor.is_none() {
             // No change -> revert
             self.contains_cursor = old_contains_cursor;
@@ -700,6 +717,7 @@ impl EventDispatcher {
         self.contains_cursor = from.contains_cursor;
         self.wants_cursor = from.wants_cursor;
         self.has_cursor = from.has_cursor;
+        self.hovered = from.hovered;
 
         // Do not include:
         // self.cursor_capture = from.cursor_capture;
diff --git a/kayak_core/src/lib.rs b/kayak_core/src/lib.rs
index 02bc713..b0a21bb 100644
--- a/kayak_core/src/lib.rs
+++ b/kayak_core/src/lib.rs
@@ -25,6 +25,7 @@ pub mod tree;
 mod vec;
 pub mod widget;
 pub mod widget_manager;
+mod cursor_icon;
 
 use std::sync::{Arc, RwLock};
 
@@ -34,6 +35,7 @@ pub use color::Color;
 pub use context::*;
 pub use context_ref::KayakContextRef;
 pub use cursor::PointerEvents;
+pub use cursor_icon::CursorIcon;
 pub use event::*;
 pub use focus_tree::FocusTree;
 pub use fragment::{Fragment, FragmentProps};
diff --git a/kayak_core/src/styles.rs b/kayak_core/src/styles.rs
index 3929618..9c5934c 100644
--- a/kayak_core/src/styles.rs
+++ b/kayak_core/src/styles.rs
@@ -1,7 +1,7 @@
 pub use morphorm::{LayoutType, PositionType, Units};
 
 use crate::cursor::PointerEvents;
-use crate::{color::Color, render_command::RenderCommand};
+use crate::{color::Color, CursorIcon, render_command::RenderCommand};
 
 #[derive(Debug, Clone, PartialEq)]
 pub enum StyleProp<T: Default + Clone> {
@@ -142,6 +142,7 @@ define_styles! {
         pub border: StyleProp<(f32, f32, f32, f32)>,
         pub bottom: StyleProp<Units>,
         pub color: StyleProp<Color>,
+        pub cursor: StyleProp<CursorIcon>,
         pub height: StyleProp<Units>,
         pub layout_type: StyleProp<LayoutType>,
         pub left: StyleProp<Units>,
@@ -179,6 +180,7 @@ impl Style {
             border_radius: StyleProp::Default,
             bottom: StyleProp::Default,
             color: StyleProp::Inherit,
+            cursor: StyleProp::Inherit,
             height: StyleProp::Default,
             layout_type: StyleProp::Default,
             left: StyleProp::Default,
diff --git a/src/widgets/button.rs b/src/widgets/button.rs
index d42de1f..2e31db7 100644
--- a/src/widgets/button.rs
+++ b/src/widgets/button.rs
@@ -1,3 +1,4 @@
+use kayak_core::CursorIcon;
 use crate::core::{
     render_command::RenderCommand,
     rsx,
@@ -51,6 +52,7 @@ pub fn Button(props: ButtonProps) {
                 height: StyleProp::Value(Units::Pixels(45.0)),
                 padding_left: StyleProp::Value(Units::Stretch(1.0)),
                 padding_right: StyleProp::Value(Units::Stretch(1.0)),
+                cursor: CursorIcon::Hand.into(),
                 ..Default::default()
             }),
     );
diff --git a/src/widgets/text.rs b/src/widgets/text.rs
index d73d383..529ac43 100644
--- a/src/widgets/text.rs
+++ b/src/widgets/text.rs
@@ -1,4 +1,4 @@
-use kayak_core::{styles::Units, Binding, Bound};
+use kayak_core::{styles::Units, Binding, Bound, CursorIcon};
 use kayak_font::{CoordinateSystem, KayakFont};
 
 use crate::core::{
@@ -73,11 +73,13 @@ pub fn Text(props: TextProps) {
             font: font_name.clone().unwrap_or("Roboto".into()),
         };
 
+        let styles = props.styles.clone().unwrap_or_default();
         props.styles = Some(Style {
             render_command: StyleProp::Value(render_command),
             width: StyleProp::Value(Units::Pixels(layout_size.0)),
             height: StyleProp::Value(Units::Pixels(layout_size.1)),
-            ..props.styles.clone().unwrap_or_default()
+            cursor: StyleProp::select(&[&styles.cursor, &StyleProp::Value(CursorIcon::Text)]).clone(),
+            ..styles
         });
     } else {
         context.mark_dirty();
diff --git a/src/widgets/text_box.rs b/src/widgets/text_box.rs
index 1a7514f..eebabc0 100644
--- a/src/widgets/text_box.rs
+++ b/src/widgets/text_box.rs
@@ -5,6 +5,7 @@ use crate::core::{
     widget, Bound, Children, Color, EventType, MutableBound, OnEvent, WidgetProps,
 };
 use std::sync::{Arc, RwLock};
+use kayak_core::CursorIcon;
 
 use crate::widgets::{Background, Clip, Text};
 
@@ -94,6 +95,7 @@ pub fn TextBox(props: TextBoxProps) {
                 top: Units::Pixels(0.0).into(),
                 bottom: Units::Pixels(0.0).into(),
                 height: Units::Pixels(26.0).into(),
+                cursor: CursorIcon::Text.into(),
                 ..Default::default()
             }),
     );
diff --git a/src/widgets/window.rs b/src/widgets/window.rs
index 8a932e3..a1ff690 100644
--- a/src/widgets/window.rs
+++ b/src/widgets/window.rs
@@ -1,3 +1,4 @@
+use kayak_core::CursorIcon;
 use crate::core::{
     color::Color,
     render_command::RenderCommand,
@@ -88,9 +89,16 @@ pub fn Window(props: WindowProps) {
         ..Style::default()
     };
 
+    let cursor = if is_dragging {
+        CursorIcon::Grabbing
+    } else {
+        CursorIcon::Grab
+    };
+
     let title_background_styles = Style {
         background_color: StyleProp::Value(Color::new(0.0781, 0.0898, 0.101, 1.0)),
         border_radius: StyleProp::Value((5.0, 0.0, 0.0, 5.0)),
+        cursor: cursor.into(),
         height: StyleProp::Value(Units::Pixels(24.0)),
         width: StyleProp::Value(Units::Stretch(1.0)),
         left: StyleProp::Value(Units::Pixels(0.0)),
@@ -103,6 +111,7 @@ pub fn Window(props: WindowProps) {
 
     let title_text_styles = Style {
         height: StyleProp::Value(Units::Pixels(25.0)),
+        cursor: StyleProp::Inherit,
         ..Style::default()
     };
 
-- 
GitLab