Checkpoint

Antonio Scandurra created

Change summary

crates/gpui3/src/elements/div.rs | 96 +++++++++++++++------------------
crates/gpui3/src/geometry.rs     | 25 ++++++++
crates/gpui3/src/interactive.rs  | 47 +++++++++++++++-
crates/gpui3/src/window.rs       |  8 ++
4 files changed, 119 insertions(+), 57 deletions(-)

Detailed changes

crates/gpui3/src/elements/div.rs 🔗

@@ -1,35 +1,12 @@
 use crate::{
-    AnyElement, BorrowWindow, Bounds, Element, ElementFocus, ElementId, ElementInteraction,
+    point, AnyElement, BorrowWindow, Bounds, Element, ElementFocus, ElementId, ElementInteraction,
     FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable, GlobalElementId,
     GroupBounds, InteractiveElementState, IntoAnyElement, LayoutId, Overflow, ParentElement,
     Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
     StatelessInteractive, Style, StyleRefinement, Styled, ViewContext,
 };
-use parking_lot::Mutex;
 use refineable::Refineable;
 use smallvec::SmallVec;
-use std::sync::Arc;
-
-#[derive(Default, Clone)]
-pub struct ScrollState(Arc<Mutex<Point<Pixels>>>);
-
-impl ScrollState {
-    pub fn x(&self) -> Pixels {
-        self.0.lock().x
-    }
-
-    pub fn set_x(&self, value: Pixels) {
-        self.0.lock().x = value;
-    }
-
-    pub fn y(&self) -> Pixels {
-        self.0.lock().y
-    }
-
-    pub fn set_y(&self, value: Pixels) {
-        self.0.lock().y = value;
-    }
-}
 
 pub struct Div<
     V: 'static + Send + Sync,
@@ -104,28 +81,6 @@ where
         self
     }
 
-    pub fn overflow_scroll(mut self, _scroll_state: ScrollState) -> Self {
-        // todo!("impl scrolling")
-        // self.scroll_state = Some(scroll_state);
-        self.base_style.overflow.x = Some(Overflow::Scroll);
-        self.base_style.overflow.y = Some(Overflow::Scroll);
-        self
-    }
-
-    pub fn overflow_x_scroll(mut self, _scroll_state: ScrollState) -> Self {
-        // todo!("impl scrolling")
-        // self.scroll_state = Some(scroll_state);
-        self.base_style.overflow.x = Some(Overflow::Scroll);
-        self
-    }
-
-    pub fn overflow_y_scroll(mut self, _scroll_state: ScrollState) -> Self {
-        // todo!("impl scrolling")
-        // self.scroll_state = Some(scroll_state);
-        self.base_style.overflow.y = Some(Overflow::Scroll);
-        self
-    }
-
     fn with_element_id<R>(
         &mut self,
         cx: &mut ViewContext<V>,
@@ -179,6 +134,22 @@ where
             base_style: self.base_style,
         }
     }
+
+    pub fn overflow_scroll(mut self) -> Self {
+        self.base_style.overflow.x = Some(Overflow::Scroll);
+        self.base_style.overflow.y = Some(Overflow::Scroll);
+        self
+    }
+
+    pub fn overflow_x_scroll(mut self) -> Self {
+        self.base_style.overflow.x = Some(Overflow::Scroll);
+        self
+    }
+
+    pub fn overflow_y_scroll(mut self) -> Self {
+        self.base_style.overflow.y = Some(Overflow::Scroll);
+        self
+    }
 }
 
 impl<V> Div<V, StatelessInteraction<V>, FocusDisabled>
@@ -225,6 +196,7 @@ where
 pub struct DivState {
     interactive: InteractiveElementState,
     focus_handle: Option<FocusHandle>,
+    child_layout_ids: SmallVec<[LayoutId; 4]>,
 }
 
 impl<V, I, F> Element for Div<V, I, F>
@@ -274,7 +246,8 @@ where
                     .children
                     .iter_mut()
                     .map(|child| child.layout(view_state, cx))
-                    .collect::<Vec<_>>();
+                    .collect::<SmallVec<_>>();
+                element_state.child_layout_ids = layout_ids.clone();
                 cx.request_layout(&style, layout_ids)
             })
         })
@@ -295,21 +268,38 @@ where
             let style = this.compute_style(bounds, element_state, cx);
             let z_index = style.z_index.unwrap_or(0);
 
