diff --git a/crates/gpui3/src/elements/div.rs b/crates/gpui3/src/elements/div.rs index d4663c950de97badbaf6e368c36c7d25a8e73bf5..634a1b6611f1c78ad83a8f02c847d21bf1aa89e2 100644 --- a/crates/gpui3/src/elements/div.rs +++ b/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>>); - -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( &mut self, cx: &mut ViewContext, @@ -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 Div, FocusDisabled> @@ -225,6 +196,7 @@ where pub struct DivState { interactive: InteractiveElementState, focus_handle: Option, + child_layout_ids: SmallVec<[LayoutId; 4]>, } impl Element for Div @@ -274,7 +246,8 @@ where .children .iter_mut() .map(|child| child.layout(view_state, cx)) - .collect::>(); + .collect::>(); + 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); } }) }) diff --git a/crates/gpui3/src/geometry.rs b/crates/gpui3/src/geometry.rs index 60a31d1da99a5c4c5b1ee8d84f985657a4e7a50a..2ac4c21b80b016785a9e850918a0c7ece7917927 100644 --- a/crates/gpui3/src/geometry.rs +++ b/crates/gpui3/src/geometry.rs @@ -205,6 +205,20 @@ where } } +impl Sub for Size +where + T: Sub + Clone + Default + Debug, +{ + type Output = Size; + + fn sub(self, rhs: Self) -> Self::Output { + Size { + width: self.width - rhs.width, + height: self.height - rhs.height, + } + } +} + impl Mul for Size where T: Mul + Clone + Default + Debug, @@ -242,6 +256,15 @@ where } } +impl From> for Size { + fn from(point: Point) -> Self { + Self { + width: point.x, + height: point.y, + } + } +} + impl From> for Size { fn from(size: Size) -> Self { Size { @@ -679,6 +702,8 @@ impl MulAssign for Pixels { } impl Pixels { + pub const MAX: Pixels = Pixels(f32::MAX); + pub fn round(&self) -> Self { Self(self.0.round()) } diff --git a/crates/gpui3/src/interactive.rs b/crates/gpui3/src/interactive.rs index dd50082bae7a2e35d7708b20b561113d648d4e74..f4a2f9984485521cf2a626f8ffb4c2558025d943 100644 --- a/crates/gpui3/src/interactive.rs +++ b/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: 'static + Send + Sync { fn paint( &mut self, bounds: Bounds, - element_state: &InteractiveElementState, + content_size: Size, + overflow: Point, + element_state: &mut InteractiveElementState, cx: &mut ViewContext, ) { let stateless = self.as_stateless(); @@ -468,6 +470,34 @@ pub trait ElementInteraction: '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>, pending_click: Arc>>, + scroll_offset: Option>>>, +} + +impl InteractiveElementState { + pub fn scroll_offset(&self) -> Option> { + self.scroll_offset + .as_ref() + .map(|offset| offset.lock().clone()) + } } impl Default for StatelessInteraction { diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 7e75d9ab42e18c5746becce1bc1be919de28005f..7609aae29f6232d532c827f383c53e0eb8c5338d 100644 --- a/crates/gpui3/src/window.rs +++ b/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; }