166 lines
5.5 KiB
Rust
166 lines
5.5 KiB
Rust
use gpui::{
|
|
AppContext as _, Context, Entity, IntoElement, Modifiers, MouseButton, MouseDownEvent,
|
|
MouseMoveEvent, ParentElement as _, Pixels, Render, Styled as _, Subscription, TestAppContext,
|
|
VisualTestContext, Window, div, point, px,
|
|
};
|
|
use gpui_native_layout::resizable::{Resizable, ResizablePane, ResizableState};
|
|
use gpui_native_theme::{ActiveTheme as _, Size, Theme};
|
|
|
|
const GROUP_WIDTH: f32 = 200.0;
|
|
const GROUP_HEIGHT: f32 = 300.0;
|
|
|
|
struct Harness {
|
|
state: Entity<ResizableState>,
|
|
_observation: Subscription,
|
|
}
|
|
|
|
impl Render for Harness {
|
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
|
div().w(px(GROUP_WIDTH)).h(px(GROUP_HEIGHT)).child(
|
|
Resizable::new("test-split", self.state.clone())
|
|
.debug_selector(|| "test-pane".to_string())
|
|
.size_full()
|
|
.pane(div())
|
|
.pane(div()),
|
|
)
|
|
}
|
|
}
|
|
|
|
fn split_window(sizes: [Pixels; 1]) -> (TestAppContext, Entity<ResizableState>, VisualTestContext) {
|
|
let mut cx = TestAppContext::single();
|
|
cx.update(Theme::init);
|
|
let state = cx.update(ResizableState::new);
|
|
state.update(&mut cx, |state, cx| state.set_sizes(sizes, cx));
|
|
let window_state = state.clone();
|
|
let window = cx.add_window(move |_, cx: &mut Context<Harness>| {
|
|
let observation = cx.observe(&window_state, |_, _, cx| cx.notify());
|
|
Harness {
|
|
state: window_state,
|
|
_observation: observation,
|
|
}
|
|
});
|
|
let window = window.into();
|
|
cx.update_window(window, |_, window, cx| {
|
|
window.draw(cx).clear(cx);
|
|
})
|
|
.unwrap();
|
|
let visual = VisualTestContext::from_window(window, &cx);
|
|
(cx, state, visual)
|
|
}
|
|
|
|
fn divider_center(visual: &mut VisualTestContext) -> gpui::Point<gpui::Pixels> {
|
|
let bounds = visual
|
|
.debug_bounds("resize-handle")
|
|
.expect("divider rendered");
|
|
point(bounds.center().x, bounds.center().y)
|
|
}
|
|
|
|
fn drag_divider(visual: &mut VisualTestContext, delta: gpui::Pixels) {
|
|
let start = divider_center(visual);
|
|
visual.simulate_mouse_down(start, MouseButton::Left, Modifiers::default());
|
|
visual.simulate_event(MouseMoveEvent {
|
|
position: point(start.x, start.y + delta),
|
|
pressed_button: Some(MouseButton::Left),
|
|
modifiers: Modifiers::default(),
|
|
});
|
|
visual.simulate_mouse_up(start, MouseButton::Left, Modifiers::default());
|
|
}
|
|
|
|
#[test]
|
|
fn a_drag_moves_the_pane_and_its_rendered_height() {
|
|
let (mut cx, state, mut visual) = split_window([px(100.)]);
|
|
drag_divider(&mut visual, px(60.));
|
|
|
|
state.update(&mut cx, |state, _| assert_eq!(state.sizes(), &[px(160.)]));
|
|
|
|
// The pane re-renders at the new size after the state notifies the owner.
|
|
visual.update(|window, cx| window.draw(cx).clear(cx));
|
|
let bounds = visual
|
|
.debug_bounds("test-pane-0")
|
|
.expect("first pane rendered");
|
|
assert_eq!(bounds.size.height, px(160.));
|
|
}
|
|
|
|
#[test]
|
|
fn a_drag_stops_where_the_remainder_keeps_its_floor() {
|
|
let (mut cx, state, mut visual) = split_window([px(100.)]);
|
|
let thickness = cx.update(|cx| cx.theme().sizes.resolve(Size::default()).dense_padding_x);
|
|
let floor = ResizablePane::DEFAULT_MIN;
|
|
|
|
drag_divider(&mut visual, px(1000.));
|
|
|
|
state.update(&mut cx, |state, _| {
|
|
assert_eq!(state.sizes(), &[px(GROUP_HEIGHT) - thickness - floor]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn a_double_click_resets_the_pane_to_the_equal_split() {
|
|
let (mut cx, state, mut visual) = split_window([px(240.)]);
|
|
let thickness = cx.update(|cx| cx.theme().sizes.resolve(Size::default()).dense_padding_x);
|
|
let start = divider_center(&mut visual);
|
|
|
|
visual.simulate_event(MouseDownEvent {
|
|
position: start,
|
|
modifiers: Modifiers::default(),
|
|
button: MouseButton::Left,
|
|
click_count: 2,
|
|
first_mouse: false,
|
|
});
|
|
|
|
state.update(&mut cx, |state, _| {
|
|
assert_eq!(
|
|
state.sizes(),
|
|
&[px((GROUP_HEIGHT - thickness.as_f32()) / 2.)]
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn arrow_keys_resize_the_focused_pane() {
|
|
let (mut cx, state, mut visual) = split_window([px(100.)]);
|
|
let start = divider_center(&mut visual);
|
|
|
|
// A single click focuses the handle without resetting anything.
|
|
visual.simulate_mouse_down(start, MouseButton::Left, Modifiers::default());
|
|
visual.simulate_mouse_up(start, MouseButton::Left, Modifiers::default());
|
|
visual.simulate_keystrokes("down");
|
|
visual.simulate_keystrokes("up");
|
|
|
|
state.update(&mut cx, |state, _| assert_eq!(state.sizes(), &[px(100.)]));
|
|
}
|
|
|
|
#[test]
|
|
fn an_unfocused_handle_ignores_arrow_keys() {
|
|
let (mut cx, state, mut visual) = split_window([px(100.)]);
|
|
let start = divider_center(&mut visual);
|
|
let _ = start;
|
|
|
|
visual.simulate_keystrokes("down");
|
|
|
|
state.update(&mut cx, |state, _| assert_eq!(state.sizes(), &[px(100.)]));
|
|
}
|
|
|
|
#[test]
|
|
fn the_panes_and_the_divider_fill_the_group_exactly() {
|
|
let (_cx, _state, mut visual) = split_window([px(100.)]);
|
|
visual.update(|window, cx| window.draw(cx).clear(cx));
|
|
let first = visual
|
|
.debug_bounds("test-pane-0")
|
|
.expect("first pane rendered");
|
|
let second = visual
|
|
.debug_bounds("test-pane-1")
|
|
.expect("remainder pane rendered");
|
|
let divider = visual
|
|
.debug_bounds("resize-handle")
|
|
.expect("divider rendered");
|
|
assert_eq!(
|
|
first.size.height + divider.size.height + second.size.height,
|
|
px(GROUP_HEIGHT)
|
|
);
|
|
assert_eq!(
|
|
second.size.height,
|
|
px(GROUP_HEIGHT) - first.size.height - divider.size.height
|
|
);
|
|
}
|