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:
parent
a1ca8624af
commit
2727a65494
9 changed files with 163 additions and 277 deletions
|
|
@ -18,7 +18,7 @@ use gpui_component::{
|
||||||
self, CodeActionProvider, CompletionProvider, DefinitionProvider, DocumentColorProvider,
|
self, CodeActionProvider, CompletionProvider, DefinitionProvider, DocumentColorProvider,
|
||||||
HoverProvider, Input, InputEvent, InputState, Position, Rope, RopeExt, TabSize,
|
HoverProvider, Input, InputEvent, InputState, Position, Rope, RopeExt, TabSize,
|
||||||
},
|
},
|
||||||
resizable::{ResizableState, h_resizable, resizable_panel},
|
resizable::{h_resizable, resizable_panel},
|
||||||
tree, v_flex,
|
tree, v_flex,
|
||||||
};
|
};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
|
|
@ -44,7 +44,6 @@ fn init() {
|
||||||
pub struct Example {
|
pub struct Example {
|
||||||
editor: Entity<InputState>,
|
editor: Entity<InputState>,
|
||||||
tree_state: Entity<TreeState>,
|
tree_state: Entity<TreeState>,
|
||||||
panels_state: Entity<ResizableState>,
|
|
||||||
go_to_line_state: Entity<InputState>,
|
go_to_line_state: Entity<InputState>,
|
||||||
language: Language,
|
language: Language,
|
||||||
line_number: bool,
|
line_number: bool,
|
||||||
|
|
@ -662,12 +661,9 @@ impl Example {
|
||||||
this.lint_document(cx);
|
this.lint_document(cx);
|
||||||
})];
|
})];
|
||||||
|
|
||||||
let panels_state = ResizableState::new(cx);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
editor,
|
editor,
|
||||||
tree_state,
|
tree_state,
|
||||||
panels_state,
|
|
||||||
go_to_line_state,
|
go_to_line_state,
|
||||||
language: default_language,
|
language: default_language,
|
||||||
line_number: true,
|
line_number: true,
|
||||||
|
|
@ -993,22 +989,21 @@ impl Render for Example {
|
||||||
.w_full()
|
.w_full()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.child(
|
.child(
|
||||||
h_resizable("editor-container", self.panels_state.clone())
|
h_resizable("editor-container")
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
.size(px(240.))
|
.size(px(240.))
|
||||||
.child(self.render_file_tree(window, cx)),
|
.child(self.render_file_tree(window, cx)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
resizable_panel().child(
|
|
||||||
Input::new(&self.editor)
|
Input::new(&self.editor)
|
||||||
.bordered(false)
|
.bordered(false)
|
||||||
.p_0()
|
.p_0()
|
||||||
.h_full()
|
.h_full()
|
||||||
.font_family("Monaco")
|
.font_family("Monaco")
|
||||||
.text_size(px(12.))
|
.text_size(px(12.))
|
||||||
.focus_bordered(false),
|
.focus_bordered(false)
|
||||||
),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,13 @@ use gpui::*;
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
highlighter::Language,
|
highlighter::Language,
|
||||||
input::{Input, InputState, TabSize},
|
input::{Input, InputState, TabSize},
|
||||||
resizable::{ResizableState, h_resizable, resizable_panel},
|
resizable::h_resizable,
|
||||||
text::TextView,
|
text::TextView,
|
||||||
};
|
};
|
||||||
use story::Assets;
|
use story::Assets;
|
||||||
|
|
||||||
pub struct Example {
|
pub struct Example {
|
||||||
input_state: Entity<InputState>,
|
input_state: Entity<InputState>,
|
||||||
resizable_state: Entity<ResizableState>,
|
|
||||||
_subscribe: Subscription,
|
_subscribe: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,8 +27,6 @@ impl Example {
|
||||||
.placeholder("Enter your HTML here...")
|
.placeholder("Enter your HTML here...")
|
||||||
});
|
});
|
||||||
|
|
||||||
let resizable_state = ResizableState::new(cx);
|
|
||||||
|
|
||||||
let _subscribe = cx.subscribe(
|
let _subscribe = cx.subscribe(
|
||||||
&input_state,
|
&input_state,
|
||||||
|_, _, _: &gpui_component::input::InputEvent, cx| {
|
|_, _, _: &gpui_component::input::InputEvent, cx| {
|
||||||
|
|
@ -39,7 +36,6 @@ impl Example {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
input_state,
|
input_state,
|
||||||
resizable_state,
|
|
||||||
_subscribe,
|
_subscribe,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -51,9 +47,8 @@ impl Example {
|
||||||
|
|
||||||
impl Render for Example {
|
impl Render for Example {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
h_resizable("container", self.resizable_state.clone())
|
h_resizable("container")
|
||||||
.child(
|
.child(
|
||||||
resizable_panel().child(
|
|
||||||
div()
|
div()
|
||||||
.id("source")
|
.id("source")
|
||||||
.size_full()
|
.size_full()
|
||||||
|
|
@ -64,11 +59,10 @@ impl Render for Example {
|
||||||
.h_full()
|
.h_full()
|
||||||
.appearance(false)
|
.appearance(false)
|
||||||
.focus_bordered(false),
|
.focus_bordered(false),
|
||||||
),
|
)
|
||||||
),
|
.into_any(),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
resizable_panel().child(
|
|
||||||
TextView::html(
|
TextView::html(
|
||||||
"preview",
|
"preview",
|
||||||
self.input_state.read(cx).value().clone(),
|
self.input_state.read(cx).value().clone(),
|
||||||
|
|
@ -77,8 +71,8 @@ impl Render for Example {
|
||||||
)
|
)
|
||||||
.p_5()
|
.p_5()
|
||||||
.scrollable()
|
.scrollable()
|
||||||
.selectable(),
|
.selectable()
|
||||||
),
|
.into_any(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,13 @@ use gpui::*;
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
highlighter::Language,
|
highlighter::Language,
|
||||||
input::{Input, InputEvent, InputState, TabSize},
|
input::{Input, InputEvent, InputState, TabSize},
|
||||||
resizable::{ResizableState, h_resizable, resizable_panel},
|
resizable::{h_resizable, resizable_panel},
|
||||||
text::TextView,
|
text::TextView,
|
||||||
};
|
};
|
||||||
use story::{Assets, Open};
|
use story::{Assets, Open};
|
||||||
|
|
||||||
pub struct Example {
|
pub struct Example {
|
||||||
input_state: Entity<InputState>,
|
input_state: Entity<InputState>,
|
||||||
resizable_state: Entity<ResizableState>,
|
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,12 +28,10 @@ impl Example {
|
||||||
.placeholder("Enter your Markdown here...")
|
.placeholder("Enter your Markdown here...")
|
||||||
.default_value(EXAMPLE)
|
.default_value(EXAMPLE)
|
||||||
});
|
});
|
||||||
let resizable_state = ResizableState::new(cx);
|
|
||||||
|
|
||||||
let _subscriptions = vec![cx.subscribe(&input_state, |_, _, _: &InputEvent, _| {})];
|
let _subscriptions = vec![cx.subscribe(&input_state, |_, _, _: &InputEvent, _| {})];
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
resizable_state,
|
|
||||||
input_state,
|
input_state,
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +76,7 @@ impl Render for Example {
|
||||||
.size_full()
|
.size_full()
|
||||||
.on_action(cx.listener(Self::on_action_open))
|
.on_action(cx.listener(Self::on_action_open))
|
||||||
.child(
|
.child(
|
||||||
h_resizable("container", self.resizable_state.clone())
|
h_resizable("container")
|
||||||
.child(
|
.child(
|
||||||
resizable_panel().child(
|
resizable_panel().child(
|
||||||
div()
|
div()
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use gpui::{prelude::*, *};
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
ActiveTheme as _, Icon, IconName, h_flex,
|
ActiveTheme as _, Icon, IconName, h_flex,
|
||||||
input::{Input, InputEvent, InputState},
|
input::{Input, InputEvent, InputState},
|
||||||
resizable::{ResizableState, h_resizable, resizable_panel},
|
resizable::{h_resizable, resizable_panel},
|
||||||
sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem},
|
sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem},
|
||||||
v_flex,
|
v_flex,
|
||||||
};
|
};
|
||||||
|
|
@ -14,7 +14,6 @@ pub struct Gallery {
|
||||||
active_index: Option<usize>,
|
active_index: Option<usize>,
|
||||||
collapsed: bool,
|
collapsed: bool,
|
||||||
search_input: Entity<InputState>,
|
search_input: Entity<InputState>,
|
||||||
sidebar_state: Entity<ResizableState>,
|
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,7 +90,6 @@ impl Gallery {
|
||||||
active_group_index: Some(0),
|
active_group_index: Some(0),
|
||||||
active_index: Some(0),
|
active_index: Some(0),
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
sidebar_state: ResizableState::new(cx),
|
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -148,7 +146,7 @@ impl Render for Gallery {
|
||||||
("".into(), "".into())
|
("".into(), "".into())
|
||||||
};
|
};
|
||||||
|
|
||||||
h_resizable("gallery-container", self.sidebar_state.clone())
|
h_resizable("gallery-container")
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
.size(px(255.))
|
.size(px(255.))
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, AnyElement, App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement,
|
AnyElement, App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement,
|
||||||
ParentElement as _, Pixels, Render, SharedString, Styled, Window,
|
ParentElement as _, Pixels, Render, SharedString, Styled, Window, div, px,
|
||||||
};
|
};
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
resizable::{h_resizable, resizable_panel, v_resizable, ResizableState},
|
ActiveTheme,
|
||||||
v_flex, ActiveTheme,
|
resizable::{h_resizable, resizable_panel, v_resizable},
|
||||||
|
v_flex,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ResizableStory {
|
pub struct ResizableStory {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
state1: Entity<ResizableState>,
|
|
||||||
state2: Entity<ResizableState>,
|
|
||||||
state3: Entity<ResizableState>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Story for ResizableStory {
|
impl super::Story for ResizableStory {
|
||||||
|
|
@ -40,15 +38,8 @@ impl ResizableStory {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(_: &mut Window, cx: &mut App) -> Self {
|
fn new(_: &mut Window, cx: &mut App) -> Self {
|
||||||
let state1 = ResizableState::new(cx);
|
|
||||||
let state2 = ResizableState::new(cx);
|
|
||||||
let state3 = ResizableState::new(cx);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
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 {
|
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()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.gap_6()
|
.gap_6()
|
||||||
|
|
@ -72,9 +63,12 @@ impl Render for ResizableStory {
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().border)
|
.border_color(cx.theme().border)
|
||||||
.child(
|
.child(
|
||||||
v_resizable("resizable-1", self.state1.clone())
|
v_resizable("resizable-1")
|
||||||
.group(
|
.on_resize(|state, _, cx| {
|
||||||
h_resizable("resizable-1.1", self.state2.clone())
|
println!("Resized: {:?}", state.read(cx).sizes());
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
h_resizable("resizable-1.1")
|
||||||
.size(px(150.))
|
.size(px(150.))
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
|
|
@ -82,14 +76,14 @@ impl Render for ResizableStory {
|
||||||
.size_range(px(120.)..px(300.))
|
.size_range(px(120.)..px(300.))
|
||||||
.child(panel_box("Left (120px .. 300px)", cx)),
|
.child(panel_box("Left (120px .. 300px)", cx)),
|
||||||
)
|
)
|
||||||
.child(resizable_panel().child(panel_box("Center", cx)))
|
.child(panel_box("Center", cx))
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
.size(px(300.))
|
.size(px(300.))
|
||||||
.child(panel_box("Right", cx)),
|
.child(panel_box("Right", cx)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(resizable_panel().child(panel_box("Center", cx)))
|
.child(panel_box("Center", cx))
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
.size(px(80.))
|
.size(px(80.))
|
||||||
|
|
@ -104,14 +98,14 @@ impl Render for ResizableStory {
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().border)
|
.border_color(cx.theme().border)
|
||||||
.child(
|
.child(
|
||||||
h_resizable("resizable-3", self.state3.clone())
|
h_resizable("resizable-3")
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
.size(px(200.))
|
.size(px(200.))
|
||||||
.size_range(px(200.)..px(400.))
|
.size_range(px(200.)..px(400.))
|
||||||
.child(panel_box("Left 2", cx)),
|
.child(panel_box("Left 2", cx)),
|
||||||
)
|
)
|
||||||
.child(resizable_panel().child(panel_box("Right (Grow)", cx))),
|
.child(panel_box("Right (Grow)", cx)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,9 @@ use crate::{
|
||||||
|
|
||||||
use super::{DockArea, Panel, PanelEvent, PanelState, PanelView, TabPanel};
|
use super::{DockArea, Panel, PanelEvent, PanelState, PanelView, TabPanel};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, Axis, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
|
App, AppContext as _, Axis, Context, DismissEvent, Entity, EventEmitter, FocusHandle,
|
||||||
ParentElement, Pixels, Render, Styled, Subscription, WeakEntity, Window,
|
Focusable, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, WeakEntity,
|
||||||
|
Window,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
|
@ -53,7 +54,7 @@ impl Panel for StackPanel {
|
||||||
|
|
||||||
impl StackPanel {
|
impl StackPanel {
|
||||||
pub fn new(axis: Axis, _: &mut Window, cx: &mut Context<Self>) -> Self {
|
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.
|
// Bubble up the resize event.
|
||||||
let _subscriptions = vec![cx.subscribe(&state, |_, _, _: &ResizablePanelEvent, cx| {
|
let _subscriptions = vec![cx.subscribe(&state, |_, _, _: &ResizablePanelEvent, cx| {
|
||||||
|
|
@ -417,7 +418,8 @@ impl Render for StackPanel {
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.bg(cx.theme().tab_bar)
|
.bg(cx.theme().tab_bar)
|
||||||
.child(
|
.child(
|
||||||
ResizablePanelGroup::new("stack-panel-group", self.state.clone())
|
ResizablePanelGroup::new("stack-panel-group")
|
||||||
|
.with_state(&self.state)
|
||||||
.axis(self.axis)
|
.axis(self.axis)
|
||||||
.children(self.panels.clone().into_iter().map(|panel| {
|
.children(self.panels.clone().into_iter().map(|panel| {
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{px, Along, App, Axis, Bounds, Context, ElementId, EventEmitter, Pixels, Window};
|
||||||
px, Along, App, AppContext, Axis, Bounds, Context, ElementId, Entity, EventEmitter, Pixels,
|
|
||||||
Window,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::PixelsExt;
|
use crate::PixelsExt;
|
||||||
|
|
||||||
|
|
@ -15,13 +12,13 @@ pub(crate) use resize_handle::*;
|
||||||
pub(crate) const PANEL_MIN_SIZE: Pixels = px(100.);
|
pub(crate) const PANEL_MIN_SIZE: Pixels = px(100.);
|
||||||
|
|
||||||
/// Create a [`ResizablePanelGroup`] with horizontal resizing
|
/// Create a [`ResizablePanelGroup`] with horizontal resizing
|
||||||
pub fn h_resizable(id: impl Into<ElementId>, state: Entity<ResizableState>) -> ResizablePanelGroup {
|
pub fn h_resizable(id: impl Into<ElementId>) -> ResizablePanelGroup {
|
||||||
ResizablePanelGroup::new(id, state).axis(Axis::Horizontal)
|
ResizablePanelGroup::new(id).axis(Axis::Horizontal)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a [`ResizablePanelGroup`] with vertical resizing
|
/// Create a [`ResizablePanelGroup`] with vertical resizing
|
||||||
pub fn v_resizable(id: impl Into<ElementId>, state: Entity<ResizableState>) -> ResizablePanelGroup {
|
pub fn v_resizable(id: impl Into<ElementId>) -> ResizablePanelGroup {
|
||||||
ResizablePanelGroup::new(id, state).axis(Axis::Vertical)
|
ResizablePanelGroup::new(id).axis(Axis::Vertical)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a [`ResizablePanel`].
|
/// Create a [`ResizablePanel`].
|
||||||
|
|
@ -29,8 +26,8 @@ pub fn resizable_panel() -> ResizablePanel {
|
||||||
ResizablePanel::new()
|
ResizablePanel::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
/// State for a [`ResizablePanel`]
|
/// State for a [`ResizablePanel`]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct ResizableState {
|
pub struct ResizableState {
|
||||||
/// The `axis` will sync to actual axis of the ResizablePanelGroup in use.
|
/// The `axis` will sync to actual axis of the ResizablePanelGroup in use.
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
|
|
@ -40,18 +37,25 @@ pub struct ResizableState {
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResizableState {
|
impl Default for ResizableState {
|
||||||
pub fn new(cx: &mut App) -> Entity<Self> {
|
fn default() -> Self {
|
||||||
cx.new(|_| Self {
|
Self {
|
||||||
axis: Axis::Horizontal,
|
axis: Axis::Horizontal,
|
||||||
panels: vec![],
|
panels: vec![],
|
||||||
sizes: vec![],
|
sizes: vec![],
|
||||||
resizing_panel_ix: None,
|
resizing_panel_ix: None,
|
||||||
bounds: Bounds::default(),
|
bounds: Bounds::default(),
|
||||||
})
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_panel(
|
impl ResizableState {
|
||||||
|
/// Get the size of the panels.
|
||||||
|
pub fn sizes(&self) -> &Vec<Pixels> {
|
||||||
|
&self.sizes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn insert_panel(
|
||||||
&mut self,
|
&mut self,
|
||||||
size: Option<Pixels>,
|
size: Option<Pixels>,
|
||||||
ix: Option<usize>,
|
ix: Option<usize>,
|
||||||
|
|
@ -126,11 +130,6 @@ impl ResizableState {
|
||||||
self.sizes.clear();
|
self.sizes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the size of the panels.
|
|
||||||
pub fn sizes(&self) -> &Vec<Pixels> {
|
|
||||||
&self.sizes
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn total_size(&self) -> Pixels {
|
pub(crate) fn total_size(&self) -> Pixels {
|
||||||
self.sizes.iter().map(|s| s.as_f32()).sum::<f32>().into()
|
self.sizes.iter().map(|s| s.as_f32()).sum::<f32>().into()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
use std::ops::{Deref, Range};
|
use std::{
|
||||||
|
ops::{Deref, Range},
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
canvas, div, prelude::FluentBuilder, AnyElement, App, AppContext, Axis, Bounds, Context,
|
canvas, div, prelude::FluentBuilder, AnyElement, App, AppContext, Axis, Bounds, Context,
|
||||||
|
|
@ -26,23 +29,32 @@ impl Render for DragPanel {
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct ResizablePanelGroup {
|
pub struct ResizablePanelGroup {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
state: Entity<ResizableState>,
|
state: Option<Entity<ResizableState>>,
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
size: Option<Pixels>,
|
size: Option<Pixels>,
|
||||||
children: Vec<ResizablePanel>,
|
children: Vec<ResizablePanel>,
|
||||||
|
on_resize: Rc<dyn Fn(&Entity<ResizableState>, &mut Window, &mut App)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResizablePanelGroup {
|
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 {
|
Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
axis: Axis::Horizontal,
|
axis: Axis::Horizontal,
|
||||||
children: vec![],
|
children: vec![],
|
||||||
state,
|
state: None,
|
||||||
size: 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.
|
/// Set the axis of the resizable panel group, default is horizontal.
|
||||||
pub fn axis(mut self, axis: Axis) -> Self {
|
pub fn axis(mut self, axis: Axis) -> Self {
|
||||||
self.axis = axis;
|
self.axis = axis;
|
||||||
|
|
@ -67,11 +79,6 @@ impl ResizablePanelGroup {
|
||||||
self
|
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
|
/// Set size of the resizable panel group
|
||||||
///
|
///
|
||||||
/// - When the axis is horizontal, the size is the height of the 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.size = Some(size);
|
||||||
self
|
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
|
impl<T> From<T> for ResizablePanel
|
||||||
where
|
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 EventEmitter<ResizablePanelEvent> for ResizablePanelGroup {}
|
||||||
|
|
||||||
impl RenderOnce for ResizablePanelGroup {
|
impl RenderOnce for ResizablePanelGroup {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let state = self.state.clone();
|
let state = self.state.unwrap_or(
|
||||||
|
window.use_keyed_state(self.id.clone(), cx, |_, _| ResizableState::default()),
|
||||||
|
);
|
||||||
let container = if self.axis.is_horizontal() {
|
let container = if self.axis.is_horizontal() {
|
||||||
h_flex()
|
h_flex()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -103,7 +131,7 @@ impl RenderOnce for ResizablePanelGroup {
|
||||||
|
|
||||||
// Sync panels to the state
|
// Sync panels to the state
|
||||||
let panels_count = self.children.len();
|
let panels_count = self.children.len();
|
||||||
self.state.update(cx, |state, _| {
|
state.update(cx, |state, _| {
|
||||||
state.sync_panels_count(self.axis, panels_count);
|
state.sync_panels_count(self.axis, panels_count);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -117,21 +145,25 @@ impl RenderOnce for ResizablePanelGroup {
|
||||||
.map(|(ix, mut panel)| {
|
.map(|(ix, mut panel)| {
|
||||||
panel.panel_ix = ix;
|
panel.panel_ix = ix;
|
||||||
panel.axis = self.axis;
|
panel.axis = self.axis;
|
||||||
panel.state = Some(self.state.clone());
|
panel.state = Some(state.clone());
|
||||||
panel
|
panel
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child({
|
.child({
|
||||||
canvas(
|
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()
|
.absolute()
|
||||||
.size_full()
|
.size_full()
|
||||||
})
|
})
|
||||||
.child(ResizePanelGroupElement {
|
.child(ResizePanelGroupElement {
|
||||||
state: self.state.clone(),
|
state: state.clone(),
|
||||||
axis: self.axis,
|
axis: self.axis,
|
||||||
|
on_resize: self.on_resize.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -267,6 +299,7 @@ impl RenderOnce for ResizablePanel {
|
||||||
|
|
||||||
struct ResizePanelGroupElement {
|
struct ResizePanelGroupElement {
|
||||||
state: Entity<ResizableState>,
|
state: Entity<ResizableState>,
|
||||||
|
on_resize: Rc<dyn Fn(&Entity<ResizableState>, &mut Window, &mut App)>,
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,12 +385,14 @@ impl Element for ResizePanelGroupElement {
|
||||||
window.on_mouse_event({
|
window.on_mouse_event({
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
let current_ix = state.read(cx).resizing_panel_ix;
|
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() {
|
if current_ix.is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if phase.bubble() {
|
if phase.bubble() {
|
||||||
state.update(cx, |state, cx| state.done_resizing(cx));
|
state.update(cx, |state, cx| state.done_resizing(cx));
|
||||||
|
on_resize(&state, window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -18,37 +18,49 @@ use gpui_component::resizable::{
|
||||||
|
|
||||||
## Usage
|
## 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
|
```rust
|
||||||
let state = ResizableState::new(cx);
|
h_resizable("my-layout")
|
||||||
|
.on_resize(|state, window, cx| {
|
||||||
h_resizable("my-layout", state)
|
// Handle resize event
|
||||||
|
// You can read the panel sizes from the state.
|
||||||
|
let state = state.read(cx);
|
||||||
|
let sizes = state.sizes();
|
||||||
|
})
|
||||||
.child(
|
.child(
|
||||||
|
// Use resizable_panel() to create a sized panel.
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
.size(px(200.))
|
.size(px(200.))
|
||||||
.child("Left Panel")
|
.child("Left Panel")
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
// Or you can just add AnyElement without a size.
|
||||||
|
div()
|
||||||
.child("Right Panel")
|
.child("Right Panel")
|
||||||
|
.into_any_element()
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Basic Vertical Layout
|
The `v_resizable` component is used to create a vertical layout.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let state = ResizableState::new(cx);
|
v_resizable("vertical-layout")
|
||||||
|
|
||||||
v_resizable("vertical-layout", state)
|
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
.size(px(100.))
|
.size(px(100.))
|
||||||
.child("Top Panel")
|
.child("Top Panel")
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
div()
|
||||||
.child("Bottom Panel")
|
.child("Bottom Panel")
|
||||||
|
.into_any_element()
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -85,15 +97,12 @@ h_resizable("multi-panel", state)
|
||||||
### Nested Layouts
|
### Nested Layouts
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let main_state = ResizableState::new(cx);
|
v_resizable("main-layout", window, cx)
|
||||||
let nested_state = ResizableState::new(cx);
|
|
||||||
|
|
||||||
v_resizable("main-layout", main_state)
|
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
.size(px(300.))
|
.size(px(300.))
|
||||||
.child(
|
.child(
|
||||||
h_resizable("nested-layout", nested_state)
|
h_resizable("nested-layout", window, cx)
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
.size(px(200.))
|
.size(px(200.))
|
||||||
|
|
@ -114,17 +123,14 @@ v_resizable("main-layout", main_state)
|
||||||
### Nested Panel Groups
|
### Nested Panel Groups
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let outer_state = ResizableState::new(cx);
|
h_resizable("outer", window, cx)
|
||||||
let inner_state = ResizableState::new(cx);
|
|
||||||
|
|
||||||
h_resizable("outer", outer_state)
|
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
.size(px(200.))
|
.size(px(200.))
|
||||||
.child("Left Panel")
|
.child("Left Panel")
|
||||||
)
|
)
|
||||||
.group(
|
.group(
|
||||||
v_resizable("inner", inner_state)
|
v_resizable("inner", window, cx)
|
||||||
.child(
|
.child(
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
.size(px(150.))
|
.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
|
### Conditional Panel Visibility
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
|
@ -202,114 +172,18 @@ resizable_panel()
|
||||||
.child("Fixed 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
|
## Examples
|
||||||
|
|
||||||
### File Explorer Layout
|
### File Explorer Layout
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
struct FileExplorer {
|
struct FileExplorer {
|
||||||
layout_state: Entity<ResizableState>,
|
|
||||||
show_sidebar: bool,
|
show_sidebar: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for FileExplorer {
|
impl Render for FileExplorer {
|
||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
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(
|
.child(
|
||||||
resizable_panel()
|
resizable_panel()
|
||||||
.visible(self.show_sidebar)
|
.visible(self.show_sidebar)
|
||||||
|
|
@ -324,8 +198,6 @@ impl Render for FileExplorer {
|
||||||
.child("• Downloads")
|
.child("• Downloads")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.child(
|
|
||||||
resizable_panel()
|
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.p_4()
|
.p_4()
|
||||||
|
|
@ -333,7 +205,7 @@ impl Render for FileExplorer {
|
||||||
.child("file1.txt")
|
.child("file1.txt")
|
||||||
.child("file2.pdf")
|
.child("file2.pdf")
|
||||||
.child("image.png")
|
.child("image.png")
|
||||||
)
|
.into_any_element()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue