use gpui::{
AnyElement, Point, ScrollDelta, ScrollHandle, ScrollWheelEvent, SharedString, TestAppContext,
VisualTestContext, div, point, prelude::*, px, size,
};
use ui::scroll::{self, Axes};
const WIDTH: f32 = 400.0;
const HEIGHT: f32 = 300.0;
const CONTENT: f32 = 2000.0;
const SWIPE: f32 = -80.0;
#[derive(Clone, Copy)]
enum Built {
Plain(Axes),
Pane(Axes),
}
struct Host {
scroll: ScrollHandle,
built: Built,
}
impl gpui::Render for Host {
fn render(&mut self, _: &mut gpui::Window, _: &mut gpui::Context<Self>) -> impl IntoElement {
let content = div().w(px(CONTENT)).h(px(CONTENT));
match self.built {
Built::Plain(axes) => {
let el = div().id("pane").size_full();
match axes {
Axes::Vertical => el.overflow_y_scroll(),
Axes::Horizontal => el.overflow_x_scroll(),
Axes::Both => el.overflow_scroll(),
}
.track_scroll(&self.scroll)
.child(content)
.into_any_element()
}
Built::Pane(axes) => scroll::pane("pane", axes)
.size_full()
.track_scroll(&self.scroll)
.child(content)
.into_any_element(),
}
}
}
fn swipe(built: Built, delta: Point<gpui::Pixels>, cx: &mut TestAppContext) -> (f32, f32) {
cx.update(|cx| theme::Theme::install(theme::Appearance::Dark, cx));
let window = cx.add_window(|_, _| Host {
scroll: ScrollHandle::new(),
built,
});
let view = window.root(cx).unwrap();
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.simulate_resize(size(px(WIDTH), px(HEIGHT)));
cx.run_until_parked();
cx.simulate_event(ScrollWheelEvent {
position: point(px(WIDTH / 2.0), px(HEIGHT / 2.0)),
delta: ScrollDelta::Pixels(delta),
modifiers: Default::default(),
touch_phase: Default::default(),
});
cx.run_until_parked();
cx.update(|_, cx| {
let offset = view.read(cx).scroll.offset();
(f32::from(offset.x), f32::from(offset.y))
})
}
fn sideways() -> Point<gpui::Pixels> {
point(px(SWIPE), px(0.0))
}
fn downwards() -> Point<gpui::Pixels> {
point(px(0.0), px(SWIPE))
}
#[gpui::test]
fn a_sideways_swipe_leaves_a_vertical_pane_where_it_was(cx: &mut TestAppContext) {
assert_eq!(
swipe(Built::Plain(Axes::Vertical), sideways(), cx),
(0.0, SWIPE),
"the bug: a plain overflow_y_scroll div takes the x delta as y"
);
assert_eq!(
swipe(Built::Pane(Axes::Vertical), sideways(), cx),
(0.0, 0.0)
);
}
#[gpui::test]
fn a_downward_swipe_leaves_a_horizontal_pane_where_it_was(cx: &mut TestAppContext) {
assert_eq!(
swipe(Built::Plain(Axes::Horizontal), downwards(), cx),
(SWIPE, 0.0),
"the bug: a plain overflow_x_scroll div takes the y delta as x"
);
assert_eq!(
swipe(Built::Pane(Axes::Horizontal), downwards(), cx),
(0.0, 0.0)
);
}
#[gpui::test]
fn a_pane_still_answers_its_own_axis(cx: &mut TestAppContext) {
assert_eq!(
swipe(Built::Pane(Axes::Vertical), downwards(), cx),
(0.0, SWIPE)
);
assert_eq!(
swipe(Built::Pane(Axes::Horizontal), sideways(), cx),
(SWIPE, 0.0)
);
}
#[gpui::test]
fn a_both_axes_pane_answers_either(cx: &mut TestAppContext) {
assert_eq!(swipe(Built::Pane(Axes::Both), sideways(), cx), (SWIPE, 0.0));
assert_eq!(
swipe(Built::Pane(Axes::Both), downwards(), cx),
(0.0, SWIPE)
);
}
const COLUMN: f32 = 150.0;
const COLUMNS: usize = 4;
struct BoardHost {
board: ScrollHandle,
column: ScrollHandle,
fixed: bool,
}
impl BoardHost {
fn column(&self) -> AnyElement {
let cards = div().w_full().h(px(CONTENT));
let inner = match self.fixed {
true => scroll::pane("column", Axes::Vertical).size_full(),
false => div().id("column").size_full().overflow_y_scroll(),
};
div()
.flex_none()
.w(px(COLUMN))
.h_full()
.child(inner.track_scroll(&self.column).child(cards))
.into_any_element()
}
}
impl gpui::Render for BoardHost {
fn render(&mut self, _: &mut gpui::Window, _: &mut gpui::Context<Self>) -> impl IntoElement {
let outer = match self.fixed {
true => scroll::pane("board", Axes::Horizontal).size_full(),
false => div().id("board").size_full().overflow_x_scroll(),
};
div().size_full().child(
outer
.flex()
.flex_row()
.track_scroll(&self.board)
.child(self.column())
.children((1..COLUMNS).map(|ix| {
div()
.flex_none()
.w(px(COLUMN))
.h_full()
.child(SharedString::from(format!("filler {ix}")))
})),
)
}
}
fn swipe_board(fixed: bool, delta: Point<gpui::Pixels>, cx: &mut TestAppContext) -> (f32, f32) {
cx.update(|cx| theme::Theme::install(theme::Appearance::Dark, cx));
let window = cx.add_window(|_, _| BoardHost {
board: ScrollHandle::new(),
column: ScrollHandle::new(),
fixed,
});
let view = window.root(cx).unwrap();
let mut cx = VisualTestContext::from_window(window.into(), cx);
cx.simulate_resize(size(px(WIDTH), px(HEIGHT)));
cx.run_until_parked();
cx.simulate_event(ScrollWheelEvent {
position: point(px(COLUMN / 2.0), px(HEIGHT / 2.0)),
delta: ScrollDelta::Pixels(delta),
modifiers: Default::default(),
touch_phase: Default::default(),
});
cx.run_until_parked();
cx.update(|_, cx| {
let host = view.read(cx);
(
f32::from(host.board.offset().x),
f32::from(host.column.offset().y),
)
})
}
#[gpui::test]
fn a_column_scrolls_without_panning_the_board(cx: &mut TestAppContext) {
assert_eq!(
swipe_board(false, downwards(), cx),
(SWIPE, SWIPE),
"the bug: the column scrolls down and the board pans across with it"
);
assert_eq!(swipe_board(true, downwards(), cx), (0.0, SWIPE));
}
#[gpui::test]
fn the_board_pans_without_scrolling_a_column(cx: &mut TestAppContext) {
assert_eq!(
swipe_board(false, sideways(), cx),
(SWIPE, SWIPE),
"the bug: the board pans across and the column scrolls down with it"
);
assert_eq!(swipe_board(true, sideways(), cx), (SWIPE, 0.0));
}