Add hover styling support

Nathan Sobo created

Change summary

crates/gpui/playground/src/hoverable.rs  | 33 +++++++++++++------------
crates/gpui/playground/src/playground.rs |  9 ++++--
crates/gpui/playground/src/style.rs      |  8 ++++++
crates/gpui/src/app.rs                   |  9 ++++++
crates/gpui/src/app/window.rs            |  3 +
crates/gpui/src/platform.rs              |  1 
crates/gpui/src/platform/mac/platform.rs |  8 +++++
crates/gpui/src/platform/test.rs         |  4 +++
8 files changed, 53 insertions(+), 22 deletions(-)

Detailed changes

crates/gpui/playground/src/hoverable.rs 🔗

@@ -2,40 +2,38 @@ use crate::{
     element::{Element, Layout},
     layout_context::LayoutContext,
     paint_context::PaintContext,
-    style::{StyleRefinement, Styleable},
+    style::{Style, StyleHelpers, StyleRefinement, Styleable},
 };
 use anyhow::Result;
 use gpui::platform::MouseMovedEvent;
 use refineable::Refineable;
-use std::{cell::Cell, marker::PhantomData};
+use std::cell::Cell;
 
-pub struct Hoverable<V: 'static, E: Element<V> + Styleable> {
+pub struct Hoverable<E: Styleable> {
     hovered: Cell<bool>,
     child_style: StyleRefinement,
     hovered_style: StyleRefinement,
     child: E,
-    view_type: PhantomData<V>,
 }
 
-pub fn hoverable<V, E: Element<V> + Styleable>(mut child: E) -> Hoverable<V, E> {
+pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
     Hoverable {
         hovered: Cell::new(false),
         child_style: child.declared_style().clone(),
         hovered_style: Default::default(),
         child,
-        view_type: PhantomData,
     }
 }
 
-impl<V, E: Element<V> + Styleable> Styleable for Hoverable<V, E> {
+impl<E: Styleable> Styleable for Hoverable<E> {
     type Style = E::Style;
 
     fn declared_style(&mut self) -> &mut crate::style::StyleRefinement {
-        self.child.declared_style()
+        &mut self.hovered_style
     }
 }
 
-impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<V, E> {
+impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
     type Layout = E::Layout;
 
     fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<Layout<V, Self::Layout>>
@@ -53,6 +51,10 @@ impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<V, E> {
     ) where
         Self: Sized,
     {
+        let bounds = layout.bounds(cx);
+        let order = layout.order(cx);
+
+        self.hovered.set(bounds.contains_point(cx.mouse_position()));
         if self.hovered.get() {
             // If hovered, refine the child's style with this element's style.
             self.child.declared_style().refine(&self.hovered_style);
@@ -61,16 +63,15 @@ impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<V, E> {
             *self.child.declared_style() = self.child_style.clone();
         }
 
-        let bounds = layout.bounds(cx);
-        let order = layout.order(cx);
-        self.hovered.set(bounds.contains_point(cx.mouse_position()));
-        let was_hovered = self.hovered.clone();
+        let hovered = self.hovered.clone();
         cx.on_event(order, move |view, event: &MouseMovedEvent, cx| {
-            let is_hovered = bounds.contains_point(event.position);
-            if is_hovered != was_hovered.get() {
-                was_hovered.set(is_hovered);
+            if bounds.contains_point(event.position) != hovered.get() {
                 cx.repaint();
             }
         });
+
+        self.child.paint(view, layout, cx);
     }
 }
+
+impl<E: Styleable<Style = Style>> StyleHelpers for Hoverable<E> {}

crates/gpui/playground/src/playground.rs 🔗

@@ -1,5 +1,8 @@
 #![allow(dead_code, unused_variables)]
-use crate::{color::black, style::StyleHelpers};
+use crate::{
+    color::black,
+    style::{StyleHelpers, Styleable},
+};
 use element::Element;
 use gpui::{
     geometry::{rect::RectF, vector::vec2f},
@@ -51,8 +54,8 @@ fn playground<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
         .h_full()
         .w_1_2()
         .fill(theme.success(0.5))
-    // .hover()
-    // .fill(theme.error(0.5))
+        .hoverable()
+        .fill(theme.error(0.5))
     // .child(button().label("Hello").click(|_, _, _| println!("click!")))
 }
 

crates/gpui/playground/src/style.rs 🔗

@@ -1,6 +1,7 @@
 use crate::{
     color::Hsla,
     element::{Element, Layout},
+    hoverable::{hoverable, Hoverable},
     paint_context::PaintContext,
 };
 use gpui::{
@@ -255,6 +256,13 @@ pub trait Styleable {
         style.refine(self.declared_style());
         style
     }
+
+    fn hoverable(self) -> Hoverable<Self>
+    where
+        Self: Sized,
+    {
+        hoverable(self)
+    }
 }
 
 // Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.

crates/gpui/src/app.rs 🔗

@@ -1363,7 +1363,14 @@ impl AppContext {
             window: handle,
         }));
 
-        let mut window = Window::new(handle, platform_window, self, build_root_view);
+        let mouse_position = self.platform.mouse_position();
+        let mut window = Window::new(
+            handle,
+            platform_window,
+            mouse_position,
+            self,
+            build_root_view,
+        );
         let mut cx = WindowContext::mutable(self, &mut window, handle);
         cx.layout(false).expect("initial layout should not error");
         let scene = cx.paint().expect("initial paint should not error");

crates/gpui/src/app/window.rs 🔗

@@ -70,6 +70,7 @@ impl Window {
     pub fn new<V, F>(
         handle: AnyWindowHandle,
         platform_window: Box<dyn platform::Window>,
+        mouse_position: Vector2F,
         cx: &mut AppContext,
         build_view: F,
     ) -> Self
@@ -97,7 +98,7 @@ impl Window {
             hovered_region_ids: Default::default(),
             clicked_region_ids: Default::default(),
             clicked_region: None,
-            mouse_position: vec2f(0., 0.),
+            mouse_position,
             titlebar_height,
             appearance,
         };

crates/gpui/src/platform.rs 🔗

@@ -75,6 +75,7 @@ pub trait Platform: Send + Sync {
     fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
     fn delete_credentials(&self, url: &str) -> Result<()>;
 
+    fn mouse_position(&self) -> Vector2F;
     fn set_cursor_style(&self, style: CursorStyle);
     fn should_auto_hide_scrollbars(&self) -> bool;
 

crates/gpui/src/platform/mac/platform.rs 🔗

@@ -18,7 +18,7 @@ use cocoa::{
     },
     base::{id, nil, selector, BOOL, YES},
     foundation::{
-        NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString,
+        NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSPoint, NSProcessInfo, NSString,
         NSUInteger, NSURL,
     },
 };
@@ -37,6 +37,7 @@ use objc::{
     runtime::{Class, Object, Sel},
     sel, sel_impl,
 };
+use pathfinder_geometry::vector::{vec2f, Vector2F};
 use postage::oneshot;
 use ptr::null_mut;
 use std::{
@@ -784,6 +785,11 @@ impl platform::Platform for MacPlatform {
         Ok(())
     }
 
+    fn mouse_position(&self) -> Vector2F {
+        let position: NSPoint = unsafe { msg_send![class!(NSEvent), mouseLocation] };
+        vec2f(position.x as f32, position.y as f32)
+    }
+
     fn set_cursor_style(&self, style: CursorStyle) {
         unsafe {
             let new_cursor: id = match style {

crates/gpui/src/platform/test.rs 🔗

@@ -195,6 +195,10 @@ impl super::Platform for Platform {
         Ok(())
     }
 
+    fn mouse_position(&self) -> Vector2F {
+        Vector2F::zero()
+    }
+
     fn set_cursor_style(&self, style: CursorStyle) {
         *self.cursor.lock() = style;
     }