woocraft 0.4.5

GPUI components lib for Woocraft design system.
Documentation
use std::{panic::Location, rc::Rc};

use gpui::{
  App, Div, Element, ElementId, InteractiveElement, IntoElement, ParentElement, RenderOnce,
  ScrollHandle, Stateful, StatefulInteractiveElement, StyleRefinement, Styled, Window, div,
  prelude::FluentBuilder,
};

use super::{ScrollableMask, Scrollbar, ScrollbarAxis, ScrollbarHandle};
use crate::StyledExt;

pub trait ScrollableElement: InteractiveElement + Styled + ParentElement + Element {
  #[track_caller]
  fn scrollbar<H: ScrollbarHandle + Clone>(
    self, scroll_handle: &H, axis: impl Into<ScrollbarAxis>,
  ) -> Self {
    self.child(ScrollbarLayer {
      id: "scrollbar_layer".into(),
      axis: axis.into(),
      scroll_handle: Rc::new(scroll_handle.clone()),
    })
  }

  #[track_caller]
  fn vertical_scrollbar<H: ScrollbarHandle + Clone>(self, scroll_handle: &H) -> Self {
    self.scrollbar(scroll_handle, ScrollbarAxis::Vertical)
  }

  #[track_caller]
  fn horizontal_scrollbar<H: ScrollbarHandle + Clone>(self, scroll_handle: &H) -> Self {
    self.scrollbar(scroll_handle, ScrollbarAxis::Horizontal)
  }

  #[track_caller]
  fn overflow_scrollbar(self) -> Scrollable<Self> {
    Scrollable::new(self, ScrollbarAxis::Both)
  }

  #[track_caller]
  fn overflow_x_scrollbar(self) -> Scrollable<Self> {
    Scrollable::new(self, ScrollbarAxis::Horizontal)
  }

  #[track_caller]
  fn overflow_y_scrollbar(self) -> Scrollable<Self> {
    Scrollable::new(self, ScrollbarAxis::Vertical)
  }
}

#[derive(IntoElement)]
pub struct Scrollable<E: InteractiveElement + Styled + ParentElement + Element> {
  id: ElementId,
  style: StyleRefinement,
  element: E,
  axis: ScrollbarAxis,
}

impl<E> Scrollable<E>
where
  E: InteractiveElement + Styled + ParentElement + Element,
{
  #[track_caller]
  fn new(element: E, axis: impl Into<ScrollbarAxis>) -> Self {
    let caller = Location::caller();
    Self {
      id: ElementId::CodeLocation(*caller),
      style: StyleRefinement::default(),
      element,
      axis: axis.into(),
    }
  }
}

impl<E> Styled for Scrollable<E>
where
  E: InteractiveElement + Styled + ParentElement + Element,
{
  fn style(&mut self) -> &mut StyleRefinement {
    &mut self.style
  }
}

impl<E> ParentElement for Scrollable<E>
where
  E: InteractiveElement + Styled + ParentElement + Element,
{
  fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
    self.element.extend(elements)
  }
}

impl InteractiveElement for Scrollable<Div> {
  fn interactivity(&mut self) -> &mut gpui::Interactivity {
    self.element.interactivity()
  }
}

impl InteractiveElement for Scrollable<Stateful<Div>> {
  fn interactivity(&mut self) -> &mut gpui::Interactivity {
    self.element.interactivity()
  }
}

impl<E> RenderOnce for Scrollable<E>
where
  E: InteractiveElement + Styled + ParentElement + Element + 'static,
{
  fn render(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();

    div()
      .id(self.id)
      .refine_style(&self.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()),
      )
      .child(ScrollableMask::new(self.axis, &scroll_handle))
      .child(render_scrollbar(
        "scrollbar",
        &scroll_handle,
        self.axis,
        window,
        cx,
      ))
  }
}

impl ScrollableElement for Div {}
impl<E> ScrollableElement for Stateful<E>
where
  E: ParentElement + Styled + Element,
  Self: InteractiveElement,
{
}

#[derive(IntoElement)]
struct ScrollbarLayer<H: ScrollbarHandle + Clone> {
  id: ElementId,
  axis: ScrollbarAxis,
  scroll_handle: Rc<H>,
}

impl<H> RenderOnce for ScrollbarLayer<H>
where
  H: ScrollbarHandle + Clone + 'static,
{
  fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
    render_scrollbar(self.id, self.scroll_handle.as_ref(), self.axis, window, cx)
  }
}

#[inline]
#[track_caller]
fn render_scrollbar<H: ScrollbarHandle + Clone>(
  id: impl Into<ElementId>, scroll_handle: &H, axis: ScrollbarAxis, window: &mut Window,
  cx: &mut App,
) -> Div {
  if window.is_inspector_picking(cx) {
    return div();
  }

  div()
    .absolute()
    .top_0()
    .left_0()
    .right_0()
    .bottom_0()
    .child(Scrollbar::new(scroll_handle).id(id).axis(axis))
}