resizable: Refactor to use stateless Resizable element. (#852)
## Break Changes - The `ResizablePanelGroup` and `ResizablePanel` are changed to as stateless element, please check out the diff to migrate.
This commit is contained in:
parent
0a95f7c0f1
commit
1535df796e
6 changed files with 553 additions and 551 deletions
|
|
@ -2,6 +2,7 @@ use gpui::{prelude::*, *};
|
|||
use gpui_component::{
|
||||
h_flex,
|
||||
input::{InputEvent, TextInput},
|
||||
resizable::{h_resizable, resizable_panel, ResizableState},
|
||||
sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem},
|
||||
v_flex, ActiveTheme as _, Icon, IconName,
|
||||
};
|
||||
|
|
@ -13,6 +14,7 @@ pub struct Gallery {
|
|||
active_index: Option<usize>,
|
||||
collapsed: bool,
|
||||
search_input: Entity<TextInput>,
|
||||
sidebar_state: Entity<ResizableState>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +88,7 @@ impl Gallery {
|
|||
active_group_index: Some(0),
|
||||
active_index: Some(0),
|
||||
collapsed: false,
|
||||
sidebar_state: ResizableState::new(cx),
|
||||
_subscriptions,
|
||||
}
|
||||
}
|
||||
|
|
@ -129,89 +132,103 @@ impl Render for Gallery {
|
|||
("".into(), "".into())
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.id("gallery-container")
|
||||
.size_full()
|
||||
h_resizable("gallery-container", self.sidebar_state.clone())
|
||||
.child(
|
||||
Sidebar::left()
|
||||
.collapsed(self.collapsed)
|
||||
.header(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.gap_4()
|
||||
.child(
|
||||
SidebarHeader::new()
|
||||
resizable_panel()
|
||||
.size(px(255.))
|
||||
.size_range(px(200.)..px(320.))
|
||||
.child(
|
||||
Sidebar::left()
|
||||
.width(relative(1.))
|
||||
.border_width(px(0.))
|
||||
.collapsed(self.collapsed)
|
||||
.header(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.gap_4()
|
||||
.child(
|
||||
SidebarHeader::new()
|
||||
.w_full()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(cx.theme().primary)
|
||||
.text_color(cx.theme().primary_foreground)
|
||||
.size_8()
|
||||
.flex_shrink_0()
|
||||
.when(!self.collapsed, |this| {
|
||||
this.child(Icon::new(
|
||||
IconName::GalleryVerticalEnd,
|
||||
))
|
||||
})
|
||||
.when(self.collapsed, |this| {
|
||||
this.size_4()
|
||||
.bg(cx.theme().transparent)
|
||||
.text_color(cx.theme().foreground)
|
||||
.child(Icon::new(
|
||||
IconName::GalleryVerticalEnd,
|
||||
))
|
||||
})
|
||||
.rounded_lg(),
|
||||
)
|
||||
.when(!self.collapsed, |this| {
|
||||
this.child(
|
||||
v_flex()
|
||||
.gap_0()
|
||||
.text_sm()
|
||||
.flex_1()
|
||||
.line_height(relative(1.25))
|
||||
.overflow_hidden()
|
||||
.text_ellipsis()
|
||||
.child("GPUI Component")
|
||||
.child(
|
||||
div()
|
||||
.text_color(
|
||||
cx.theme().muted_foreground,
|
||||
)
|
||||
.child("Gallery")
|
||||
.text_xs(),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.rounded(cx.theme().radius)
|
||||
.bg(cx.theme().primary)
|
||||
.text_color(cx.theme().primary_foreground)
|
||||
.size_8()
|
||||
.flex_shrink_0()
|
||||
.when(!self.collapsed, |this| {
|
||||
this.child(Icon::new(IconName::GalleryVerticalEnd))
|
||||
})
|
||||
.when(self.collapsed, |this| {
|
||||
this.size_4()
|
||||
.bg(cx.theme().transparent)
|
||||
.text_color(cx.theme().foreground)
|
||||
.child(Icon::new(IconName::GalleryVerticalEnd))
|
||||
})
|
||||
.rounded_lg(),
|
||||
)
|
||||
.when(!self.collapsed, |this| {
|
||||
this.child(
|
||||
v_flex()
|
||||
.gap_0()
|
||||
.text_sm()
|
||||
.flex_1()
|
||||
.line_height(relative(1.25))
|
||||
.overflow_hidden()
|
||||
.text_ellipsis()
|
||||
.child("GPUI Component")
|
||||
.child(
|
||||
div()
|
||||
.text_color(cx.theme().muted_foreground)
|
||||
.child("Gallery")
|
||||
.text_xs(),
|
||||
),
|
||||
)
|
||||
}),
|
||||
.bg(cx.theme().sidebar_border)
|
||||
.px_1()
|
||||
.rounded_full()
|
||||
.flex_1()
|
||||
.mx_1()
|
||||
.child(self.search_input.clone()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.bg(cx.theme().sidebar_border)
|
||||
.px_1()
|
||||
.rounded_full()
|
||||
.flex_1()
|
||||
.mx_1()
|
||||
.child(self.search_input.clone()),
|
||||
),
|
||||
)
|
||||
.children(stories.clone().into_iter().enumerate().map(
|
||||
|(group_ix, (group_name, sub_stories))| {
|
||||
SidebarGroup::new(*group_name).child(SidebarMenu::new().children(
|
||||
sub_stories.iter().enumerate().map(|(ix, story)| {
|
||||
SidebarMenuItem::new(story.read(cx).name.clone())
|
||||
.active(
|
||||
self.active_group_index == Some(group_ix)
|
||||
&& self.active_index == Some(ix),
|
||||
)
|
||||
.on_click(cx.listener(
|
||||
move |this, _: &ClickEvent, _, cx| {
|
||||
this.active_group_index = Some(group_ix);
|
||||
this.active_index = Some(ix);
|
||||
cx.notify();
|
||||
},
|
||||
))
|
||||
}),
|
||||
))
|
||||
},
|
||||
)),
|
||||
.children(stories.clone().into_iter().enumerate().map(
|
||||
|(group_ix, (group_name, sub_stories))| {
|
||||
SidebarGroup::new(*group_name).child(
|
||||
SidebarMenu::new().children(
|
||||
sub_stories.iter().enumerate().map(|(ix, story)| {
|
||||
SidebarMenuItem::new(story.read(cx).name.clone())
|
||||
.active(
|
||||
self.active_group_index == Some(group_ix)
|
||||
&& self.active_index == Some(ix),
|
||||
)
|
||||
.on_click(cx.listener(
|
||||
move |this, _: &ClickEvent, _, cx| {
|
||||
this.active_group_index =
|
||||
Some(group_ix);
|
||||
this.active_index = Some(ix);
|
||||
cx.notify();
|
||||
},
|
||||
))
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
|
|
@ -245,7 +262,8 @@ impl Render for Gallery {
|
|||
.when_some(active_story, |this, active_story| {
|
||||
this.child(active_story.clone())
|
||||
}),
|
||||
),
|
||||
)
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@ use gpui::{
|
|||
ParentElement as _, Pixels, Render, SharedString, Styled, Window,
|
||||
};
|
||||
use gpui_component::{
|
||||
resizable::{h_resizable, resizable_panel, v_resizable, ResizablePanelGroup},
|
||||
resizable::{h_resizable, resizable_panel, v_resizable, ResizableState},
|
||||
v_flex, ActiveTheme,
|
||||
};
|
||||
|
||||
pub struct ResizableStory {
|
||||
focus_handle: gpui::FocusHandle,
|
||||
group1: Entity<ResizablePanelGroup>,
|
||||
group2: Entity<ResizablePanelGroup>,
|
||||
state1: Entity<ResizableState>,
|
||||
state2: Entity<ResizableState>,
|
||||
state3: Entity<ResizableState>,
|
||||
}
|
||||
|
||||
impl super::Story for ResizableStory {
|
||||
|
|
@ -39,81 +40,71 @@ impl ResizableStory {
|
|||
}
|
||||
|
||||
fn new(_: &mut Window, cx: &mut App) -> Self {
|
||||
fn panel_box(content: impl Into<SharedString>, cx: &App) -> AnyElement {
|
||||
div()
|
||||
.p_4()
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.size_full()
|
||||
.child(content.into())
|
||||
.into_any_element()
|
||||
}
|
||||
let state1 = ResizableState::new(cx);
|
||||
let state2 = ResizableState::new(cx);
|
||||
let state3 = ResizableState::new(cx);
|
||||
|
||||
let group1 = cx.new(|cx| {
|
||||
v_resizable()
|
||||
.group(
|
||||
h_resizable()
|
||||
.size(px(150.))
|
||||
.child(
|
||||
resizable_panel()
|
||||
.size(px(150.))
|
||||
.size_range(px(120.)..px(300.))
|
||||
.content(|_, cx| panel_box("Left (120px .. 300px)", cx)),
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
resizable_panel().content(|_, cx| panel_box("Center", cx)),
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
resizable_panel()
|
||||
.size(px(300.))
|
||||
.content(|_, cx| panel_box("Right", cx)),
|
||||
cx,
|
||||
),
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
resizable_panel().content(|_, cx| panel_box("Center", cx)),
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
resizable_panel()
|
||||
.size(px(80.))
|
||||
.size_range(px(80.)..Pixels::MAX)
|
||||
.content(|_, cx| panel_box("Bottom (80px .. 150px)", cx)),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let group2 = cx.new(|cx| {
|
||||
h_resizable()
|
||||
.child(
|
||||
resizable_panel()
|
||||
.size(px(200.))
|
||||
.size_range(px(200.)..px(400.))
|
||||
.content(|_, cx| panel_box("Left 2", cx)),
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
resizable_panel().content(|_, cx| panel_box("Right (Grow)", cx)),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
group1,
|
||||
group2,
|
||||
state1,
|
||||
state2,
|
||||
state3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn panel_box(content: impl Into<SharedString>, cx: &App) -> AnyElement {
|
||||
div()
|
||||
.p_4()
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.size_full()
|
||||
.child(content.into())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
impl Render for ResizableStory {
|
||||
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 {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.gap_6()
|
||||
.child(div().h(px(800.)).child(self.group1.clone()))
|
||||
.child(self.group2.clone())
|
||||
.child(
|
||||
div().h(px(800.)).child(
|
||||
v_resizable("resizable-1", self.state1.clone())
|
||||
.group(
|
||||
h_resizable("resizable-1.1", self.state2.clone())
|
||||
.size(px(150.))
|
||||
.child(
|
||||
resizable_panel()
|
||||
.size(px(150.))
|
||||
.size_range(px(120.)..px(300.))
|
||||
.child(panel_box("Left (120px .. 300px)", cx)),
|
||||
)
|
||||
.child(resizable_panel().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(
|
||||
resizable_panel()
|
||||
.size(px(80.))
|
||||
.size_range(px(80.)..Pixels::MAX)
|
||||
.child(panel_box("Bottom (80px .. 150px)", cx)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_resizable("resizable-3", self.state3.clone())
|
||||
.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))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,17 +4,16 @@ use crate::{
|
|||
dock::PanelInfo,
|
||||
h_flex,
|
||||
resizable::{
|
||||
h_resizable, resizable_panel, v_resizable, ResizablePanel, ResizablePanelEvent,
|
||||
ResizablePanelGroup, PANEL_MIN_SIZE,
|
||||
resizable_panel, ResizablePanelEvent, ResizablePanelGroup, ResizablePanelState,
|
||||
ResizableState, PANEL_MIN_SIZE,
|
||||
},
|
||||
ActiveTheme, AxisExt as _, Placement,
|
||||
};
|
||||
|
||||
use super::{DockArea, Panel, PanelEvent, PanelState, PanelView, TabPanel};
|
||||
use gpui::{
|
||||
prelude::FluentBuilder as _, App, AppContext, Axis, Context, DismissEvent, Entity,
|
||||
EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Pixels, Render, Styled,
|
||||
Subscription, WeakEntity, Window,
|
||||
App, Axis, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
|
||||
ParentElement, Pixels, Render, Styled, Subscription, WeakEntity, Window,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
|
|
@ -23,7 +22,7 @@ pub struct StackPanel {
|
|||
pub(super) axis: Axis,
|
||||
focus_handle: FocusHandle,
|
||||
pub(crate) panels: SmallVec<[Arc<dyn PanelView>; 2]>,
|
||||
panel_group: Entity<ResizablePanelGroup>,
|
||||
state: Entity<ResizableState>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +40,7 @@ impl Panel for StackPanel {
|
|||
}
|
||||
}
|
||||
fn dump(&self, cx: &App) -> PanelState {
|
||||
let sizes = self.panel_group.read(cx).sizes();
|
||||
let sizes = self.state.read(cx).sizes().clone();
|
||||
let mut state = PanelState::new(self);
|
||||
for panel in &self.panels {
|
||||
state.add_child(panel.dump(cx));
|
||||
|
|
@ -54,26 +53,19 @@ impl Panel for StackPanel {
|
|||
|
||||
impl StackPanel {
|
||||
pub fn new(axis: Axis, _: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let panel_group = cx.new(|_| {
|
||||
if axis == Axis::Horizontal {
|
||||
h_resizable()
|
||||
} else {
|
||||
v_resizable()
|
||||
}
|
||||
});
|
||||
let state = ResizableState::new(cx);
|
||||
|
||||
// Bubble up the resize event.
|
||||
let _subscriptions = vec![cx
|
||||
.subscribe(&panel_group, |_, _, _: &ResizablePanelEvent, cx| {
|
||||
cx.emit(PanelEvent::LayoutChanged)
|
||||
})];
|
||||
let _subscriptions = vec![cx.subscribe(&state, |_, _, _: &ResizablePanelEvent, cx| {
|
||||
cx.emit(PanelEvent::LayoutChanged)
|
||||
})];
|
||||
|
||||
Self {
|
||||
axis,
|
||||
parent: None,
|
||||
focus_handle: cx.focus_handle(),
|
||||
panels: SmallVec::new(),
|
||||
panel_group,
|
||||
state,
|
||||
_subscriptions,
|
||||
}
|
||||
}
|
||||
|
|
@ -186,13 +178,6 @@ impl StackPanel {
|
|||
self.insert_panel(panel, ix + 1, size, dock_area, window, cx);
|
||||
}
|
||||
|
||||
fn new_resizable_panel(panel: Arc<dyn PanelView>, size: Option<Pixels>) -> ResizablePanel {
|
||||
resizable_panel()
|
||||
.content_view(panel.view())
|
||||
.content_visible(move |_, cx| panel.visible(cx))
|
||||
.when_some(size, |this, size| this.size(size))
|
||||
}
|
||||
|
||||
fn insert_panel(
|
||||
&mut self,
|
||||
panel: Arc<dyn PanelView>,
|
||||
|
|
@ -242,22 +227,15 @@ impl StackPanel {
|
|||
let size = match size {
|
||||
Some(size) => size,
|
||||
None => {
|
||||
let panel_group = self.panel_group.read(cx);
|
||||
(panel_group.total_size() / (panel_group.sizes().len() + 1) as f32)
|
||||
.max(PANEL_MIN_SIZE)
|
||||
let state = self.state.read(cx);
|
||||
(state.total_size() / (state.sizes().len() + 1) as f32).max(PANEL_MIN_SIZE)
|
||||
}
|
||||
};
|
||||
|
||||
self.panels.insert(ix, panel.clone());
|
||||
self.panel_group.update(cx, |view, cx| {
|
||||
view.insert_child(
|
||||
Self::new_resizable_panel(panel.clone(), Some(size)),
|
||||
ix,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
self.state.update(cx, |state, cx| {
|
||||
state.insert_panel(Some(size), Some(ix), cx);
|
||||
});
|
||||
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
cx.notify();
|
||||
}
|
||||
|
|
@ -271,8 +249,8 @@ impl StackPanel {
|
|||
) {
|
||||
if let Some(ix) = self.index_of_panel(panel.clone()) {
|
||||
self.panels.remove(ix);
|
||||
self.panel_group.update(cx, |view, cx| {
|
||||
view.remove_child(ix, window, cx);
|
||||
self.state.update(cx, |state, cx| {
|
||||
state.remove_panel(ix, cx);
|
||||
});
|
||||
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
|
|
@ -287,18 +265,15 @@ impl StackPanel {
|
|||
&mut self,
|
||||
old_panel: Arc<dyn PanelView>,
|
||||
new_panel: Entity<StackPanel>,
|
||||
window: &mut Window,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(ix) = self.index_of_panel(old_panel.clone()) {
|
||||
self.panels[ix] = Arc::new(new_panel.clone());
|
||||
self.panel_group.update(cx, |view, cx| {
|
||||
view.replace_child(
|
||||
Self::new_resizable_panel(Arc::new(new_panel.clone()), None),
|
||||
ix,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
let panel_state = ResizablePanelState::default();
|
||||
self.state.update(cx, |state, cx| {
|
||||
state.replace_panel(ix, panel_state, cx);
|
||||
});
|
||||
cx.emit(PanelEvent::LayoutChanged);
|
||||
}
|
||||
|
|
@ -387,17 +362,17 @@ impl StackPanel {
|
|||
}
|
||||
|
||||
/// Remove all panels from the stack.
|
||||
pub(super) fn remove_all_panels(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
pub(super) fn remove_all_panels(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.panels.clear();
|
||||
self.panel_group
|
||||
.update(cx, |view, cx| view.remove_all_children(window, cx));
|
||||
self.state.update(cx, |state, cx| {
|
||||
state.clear();
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
||||
/// Change the axis of the stack panel.
|
||||
pub(super) fn set_axis(&mut self, axis: Axis, window: &mut Window, cx: &mut Context<Self>) {
|
||||
pub(super) fn set_axis(&mut self, axis: Axis, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.axis = axis;
|
||||
self.panel_group
|
||||
.update(cx, |view, cx| view.set_axis(axis, window, cx));
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
|
@ -415,6 +390,14 @@ impl Render for StackPanel {
|
|||
.size_full()
|
||||
.overflow_hidden()
|
||||
.bg(cx.theme().tab_bar)
|
||||
.child(self.panel_group.clone())
|
||||
.child(
|
||||
ResizablePanelGroup::new("stack-panel-group", self.state.clone())
|
||||
.axis(self.axis)
|
||||
.children(self.panels.clone().into_iter().map(|panel| {
|
||||
resizable_panel()
|
||||
.child(panel.view())
|
||||
.visible(panel.visible(cx))
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,232 @@
|
|||
use gpui::Axis;
|
||||
use std::ops::Range;
|
||||
|
||||
use gpui::{
|
||||
px, Along, App, AppContext, Axis, Bounds, Context, ElementId, Entity, EventEmitter, Pixels,
|
||||
Window,
|
||||
};
|
||||
|
||||
mod panel;
|
||||
mod resize_handle;
|
||||
pub use panel::*;
|
||||
pub(crate) use resize_handle::*;
|
||||
|
||||
pub fn h_resizable() -> ResizablePanelGroup {
|
||||
ResizablePanelGroup::new().axis(Axis::Horizontal)
|
||||
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 v_resizable() -> ResizablePanelGroup {
|
||||
ResizablePanelGroup::new().axis(Axis::Vertical)
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Create a [`ResizablePanel`].
|
||||
pub fn resizable_panel() -> ResizablePanel {
|
||||
ResizablePanel::new()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// State for a [`ResizablePanel`]
|
||||
pub struct ResizableState {
|
||||
/// The `axis` will sync to actual axis of the ResizablePanelGroup in use.
|
||||
axis: Axis,
|
||||
panels: Vec<ResizablePanelState>,
|
||||
sizes: Vec<Pixels>,
|
||||
pub(crate) resizing_panel_ix: Option<usize>,
|
||||
bounds: Bounds<Pixels>,
|
||||
}
|
||||
|
||||
impl ResizableState {
|
||||
pub fn new(cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|_| Self {
|
||||
axis: Axis::Horizontal,
|
||||
panels: vec![],
|
||||
sizes: vec![],
|
||||
resizing_panel_ix: None,
|
||||
bounds: Bounds::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn insert_panel(
|
||||
&mut self,
|
||||
size: Option<Pixels>,
|
||||
ix: Option<usize>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let panel_state = ResizablePanelState {
|
||||
size,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(ix) = ix {
|
||||
self.panels.insert(ix, panel_state);
|
||||
self.sizes.insert(ix, size.unwrap_or(PANEL_MIN_SIZE));
|
||||
} else {
|
||||
self.panels.push(panel_state);
|
||||
self.sizes.push(size.unwrap_or(PANEL_MIN_SIZE));
|
||||
};
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn sync_panels_count(&mut self, axis: Axis, panels_count: usize) {
|
||||
self.axis = axis;
|
||||
if panels_count > self.panels.len() {
|
||||
let diff = panels_count - self.panels.len();
|
||||
self.panels
|
||||
.extend(vec![ResizablePanelState::default(); diff]);
|
||||
self.sizes.extend(vec![PANEL_MIN_SIZE; diff]);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_panel_size(
|
||||
&mut self,
|
||||
panel_ix: usize,
|
||||
bounds: Bounds<Pixels>,
|
||||
size_range: Range<Pixels>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let size = bounds.size.along(self.axis);
|
||||
self.sizes[panel_ix] = size;
|
||||
self.panels[panel_ix].size = Some(size);
|
||||
self.panels[panel_ix].bounds = bounds;
|
||||
self.panels[panel_ix].size_range = size_range;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn remove_panel(&mut self, panel_ix: usize, cx: &mut Context<Self>) {
|
||||
self.panels.remove(panel_ix);
|
||||
self.sizes.remove(panel_ix);
|
||||
if let Some(resizing_panel_ix) = self.resizing_panel_ix {
|
||||
if resizing_panel_ix > panel_ix {
|
||||
self.resizing_panel_ix = Some(resizing_panel_ix - 1);
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn replace_panel(
|
||||
&mut self,
|
||||
panel_ix: usize,
|
||||
panel: ResizablePanelState,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let old_size = self.sizes[panel_ix];
|
||||
|
||||
self.panels[panel_ix] = panel;
|
||||
self.sizes[panel_ix] = old_size;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.panels.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 {
|
||||
self.sizes.iter().map(|s| s.0).sum::<f32>().into()
|
||||
}
|
||||
|
||||
pub(crate) fn done_resizing(&mut self, cx: &mut Context<Self>) {
|
||||
self.resizing_panel_ix = None;
|
||||
cx.emit(ResizablePanelEvent::Resized);
|
||||
}
|
||||
|
||||
fn panel_size_range(&self, ix: usize) -> Range<Pixels> {
|
||||
let Some(panel) = self.panels.get(ix) else {
|
||||
return PANEL_MIN_SIZE..Pixels::MAX;
|
||||
};
|
||||
|
||||
panel.size_range.clone()
|
||||
}
|
||||
|
||||
fn sync_real_panel_sizes(&mut self, _: &App) {
|
||||
for (i, panel) in self.panels.iter().enumerate() {
|
||||
self.sizes[i] = panel.bounds.size.along(self.axis).floor();
|
||||
}
|
||||
}
|
||||
|
||||
/// The `ix`` is the index of the panel to resize,
|
||||
/// and the `size` is the new size for the panel.
|
||||
fn resize_panel(&mut self, ix: usize, size: Pixels, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let old_sizes = self.sizes.clone();
|
||||
|
||||
let mut ix = ix;
|
||||
// Only resize the left panels.
|
||||
if ix >= old_sizes.len() - 1 {
|
||||
return;
|
||||
}
|
||||
let size = size.floor();
|
||||
let container_size = self.bounds.size.along(self.axis);
|
||||
self.sync_real_panel_sizes(cx);
|
||||
|
||||
let move_changed = size - old_sizes[ix];
|
||||
if move_changed == px(0.) {
|
||||
return;
|
||||
}
|
||||
|
||||
let size_range = self.panel_size_range(ix);
|
||||
let new_size = size.clamp(size_range.start, size_range.end);
|
||||
let is_expand = move_changed > px(0.);
|
||||
|
||||
let main_ix = ix;
|
||||
let mut new_sizes = old_sizes.clone();
|
||||
|
||||
if is_expand {
|
||||
let mut changed = new_size - old_sizes[ix];
|
||||
new_sizes[ix] = new_size;
|
||||
|
||||
while changed > px(0.) && ix < old_sizes.len() - 1 {
|
||||
ix += 1;
|
||||
let size_range = self.panel_size_range(ix);
|
||||
let available_size = (new_sizes[ix] - size_range.start).max(px(0.));
|
||||
let to_reduce = changed.min(available_size);
|
||||
new_sizes[ix] -= to_reduce;
|
||||
changed -= to_reduce;
|
||||
}
|
||||
} else {
|
||||
let mut changed = new_size - size;
|
||||
new_sizes[ix + 1] += old_sizes[ix] - new_size;
|
||||
new_sizes[ix] = new_size;
|
||||
|
||||
while changed > px(0.) && ix > 0 {
|
||||
ix -= 1;
|
||||
let size_range = self.panel_size_range(ix);
|
||||
let available_size = (new_sizes[ix] - size_range.start).max(px(0.));
|
||||
let to_reduce = changed.min(available_size);
|
||||
changed -= to_reduce;
|
||||
new_sizes[ix] -= to_reduce;
|
||||
}
|
||||
}
|
||||
|
||||
// If total size exceeds container size, adjust the main panel
|
||||
let total_size: Pixels = new_sizes.iter().map(|s| s.0).sum::<f32>().into();
|
||||
if total_size > container_size {
|
||||
let overflow = total_size - container_size;
|
||||
new_sizes[main_ix] = (new_sizes[main_ix] - overflow).max(size_range.start);
|
||||
}
|
||||
|
||||
for (i, _) in old_sizes.iter().enumerate() {
|
||||
let size = new_sizes[i];
|
||||
self.panels[i].size = Some(size);
|
||||
}
|
||||
|
||||
self.sizes = new_sizes;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ResizablePanelEvent> for ResizableState {}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct ResizablePanelState {
|
||||
pub size: Option<Pixels>,
|
||||
pub size_range: Range<Pixels>,
|
||||
bounds: Bounds<Pixels>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,21 @@
|
|||
use std::{
|
||||
ops::{Deref, Range},
|
||||
rc::Rc,
|
||||
};
|
||||
use std::ops::{Deref, Range};
|
||||
|
||||
use gpui::{
|
||||
canvas, div, prelude::FluentBuilder, px, Along, AnyElement, AnyView, App, AppContext, Axis,
|
||||
Bounds, Context, Element, Empty, Entity, EntityId, EventEmitter, IntoElement, IsZero,
|
||||
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Render, Style, Styled, WeakEntity, Window,
|
||||
canvas, div, prelude::FluentBuilder, AnyElement, App, AppContext, Axis, Bounds, Context,
|
||||
Element, ElementId, Empty, Entity, EventEmitter, InteractiveElement as _, IntoElement, IsZero,
|
||||
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Render, RenderOnce, Style, Styled, Window,
|
||||
};
|
||||
|
||||
use crate::{h_flex, v_flex, AxisExt};
|
||||
use crate::{h_flex, resizable::PANEL_MIN_SIZE, v_flex, AxisExt};
|
||||
|
||||
use super::{resizable_panel, resize_handle};
|
||||
|
||||
pub(crate) const PANEL_MIN_SIZE: Pixels = px(100.);
|
||||
use super::{resizable_panel, resize_handle, ResizableState};
|
||||
|
||||
pub enum ResizablePanelEvent {
|
||||
Resized,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DragPanel(pub (EntityId, usize, Axis));
|
||||
pub struct DragPanel(pub (usize, Axis));
|
||||
|
||||
impl Render for DragPanel {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
|
||||
|
|
@ -28,58 +23,53 @@ impl Render for DragPanel {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(IntoElement)]
|
||||
pub struct ResizablePanelGroup {
|
||||
panels: Vec<Entity<ResizablePanel>>,
|
||||
sizes: Vec<Pixels>,
|
||||
id: ElementId,
|
||||
state: Entity<ResizableState>,
|
||||
axis: Axis,
|
||||
size: Option<Pixels>,
|
||||
|
||||
bounds: Bounds<Pixels>,
|
||||
resizing_panel_ix: Option<usize>,
|
||||
children: Vec<ResizablePanel>,
|
||||
}
|
||||
|
||||
impl ResizablePanelGroup {
|
||||
pub(super) fn new() -> Self {
|
||||
pub(crate) fn new(id: impl Into<ElementId>, state: Entity<ResizableState>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
axis: Axis::Horizontal,
|
||||
sizes: Vec::new(),
|
||||
panels: Vec::new(),
|
||||
children: vec![],
|
||||
state,
|
||||
size: None,
|
||||
bounds: Bounds::default(),
|
||||
resizing_panel_ix: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(&mut self, sizes: Vec<Pixels>, panels: Vec<Entity<ResizablePanel>>) {
|
||||
self.sizes = sizes;
|
||||
self.panels = panels;
|
||||
}
|
||||
|
||||
/// Set the axis of the resizable panel group, default is horizontal.
|
||||
pub fn axis(mut self, axis: Axis) -> Self {
|
||||
self.axis = axis;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn set_axis(&mut self, axis: Axis, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.axis = axis;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Add a panel to the group.
|
||||
///
|
||||
/// - The `axis` will be set to the same axis as the group.
|
||||
/// - The `initial_size` will be set to the average size of all panels if not provided.
|
||||
/// - The `group` will be set to the group entity.
|
||||
pub fn child(mut self, panel: ResizablePanel, cx: &mut Context<Self>) -> Self {
|
||||
self._insert_child(panel, self.panels.len(), cx);
|
||||
pub fn child(mut self, panel: impl Into<ResizablePanel>) -> Self {
|
||||
self.children.push(panel.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn children<I>(mut self, panels: impl IntoIterator<Item = I>) -> Self
|
||||
where
|
||||
I: Into<ResizablePanel>,
|
||||
{
|
||||
self.children = panels.into_iter().map(|panel| panel.into()).collect();
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a ResizablePanelGroup as a child to the group.
|
||||
pub fn group(self, group: ResizablePanelGroup, cx: &mut Context<Self>) -> Self {
|
||||
self.child(resizable_panel().content_view(cx.new(|_| group).into()), cx)
|
||||
pub fn group(self, group: ResizablePanelGroup) -> Self {
|
||||
self.child(resizable_panel().child(group.into_any_element()))
|
||||
}
|
||||
|
||||
/// Set size of the resizable panel group
|
||||
|
|
@ -90,293 +80,95 @@ impl ResizablePanelGroup {
|
|||
self.size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the sizes of the resizable panels.
|
||||
pub(crate) fn sizes(&self) -> Vec<Pixels> {
|
||||
self.sizes.clone()
|
||||
}
|
||||
|
||||
/// Calculates the sum of all panel sizes within the group.
|
||||
pub fn total_size(&self) -> Pixels {
|
||||
self.sizes.iter().fold(px(0.0), |acc, &size| acc + size)
|
||||
}
|
||||
|
||||
/// Insert child to panel group.
|
||||
///
|
||||
/// - The `ix` is the index of the panel to insert.
|
||||
/// - The `axis` will be set to the same axis as the group.
|
||||
/// - The `initial_size` will be set to the average size of all panels if not provided.
|
||||
/// - The `group` will be set to the group entity.
|
||||
pub fn insert_child(
|
||||
&mut self,
|
||||
panel: ResizablePanel,
|
||||
ix: usize,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self._insert_child(panel, ix, cx);
|
||||
window.on_next_frame({
|
||||
let view = cx.entity();
|
||||
move |window, cx| {
|
||||
view.update(cx, |this, cx| {
|
||||
this.sync_real_panel_sizes(window, cx);
|
||||
})
|
||||
}
|
||||
});
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn _insert_child(&mut self, panel: ResizablePanel, ix: usize, cx: &mut Context<Self>) {
|
||||
let mut panel = panel;
|
||||
panel.axis = self.axis;
|
||||
panel.group = Some(cx.entity().downgrade());
|
||||
let initial_size = match panel.initial_size {
|
||||
// Use the initial size if provided.
|
||||
Some(size) => size,
|
||||
// Split to add child, use average size of all panels
|
||||
None => (self.total_size() / (self.panels.len() + 1) as f32).max(PANEL_MIN_SIZE),
|
||||
};
|
||||
|
||||
// Here we need allows `initial_size` is none, for some children use flex auto size.
|
||||
|
||||
self.sizes.insert(ix, initial_size);
|
||||
self.panels.insert(ix, cx.new(|_| panel));
|
||||
}
|
||||
|
||||
/// Replace a child panel with a new panel at the given index.
|
||||
pub(crate) fn replace_child(
|
||||
&mut self,
|
||||
panel: ResizablePanel,
|
||||
ix: usize,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let old_panel = self.panels[ix].read(cx);
|
||||
|
||||
let mut panel = panel;
|
||||
panel.initial_size = old_panel.initial_size;
|
||||
panel.size = old_panel.size;
|
||||
panel.axis = self.axis;
|
||||
panel.group = Some(cx.entity().downgrade());
|
||||
|
||||
self.panels[ix] = cx.new(|_| panel);
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
pub fn remove_child(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.sizes.remove(ix);
|
||||
self.panels.remove(ix);
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
pub(crate) fn remove_all_children(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.sizes.clear();
|
||||
self.panels.clear();
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn render_resize_handle(
|
||||
&self,
|
||||
ix: usize,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let view = cx.entity().clone();
|
||||
resize_handle(("resizable-handle", ix), self.axis).on_drag(
|
||||
DragPanel((cx.entity_id(), ix, self.axis)),
|
||||
move |drag_panel, _, _, cx| {
|
||||
cx.stop_propagation();
|
||||
// Set current resizing panel ix
|
||||
view.update(cx, |view, _| {
|
||||
view.resizing_panel_ix = Some(ix);
|
||||
});
|
||||
cx.new(|_| drag_panel.deref().clone())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn done_resizing(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.emit(ResizablePanelEvent::Resized);
|
||||
self.resizing_panel_ix = None;
|
||||
}
|
||||
|
||||
fn sync_real_panel_sizes(&mut self, _: &Window, cx: &App) {
|
||||
for (i, panel) in self.panels.iter().enumerate() {
|
||||
self.sizes[i] = panel.read(cx).bounds.size.along(self.axis).floor();
|
||||
}
|
||||
}
|
||||
|
||||
fn panel_size_range(&self, ix: usize, cx: &App) -> Range<Pixels> {
|
||||
let Some(panel) = self.panels.get(ix) else {
|
||||
return PANEL_MIN_SIZE..Pixels::MAX;
|
||||
};
|
||||
|
||||
panel.read(cx).size_range.clone()
|
||||
}
|
||||
|
||||
/// The `ix`` is the index of the panel to resize,
|
||||
/// and the `size` is the new size for the panel.
|
||||
fn resize_panels(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
size: Pixels,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut ix = ix;
|
||||
// Only resize the left panels.
|
||||
if ix >= self.panels.len() - 1 {
|
||||
return;
|
||||
}
|
||||
let size = size.floor();
|
||||
let container_size = self.bounds.size.along(self.axis);
|
||||
self.sync_real_panel_sizes(window, cx);
|
||||
|
||||
let move_changed = size - self.sizes[ix];
|
||||
if move_changed == px(0.) {
|
||||
return;
|
||||
}
|
||||
let size_range = self.panel_size_range(ix, cx);
|
||||
let new_size = size.clamp(size_range.start, size_range.end);
|
||||
let is_expand = move_changed > px(0.);
|
||||
|
||||
let main_ix = ix;
|
||||
let mut new_sizes = self.sizes.clone();
|
||||
|
||||
if is_expand {
|
||||
let mut changed = new_size - self.sizes[ix];
|
||||
new_sizes[ix] = new_size;
|
||||
|
||||
while changed > px(0.) && ix < self.panels.len() - 1 {
|
||||
ix += 1;
|
||||
let size_range = self.panel_size_range(ix, cx);
|
||||
let available_size = (new_sizes[ix] - size_range.start).max(px(0.));
|
||||
let to_reduce = changed.min(available_size);
|
||||
new_sizes[ix] -= to_reduce;
|
||||
changed -= to_reduce;
|
||||
}
|
||||
} else {
|
||||
let mut changed = new_size - size;
|
||||
new_sizes[ix + 1] += self.sizes[ix] - new_size;
|
||||
new_sizes[ix] = new_size;
|
||||
|
||||
while changed > px(0.) && ix > 0 {
|
||||
ix -= 1;
|
||||
let size_range = self.panel_size_range(ix, cx);
|
||||
let available_size = (new_sizes[ix] - size_range.start).max(px(0.));
|
||||
let to_reduce = changed.min(available_size);
|
||||
changed -= to_reduce;
|
||||
new_sizes[ix] -= to_reduce;
|
||||
}
|
||||
}
|
||||
|
||||
// If total size exceeds container size, adjust the main panel
|
||||
let total_size: Pixels = new_sizes.iter().map(|s| s.0).sum::<f32>().into();
|
||||
if total_size > container_size {
|
||||
let overflow = total_size - container_size;
|
||||
new_sizes[main_ix] = (new_sizes[main_ix] - overflow).max(size_range.start);
|
||||
}
|
||||
|
||||
for (i, panel) in self.panels.iter().enumerate() {
|
||||
let size = new_sizes[i];
|
||||
let is_changed = self.sizes[i] != size;
|
||||
if size > px(0.) && is_changed {
|
||||
panel.update(cx, |this, _| {
|
||||
this.size = Some(size);
|
||||
});
|
||||
}
|
||||
}
|
||||
self.sizes = new_sizes;
|
||||
}
|
||||
impl<T> From<T> for ResizablePanel
|
||||
where
|
||||
T: Into<AnyElement>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
resizable_panel().child(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ResizablePanelEvent> for ResizablePanelGroup {}
|
||||
impl Render for ResizablePanelGroup {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let view = cx.entity().clone();
|
||||
|
||||
impl RenderOnce for ResizablePanelGroup {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let state = self.state.clone();
|
||||
let container = if self.axis.is_horizontal() {
|
||||
h_flex()
|
||||
} else {
|
||||
v_flex()
|
||||
};
|
||||
|
||||
container
|
||||
.size_full()
|
||||
.children(self.panels.iter().enumerate().map(|(ix, panel)| {
|
||||
if ix > 0 {
|
||||
let handle = self.render_resize_handle(ix - 1, window, cx);
|
||||
panel.update(cx, |view, _| {
|
||||
view.resize_handle = Some(handle.into_any_element())
|
||||
});
|
||||
}
|
||||
// Sync panels to the state
|
||||
let panels_count = self.children.len();
|
||||
self.state.update(cx, |state, _| {
|
||||
state.sync_panels_count(self.axis, panels_count);
|
||||
});
|
||||
|
||||
panel.clone()
|
||||
}))
|
||||
container
|
||||
.id(self.id)
|
||||
.size_full()
|
||||
.children(
|
||||
self.children
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(ix, mut panel)| {
|
||||
panel.panel_ix = ix;
|
||||
panel.axis = self.axis;
|
||||
panel.state = Some(self.state.clone());
|
||||
panel
|
||||
}),
|
||||
)
|
||||
.child({
|
||||
canvas(
|
||||
move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|
||||
move |bounds, _, cx| state.update(cx, |state, _| state.bounds = bounds),
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.size_full()
|
||||
})
|
||||
.child(ResizePanelGroupElement {
|
||||
view: cx.entity().clone(),
|
||||
state: self.state.clone(),
|
||||
axis: self.axis,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ResizablePanel {
|
||||
group: Option<WeakEntity<ResizablePanelGroup>>,
|
||||
axis: Axis,
|
||||
panel_ix: usize,
|
||||
state: Option<Entity<ResizableState>>,
|
||||
/// Initial size is the size that the panel has when it is created.
|
||||
initial_size: Option<Pixels>,
|
||||
/// size is the size that the panel has when it is resized or adjusted by flex layout.
|
||||
size: Option<Pixels>,
|
||||
/// size range limit of this panel.
|
||||
size_range: Range<Pixels>,
|
||||
axis: Axis,
|
||||
content_builder: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>>,
|
||||
content_view: Option<AnyView>,
|
||||
content_visible: Rc<Box<dyn Fn(&Window, &App) -> bool>>,
|
||||
/// The bounds of the resizable panel, when render the bounds will be updated.
|
||||
bounds: Bounds<Pixels>,
|
||||
resize_handle: Option<AnyElement>,
|
||||
children: Vec<AnyElement>,
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
impl ResizablePanel {
|
||||
pub(super) fn new() -> Self {
|
||||
Self {
|
||||
group: None,
|
||||
panel_ix: 0,
|
||||
initial_size: None,
|
||||
size: None,
|
||||
state: None,
|
||||
size_range: (PANEL_MIN_SIZE..Pixels::MAX),
|
||||
axis: Axis::Horizontal,
|
||||
content_builder: None,
|
||||
content_view: None,
|
||||
content_visible: Rc::new(Box::new(|_, _| true)),
|
||||
bounds: Bounds::default(),
|
||||
resize_handle: None,
|
||||
children: vec![],
|
||||
visible: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content<F>(mut self, content: F) -> Self
|
||||
where
|
||||
F: Fn(&mut Window, &mut App) -> AnyElement + 'static,
|
||||
{
|
||||
self.content_builder = Some(Rc::new(content));
|
||||
pub fn child(mut self, child: impl IntoElement) -> Self {
|
||||
self.children.push(child.into_any_element());
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn content_visible<F>(mut self, content_visible: F) -> Self
|
||||
where
|
||||
F: Fn(&Window, &App) -> bool + 'static,
|
||||
{
|
||||
self.content_visible = Rc::new(Box::new(content_visible));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn content_view(mut self, content: AnyView) -> Self {
|
||||
self.content_view = Some(content);
|
||||
pub fn visible(mut self, visible: bool) -> Self {
|
||||
self.visible = visible;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -393,43 +185,26 @@ impl ResizablePanel {
|
|||
self.size_range = range.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Save the real panel size, and update group sizes
|
||||
fn update_size(&mut self, bounds: Bounds<Pixels>, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.bounds = bounds;
|
||||
let new_size = bounds.size.along(self.axis);
|
||||
if self.size == Some(new_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.size = Some(new_size);
|
||||
let entity_id = cx.entity().entity_id();
|
||||
if let Some(group) = self.group.as_ref() {
|
||||
_ = group.update(cx, |view, _| {
|
||||
if let Some(ix) = view.panels.iter().position(|v| v.entity_id() == entity_id) {
|
||||
view.sizes[ix] = new_size;
|
||||
}
|
||||
});
|
||||
}
|
||||
// cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
impl FluentBuilder for ResizablePanel {}
|
||||
|
||||
impl Render for ResizablePanel {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
if !(self.content_visible)(window, cx) {
|
||||
// To keep size as initial size, to make sure the size will not be changed.
|
||||
self.initial_size = self.size;
|
||||
self.size = None;
|
||||
return div();
|
||||
impl RenderOnce for ResizablePanel {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
if !self.visible {
|
||||
return div().id(("resizable-panel", self.panel_ix));
|
||||
}
|
||||
|
||||
let view = cx.entity().clone();
|
||||
let state = self
|
||||
.state
|
||||
.expect("BUG: The `state` in ResizablePanel should be present.");
|
||||
let panel_state = state
|
||||
.read(cx)
|
||||
.panels
|
||||
.get(self.panel_ix)
|
||||
.expect("BUG: The `index` of ResizablePanel should be one of in `state`.");
|
||||
let size_range = self.size_range.clone();
|
||||
|
||||
div()
|
||||
.id(("resizable-panel", self.panel_ix))
|
||||
.flex()
|
||||
.flex_grow()
|
||||
.size_full()
|
||||
|
|
@ -447,36 +222,52 @@ impl Render for ResizablePanel {
|
|||
.when_some(self.initial_size, |this, initial_size| {
|
||||
// The `self.size` is None, that mean the initial size for the panel,
|
||||
// so we need set `flex_shrink_0` To let it keep the initial size.
|
||||
this.when(self.size.is_none() && !initial_size.is_zero(), |this| {
|
||||
this.flex_shrink_0()
|
||||
})
|
||||
this.when(
|
||||
panel_state.size.is_none() && !initial_size.is_zero(),
|
||||
|this| this.flex_none(),
|
||||
)
|
||||
.flex_basis(initial_size)
|
||||
})
|
||||
.map(|this| match self.size {
|
||||
.map(|this| match panel_state.size {
|
||||
Some(size) => this.flex_basis(size),
|
||||
None => this,
|
||||
})
|
||||
.child({
|
||||
canvas(
|
||||
move |bounds, window, cx| {
|
||||
view.update(cx, |r, cx| r.update_size(bounds, window, cx))
|
||||
{
|
||||
let state = state.clone();
|
||||
move |bounds, _, cx| {
|
||||
state.update(cx, |state, cx| {
|
||||
state.update_panel_size(self.panel_ix, bounds, self.size_range, cx)
|
||||
})
|
||||
}
|
||||
},
|
||||
|_, _, _, _| {},
|
||||
)
|
||||
.absolute()
|
||||
.size_full()
|
||||
})
|
||||
.when_some(self.content_builder.clone(), |this, c| {
|
||||
this.child(c(window, cx))
|
||||
.children(self.children)
|
||||
.when(self.panel_ix > 0, |this| {
|
||||
let ix = self.panel_ix - 1;
|
||||
this.child(resize_handle(("resizable-handle", ix), self.axis).on_drag(
|
||||
DragPanel((ix, self.axis)),
|
||||
move |drag_panel, _, _, cx| {
|
||||
cx.stop_propagation();
|
||||
// Set current resizing panel ix
|
||||
state.update(cx, |state, _| {
|
||||
state.resizing_panel_ix = Some(ix);
|
||||
});
|
||||
cx.new(|_| drag_panel.deref().clone())
|
||||
},
|
||||
))
|
||||
})
|
||||
.when_some(self.content_view.clone(), |this, c| this.child(c))
|
||||
.when_some(self.resize_handle.take(), |this, c| this.child(c))
|
||||
}
|
||||
}
|
||||
|
||||
struct ResizePanelGroupElement {
|
||||
state: Entity<ResizableState>,
|
||||
axis: Axis,
|
||||
view: Entity<ResizablePanelGroup>,
|
||||
}
|
||||
|
||||
impl IntoElement for ResizePanelGroupElement {
|
||||
|
|
@ -525,44 +316,41 @@ impl Element for ResizePanelGroupElement {
|
|||
cx: &mut App,
|
||||
) {
|
||||
window.on_mouse_event({
|
||||
let view = self.view.clone();
|
||||
let state = self.state.clone();
|
||||
let axis = self.axis;
|
||||
let current_ix = view.read(cx).resizing_panel_ix;
|
||||
let current_ix = state.read(cx).resizing_panel_ix;
|
||||
move |e: &MouseMoveEvent, phase, window, cx| {
|
||||
if !phase.bubble() {
|
||||
return;
|
||||
}
|
||||
let Some(ix) = current_ix else { return };
|
||||
|
||||
view.update(cx, |view, cx| {
|
||||
let panel = view
|
||||
.panels
|
||||
.get(ix)
|
||||
.expect("BUG: invalid panel index")
|
||||
.read(cx);
|
||||
state.update(cx, |state, cx| {
|
||||
let panel = state.panels.get(ix).expect("BUG: invalid panel index");
|
||||
|
||||
match axis {
|
||||
Axis::Horizontal => {
|
||||
view.resize_panels(ix, e.position.x - panel.bounds.left(), window, cx)
|
||||
state.resize_panel(ix, e.position.x - panel.bounds.left(), window, cx)
|
||||
}
|
||||
Axis::Vertical => {
|
||||
view.resize_panels(ix, e.position.y - panel.bounds.top(), window, cx);
|
||||
state.resize_panel(ix, e.position.y - panel.bounds.top(), window, cx);
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// When any mouse up, stop dragging
|
||||
window.on_mouse_event({
|
||||
let view = self.view.clone();
|
||||
let current_ix = view.read(cx).resizing_panel_ix;
|
||||
move |_: &MouseUpEvent, phase, window, cx| {
|
||||
let state = self.state.clone();
|
||||
let current_ix = state.read(cx).resizing_panel_ix;
|
||||
move |_: &MouseUpEvent, phase, _, cx| {
|
||||
if current_ix.is_none() {
|
||||
return;
|
||||
}
|
||||
if phase.bubble() {
|
||||
view.update(cx, |view, cx| view.done_resizing(window, cx));
|
||||
state.update(cx, |state, cx| state.done_resizing(cx));
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ use crate::{
|
|||
v_flex, ActiveTheme, Collapsible, Icon, IconName, Side, Sizable, StyledExt,
|
||||
};
|
||||
use gpui::{
|
||||
div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, InteractiveElement as _,
|
||||
IntoElement, ParentElement, Pixels, RenderOnce, Styled, Window,
|
||||
div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, DefiniteLength,
|
||||
InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce, Styled, Window,
|
||||
};
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -33,7 +33,8 @@ pub struct Sidebar<E: Collapsible + IntoElement + 'static> {
|
|||
/// The side of the sidebar
|
||||
side: Side,
|
||||
collapsible: bool,
|
||||
width: Pixels,
|
||||
width: DefiniteLength,
|
||||
border_width: Pixels,
|
||||
collapsed: bool,
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +46,8 @@ impl<E: Collapsible + IntoElement> Sidebar<E> {
|
|||
footer: None,
|
||||
side,
|
||||
collapsible: true,
|
||||
width: DEFAULT_WIDTH,
|
||||
width: DEFAULT_WIDTH.into(),
|
||||
border_width: px(1.),
|
||||
collapsed: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -59,8 +61,14 @@ impl<E: Collapsible + IntoElement> Sidebar<E> {
|
|||
}
|
||||
|
||||
/// Set the width of the sidebar
|
||||
pub fn width(mut self, width: Pixels) -> Self {
|
||||
self.width = width;
|
||||
pub fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
|
||||
self.width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set border width of the sidebar
|
||||
pub fn border_width(mut self, border_width: impl Into<Pixels>) -> Self {
|
||||
self.border_width = border_width.into();
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -191,8 +199,8 @@ impl<E: Collapsible + IntoElement> RenderOnce for Sidebar<E> {
|
|||
.text_color(cx.theme().sidebar_foreground)
|
||||
.border_color(cx.theme().sidebar_border)
|
||||
.map(|this| match self.side {
|
||||
Side::Left => this.border_r_1(),
|
||||
Side::Right => this.border_l_1(),
|
||||
Side::Left => this.border_r(self.border_width),
|
||||
Side::Right => this.border_l(self.border_width),
|
||||
})
|
||||
.when_some(self.header.take(), |this, header| {
|
||||
this.child(h_flex().id("header").p_2().gap_2().child(header))
|
||||
|
|
|
|||
Loading…
Reference in a new issue