Column shell: a resizable column drags by its border toward the filling column, whose handle draws that border and occludes the header under it, a drag setting off from the width drawn; a set width is a flex basis, so a column holds its header's own parts, where Windows' caption buttons ran out of the window
CI / platforms (ubuntu-24.04) (push) Waiting to run
CI / site (push) Successful in 30s
CI / platforms (windows-latest) (push) Successful in 4m47s
CI / crate (push) Failing after 12m32s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-16 19:56:21 -04:00
1 parent 2e65b05a65
commit 5c2822cfb6
4 files changed
+339 -28

No files matched your search

+168
View File
@@ -0,0 +1,168 @@
use gpui::{
Context, Entity, IntoElement, Modifiers, MouseButton, ParentElement, Pixels, Point, Render,
TestAppContext, VisualTestContext, Window, div, point, prelude::*, px,
};
use crate::shell::{ColumnShell, ShellColumn};
use crate::theme::{Platform, Theme};
/// Two set columns round a filling one; this view keeps their widths.
struct Sided {
platform: Platform,
widths: [Pixels; 2],
heard: Vec<Pixels>,
}
impl Render for Sided {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
let side = |id: &'static str, ix: usize| {
let view = view.clone();
ShellColumn::new(id).title(id).resizable(
self.widths[ix],
px(100.0),
px(400.0),
move |width, _, cx| {
view.update(cx, |view, cx| {
view.widths[ix] = width;
view.heard.push(width);
cx.notify();
})
},
)
};
div().w(px(1000.0)).h(px(400.0)).flex().flex_col().child(
ColumnShell::new("shell")
.platform(self.platform)
.column(side("left", 0))
.column(ShellColumn::new("main").title("main"))
.column(side("right", 1)),
)
}
}
fn sided(
platform: Platform,
widths: [Pixels; 2],
cx: &mut TestAppContext,
) -> (Entity<Sided>, &mut VisualTestContext) {
let (view, cx) = cx.add_window_view(|_, _| Sided {
platform,
widths,
heard: Vec::new(),
});
cx.run_until_parked();
(view, cx)
}
fn column(ix: usize, cx: &mut VisualTestContext) -> gpui::Bounds<Pixels> {
let name: &'static str = ["column-shell-0", "column-shell-1", "column-shell-2"][ix];
cx.debug_bounds(name).expect("a column")
}
/// Presses at `from`, moves past gpui's drag threshold, then by `by`, and lets go.
fn drag(from: Point<Pixels>, by: Pixels, cx: &mut VisualTestContext) {
let none = Modifiers::none();
cx.simulate_mouse_down(from, MouseButton::Left, none);
let set_off = from + point(px(0.0), px(4.0));
cx.simulate_mouse_move(set_off, Some(MouseButton::Left), none);
cx.run_until_parked();
let to = set_off + point(by, px(0.0));
cx.simulate_mouse_move(to, Some(MouseButton::Left), none);
cx.run_until_parked();
cx.simulate_mouse_up(to, MouseButton::Left, none);
cx.run_until_parked();
}
fn heard(view: &Entity<Sided>, cx: &mut VisualTestContext) -> Option<Pixels> {
view.read_with(cx, |view, _| view.heard.last().copied())
}
/// A column before the filling one drags by its right border, one after it by its left.
#[gpui::test]
fn a_border_toward_the_filling_column_drags_its_width(cx: &mut TestAppContext) {
cx.update(Theme::init);
let (view, cx) = sided(Platform::Linux, [px(200.0), px(200.0)], cx);
let left = cx.debug_bounds("column-shell-border-0").expect("a border");
let right = cx.debug_bounds("column-shell-border-2").expect("a border");
assert_eq!(left.right(), column(0, cx).right());
assert_eq!(right.left(), column(2, cx).left());
drag(left.center(), px(50.0), cx);
assert_eq!(heard(&view, cx), Some(px(250.0)));
assert_eq!(column(0, cx).size.width, px(250.0));
drag(right.center(), px(-50.0), cx);
assert_eq!(heard(&view, cx), Some(px(250.0)));
let (main, panel) = (column(1, cx), column(2, cx));
assert_eq!((panel.size.width, panel.right()), (px(250.0), px(1000.0)));
assert_eq!((main.left(), main.right()), (px(250.0), px(750.0)));
}
/// A drag stops at the column's least and greatest width.
#[gpui::test]
fn a_border_drag_keeps_within_the_limits(cx: &mut TestAppContext) {
cx.update(Theme::init);
let (view, cx) = sided(Platform::Linux, [px(200.0), px(200.0)], cx);
let border = |cx: &mut VisualTestContext| {
cx.debug_bounds("column-shell-border-0")
.expect("a border")
.center()
};
drag(border(cx), px(900.0), cx);
assert_eq!(heard(&view, cx), Some(px(400.0)));
drag(border(cx), px(-900.0), cx);
assert_eq!(heard(&view, cx), Some(px(100.0)));
}
/// In a header the border takes the press, so no system's drag moves the window, which panics here.
#[gpui::test]
fn a_border_drag_in_a_header_moves_no_window(cx: &mut TestAppContext) {
cx.update(Theme::init);
for platform in [Platform::Windows, Platform::Mac, Platform::Linux] {
let (view, cx) = sided(platform, [px(200.0), px(200.0)], cx);
for (ix, by) in [(0, px(30.0)), (2, px(-30.0))] {
let border = cx
.debug_bounds(["column-shell-border-0", "", "column-shell-border-2"][ix])
.expect("a border");
let header = column(ix, cx).top() + px(4.0);
drag(point(border.center().x, header), by, cx);
assert_eq!(heard(&view, cx), Some(px(230.0)), "{platform:?}");
}
}
}
/// A header's own parts hold a column past the owner's width; a drag sets off from the drawn one.
#[gpui::test]
fn a_border_drag_sets_off_from_the_drawn_width(cx: &mut TestAppContext) {
cx.update(Theme::init);
let (view, cx) = sided(Platform::Windows, [px(200.0), px(100.0)], cx);
let (panel, close) = (column(2, cx), cx.debug_bounds("caption-Close"));
let close = close.expect("a close button");
assert!(close.right() <= panel.right(), "{close:?} in {panel:?}");
let drawn = panel.size.width;
assert!(drawn > px(100.0), "{drawn:?} holds the caption buttons");
let border = cx.debug_bounds("column-shell-border-2").expect("a border");
drag(border.center(), px(-10.0), cx);
assert_eq!(heard(&view, cx), Some(drawn + px(10.0)));
}
/// Two set columns and nothing that fills.
struct Unfilled;
impl Render for Unfilled {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
let column = |id: &'static str| ShellColumn::new(id).width(px(200.0));
ColumnShell::new("shell")
.platform(Platform::Linux)
.column(column("first"))
.column(column("last").resizable(px(200.0), px(100.0), px(400.0), |_, _, _| {}))
}
}
/// The last column of a shell that nothing fills has no border to drag.
#[gpui::test]
#[should_panic(expected = "no border lies toward a filling column")]
fn a_resizing_column_needs_a_border_toward_a_filling_one(cx: &mut TestAppContext) {
cx.update(Theme::init);
let (_, cx) = cx.add_window_view(|_, _| Unfilled);
cx.run_until_parked();
}