-            // Paint background and event handlers.
+            let mut child_min = point(Pixels::MAX, Pixels::MAX);
+            let mut child_max = Point::default();
+
+            let content_size = if element_state.child_layout_ids.is_empty() {
+                bounds.size
+            } else {
+                for child_layout_id in &element_state.child_layout_ids {
+                    let child_bounds = cx.layout_bounds(*child_layout_id);
+                    child_min = child_min.min(&child_bounds.origin);
+                    child_max = child_min.max(&child_bounds.lower_right());
+                }
+                (child_max - child_min).into()
+            };
+
             cx.stack(z_index, |cx| {
                 cx.stack(0, |cx| {
                     style.paint(bounds, cx);
-
                     this.focus.paint(bounds, cx);
-                    this.interaction
-                        .paint(bounds, &element_state.interactive, cx);
+                    this.interaction.paint(
+                        bounds,
+                        content_size,
+                        style.overflow,
+                        &mut element_state.interactive,
+                        cx,
+                    );
                 });
-
                 cx.stack(1, |cx| {
                     style.apply_text_style(cx, |cx| {
                         style.apply_overflow(bounds, cx, |cx| {
+                            let scroll_offset = element_state.interactive.scroll_offset();
                             for child in &mut this.children {
-                                child.paint(view_state, None, cx);
+                                child.paint(view_state, scroll_offset, cx);
                             }
                         })
                     })

crates/gpui3/src/geometry.rs 🔗

@@ -205,6 +205,20 @@ where
     }
 }
 
+impl<T> Sub for Size<T>
+where
+    T: Sub<Output = T> + Clone + Default + Debug,
+{
+    type Output = Size<T>;
+
+    fn sub(self, rhs: Self) -> Self::Output {
+        Size {
+            width: self.width - rhs.width,
+            height: self.height - rhs.height,
+        }
+    }
+}
+
 impl<T, Rhs> Mul<Rhs> for Size<T>
 where
     T: Mul<Rhs, Output = Rhs> + Clone + Default + Debug,
@@ -242,6 +256,15 @@ where
     }
 }
 
+impl<T: Clone + Default + Debug> From<Point<T>> for Size<T> {
+    fn from(point: Point<T>) -> Self {
+        Self {
+            width: point.x,
+            height: point.y,
+        }
+    }
+}
+
 impl From<Size<Pixels>> for Size<GlobalPixels> {
     fn from(size: Size<Pixels>) -> Self {
         Size {
@@ -679,6 +702,8 @@ impl MulAssign<f32> for Pixels {
 }
 
 impl Pixels {
+    pub const MAX: Pixels = Pixels(f32::MAX);
+
     pub fn round(&self) -> Self {
         Self(self.0.round())
     }

crates/gpui3/src/interactive.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
-    point, Action, AppContext, BorrowWindow, Bounds, DispatchContext, DispatchPhase, Element,
-    ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Pixels, Point, SharedString, Style,
-    StyleRefinement, ViewContext,
+    point, px, Action, AppContext, BorrowWindow, Bounds, DispatchContext, DispatchPhase, Element,
+    ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Overflow, Pixels, Point, SharedString,
+    Size, Style, StyleRefinement, ViewContext,
 };
 use collections::HashMap;
 use derive_more::{Deref, DerefMut};
@@ -375,7 +375,9 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        element_state: &InteractiveElementState,
+        content_size: Size<Pixels>,
+        overflow: Point<Overflow>,
+        element_state: &mut InteractiveElementState,
         cx: &mut ViewContext<V>,
     ) {
         let stateless = self.as_stateless();
@@ -468,6 +470,34 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
                     }
                 });
             }
+
+            if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll {
+                let scroll_offset = element_state
+                    .scroll_offset
+                    .get_or_insert_with(Arc::default)
+                    .clone();
+                let line_height = cx.line_height();
+                let scroll_max = content_size - bounds.size;
+
+                cx.on_mouse_event(move |_, event: &ScrollWheelEvent, _, cx| {
+                    if bounds.contains_point(&event.position) {
+                        let mut scroll_offset = scroll_offset.lock();
+                        let delta = event.delta.pixel_delta(line_height);
+
+                        if overflow.x == Overflow::Scroll {
+                            scroll_offset.x =
+                                (scroll_offset.x - delta.x).clamp(px(0.), scroll_max.width);
+                        }
+
+                        if overflow.y == Overflow::Scroll {
+                            scroll_offset.y =
+                                (scroll_offset.y - delta.y).clamp(px(0.), scroll_max.height);
+                        }
+
+                        cx.notify();
+                    }
+                });
+            }
         }
     }
 }
@@ -609,6 +639,15 @@ impl ActiveState {
 pub struct InteractiveElementState {
     active_state: Arc<Mutex<ActiveState>>,
     pending_click: Arc<Mutex<Option<MouseDownEvent>>>,
+    scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
+}
+
+impl InteractiveElementState {
+    pub fn scroll_offset(&self) -> Option<Point<Pixels>> {
+        self.scroll_offset
+            .as_ref()
+            .map(|offset| offset.lock().clone())
+    }
 }
 
 impl<V> Default for StatelessInteraction<V> {

crates/gpui3/src/window.rs 🔗

@@ -457,6 +457,14 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         self.window.rem_size
     }
 
+    pub fn line_height(&self) -> Pixels {
+        let rem_size = self.rem_size();
+        let text_style = self.text_style();
+        text_style
+            .line_height
+            .to_pixels(text_style.font_size.into(), rem_size)
+    }
+
     pub fn stop_propagation(&mut self) {
         self.window.propagate = false;
     }