use gpui::{ canvas, div, relative, AnyElement, App, Div, Element, ElementId, EntityId, GlobalElementId, InteractiveElement, IntoElement, ParentElement, Pixels, Position, ScrollHandle, SharedString, Size, Stateful, StatefulInteractiveElement, Style, StyleRefinement, Styled, Window, }; use std::{cell::Cell, rc::Rc}; use super::{Scrollbar, ScrollbarAxis, ScrollbarState}; /// A scroll view is a container that allows the user to scroll through a large amount of content. pub struct Scrollable { id: ElementId, element: Option, view_id: EntityId, axis: ScrollbarAxis, /// This is a fake element to handle Styled, InteractiveElement, not used. _element: Stateful
, } impl Scrollable where E: Element, { pub(crate) fn new(view_id: EntityId, element: E, axis: ScrollbarAxis) -> Self { let id = ElementId::Name(SharedString::from(format!( "ScrollView:{}-{:?}", view_id, element.id(), ))); Self { element: Some(element), _element: div().id("fake"), id, view_id, axis, } } /// Set only a vertical scrollbar. pub fn vertical(mut self) -> Self { self.set_axis(ScrollbarAxis::Vertical); self } /// Set only a horizontal scrollbar. /// In current implementation, this is not supported yet. pub fn horizontal(mut self) -> Self { self.set_axis(ScrollbarAxis::Horizontal); self } /// Set the axis of the scroll view. pub fn set_axis(&mut self, axis: ScrollbarAxis) { self.axis = axis; } fn with_element_state( &mut self, id: &GlobalElementId, window: &mut Window, cx: &mut App, f: impl FnOnce(&mut Self, &mut ScrollViewState, &mut Window, &mut App) -> R, ) -> R { window.with_optional_element_state::( Some(id), |element_state, window| { let mut element_state = element_state.unwrap().unwrap_or_default(); let result = f(self, &mut element_state, window, cx); (result, Some(element_state)) }, ) } } pub struct ScrollViewState { scroll_size: Rc>>, state: Rc>, handle: ScrollHandle, } impl Default for ScrollViewState { fn default() -> Self { Self { handle: ScrollHandle::new(), scroll_size: Rc::new(Cell::new(Size::default())), state: Rc::new(Cell::new(ScrollbarState::default())), } } } impl ParentElement for Scrollable where E: Element + ParentElement, { fn extend(&mut self, elements: impl IntoIterator) { if let Some(element) = &mut self.element { element.extend(elements); } } } impl Styled for Scrollable where E: Element + Styled, { fn style(&mut self) -> &mut StyleRefinement { if let Some(element) = &mut self.element { element.style() } else { self._element.style() } } } impl InteractiveElement for Scrollable where E: Element + InteractiveElement, { fn interactivity(&mut self) -> &mut gpui::Interactivity { if let Some(element) = &mut self.element { element.interactivity() } else { self._element.interactivity() } } } impl StatefulInteractiveElement for Scrollable where E: Element + StatefulInteractiveElement {} impl IntoElement for Scrollable where E: Element, { type Element = Self; fn into_element(self) -> Self::Element { self } } impl Element for Scrollable where E: Element, { type RequestLayoutState = AnyElement; type PrepaintState = ScrollViewState; fn id(&self) -> Option { Some(self.id.clone()) } fn request_layout( &mut self, id: Option<&gpui::GlobalElementId>, window: &mut Window, cx: &mut App, ) -> (gpui::LayoutId, Self::RequestLayoutState) { let style = Style { position: Position::Relative, flex_grow: 1.0, flex_shrink: 1.0, size: Size { width: relative(1.).into(), height: relative(1.).into(), }, ..Default::default() }; let axis = self.axis; let view_id = self.view_id; let scroll_id = self.id.clone(); let content = self.element.take().map(|c| c.into_any_element()); self.with_element_state(id.unwrap(), window, cx, |_, element_state, window, cx| { let handle = element_state.handle.clone(); let state = element_state.state.clone(); let scroll_size = element_state.scroll_size.clone(); let mut element = div() .relative() .size_full() .overflow_hidden() .child( div() .id(scroll_id) .track_scroll(&handle) .overflow_scroll() .relative() .size_full() .child(div().children(content).child({ let scroll_size = element_state.scroll_size.clone(); canvas(move |b, _, _| scroll_size.set(b.size), |_, _, _, _| {}) .absolute() .size_full() })), ) .child( div() .absolute() .top_0() .left_0() .right_0() .bottom_0() .child( Scrollbar::both(view_id, state, handle.clone(), scroll_size.get()) .axis(axis), ), ) .into_any_element(); let element_id = element.request_layout(window, cx); let layout_id = window.request_layout(style, vec![element_id], cx); (layout_id, element) }) } fn prepaint( &mut self, _: Option<&gpui::GlobalElementId>, _: gpui::Bounds, element: &mut Self::RequestLayoutState, window: &mut Window, cx: &mut App, ) -> Self::PrepaintState { element.prepaint(window, cx); // do nothing ScrollViewState::default() } fn paint( &mut self, _: Option<&gpui::GlobalElementId>, _: gpui::Bounds, element: &mut Self::RequestLayoutState, _: &mut Self::PrepaintState, window: &mut Window, cx: &mut App, ) { element.paint(window, cx) } }