From 2727a6549407140abf301ff213272f0de6a6b5c0 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Wed, 29 Oct 2025 23:01:05 +0800 Subject: [PATCH] resizable: Simplify Resizable API. (#1459) - Added `on_resize` callback method to `ResizabelPanelGroup`. ## Break Changes - Remove `state` argument from `h_resizable`, `v_resizable` method. ```diff - v_resizable("resizable-1", state) + v_resizable("resizable-1") ``` - Removed `group` method from `ResizabelPanelGroup`, use `child` instead. ```diff - v_resizable(..).group(..) + v_resizable(..).child(..) ``` --- crates/story/examples/editor.rs | 25 ++-- crates/story/examples/html.rs | 54 ++++---- crates/story/examples/markdown.rs | 7 +- crates/story/src/main.rs | 6 +- crates/story/src/resizable_story.rs | 38 +++--- crates/ui/src/dock/stack_panel.rs | 10 +- crates/ui/src/resizable/mod.rs | 37 +++--- crates/ui/src/resizable/panel.rs | 67 +++++++--- docs/docs/components/resizable.md | 196 +++++----------------------- 9 files changed, 163 insertions(+), 277 deletions(-) diff --git a/crates/story/examples/editor.rs b/crates/story/examples/editor.rs index d61caeeb..afa40a44 100644 --- a/crates/story/examples/editor.rs +++ b/crates/story/examples/editor.rs @@ -18,7 +18,7 @@ use gpui_component::{ self, CodeActionProvider, CompletionProvider, DefinitionProvider, DocumentColorProvider, HoverProvider, Input, InputEvent, InputState, Position, Rope, RopeExt, TabSize, }, - resizable::{ResizableState, h_resizable, resizable_panel}, + resizable::{h_resizable, resizable_panel}, tree, v_flex, }; use lsp_types::{ @@ -44,7 +44,6 @@ fn init() { pub struct Example { editor: Entity, tree_state: Entity, - panels_state: Entity, go_to_line_state: Entity, language: Language, line_number: bool, @@ -662,12 +661,9 @@ impl Example { this.lint_document(cx); })]; - let panels_state = ResizableState::new(cx); - Self { editor, tree_state, - panels_state, go_to_line_state, language: default_language, line_number: true, @@ -993,22 +989,21 @@ impl Render for Example { .w_full() .flex_1() .child( - h_resizable("editor-container", self.panels_state.clone()) + h_resizable("editor-container") .child( resizable_panel() .size(px(240.)) .child(self.render_file_tree(window, cx)), ) .child( - resizable_panel().child( - Input::new(&self.editor) - .bordered(false) - .p_0() - .h_full() - .font_family("Monaco") - .text_size(px(12.)) - .focus_bordered(false), - ), + Input::new(&self.editor) + .bordered(false) + .p_0() + .h_full() + .font_family("Monaco") + .text_size(px(12.)) + .focus_bordered(false) + .into_any_element(), ), ) .child( diff --git a/crates/story/examples/html.rs b/crates/story/examples/html.rs index dbb1c9e1..81eb39c4 100644 --- a/crates/story/examples/html.rs +++ b/crates/story/examples/html.rs @@ -2,14 +2,13 @@ use gpui::*; use gpui_component::{ highlighter::Language, input::{Input, InputState, TabSize}, - resizable::{ResizableState, h_resizable, resizable_panel}, + resizable::h_resizable, text::TextView, }; use story::Assets; pub struct Example { input_state: Entity, - resizable_state: Entity, _subscribe: Subscription, } @@ -28,8 +27,6 @@ impl Example { .placeholder("Enter your HTML here...") }); - let resizable_state = ResizableState::new(cx); - let _subscribe = cx.subscribe( &input_state, |_, _, _: &gpui_component::input::InputEvent, cx| { @@ -39,7 +36,6 @@ impl Example { Self { input_state, - resizable_state, _subscribe, } } @@ -51,34 +47,32 @@ impl Example { impl Render for Example { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - h_resizable("container", self.resizable_state.clone()) + h_resizable("container") .child( - resizable_panel().child( - div() - .id("source") - .size_full() - .font_family("Menlo") - .text_size(px(13.)) - .child( - Input::new(&self.input_state) - .h_full() - .appearance(false) - .focus_bordered(false), - ), - ), + div() + .id("source") + .size_full() + .font_family("Menlo") + .text_size(px(13.)) + .child( + Input::new(&self.input_state) + .h_full() + .appearance(false) + .focus_bordered(false), + ) + .into_any(), ) .child( - resizable_panel().child( - TextView::html( - "preview", - self.input_state.read(cx).value().clone(), - window, - cx, - ) - .p_5() - .scrollable() - .selectable(), - ), + TextView::html( + "preview", + self.input_state.read(cx).value().clone(), + window, + cx, + ) + .p_5() + .scrollable() + .selectable() + .into_any(), ) } } diff --git a/crates/story/examples/markdown.rs b/crates/story/examples/markdown.rs index 9ddcffc4..a34329de 100644 --- a/crates/story/examples/markdown.rs +++ b/crates/story/examples/markdown.rs @@ -2,14 +2,13 @@ use gpui::*; use gpui_component::{ highlighter::Language, input::{Input, InputEvent, InputState, TabSize}, - resizable::{ResizableState, h_resizable, resizable_panel}, + resizable::{h_resizable, resizable_panel}, text::TextView, }; use story::{Assets, Open}; pub struct Example { input_state: Entity, - resizable_state: Entity, _subscriptions: Vec, } @@ -29,12 +28,10 @@ impl Example { .placeholder("Enter your Markdown here...") .default_value(EXAMPLE) }); - let resizable_state = ResizableState::new(cx); let _subscriptions = vec![cx.subscribe(&input_state, |_, _, _: &InputEvent, _| {})]; Self { - resizable_state, input_state, _subscriptions, } @@ -79,7 +76,7 @@ impl Render for Example { .size_full() .on_action(cx.listener(Self::on_action_open)) .child( - h_resizable("container", self.resizable_state.clone()) + h_resizable("container") .child( resizable_panel().child( div() diff --git a/crates/story/src/main.rs b/crates/story/src/main.rs index b1fcf7eb..84c8ac9f 100644 --- a/crates/story/src/main.rs +++ b/crates/story/src/main.rs @@ -2,7 +2,7 @@ use gpui::{prelude::*, *}; use gpui_component::{ ActiveTheme as _, Icon, IconName, h_flex, input::{Input, InputEvent, InputState}, - resizable::{ResizableState, h_resizable, resizable_panel}, + resizable::{h_resizable, resizable_panel}, sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem}, v_flex, }; @@ -14,7 +14,6 @@ pub struct Gallery { active_index: Option, collapsed: bool, search_input: Entity, - sidebar_state: Entity, _subscriptions: Vec, } @@ -91,7 +90,6 @@ impl Gallery { active_group_index: Some(0), active_index: Some(0), collapsed: false, - sidebar_state: ResizableState::new(cx), _subscriptions, }; @@ -148,7 +146,7 @@ impl Render for Gallery { ("".into(), "".into()) }; - h_resizable("gallery-container", self.sidebar_state.clone()) + h_resizable("gallery-container") .child( resizable_panel() .size(px(255.)) diff --git a/crates/story/src/resizable_story.rs b/crates/story/src/resizable_story.rs index a147f647..acd7bbca 100644 --- a/crates/story/src/resizable_story.rs +++ b/crates/story/src/resizable_story.rs @@ -1,17 +1,15 @@ use gpui::{ - div, px, AnyElement, App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, - ParentElement as _, Pixels, Render, SharedString, Styled, Window, + AnyElement, App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, + ParentElement as _, Pixels, Render, SharedString, Styled, Window, div, px, }; use gpui_component::{ - resizable::{h_resizable, resizable_panel, v_resizable, ResizableState}, - v_flex, ActiveTheme, + ActiveTheme, + resizable::{h_resizable, resizable_panel, v_resizable}, + v_flex, }; pub struct ResizableStory { focus_handle: FocusHandle, - state1: Entity, - state2: Entity, - state3: Entity, } impl super::Story for ResizableStory { @@ -40,15 +38,8 @@ impl ResizableStory { } fn new(_: &mut Window, cx: &mut App) -> Self { - let state1 = ResizableState::new(cx); - let state2 = ResizableState::new(cx); - let state3 = ResizableState::new(cx); - Self { focus_handle: cx.focus_handle(), - state1, - state2, - state3, } } } @@ -62,7 +53,7 @@ fn panel_box(content: impl Into, _: &App) -> AnyElement { } impl Render for ResizableStory { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { v_flex() .size_full() .gap_6() @@ -72,9 +63,12 @@ impl Render for ResizableStory { .border_1() .border_color(cx.theme().border) .child( - v_resizable("resizable-1", self.state1.clone()) - .group( - h_resizable("resizable-1.1", self.state2.clone()) + v_resizable("resizable-1") + .on_resize(|state, _, cx| { + println!("Resized: {:?}", state.read(cx).sizes()); + }) + .child( + h_resizable("resizable-1.1") .size(px(150.)) .child( resizable_panel() @@ -82,14 +76,14 @@ impl Render for ResizableStory { .size_range(px(120.)..px(300.)) .child(panel_box("Left (120px .. 300px)", cx)), ) - .child(resizable_panel().child(panel_box("Center", cx))) + .child(panel_box("Center", cx)) .child( resizable_panel() .size(px(300.)) .child(panel_box("Right", cx)), ), ) - .child(resizable_panel().child(panel_box("Center", cx))) + .child(panel_box("Center", cx)) .child( resizable_panel() .size(px(80.)) @@ -104,14 +98,14 @@ impl Render for ResizableStory { .border_1() .border_color(cx.theme().border) .child( - h_resizable("resizable-3", self.state3.clone()) + h_resizable("resizable-3") .child( resizable_panel() .size(px(200.)) .size_range(px(200.)..px(400.)) .child(panel_box("Left 2", cx)), ) - .child(resizable_panel().child(panel_box("Right (Grow)", cx))), + .child(panel_box("Right (Grow)", cx)), ), ) } diff --git a/crates/ui/src/dock/stack_panel.rs b/crates/ui/src/dock/stack_panel.rs index 6c107b41..870ae2e0 100644 --- a/crates/ui/src/dock/stack_panel.rs +++ b/crates/ui/src/dock/stack_panel.rs @@ -12,8 +12,9 @@ use crate::{ use super::{DockArea, Panel, PanelEvent, PanelState, PanelView, TabPanel}; use gpui::{ - App, Axis, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, - ParentElement, Pixels, Render, Styled, Subscription, WeakEntity, Window, + App, AppContext as _, Axis, Context, DismissEvent, Entity, EventEmitter, FocusHandle, + Focusable, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, WeakEntity, + Window, }; use smallvec::SmallVec; @@ -53,7 +54,7 @@ impl Panel for StackPanel { impl StackPanel { pub fn new(axis: Axis, _: &mut Window, cx: &mut Context) -> Self { - let state = ResizableState::new(cx); + let state = cx.new(|_| ResizableState::default()); // Bubble up the resize event. let _subscriptions = vec![cx.subscribe(&state, |_, _, _: &ResizablePanelEvent, cx| { @@ -417,7 +418,8 @@ impl Render for StackPanel { .overflow_hidden() .bg(cx.theme().tab_bar) .child( - ResizablePanelGroup::new("stack-panel-group", self.state.clone()) + ResizablePanelGroup::new("stack-panel-group") + .with_state(&self.state) .axis(self.axis) .children(self.panels.clone().into_iter().map(|panel| { resizable_panel() diff --git a/crates/ui/src/resizable/mod.rs b/crates/ui/src/resizable/mod.rs index 7f98b7c8..3d6eb65a 100644 --- a/crates/ui/src/resizable/mod.rs +++ b/crates/ui/src/resizable/mod.rs @@ -1,9 +1,6 @@ use std::ops::Range; -use gpui::{ - px, Along, App, AppContext, Axis, Bounds, Context, ElementId, Entity, EventEmitter, Pixels, - Window, -}; +use gpui::{px, Along, App, Axis, Bounds, Context, ElementId, EventEmitter, Pixels, Window}; use crate::PixelsExt; @@ -15,13 +12,13 @@ pub(crate) use resize_handle::*; pub(crate) const PANEL_MIN_SIZE: Pixels = px(100.); /// Create a [`ResizablePanelGroup`] with horizontal resizing -pub fn h_resizable(id: impl Into, state: Entity) -> ResizablePanelGroup { - ResizablePanelGroup::new(id, state).axis(Axis::Horizontal) +pub fn h_resizable(id: impl Into) -> ResizablePanelGroup { + ResizablePanelGroup::new(id).axis(Axis::Horizontal) } /// Create a [`ResizablePanelGroup`] with vertical resizing -pub fn v_resizable(id: impl Into, state: Entity) -> ResizablePanelGroup { - ResizablePanelGroup::new(id, state).axis(Axis::Vertical) +pub fn v_resizable(id: impl Into) -> ResizablePanelGroup { + ResizablePanelGroup::new(id).axis(Axis::Vertical) } /// Create a [`ResizablePanel`]. @@ -29,8 +26,8 @@ pub fn resizable_panel() -> ResizablePanel { ResizablePanel::new() } -#[derive(Debug, Clone)] /// State for a [`ResizablePanel`] +#[derive(Debug, Clone)] pub struct ResizableState { /// The `axis` will sync to actual axis of the ResizablePanelGroup in use. axis: Axis, @@ -40,18 +37,25 @@ pub struct ResizableState { bounds: Bounds, } -impl ResizableState { - pub fn new(cx: &mut App) -> Entity { - cx.new(|_| Self { +impl Default for ResizableState { + fn default() -> Self { + Self { axis: Axis::Horizontal, panels: vec![], sizes: vec![], resizing_panel_ix: None, bounds: Bounds::default(), - }) + } + } +} + +impl ResizableState { + /// Get the size of the panels. + pub fn sizes(&self) -> &Vec { + &self.sizes } - pub fn insert_panel( + pub(crate) fn insert_panel( &mut self, size: Option, ix: Option, @@ -126,11 +130,6 @@ impl ResizableState { self.sizes.clear(); } - /// Get the size of the panels. - pub fn sizes(&self) -> &Vec { - &self.sizes - } - pub(crate) fn total_size(&self) -> Pixels { self.sizes.iter().map(|s| s.as_f32()).sum::().into() } diff --git a/crates/ui/src/resizable/panel.rs b/crates/ui/src/resizable/panel.rs index ffe8b713..f711ed1e 100644 --- a/crates/ui/src/resizable/panel.rs +++ b/crates/ui/src/resizable/panel.rs @@ -1,4 +1,7 @@ -use std::ops::{Deref, Range}; +use std::{ + ops::{Deref, Range}, + rc::Rc, +}; use gpui::{ canvas, div, prelude::FluentBuilder, AnyElement, App, AppContext, Axis, Bounds, Context, @@ -26,23 +29,32 @@ impl Render for DragPanel { #[derive(IntoElement)] pub struct ResizablePanelGroup { id: ElementId, - state: Entity, + state: Option>, axis: Axis, size: Option, children: Vec, + on_resize: Rc, &mut Window, &mut App)>, } impl ResizablePanelGroup { - pub(crate) fn new(id: impl Into, state: Entity) -> Self { + /// Create a new resizable panel group. + pub fn new(id: impl Into) -> Self { Self { id: id.into(), axis: Axis::Horizontal, children: vec![], - state, + state: None, size: None, + on_resize: Rc::new(|_, _, _| {}), } } + /// Bind yourself to a resizable state entity. + pub fn with_state(mut self, state: &Entity) -> Self { + self.state = Some(state.clone()); + self + } + /// Set the axis of the resizable panel group, default is horizontal. pub fn axis(mut self, axis: Axis) -> Self { self.axis = axis; @@ -67,11 +79,6 @@ impl ResizablePanelGroup { self } - /// Add a ResizablePanelGroup as a child to the group. - pub fn group(self, group: ResizablePanelGroup) -> Self { - self.child(resizable_panel().child(group.into_any_element())) - } - /// Set size of the resizable panel group /// /// - When the axis is horizontal, the size is the height of the group. @@ -80,6 +87,19 @@ impl ResizablePanelGroup { self.size = Some(size); self } + + /// Set the callback to be called when the panels are resized. + /// + /// ## Callback arguments + /// + /// - Entity: The state of the ResizablePanelGroup. + pub fn on_resize( + mut self, + on_resize: impl Fn(&Entity, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_resize = Rc::new(on_resize); + self + } } impl From for ResizablePanel where @@ -90,11 +110,19 @@ where } } +impl From for ResizablePanel { + fn from(value: ResizablePanelGroup) -> Self { + resizable_panel().child(value) + } +} + impl EventEmitter for ResizablePanelGroup {} impl RenderOnce for ResizablePanelGroup { - fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { - let state = self.state.clone(); + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + let state = self.state.unwrap_or( + window.use_keyed_state(self.id.clone(), cx, |_, _| ResizableState::default()), + ); let container = if self.axis.is_horizontal() { h_flex() } else { @@ -103,7 +131,7 @@ impl RenderOnce for ResizablePanelGroup { // Sync panels to the state let panels_count = self.children.len(); - self.state.update(cx, |state, _| { + state.update(cx, |state, _| { state.sync_panels_count(self.axis, panels_count); }); @@ -117,21 +145,25 @@ impl RenderOnce for ResizablePanelGroup { .map(|(ix, mut panel)| { panel.panel_ix = ix; panel.axis = self.axis; - panel.state = Some(self.state.clone()); + panel.state = Some(state.clone()); panel }), ) .child({ canvas( - move |bounds, _, cx| state.update(cx, |state, _| state.bounds = bounds), + { + let state = state.clone(); + move |bounds, _, cx| state.update(cx, |state, _| state.bounds = bounds) + }, |_, _, _, _| {}, ) .absolute() .size_full() }) .child(ResizePanelGroupElement { - state: self.state.clone(), + state: state.clone(), axis: self.axis, + on_resize: self.on_resize.clone(), }) } } @@ -267,6 +299,7 @@ impl RenderOnce for ResizablePanel { struct ResizePanelGroupElement { state: Entity, + on_resize: Rc, &mut Window, &mut App)>, axis: Axis, } @@ -352,12 +385,14 @@ impl Element for ResizePanelGroupElement { window.on_mouse_event({ let state = self.state.clone(); let current_ix = state.read(cx).resizing_panel_ix; - move |_: &MouseUpEvent, phase, _, cx| { + let on_resize = self.on_resize.clone(); + move |_: &MouseUpEvent, phase, window, cx| { if current_ix.is_none() { return; } if phase.bubble() { state.update(cx, |state, cx| state.done_resizing(cx)); + on_resize(&state, window, cx); } } }) diff --git a/docs/docs/components/resizable.md b/docs/docs/components/resizable.md index 4f951d81..ac8a7088 100644 --- a/docs/docs/components/resizable.md +++ b/docs/docs/components/resizable.md @@ -18,37 +18,49 @@ use gpui_component::resizable::{ ## Usage -### Basic Horizontal Layout +Use `h_resizable` to create a horizontal layout, `v_resizable` to create a vertical layout. + +The first argument is the `id` for this [ResizablePanelGroup]. + +:::tip +In GPUI, the `id` must be unique within the layout scope (The nearest parent has presents `id`). +::: ```rust -let state = ResizableState::new(cx); - -h_resizable("my-layout", state) +h_resizable("my-layout") + .on_resize(|state, window, cx| { + // Handle resize event + // You can read the panel sizes from the state. + let state = state.read(cx); + let sizes = state.sizes(); + }) .child( + // Use resizable_panel() to create a sized panel. resizable_panel() .size(px(200.)) .child("Left Panel") ) .child( - resizable_panel() + // Or you can just add AnyElement without a size. + div() .child("Right Panel") + .into_any_element() ) ``` -### Basic Vertical Layout +The `v_resizable` component is used to create a vertical layout. ```rust -let state = ResizableState::new(cx); - -v_resizable("vertical-layout", state) +v_resizable("vertical-layout") .child( resizable_panel() .size(px(100.)) .child("Top Panel") ) .child( - resizable_panel() + div() .child("Bottom Panel") + .into_any_element() ) ``` @@ -85,15 +97,12 @@ h_resizable("multi-panel", state) ### Nested Layouts ```rust -let main_state = ResizableState::new(cx); -let nested_state = ResizableState::new(cx); - -v_resizable("main-layout", main_state) +v_resizable("main-layout", window, cx) .child( resizable_panel() .size(px(300.)) .child( - h_resizable("nested-layout", nested_state) + h_resizable("nested-layout", window, cx) .child( resizable_panel() .size(px(200.)) @@ -114,17 +123,14 @@ v_resizable("main-layout", main_state) ### Nested Panel Groups ```rust -let outer_state = ResizableState::new(cx); -let inner_state = ResizableState::new(cx); - -h_resizable("outer", outer_state) +h_resizable("outer", window, cx) .child( resizable_panel() .size(px(200.)) .child("Left Panel") ) .group( - v_resizable("inner", inner_state) + v_resizable("inner", window, cx) .child( resizable_panel() .size(px(150.)) @@ -137,42 +143,6 @@ h_resizable("outer", outer_state) ) ``` -### Handling Resize Events - -```rust -struct MyView { - resizable_state: Entity, -} - -impl MyView { - fn new(cx: &mut Context) -> Self { - let resizable_state = ResizableState::new(cx); - - // Subscribe to resize events - let subscription = cx.subscribe(&resizable_state, |this, _, event: &ResizablePanelEvent, cx| { - match event { - ResizablePanelEvent::Resized => { - // Handle resize completion - println!("Panel resized!"); - this.handle_resize_complete(cx); - } - } - }); - - Self { - resizable_state, - } - } - - fn handle_resize_complete(&mut self, cx: &mut Context) { - // Access current panel sizes - let sizes = self.resizable_state.read(cx).sizes(); - println!("Current panel sizes: {:?}", sizes); - cx.notify(); - } -} -``` - ### Conditional Panel Visibility ```rust @@ -202,114 +172,18 @@ resizable_panel() .child("Fixed Panel") ``` -## API Reference - -### ResizableState - -| Method | Description | -| --------- | ----------------------------------------- | -| `new(cx)` | Create a new resizable state entity | -| `sizes()` | Get current panel sizes as `&Vec` | - -### Resizable Panel Group Functions - -| Function | Description | -| ------------------------ | --------------------------------------- | -| `h_resizable(id, state)` | Create horizontal resizable panel group | -| `v_resizable(id, state)` | Create vertical resizable panel group | -| `resizable_panel()` | Create a new resizable panel | - -### ResizablePanelGroup - -| Method | Description | -| ------------------ | ----------------------------------------- | -| `new(id, state)` | Create a new panel group | -| `axis(axis)` | Set resize axis (Horizontal/Vertical) | -| `child(panel)` | Add a resizable panel to the group | -| `children(panels)` | Add multiple panels at once | -| `group(group)` | Add another panel group as a nested child | -| `size(size)` | Set the size of the group container | - -### ResizablePanel - -| Method | Description | -| ------------------- | ------------------------------- | -| `new()` | Create a new resizable panel | -| `child(element)` | Add child element to the panel | -| `size(pixels)` | Set initial panel size | -| `size_range(range)` | Set size constraints (min..max) | -| `visible(bool)` | Control panel visibility | - -### Size Constraints - -| Constraint | Description | -| ----------------------------- | ------------------------------- | -| `px(100.)..px(400.)` | Panel can be 100px to 400px | -| `px(150.)..Pixels::MAX` | Panel minimum 150px, no maximum | -| `PANEL_MIN_SIZE..Pixels::MAX` | Default constraints | - -### ResizablePanelEvent - -| Event | Description | -| --------- | -------------------------------------- | -| `Resized` | Emitted when a panel finishes resizing | - -## Drag Handles - -Resize handles are automatically created between panels: - -- **Horizontal layouts**: Vertical drag handles between panels -- **Vertical layouts**: Horizontal drag handles between panels -- **Visual feedback**: Handles show hover and active states -- **Cursor changes**: Appropriate resize cursors on hover -- **Handle size**: 1px wide with 4px padding for easier interaction - -### Handle Behavior - -- Handles appear between adjacent panels -- Dragging adjusts sizes of neighboring panels -- Panels respect their size constraints during resize -- Overflow is handled by adjusting panel sizes proportionally - -## Direction Support - -### Horizontal Resizing - -```rust -h_resizable("horizontal", state) - .child(resizable_panel().child("Left")) - .child(resizable_panel().child("Right")) -``` - -- Panels are arranged side by side -- Vertical drag handles between panels -- Resize by dragging left/right - -### Vertical Resizing - -```rust -v_resizable("vertical", state) - .child(resizable_panel().child("Top")) - .child(resizable_panel().child("Bottom")) -``` - -- Panels are stacked vertically -- Horizontal drag handles between panels -- Resize by dragging up/down - ## Examples ### File Explorer Layout ```rust struct FileExplorer { - layout_state: Entity, show_sidebar: bool, } impl Render for FileExplorer { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - h_resizable("file-explorer", self.layout_state.clone()) + h_resizable("file-explorer", window, cx) .child( resizable_panel() .visible(self.show_sidebar) @@ -325,15 +199,13 @@ impl Render for FileExplorer { ) ) .child( - resizable_panel() - .child( - v_flex() - .p_4() - .child("📄 Files") - .child("file1.txt") - .child("file2.pdf") - .child("image.png") - ) + v_flex() + .p_4() + .child("📄 Files") + .child("file1.txt") + .child("file2.pdf") + .child("image.png") + .into_any_element() ) } }