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(..)
```
This commit is contained in:
Jason Lee 2025-10-29 23:01:05 +08:00 committed by GitHub
parent a1ca8624af
commit 2727a65494
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 163 additions and 277 deletions

View file

@ -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<InputState>,
tree_state: Entity<TreeState>,
panels_state: Entity<ResizableState>,
go_to_line_state: Entity<InputState>,
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(

View file

@ -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<InputState>,
resizable_state: Entity<ResizableState>,
_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<Self>) -> 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(),
)
}
}

View file

@ -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<InputState>,
resizable_state: Entity<ResizableState>,
_subscriptions: Vec<Subscription>,
}
@ -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()

View file

@ -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<usize>,
collapsed: bool,
search_input: Entity<InputState>,
sidebar_state: Entity<ResizableState>,
_subscriptions: Vec<Subscription>,
}
@ -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.))

View file

@ -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<ResizableState>,
state2: Entity<ResizableState>,
state3: Entity<ResizableState>,
}
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<SharedString>, _: &App) -> AnyElement {
}
impl Render for ResizableStory {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> 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)),
),
)
}

View file

@ -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>) -> 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()

View file

@ -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<ElementId>, state: Entity<ResizableState>) -> ResizablePanelGroup {
ResizablePanelGroup::new(id, state).axis(Axis::Horizontal)
pub fn h_resizable(id: impl Into<ElementId>) -> ResizablePanelGroup {
ResizablePanelGroup::new(id).axis(Axis::Horizontal)
}
/// Create a [`ResizablePanelGroup`] with vertical resizing
pub fn v_resizable(id: impl Into<ElementId>, state: Entity<ResizableState>) -> ResizablePanelGroup {
ResizablePanelGroup::new(id, state).axis(Axis::Vertical)
pub fn v_resizable(id: impl Into<ElementId>) -> 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<Pixels>,
}
impl ResizableState {
pub fn new(cx: &mut App) -> Entity<Self> {
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<Pixels> {
&self.sizes
}
pub fn insert_panel(
pub(crate) fn insert_panel(
&mut self,
size: Option<Pixels>,
ix: Option<usize>,
@ -126,11 +130,6 @@ impl ResizableState {
self.sizes.clear();
}
/// Get the size of the panels.
pub fn sizes(&self) -> &Vec<Pixels> {
&self.sizes
}
pub(crate) fn total_size(&self) -> Pixels {
self.sizes.iter().map(|s| s.as_f32()).sum::<f32>().into()
}

View file

@ -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<ResizableState>,
state: Option<Entity<ResizableState>>,
axis: Axis,
size: Option<Pixels>,
children: Vec<ResizablePanel>,
on_resize: Rc<dyn Fn(&Entity<ResizableState>, &mut Window, &mut App)>,
}
impl ResizablePanelGroup {
pub(crate) fn new(id: impl Into<ElementId>, state: Entity<ResizableState>) -> Self {
/// Create a new resizable panel group.
pub fn new(id: impl Into<ElementId>) -> 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<ResizableState>) -> 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<ResizableState>: The state of the ResizablePanelGroup.
pub fn on_resize(
mut self,
on_resize: impl Fn(&Entity<ResizableState>, &mut Window, &mut App) + 'static,
) -> Self {
self.on_resize = Rc::new(on_resize);
self
}
}
impl<T> From<T> for ResizablePanel
where
@ -90,11 +110,19 @@ where
}
}
impl From<ResizablePanelGroup> for ResizablePanel {
fn from(value: ResizablePanelGroup) -> Self {
resizable_panel().child(value)
}
}
impl EventEmitter<ResizablePanelEvent> 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<ResizableState>,
on_resize: Rc<dyn Fn(&Entity<ResizableState>, &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);
}
}
})

View file

@ -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<ResizableState>,
}
impl MyView {
fn new(cx: &mut Context<Self>) -> 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<Self>) {
// 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<Pixels>` |
### 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<ResizableState>,
show_sidebar: bool,
}
impl Render for FileExplorer {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> 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()
)
}
}