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::{
|
use gpui_component::{
|
||||||
h_flex,
|
h_flex,
|
||||||
input::{InputEvent, TextInput},
|
input::{InputEvent, TextInput},
|
||||||
|
resizable::{h_resizable, resizable_panel, ResizableState},
|
||||||
sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem},
|
sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem},
|
||||||
v_flex, ActiveTheme as _, Icon, IconName,
|
v_flex, ActiveTheme as _, Icon, IconName,
|
||||||
};
|
};
|
||||||
|
|
@ -13,6 +14,7 @@ pub struct Gallery {
|
||||||
active_index: Option<usize>,
|
active_index: Option<usize>,
|
||||||
collapsed: bool,
|
collapsed: bool,
|
||||||
search_input: Entity<TextInput>,
|
search_input: Entity<TextInput>,
|
||||||
|
sidebar_state: Entity<ResizableState>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,6 +88,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -129,89 +132,103 @@ impl Render for Gallery {
|
||||||
("".into(), "".into())
|
("".into(), "".into())
|
||||||
};
|
};
|
||||||
|
|
||||||
h_flex()
|
h_resizable("gallery-container", self.sidebar_state.clone())
|
||||||
.id("gallery-container")
|
|
||||||
.size_full()
|
|
||||||
.child(
|
.child(
|
||||||
Sidebar::left()
|
resizable_panel()
|
||||||
.collapsed(self.collapsed)
|
.size(px(255.))
|
||||||
.header(
|
.size_range(px(200.)..px(320.))
|
||||||
v_flex()
|
.child(
|
||||||
.w_full()
|
Sidebar::left()
|
||||||
.gap_4()
|
.width(relative(1.))
|
||||||
.child(
|
.border_width(px(0.))
|
||||||
SidebarHeader::new()
|
.collapsed(self.collapsed)
|
||||||
|
.header(
|
||||||
|
v_flex()
|
||||||
.w_full()
|
.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(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.bg(cx.theme().sidebar_border)
|
||||||
.items_center()
|
.px_1()
|
||||||
.justify_center()
|
.rounded_full()
|
||||||
.rounded(cx.theme().radius)
|
.flex_1()
|
||||||
.bg(cx.theme().primary)
|
.mx_1()
|
||||||
.text_color(cx.theme().primary_foreground)
|
.child(self.search_input.clone()),
|
||||||
.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(
|
.children(stories.clone().into_iter().enumerate().map(
|
||||||
div()
|
|(group_ix, (group_name, sub_stories))| {
|
||||||
.bg(cx.theme().sidebar_border)
|
SidebarGroup::new(*group_name).child(
|
||||||
.px_1()
|
SidebarMenu::new().children(
|
||||||
.rounded_full()
|
sub_stories.iter().enumerate().map(|(ix, story)| {
|
||||||
.flex_1()
|
SidebarMenuItem::new(story.read(cx).name.clone())
|
||||||
.mx_1()
|
.active(
|
||||||
.child(self.search_input.clone()),
|
self.active_group_index == Some(group_ix)
|
||||||
),
|
&& self.active_index == Some(ix),
|
||||||
)
|
)
|
||||||
.children(stories.clone().into_iter().enumerate().map(
|
.on_click(cx.listener(
|
||||||
|(group_ix, (group_name, sub_stories))| {
|
move |this, _: &ClickEvent, _, cx| {
|
||||||
SidebarGroup::new(*group_name).child(SidebarMenu::new().children(
|
this.active_group_index =
|
||||||
sub_stories.iter().enumerate().map(|(ix, story)| {
|
Some(group_ix);
|
||||||
SidebarMenuItem::new(story.read(cx).name.clone())
|
this.active_index = Some(ix);
|
||||||
.active(
|
cx.notify();
|
||||||
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(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
|
|
@ -245,7 +262,8 @@ impl Render for Gallery {
|
||||||
.when_some(active_story, |this, active_story| {
|
.when_some(active_story, |this, active_story| {
|
||||||
this.child(active_story.clone())
|
this.child(active_story.clone())
|
||||||
}),
|
}),
|
||||||
),
|
)
|
||||||
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,15 @@ use gpui::{
|
||||||
ParentElement as _, Pixels, Render, SharedString, Styled, Window,
|
ParentElement as _, Pixels, Render, SharedString, Styled, Window,
|
||||||
};
|
};
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
resizable::{h_resizable, resizable_panel, v_resizable, ResizablePanelGroup},
|
resizable::{h_resizable, resizable_panel, v_resizable, ResizableState},
|
||||||
v_flex, ActiveTheme,
|
v_flex, ActiveTheme,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ResizableStory {
|
pub struct ResizableStory {
|
||||||
focus_handle: gpui::FocusHandle,
|
focus_handle: gpui::FocusHandle,
|
||||||
group1: Entity<ResizablePanelGroup>,
|
state1: Entity<ResizableState>,
|
||||||
group2: Entity<ResizablePanelGroup>,
|
state2: Entity<ResizableState>,
|
||||||
|
state3: Entity<ResizableState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Story for ResizableStory {
|
impl super::Story for ResizableStory {
|
||||||
|
|
@ -39,81 +40,71 @@ impl ResizableStory {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(_: &mut Window, cx: &mut App) -> Self {
|
fn new(_: &mut Window, cx: &mut App) -> Self {
|
||||||
fn panel_box(content: impl Into<SharedString>, cx: &App) -> AnyElement {
|
let state1 = ResizableState::new(cx);
|
||||||
div()
|
let state2 = ResizableState::new(cx);
|
||||||
.p_4()
|
let state3 = ResizableState::new(cx);
|
||||||
.border_1()
|
|
||||||
.border_color(cx.theme().border)
|
|
||||||
.size_full()
|
|
||||||
.child(content.into())
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
group1,
|
state1,
|
||||||
group2,
|
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 {
|
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()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.gap_6()
|
.gap_6()
|
||||||
.child(div().h(px(800.)).child(self.group1.clone()))
|
.child(
|
||||||
.child(self.group2.clone())
|
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,
|
dock::PanelInfo,
|
||||||
h_flex,
|
h_flex,
|
||||||
resizable::{
|
resizable::{
|
||||||
h_resizable, resizable_panel, v_resizable, ResizablePanel, ResizablePanelEvent,
|
resizable_panel, ResizablePanelEvent, ResizablePanelGroup, ResizablePanelState,
|
||||||
ResizablePanelGroup, PANEL_MIN_SIZE,
|
ResizableState, PANEL_MIN_SIZE,
|
||||||
},
|
},
|
||||||
ActiveTheme, AxisExt as _, Placement,
|
ActiveTheme, AxisExt as _, Placement,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{DockArea, Panel, PanelEvent, PanelState, PanelView, TabPanel};
|
use super::{DockArea, Panel, PanelEvent, PanelState, PanelView, TabPanel};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
prelude::FluentBuilder as _, App, AppContext, Axis, Context, DismissEvent, Entity,
|
App, Axis, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
|
||||||
EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Pixels, Render, Styled,
|
ParentElement, Pixels, Render, Styled, Subscription, WeakEntity, Window,
|
||||||
Subscription, WeakEntity, Window,
|
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
|
@ -23,7 +22,7 @@ pub struct StackPanel {
|
||||||
pub(super) axis: Axis,
|
pub(super) axis: Axis,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
pub(crate) panels: SmallVec<[Arc<dyn PanelView>; 2]>,
|
pub(crate) panels: SmallVec<[Arc<dyn PanelView>; 2]>,
|
||||||
panel_group: Entity<ResizablePanelGroup>,
|
state: Entity<ResizableState>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,7 +40,7 @@ impl Panel for StackPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn dump(&self, cx: &App) -> PanelState {
|
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);
|
let mut state = PanelState::new(self);
|
||||||
for panel in &self.panels {
|
for panel in &self.panels {
|
||||||
state.add_child(panel.dump(cx));
|
state.add_child(panel.dump(cx));
|
||||||
|
|
@ -54,26 +53,19 @@ 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 panel_group = cx.new(|_| {
|
let state = ResizableState::new(cx);
|
||||||
if axis == Axis::Horizontal {
|
|
||||||
h_resizable()
|
|
||||||
} else {
|
|
||||||
v_resizable()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bubble up the resize event.
|
// Bubble up the resize event.
|
||||||
let _subscriptions = vec![cx
|
let _subscriptions = vec![cx.subscribe(&state, |_, _, _: &ResizablePanelEvent, cx| {
|
||||||
.subscribe(&panel_group, |_, _, _: &ResizablePanelEvent, cx| {
|
cx.emit(PanelEvent::LayoutChanged)
|
||||||
cx.emit(PanelEvent::LayoutChanged)
|
})];
|
||||||
})];
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
axis,
|
axis,
|
||||||
parent: None,
|
parent: None,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
panels: SmallVec::new(),
|
panels: SmallVec::new(),
|
||||||
panel_group,
|
state,
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -186,13 +178,6 @@ impl StackPanel {
|
||||||
self.insert_panel(panel, ix + 1, size, dock_area, window, cx);
|
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(
|
fn insert_panel(
|
||||||
&mut self,
|
&mut self,
|
||||||
panel: Arc<dyn PanelView>,
|
panel: Arc<dyn PanelView>,
|
||||||
|
|
@ -242,22 +227,15 @@ impl StackPanel {
|
||||||
let size = match size {
|
let size = match size {
|
||||||
Some(size) => size,
|
Some(size) => size,
|
||||||
None => {
|
None => {
|
||||||
let panel_group = self.panel_group.read(cx);
|
let state = self.state.read(cx);
|
||||||
(panel_group.total_size() / (panel_group.sizes().len() + 1) as f32)
|
(state.total_size() / (state.sizes().len() + 1) as f32).max(PANEL_MIN_SIZE)
|
||||||
.max(PANEL_MIN_SIZE)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.panels.insert(ix, panel.clone());
|
self.panels.insert(ix, panel.clone());
|
||||||
self.panel_group.update(cx, |view, cx| {
|
self.state.update(cx, |state, cx| {
|
||||||
view.insert_child(
|
state.insert_panel(Some(size), Some(ix), cx);
|
||||||
Self::new_resizable_panel(panel.clone(), Some(size)),
|
|
||||||
ix,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.emit(PanelEvent::LayoutChanged);
|
cx.emit(PanelEvent::LayoutChanged);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
@ -271,8 +249,8 @@ impl StackPanel {
|
||||||
) {
|
) {
|
||||||
if let Some(ix) = self.index_of_panel(panel.clone()) {
|
if let Some(ix) = self.index_of_panel(panel.clone()) {
|
||||||
self.panels.remove(ix);
|
self.panels.remove(ix);
|
||||||
self.panel_group.update(cx, |view, cx| {
|
self.state.update(cx, |state, cx| {
|
||||||
view.remove_child(ix, window, cx);
|
state.remove_panel(ix, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.emit(PanelEvent::LayoutChanged);
|
cx.emit(PanelEvent::LayoutChanged);
|
||||||
|
|
@ -287,18 +265,15 @@ impl StackPanel {
|
||||||
&mut self,
|
&mut self,
|
||||||
old_panel: Arc<dyn PanelView>,
|
old_panel: Arc<dyn PanelView>,
|
||||||
new_panel: Entity<StackPanel>,
|
new_panel: Entity<StackPanel>,
|
||||||
window: &mut Window,
|
_: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
if let Some(ix) = self.index_of_panel(old_panel.clone()) {
|
if let Some(ix) = self.index_of_panel(old_panel.clone()) {
|
||||||
self.panels[ix] = Arc::new(new_panel.clone());
|
self.panels[ix] = Arc::new(new_panel.clone());
|
||||||
self.panel_group.update(cx, |view, cx| {
|
|
||||||
view.replace_child(
|
let panel_state = ResizablePanelState::default();
|
||||||
Self::new_resizable_panel(Arc::new(new_panel.clone()), None),
|
self.state.update(cx, |state, cx| {
|
||||||
ix,
|
state.replace_panel(ix, panel_state, cx);
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
cx.emit(PanelEvent::LayoutChanged);
|
cx.emit(PanelEvent::LayoutChanged);
|
||||||
}
|
}
|
||||||
|
|
@ -387,17 +362,17 @@ impl StackPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove all panels from the stack.
|
/// 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.panels.clear();
|
||||||
self.panel_group
|
self.state.update(cx, |state, cx| {
|
||||||
.update(cx, |view, cx| view.remove_all_children(window, cx));
|
state.clear();
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the axis of the stack panel.
|
/// 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.axis = axis;
|
||||||
self.panel_group
|
|
||||||
.update(cx, |view, cx| view.set_axis(axis, window, cx));
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -415,6 +390,14 @@ impl Render for StackPanel {
|
||||||
.size_full()
|
.size_full()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.bg(cx.theme().tab_bar)
|
.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 panel;
|
||||||
mod resize_handle;
|
mod resize_handle;
|
||||||
pub use panel::*;
|
pub use panel::*;
|
||||||
pub(crate) use resize_handle::*;
|
pub(crate) use resize_handle::*;
|
||||||
|
|
||||||
pub fn h_resizable() -> ResizablePanelGroup {
|
pub(crate) const PANEL_MIN_SIZE: Pixels = px(100.);
|
||||||
ResizablePanelGroup::new().axis(Axis::Horizontal)
|
|
||||||
|
/// 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 {
|
/// Create a [`ResizablePanelGroup`] with vertical resizing
|
||||||
ResizablePanelGroup::new().axis(Axis::Vertical)
|
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 {
|
pub fn resizable_panel() -> ResizablePanel {
|
||||||
ResizablePanel::new()
|
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::{
|
use std::ops::{Deref, Range};
|
||||||
ops::{Deref, Range},
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
canvas, div, prelude::FluentBuilder, px, Along, AnyElement, AnyView, App, AppContext, Axis,
|
canvas, div, prelude::FluentBuilder, AnyElement, App, AppContext, Axis, Bounds, Context,
|
||||||
Bounds, Context, Element, Empty, Entity, EntityId, EventEmitter, IntoElement, IsZero,
|
Element, ElementId, Empty, Entity, EventEmitter, InteractiveElement as _, IntoElement, IsZero,
|
||||||
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Render, Style, Styled, WeakEntity, Window,
|
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};
|
use super::{resizable_panel, resize_handle, ResizableState};
|
||||||
|
|
||||||
pub(crate) const PANEL_MIN_SIZE: Pixels = px(100.);
|
|
||||||
|
|
||||||
pub enum ResizablePanelEvent {
|
pub enum ResizablePanelEvent {
|
||||||
Resized,
|
Resized,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DragPanel(pub (EntityId, usize, Axis));
|
pub struct DragPanel(pub (usize, Axis));
|
||||||
|
|
||||||
impl Render for DragPanel {
|
impl Render for DragPanel {
|
||||||
fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
|
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 {
|
pub struct ResizablePanelGroup {
|
||||||
panels: Vec<Entity<ResizablePanel>>,
|
id: ElementId,
|
||||||
sizes: Vec<Pixels>,
|
state: Entity<ResizableState>,
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
size: Option<Pixels>,
|
size: Option<Pixels>,
|
||||||
|
children: Vec<ResizablePanel>,
|
||||||
bounds: Bounds<Pixels>,
|
|
||||||
resizing_panel_ix: Option<usize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResizablePanelGroup {
|
impl ResizablePanelGroup {
|
||||||
pub(super) fn new() -> Self {
|
pub(crate) fn new(id: impl Into<ElementId>, state: Entity<ResizableState>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
id: id.into(),
|
||||||
axis: Axis::Horizontal,
|
axis: Axis::Horizontal,
|
||||||
sizes: Vec::new(),
|
children: vec![],
|
||||||
panels: Vec::new(),
|
state,
|
||||||
size: None,
|
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.
|
/// 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;
|
||||||
self
|
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.
|
/// Add a panel to the group.
|
||||||
///
|
///
|
||||||
/// - The `axis` will be set to the same axis as 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 `initial_size` will be set to the average size of all panels if not provided.
|
||||||
/// - The `group` will be set to the group entity.
|
/// - The `group` will be set to the group entity.
|
||||||
pub fn child(mut self, panel: ResizablePanel, cx: &mut Context<Self>) -> Self {
|
pub fn child(mut self, panel: impl Into<ResizablePanel>) -> Self {
|
||||||
self._insert_child(panel, self.panels.len(), cx);
|
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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a ResizablePanelGroup as a child to the group.
|
/// Add a ResizablePanelGroup as a child to the group.
|
||||||
pub fn group(self, group: ResizablePanelGroup, cx: &mut Context<Self>) -> Self {
|
pub fn group(self, group: ResizablePanelGroup) -> Self {
|
||||||
self.child(resizable_panel().content_view(cx.new(|_| group).into()), cx)
|
self.child(resizable_panel().child(group.into_any_element()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set size of the resizable panel group
|
/// Set size of the resizable panel group
|
||||||
|
|
@ -90,293 +80,95 @@ impl ResizablePanelGroup {
|
||||||
self.size = Some(size);
|
self.size = Some(size);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/// Returns the sizes of the resizable panels.
|
impl<T> From<T> for ResizablePanel
|
||||||
pub(crate) fn sizes(&self) -> Vec<Pixels> {
|
where
|
||||||
self.sizes.clone()
|
T: Into<AnyElement>,
|
||||||
}
|
{
|
||||||
|
fn from(value: T) -> Self {
|
||||||
/// Calculates the sum of all panel sizes within the group.
|
resizable_panel().child(value.into())
|
||||||
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 EventEmitter<ResizablePanelEvent> for ResizablePanelGroup {}
|
impl EventEmitter<ResizablePanelEvent> for ResizablePanelGroup {}
|
||||||
impl Render for ResizablePanelGroup {
|
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
impl RenderOnce for ResizablePanelGroup {
|
||||||
let view = cx.entity().clone();
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let state = self.state.clone();
|
||||||
let container = if self.axis.is_horizontal() {
|
let container = if self.axis.is_horizontal() {
|
||||||
h_flex()
|
h_flex()
|
||||||
} else {
|
} else {
|
||||||
v_flex()
|
v_flex()
|
||||||
};
|
};
|
||||||
|
|
||||||
container
|
// Sync panels to the state
|
||||||
.size_full()
|
let panels_count = self.children.len();
|
||||||
.children(self.panels.iter().enumerate().map(|(ix, panel)| {
|
self.state.update(cx, |state, _| {
|
||||||
if ix > 0 {
|
state.sync_panels_count(self.axis, panels_count);
|
||||||
let handle = self.render_resize_handle(ix - 1, window, cx);
|
});
|
||||||
panel.update(cx, |view, _| {
|
|
||||||
view.resize_handle = Some(handle.into_any_element())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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({
|
.child({
|
||||||
canvas(
|
canvas(
|
||||||
move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
|
move |bounds, _, cx| state.update(cx, |state, _| state.bounds = bounds),
|
||||||
|_, _, _, _| {},
|
|_, _, _, _| {},
|
||||||
)
|
)
|
||||||
.absolute()
|
.absolute()
|
||||||
.size_full()
|
.size_full()
|
||||||
})
|
})
|
||||||
.child(ResizePanelGroupElement {
|
.child(ResizePanelGroupElement {
|
||||||
view: cx.entity().clone(),
|
state: self.state.clone(),
|
||||||
axis: self.axis,
|
axis: self.axis,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
pub struct ResizablePanel {
|
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 is the size that the panel has when it is created.
|
||||||
initial_size: Option<Pixels>,
|
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 limit of this panel.
|
||||||
size_range: Range<Pixels>,
|
size_range: Range<Pixels>,
|
||||||
axis: Axis,
|
children: Vec<AnyElement>,
|
||||||
content_builder: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>>,
|
visible: bool,
|
||||||
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>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResizablePanel {
|
impl ResizablePanel {
|
||||||
pub(super) fn new() -> Self {
|
pub(super) fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
group: None,
|
panel_ix: 0,
|
||||||
initial_size: None,
|
initial_size: None,
|
||||||
size: None,
|
state: None,
|
||||||
size_range: (PANEL_MIN_SIZE..Pixels::MAX),
|
size_range: (PANEL_MIN_SIZE..Pixels::MAX),
|
||||||
axis: Axis::Horizontal,
|
axis: Axis::Horizontal,
|
||||||
content_builder: None,
|
children: vec![],
|
||||||
content_view: None,
|
visible: true,
|
||||||
content_visible: Rc::new(Box::new(|_, _| true)),
|
|
||||||
bounds: Bounds::default(),
|
|
||||||
resize_handle: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content<F>(mut self, content: F) -> Self
|
pub fn child(mut self, child: impl IntoElement) -> Self {
|
||||||
where
|
self.children.push(child.into_any_element());
|
||||||
F: Fn(&mut Window, &mut App) -> AnyElement + 'static,
|
|
||||||
{
|
|
||||||
self.content_builder = Some(Rc::new(content));
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn content_visible<F>(mut self, content_visible: F) -> Self
|
pub fn visible(mut self, visible: bool) -> Self {
|
||||||
where
|
self.visible = visible;
|
||||||
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);
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -393,43 +185,26 @@ impl ResizablePanel {
|
||||||
self.size_range = range.into();
|
self.size_range = range.into();
|
||||||
self
|
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 RenderOnce for ResizablePanel {
|
||||||
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
impl Render for ResizablePanel {
|
if !self.visible {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
return div().id(("resizable-panel", self.panel_ix));
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
let size_range = self.size_range.clone();
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
.id(("resizable-panel", self.panel_ix))
|
||||||
.flex()
|
.flex()
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
|
@ -447,36 +222,52 @@ impl Render for ResizablePanel {
|
||||||
.when_some(self.initial_size, |this, initial_size| {
|
.when_some(self.initial_size, |this, initial_size| {
|
||||||
// The `self.size` is None, that mean the initial size for the panel,
|
// 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.
|
// 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.when(
|
||||||
this.flex_shrink_0()
|
panel_state.size.is_none() && !initial_size.is_zero(),
|
||||||
})
|
|this| this.flex_none(),
|
||||||
|
)
|
||||||
.flex_basis(initial_size)
|
.flex_basis(initial_size)
|
||||||
})
|
})
|
||||||
.map(|this| match self.size {
|
.map(|this| match panel_state.size {
|
||||||
Some(size) => this.flex_basis(size),
|
Some(size) => this.flex_basis(size),
|
||||||
None => this,
|
None => this,
|
||||||
})
|
})
|
||||||
.child({
|
.child({
|
||||||
canvas(
|
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()
|
.absolute()
|
||||||
.size_full()
|
.size_full()
|
||||||
})
|
})
|
||||||
.when_some(self.content_builder.clone(), |this, c| {
|
.children(self.children)
|
||||||
this.child(c(window, cx))
|
.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 {
|
struct ResizePanelGroupElement {
|
||||||
|
state: Entity<ResizableState>,
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
view: Entity<ResizablePanelGroup>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for ResizePanelGroupElement {
|
impl IntoElement for ResizePanelGroupElement {
|
||||||
|
|
@ -525,44 +316,41 @@ impl Element for ResizePanelGroupElement {
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
window.on_mouse_event({
|
window.on_mouse_event({
|
||||||
let view = self.view.clone();
|
let state = self.state.clone();
|
||||||
let axis = self.axis;
|
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| {
|
move |e: &MouseMoveEvent, phase, window, cx| {
|
||||||
if !phase.bubble() {
|
if !phase.bubble() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some(ix) = current_ix else { return };
|
let Some(ix) = current_ix else { return };
|
||||||
|
|
||||||
view.update(cx, |view, cx| {
|
state.update(cx, |state, cx| {
|
||||||
let panel = view
|
let panel = state.panels.get(ix).expect("BUG: invalid panel index");
|
||||||
.panels
|
|
||||||
.get(ix)
|
|
||||||
.expect("BUG: invalid panel index")
|
|
||||||
.read(cx);
|
|
||||||
|
|
||||||
match axis {
|
match axis {
|
||||||
Axis::Horizontal => {
|
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 => {
|
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
|
// When any mouse up, stop dragging
|
||||||
window.on_mouse_event({
|
window.on_mouse_event({
|
||||||
let view = self.view.clone();
|
let state = self.state.clone();
|
||||||
let current_ix = view.read(cx).resizing_panel_ix;
|
let current_ix = state.read(cx).resizing_panel_ix;
|
||||||
move |_: &MouseUpEvent, phase, window, cx| {
|
move |_: &MouseUpEvent, phase, _, cx| {
|
||||||
if current_ix.is_none() {
|
if current_ix.is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if phase.bubble() {
|
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,
|
v_flex, ActiveTheme, Collapsible, Icon, IconName, Side, Sizable, StyledExt,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, InteractiveElement as _,
|
div, prelude::FluentBuilder, px, AnyElement, App, ClickEvent, DefiniteLength,
|
||||||
IntoElement, ParentElement, Pixels, RenderOnce, Styled, Window,
|
InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce, Styled, Window,
|
||||||
};
|
};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
|
@ -33,7 +33,8 @@ pub struct Sidebar<E: Collapsible + IntoElement + 'static> {
|
||||||
/// The side of the sidebar
|
/// The side of the sidebar
|
||||||
side: Side,
|
side: Side,
|
||||||
collapsible: bool,
|
collapsible: bool,
|
||||||
width: Pixels,
|
width: DefiniteLength,
|
||||||
|
border_width: Pixels,
|
||||||
collapsed: bool,
|
collapsed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +46,8 @@ impl<E: Collapsible + IntoElement> Sidebar<E> {
|
||||||
footer: None,
|
footer: None,
|
||||||
side,
|
side,
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
width: DEFAULT_WIDTH,
|
width: DEFAULT_WIDTH.into(),
|
||||||
|
border_width: px(1.),
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -59,8 +61,14 @@ impl<E: Collapsible + IntoElement> Sidebar<E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the width of the sidebar
|
/// Set the width of the sidebar
|
||||||
pub fn width(mut self, width: Pixels) -> Self {
|
pub fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
|
||||||
self.width = width;
|
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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,8 +199,8 @@ impl<E: Collapsible + IntoElement> RenderOnce for Sidebar<E> {
|
||||||
.text_color(cx.theme().sidebar_foreground)
|
.text_color(cx.theme().sidebar_foreground)
|
||||||
.border_color(cx.theme().sidebar_border)
|
.border_color(cx.theme().sidebar_border)
|
||||||
.map(|this| match self.side {
|
.map(|this| match self.side {
|
||||||
Side::Left => this.border_r_1(),
|
Side::Left => this.border_r(self.border_width),
|
||||||
Side::Right => this.border_l_1(),
|
Side::Right => this.border_l(self.border_width),
|
||||||
})
|
})
|
||||||
.when_some(self.header.take(), |this, header| {
|
.when_some(self.header.take(), |this, header| {
|
||||||
this.child(h_flex().id("header").p_2().gap_2().child(header))
|
this.child(h_flex().id("header").p_2().gap_2().child(header))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue