woocraft 0.4.5

GPUI components lib for Woocraft design system.
Documentation
use gpui::{
  App, Axis, BorderStyle, Bounds, ContentMask, Corners, Edges, Element, ElementId, GlobalElementId,
  Hitbox, Hsla, IntoElement, IsZero as _, LayoutId, PaintQuad, Pixels, Point, Position,
  ScrollHandle, ScrollWheelEvent, Style, Window, px, relative,
};

use super::ScrollbarAxis;

#[derive(Clone, Copy)]
pub enum ScrollableMaskAxis {
  Vertical,
  Horizontal,
  Both,
}

impl From<Axis> for ScrollableMaskAxis {
  fn from(axis: Axis) -> Self {
    match axis {
      Axis::Vertical => Self::Vertical,
      Axis::Horizontal => Self::Horizontal,
    }
  }
}

impl From<ScrollbarAxis> for ScrollableMaskAxis {
  fn from(axis: ScrollbarAxis) -> Self {
    match axis {
      ScrollbarAxis::Vertical => Self::Vertical,
      ScrollbarAxis::Horizontal => Self::Horizontal,
      ScrollbarAxis::Both => Self::Both,
    }
  }
}

pub struct ScrollableMask {
  axis: ScrollableMaskAxis,
  scroll_handle: ScrollHandle,
  debug: Option<Hsla>,
}

impl ScrollableMask {
  pub fn new(axis: impl Into<ScrollableMaskAxis>, scroll_handle: &ScrollHandle) -> Self {
    Self {
      scroll_handle: scroll_handle.clone(),
      axis: axis.into(),
      debug: None,
    }
  }

  #[allow(dead_code)]
  pub fn debug(mut self) -> Self {
    self.debug = Some(gpui::yellow());
    self
  }
}

impl IntoElement for ScrollableMask {
  type Element = Self;

  fn into_element(self) -> Self::Element {
    self
  }
}

impl Element for ScrollableMask {
  type RequestLayoutState = ();
  type PrepaintState = Hitbox;

  fn id(&self) -> Option<ElementId> {
    None
  }

  fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
    None
  }

  fn request_layout(
    &mut self, _: Option<&GlobalElementId>, _: Option<&gpui::InspectorElementId>,
    window: &mut Window, cx: &mut App,
  ) -> (LayoutId, Self::RequestLayoutState) {
    let mut style = Style {
      position: Position::Absolute,
      flex_grow: 1.0,
      flex_shrink: 1.0,
      ..Default::default()
    };
    style.size.width = relative(1.).into();
    style.size.height = relative(1.).into();

    (window.request_layout(style, None, cx), ())
  }

  fn prepaint(
    &mut self, _: Option<&GlobalElementId>, _: Option<&gpui::InspectorElementId>,
    bounds: Bounds<Pixels>, _: &mut Self::RequestLayoutState, window: &mut Window, _: &mut App,
  ) -> Self::PrepaintState {
    let cover_bounds = Bounds {
      origin: Point {
        x: bounds.origin.x,
        y: bounds.origin.y - bounds.size.height,
      },
      size: bounds.size,
    };

    window.insert_hitbox(cover_bounds, gpui::HitboxBehavior::Normal)
  }

  fn paint(
    &mut self, _: Option<&GlobalElementId>, _: Option<&gpui::InspectorElementId>,
    _: Bounds<Pixels>, _: &mut Self::RequestLayoutState, hitbox: &mut Self::PrepaintState,
    window: &mut Window, _: &mut App,
  ) {
    let axis = self.axis;
    let line_height = window.line_height();
    let bounds = hitbox.bounds;

    window.with_content_mask(Some(ContentMask { bounds }), |window| {
      if let Some(color) = self.debug {
        window.paint_quad(PaintQuad {
          bounds,
          border_widths: Edges::all(px(1.0)),
          border_color: color,
          background: gpui::transparent_white().into(),
          corner_radii: Corners::all(px(0.)),
          border_style: BorderStyle::default(),
        });
      }

      window.on_mouse_event({
        let view_id = window.current_view();
        let scroll_handle = self.scroll_handle.clone();

        move |event: &ScrollWheelEvent, phase, _, cx| {
          if !(bounds.contains(&event.position) && phase.capture()) {
            return;
          }

          let mut offset = scroll_handle.offset();
          let mut delta = event.delta.pixel_delta(line_height);

          if !delta.x.is_zero() && !delta.y.is_zero() {
            if delta.x.abs() > delta.y.abs() {
              delta.y = px(0.);
            } else {
              delta.x = px(0.);
            }
          }

          match axis {
            ScrollableMaskAxis::Horizontal => {
              offset.x += delta.x;
            }
            ScrollableMaskAxis::Vertical => {
              offset.y += delta.y;
            }
            ScrollableMaskAxis::Both => {
              offset.x += delta.x;
              offset.y += delta.y;
            }
          }

          if offset != scroll_handle.offset() {
            scroll_handle.set_offset(offset);
            cx.notify(view_id);
            cx.stop_propagation();
          }
        }
      });
    });
  }
}