Skip to content

resizable: Let an application build its own resize handle - #3302

Draft
ihavecoke wants to merge 1 commit into
mainfrom
ihavecoke/public-resize-handle
Draft

ihavecoke wants to merge 1 commit into
mainfrom
ihavecoke/public-resize-handle

Conversation

@ihavecoke

Copy link
Copy Markdown
Member

Description

resize_handle_appearance() says it is there for "a hand-rolled handle in an application", but an application could not build one. resize_handle was #[doc(hidden)], generic over its drag payload, and took no listener beyond its own on_drag; ResizeHandleContext has no public constructor, so the appearance could not be put on anything else either.

This makes the handle a public, standalone part, the way SliderThumb is:

  • resize_handle(id, axis) is public in gpui-base. gpui_component::resize_handle installs the divider appearance, as h_resizable does.
  • ResizeHandle implements InteractiveElement, StatefulInteractiveElement and InteractiveElementExt. The drag goes through the standard on_drag / on_drag_move, and on_hover, on_double_click and the rest attach to the band itself. Base still owns the band, the cursor, inside(HandleEdge) and the Idle → Hovered → Pressed → Dragging progression the appearance reads.
  • The band carries test_support(), so window.find(id) finds a handle.
  • The hairline's color is a theme color, resizable.border (ThemeColor::resizable_border), falling back to border; the Base projection ResizableTheme::handle reads it. Left unset, nothing changes on screen.
  • Resizable and the Dock move to the new builder, one call each.

Why: an application sidebar (Allsum) has a hand-written edge handle with the same three-level indicator — the same 20 / 28 / 44 px at 0.35 / 0.6 / 0.9. It needs a double click that restores the default width, a hover its hover-to-peek drawer counts as its own, and a lighter divider than border. h_resizable itself does not fit a collapsible fixed-width sidebar (it keeps panels proportional as the container resizes, and hides a panel without a transition), so the handle is the part it can share.

Public API

gpui-base

  • pub fn resize_handle(id: impl Into<ElementId>, axis: Axis) -> ResizeHandle — no longer hidden; a band that resizes whatever it edges.
  • pub struct ResizeHandle — now public and no longer generic.
  • impl InteractiveElement for ResizeHandle, impl StatefulInteractiveElement for ResizeHandle, impl InteractiveElementExt for ResizeHandle — listeners attach to the band.

gpui-component

  • pub fn resize_handle(id: impl Into<ElementId>, axis: Axis) -> ResizeHandle — the Base handle with resize_handle_appearance() installed.
  • resizable::{HandleEdge, ResizeHandle} — re-exported.
  • ThemeColor::resizable_border: Hsla — the divider's hairline; theme key resizable.border, optional, falls back to border.

Breaking Changes

Only the builder that was hidden:

- resize_handle::<DragPanel, DragPanel>(id, axis)
-     .on_drag(DragPanel, |drag: Rc<DragPanel>, _position, _window, cx| cx.new(|_| (*drag).clone()))
+ resize_handle(id, axis)
+     .on_drag(DragPanel, |drag: &DragPanel, _offset, _window, cx| cx.new(|_| drag.clone()))

How to Test

  1. cargo test -p gpui-base --lib resizable — a_callers_listeners_reach_the_band hovers, double clicks and drags a standalone handle, and checks the container stops reading as hovered (the band occludes), the double click fires once, a drag reports moves without clicking, and the appearance still sees Idle → Hovered → Pressed → Dragging → Idle.
  2. cargo test -p gpui-component --lib -- theme:: resizable dock — test_apply_config_reads_the_resizable_border covers an explicit resizable.border and the fallback; the projection test reads the new color.
  3. cargo run -- resizable / cargo run -- dock: dividers look and behave as before.

Checklist

  • I have read the CONTRIBUTING document and followed the guidelines.
  • Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate.
  • Passed cargo run for story tests related to the changes.

🤖 Generated with Claude Code

`resize_handle_appearance()` is documented as being for a hand-rolled
handle in an application, but the handle it draws in was hidden, generic
over its drag payload and took no listener but its own `on_drag`.

`resize_handle` is now a public, standalone part the way `SliderThumb`
is: `ResizeHandle` implements `InteractiveElement`,
`StatefulInteractiveElement` and `InteractiveElementExt`, so the drag and
any other listener attach to the band itself. `gpui-component` adds a
`resize_handle` that installs the divider appearance, and the hairline
reads a new `resizable.border` theme color that falls back to `border`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ihavecoke
ihavecoke marked this pull request as draft September 28, 2026 14:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant