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();
}
}
});
});
}
}