From 0b4848bb2aadfc00f21eccdef94e306f8fbc61df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 16 Oct 2026 20:57:32 -0400 Subject: [PATCH] Column shell: a drag sets off from the wider of the width drawn and the owner's, so a column a short shell squeezes moves with the pointer, where it jumped back against it, and a drag with no sideways move changes no width, where it wrote the squeezed one --- AGENTS.md | 2 +- src/shell/columns.rs | 10 ++++++++-- src/shell/tests/border.rs | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1680e5d..d19e998 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -221,7 +221,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark. - `typography::Ellipsis` shapes its line with the text style's own run, so a strike or an underline set on its box shows (`lists::ListItem::struck`, a closed task's title). - Focus that must follow an item to another parent is taken by the owner and keyed by the item: a handle gpui makes for an element dies with its path (`project::KanbanBoard`, whose cards keep focus across columns). - The theme's `platform`, the system's to start, decides the window buttons Ely draws, the title bar's layout and key caps, `secondary` included (`typography::keys::keystroke`). A title bar whose buttons the OS draws follows the OS. -- A window whose sidebar runs to its top is `shell::ColumnShell`: each column's header drags the window; the first leaves the traffic lights room, and drawn controls lead the first header in the macOS manner or end the last. A set width is a flex basis, not a width, so it holds its header's own parts, which a width let run past it, and yields when the shell runs short; the host's header items keep one control's width and scroll when short. A resizable column drags by its border toward the filling column, its right before it and its left after it; the handle draws that border and occludes the header under it, and a drag sets off from the width drawn, not the owner's. +- A window whose sidebar runs to its top is `shell::ColumnShell`: each column's header drags the window; the first leaves the traffic lights room, and drawn controls lead the first header in the macOS manner or end the last. A set width is a flex basis, not a width, so it holds its header's own parts, which a width let run past it, and yields when the shell runs short; the host's header items keep one control's width and scroll when short. A resizable column drags by its border toward the filling column, its right before it and its left after it; the handle draws that border and occludes the header under it, and a drag sets off from the wider of the width drawn and the owner's, since a header's parts may hold a column wider and a short shell squeeze it narrower; a drag that has not moved sideways changes nothing. - Taffy counts a flex item's text in its least width whatever width the item sets. A box that must add none lays its content absolutely inside a filling box (`shell::ColumnShell`'s title and body). - A centered title sits between a start and an end that grow alike from a zero basis, their padding on a box inside them, since taffy counts padding in the flex base; their content is their least width, so the title gives way before either end (`shell::TitleBar`). - A pane the window's blur shows through is `shell::Vibrancy`, filled with the palette's `glass`, in a window opened `WindowBackgroundAppearance::Blurred`: vibrancy on macOS, acrylic on Windows. gpui's `MicaBackdrop` and `MicaAltBackdrop` are DWM backdrops from Windows 11 build 22621; older builds get none, in silence, and macOS plain transparency. diff --git a/src/shell/columns.rs b/src/shell/columns.rs index e036ae4..1a02d9d 100644 --- a/src/shell/columns.rs +++ b/src/shell/columns.rs @@ -323,10 +323,16 @@ fn resizing( log::error!("column shell: a drag on column {ix} with no press behind it"); return; }; - // The drawn width, which a header's own parts may hold past the owner's. - let start = drag.start.get().unwrap_or(event.bounds.size.width); + // A header's parts may hold a column wider, a short shell squeeze it narrower. + let start = drag + .start + .get() + .unwrap_or(event.bounds.size.width.max(width)); drag.start.set(Some(start)); let moved = event.event.position.x - anchor; + if moved == Pixels::ZERO { + return; + } let next = match drag.side { Side::Left => start - moved, Side::Right => start + moved, diff --git a/src/shell/tests/border.rs b/src/shell/tests/border.rs index 9c54e2a..b5f50a7 100644 --- a/src/shell/tests/border.rs +++ b/src/shell/tests/border.rs @@ -9,6 +9,7 @@ use crate::theme::{Platform, Theme}; /// Two set columns round a filling one; this view keeps their widths. struct Sided { platform: Platform, + width: Pixels, widths: [Pixels; 2], heard: Vec, } @@ -31,7 +32,7 @@ impl Render for Sided { }, ) }; - div().w(px(1000.0)).h(px(400.0)).flex().flex_col().child( + div().w(self.width).h(px(400.0)).flex().flex_col().child( ColumnShell::new("shell") .platform(self.platform) .column(side("left", 0)) @@ -48,6 +49,7 @@ fn sided( ) -> (Entity, &mut VisualTestContext) { let (view, cx) = cx.add_window_view(|_, _| Sided { platform, + width: px(1000.0), widths, heard: Vec::new(), }); @@ -166,3 +168,37 @@ fn a_resizing_column_needs_a_border_toward_a_filling_one(cx: &mut TestAppContext let (_, cx) = cx.add_window_view(|_, _| Unfilled); cx.run_until_parked(); } + +/// A short shell squeezes both columns; a drag moves a border with the pointer, from the owner's width. +#[gpui::test] +fn a_squeezed_column_drags_from_the_owner_s_width(cx: &mut TestAppContext) { + cx.update(Theme::init); + let (view, cx) = cx.add_window_view(|_, _| Sided { + platform: Platform::Linux, + width: px(700.0), + widths: [px(350.0), px(350.0)], + heard: Vec::new(), + }); + cx.run_until_parked(); + let left = column(0, cx); + assert!(left.size.width < px(350.0), "{left:?} is squeezed"); + let border = |ix: usize, cx: &mut VisualTestContext| { + let name = ["column-shell-border-0", "", "column-shell-border-2"][ix]; + cx.debug_bounds(name).expect("a border").center() + }; + drag(border(0, cx), px(0.0), cx); + assert_eq!(heard(&view, cx), None, "a drag with no sideways move"); + drag(border(0, cx), px(20.0), cx); + assert_eq!(heard(&view, cx), Some(px(370.0))); + assert!( + column(0, cx).right() > left.right(), + "the left border moves right" + ); + let right = column(2, cx); + drag(border(2, cx), px(-20.0), cx); + assert_eq!(heard(&view, cx), Some(px(370.0))); + assert!( + column(2, cx).left() < right.left(), + "the right border moves left" + ); +}