use std::panic::Location; use gpui::prelude::FluentBuilder; use gpui::{ App, Div, Element, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce, ScrollHandle, StatefulInteractiveElement, StyleRefinement, Styled, Window, div, }; use gpui_base::{Scrollbar, ScrollbarAxis}; use crate::StyledExt as _; /// A trait for elements that can be made scrollable with scrollbars. pub trait ScrollableElement: InteractiveElement + Styled + ParentElement + Element { /// Almost equivalent to [`StatefulInteractiveElement::overflow_y_scroll`], but adds Vertical scrollbar. #[track_caller] fn overflow_y_scrollbar(self) -> Scrollable { Scrollable::new(self, ScrollbarAxis::Vertical) } } /// A scrollable element wrapper that adds scrollbars to an interactive element. #[derive(IntoElement)] pub struct Scrollable { id: ElementId, element: E, axis: ScrollbarAxis, } impl Scrollable where E: InteractiveElement + Styled + ParentElement + Element, { #[track_caller] fn new(element: E, axis: impl Into) -> Self { let caller = Location::caller(); Self { id: ElementId::CodeLocation(*caller), element, axis: axis.into(), } } } impl ParentElement for Scrollable where E: InteractiveElement + Styled + ParentElement + Element, { fn extend(&mut self, elements: impl IntoIterator) { self.element.extend(elements) } } impl RenderOnce for Scrollable where E: InteractiveElement + Styled + ParentElement + Element + 'static, { fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement { let scroll_handle = window .use_keyed_state(self.id.clone(), cx, |_, _| ScrollHandle::default()) .read(cx) .clone(); let style = StyleRefinement { size: self.element.style().size.clone(), ..Default::default() }; div() .id(self.id) .size_full() .refine_style(&style) .relative() .child( div() .id("scroll-area") .flex() .size_full() .track_scroll(&scroll_handle) .map(|this| match self.axis { ScrollbarAxis::Vertical => this.flex_col().overflow_y_scroll(), ScrollbarAxis::Horizontal => this.flex_row().overflow_x_scroll(), ScrollbarAxis::Both => this.overflow_scroll(), }) .child(self.element.size_auto().flex_1()), ) .child( Scrollbar::new(&scroll_handle) .axis(self.axis) .id("scrollbar"), ) } } impl ScrollableElement for Div {}