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,
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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.))
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue