use gpui_kit::{
AnyElement, App, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,
ScrollHandle, StatefulInteractiveElement as _, StyleRefinement, Styled, Window,
base::{ScrollBounce, StyledExt as _, TestSupportExt as _},
div, px,
};
#[derive(IntoElement)]
pub struct ScrollArea {
id: ElementId,
children: Vec<AnyElement>,
style: StyleRefinement,
}
impl ScrollArea {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
children: Vec::new(),
style: StyleRefinement::default(),
}
}
}
impl Styled for ScrollArea {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl ParentElement for ScrollArea {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl RenderOnce for ScrollArea {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let handle = window
.use_keyed_state(
ElementId::NamedChild(self.id.clone().into(), "scroll".into()),
cx,
|_, _| ScrollHandle::new(),
)
.read(cx)
.clone();
let (frame_style, viewport_style) = split_style(self.style);
let viewport = div()
.id(self.id.clone())
.test_support()
.size_full()
.overflow_y_scroll()
.track_scroll(&handle)
.refine_style(&viewport_style)
.children(self.children);
let bounce = ScrollBounce::new(
ElementId::NamedChild(self.id.clone().into(), "bounce".into()),
&handle,
viewport,
)
.enabled(true);
div()
.id(ElementId::NamedChild(self.id.into(), "frame".into()))
.refine_style(&frame_style)
.on_scroll_wheel(move |_, _, cx| {
if handle.max_offset().y > px(0.) {
cx.stop_propagation();
}
})
.child(bounce)
}
}
fn split_style(style: StyleRefinement) -> (StyleRefinement, StyleRefinement) {
let mut frame = StyleRefinement::default();
let mut viewport = style;
frame.size = std::mem::take(&mut viewport.size);
frame.min_size = std::mem::take(&mut viewport.min_size);
frame.max_size = std::mem::take(&mut viewport.max_size);
frame.flex_grow = viewport.flex_grow.take();
frame.flex_shrink = viewport.flex_shrink.take();
frame.flex_basis = viewport.flex_basis.take();
frame.align_self = viewport.align_self.take();
frame.margin = std::mem::take(&mut viewport.margin);
frame.position = viewport.position.take();
frame.inset = std::mem::take(&mut viewport.inset);
(frame, viewport)
}