chore: migrate to gpui-base (#51)

Reviewed-on: #51
This commit was merged in pull request #51.
This commit is contained in:
2026-09-17 11:50:14 +00:00
parent 874f103752
commit 0c1c23fe9c
69 changed files with 2458 additions and 15637 deletions
-438
View File
@@ -1,438 +0,0 @@
use std::ops::Deref;
use std::sync::Arc;
use gpui::prelude::FluentBuilder as _;
use gpui::{
App, AppContext, Axis, Context, Element, Empty, Entity, IntoElement, MouseMoveEvent,
MouseUpEvent, ParentElement as _, Pixels, Point, Render, Style, StyleRefinement, Styled as _,
WeakEntity, Window, div, px,
};
use super::{DockArea, DockItem};
use crate::StyledExt;
use crate::dock::panel::PanelView;
use crate::dock::tab_panel::TabPanel;
use crate::resizable::{PANEL_MIN_SIZE, resize_handle};
#[derive(Clone)]
struct ResizePanel;
impl Render for ResizePanel {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Empty
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DockPlacement {
Center,
Left,
Bottom,
Right,
}
impl DockPlacement {
fn axis(&self) -> Axis {
match self {
Self::Left | Self::Right => Axis::Horizontal,
Self::Bottom => Axis::Vertical,
Self::Center => unreachable!(),
}
}
pub fn is_left(&self) -> bool {
matches!(self, Self::Left)
}
pub fn is_bottom(&self) -> bool {
matches!(self, Self::Bottom)
}
pub fn is_right(&self) -> bool {
matches!(self, Self::Right)
}
}
/// The Dock is a fixed container that places at left, bottom, right of the Windows.
///
/// This is unlike Panel, it can't be move or add any other panel.
pub struct Dock {
pub(super) placement: DockPlacement,
dock_area: WeakEntity<DockArea>,
/// Dock layout
pub(crate) panel: DockItem,
/// The size is means the width or height of the Dock, if the placement is left or right, the size is width, otherwise the size is height.
pub(super) size: Pixels,
/// Whether the Dock is open
pub(super) open: bool,
/// Whether the Dock is collapsible, default: true
pub(super) collapsible: bool,
/// Whether the Dock is resizing
resizing: bool,
}
impl Dock {
pub(crate) fn new(
dock_area: WeakEntity<DockArea>,
placement: DockPlacement,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let panel = cx.new(|cx| {
let mut tab = TabPanel::new(None, dock_area.clone(), window, cx);
tab.closable = true;
tab
});
let panel = DockItem::Tabs {
items: Vec::new(),
active_ix: 0,
view: panel.clone(),
};
Self::subscribe_panel_events(dock_area.clone(), &panel, window, cx);
Self {
placement,
dock_area,
panel,
open: true,
collapsible: true,
size: px(200.0),
resizing: false,
}
}
pub fn left(
dock_area: WeakEntity<DockArea>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
Self::new(dock_area, DockPlacement::Left, window, cx)
}
pub fn bottom(
dock_area: WeakEntity<DockArea>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
Self::new(dock_area, DockPlacement::Bottom, window, cx)
}
pub fn right(
dock_area: WeakEntity<DockArea>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
Self::new(dock_area, DockPlacement::Right, window, cx)
}
/// Update the Dock to be collapsible or not.
///
/// And if the Dock is not collapsible, it will be open.
pub fn set_collapsible(
&mut self,
collapsible: bool,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.collapsible = collapsible;
if !collapsible {
self.open = true
}
cx.notify();
}
fn subscribe_panel_events(
dock_area: WeakEntity<DockArea>,
panel: &DockItem,
window: &mut Window,
cx: &mut App,
) {
match panel {
DockItem::Tabs { view, .. } => {
window.defer(cx, {
let view = view.clone();
move |window, cx| {
_ = dock_area.update(cx, |this, cx| {
this.subscribe_panel(&view, window, cx);
});
}
});
}
DockItem::Split { items, view, .. } => {
for item in items {
Self::subscribe_panel_events(dock_area.clone(), item, window, cx);
}
window.defer(cx, {
let view = view.clone();
move |window, cx| {
_ = dock_area.update(cx, |this, cx| {
this.subscribe_panel(&view, window, cx);
});
}
});
}
DockItem::Panel { .. } => {
// Not supported
}
}
}
pub fn set_panel(&mut self, panel: DockItem, _window: &mut Window, cx: &mut Context<Self>) {
self.panel = panel;
cx.notify();
}
pub fn is_open(&self) -> bool {
self.open
}
pub fn toggle_open(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.set_open(!self.open, window, cx);
}
/// Returns the size of the Dock, the size is means the width or height of
/// the Dock, if the placement is left or right, the size is width,
/// otherwise the size is height.
pub fn size(&self) -> Pixels {
self.size
}
/// Set the size of the Dock.
pub fn set_size(&mut self, size: Pixels, _window: &mut Window, cx: &mut Context<Self>) {
self.size = size.max(PANEL_MIN_SIZE);
cx.notify();
}
/// Set the open state of the Dock.
pub fn set_open(&mut self, open: bool, window: &mut Window, cx: &mut Context<Self>) {
self.open = open;
let item = self.panel.clone();
// Use defer_in (not window.defer) so the callback is cancelled
// if this Dock entity is dropped before the deferred frame runs.
cx.defer_in(window, move |_, window, cx| {
item.set_collapsed(!open, window, cx);
});
cx.notify();
}
/// Add item to the Dock.
pub fn add_panel(
&mut self,
panel: Arc<dyn PanelView>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.panel.add_panel(panel, &self.dock_area, window, cx);
cx.notify();
}
fn render_resize_handle(
&mut self,
_window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let axis = self.placement.axis();
let view = cx.entity().clone();
resize_handle("resize-handle", axis)
.placement(self.placement)
.on_drag(ResizePanel {}, move |info, _, _, cx| {
cx.stop_propagation();
view.update(cx, |view, _cx| {
view.resizing = true;
});
cx.new(|_| info.deref().clone())
})
}
fn resize(
&mut self,
mouse_position: Point<Pixels>,
_window: &mut Window,
cx: &mut Context<Self>,
) {
if !self.resizing {
return;
}
let dock_area = self
.dock_area
.upgrade()
.expect("DockArea is missing")
.read(cx);
let area_bounds = dock_area.bounds;
let mut left_dock_size = px(0.0);
let mut right_dock_size = px(0.0);
// Get the size of the left dock if it's open and not the current dock
if let Some(left_dock) = &dock_area.left_dock
&& left_dock.entity_id() != cx.entity().entity_id()
{
let left_dock_read = left_dock.read(cx);
if left_dock_read.is_open() {
left_dock_size = left_dock_read.size;
}
}
// Get the size of the right dock if it's open and not the current dock
if let Some(right_dock) = &dock_area.right_dock
&& right_dock.entity_id() != cx.entity().entity_id()
{
let right_dock_read = right_dock.read(cx);
if right_dock_read.is_open() {
right_dock_size = right_dock_read.size;
}
}
let size = match self.placement {
DockPlacement::Left => mouse_position.x - area_bounds.left(),
DockPlacement::Right => area_bounds.right() - mouse_position.x,
DockPlacement::Bottom => area_bounds.bottom() - mouse_position.y,
DockPlacement::Center => unreachable!(),
};
match self.placement {
DockPlacement::Left => {
let max_size = area_bounds.size.width - PANEL_MIN_SIZE - right_dock_size;
self.size = size.clamp(PANEL_MIN_SIZE, max_size);
}
DockPlacement::Right => {
let max_size = area_bounds.size.width - PANEL_MIN_SIZE - left_dock_size;
self.size = size.clamp(PANEL_MIN_SIZE, max_size);
}
DockPlacement::Bottom => {
let max_size = area_bounds.size.height - PANEL_MIN_SIZE;
self.size = size.clamp(PANEL_MIN_SIZE, max_size);
}
DockPlacement::Center => unreachable!(),
}
cx.notify();
}
fn done_resizing(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
self.resizing = false;
}
}
impl Render for Dock {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {
if !self.open && !self.placement.is_bottom() {
return div();
}
let cache_style = StyleRefinement::default().absolute().size_full();
div()
.relative()
.overflow_hidden()
.map(|this| match self.placement {
DockPlacement::Left | DockPlacement::Right => this.h_flex().h_full().w(self.size),
DockPlacement::Bottom => this.w_full().h(self.size),
DockPlacement::Center => unreachable!(),
})
// Bottom Dock should keep the title bar, then user can click the Toggle button
.when(!self.open && self.placement.is_bottom(), |this| {
this.h(px(29.))
})
.map(|this| match &self.panel {
DockItem::Split { view, .. } => this.child(view.clone()),
DockItem::Tabs { view, .. } => this.child(view.clone()),
DockItem::Panel { view, .. } => this.child(view.clone().view().cached(cache_style)),
})
.child(self.render_resize_handle(window, cx))
.child(DockElement {
view: cx.entity().clone(),
})
}
}
struct DockElement {
view: Entity<Dock>,
}
impl IntoElement for DockElement {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Element for DockElement {
type PrepaintState = ();
type RequestLayoutState = ();
fn id(&self) -> Option<gpui::ElementId> {
None
}
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_: Option<&gpui::GlobalElementId>,
_: Option<&gpui::InspectorElementId>,
window: &mut gpui::Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
(window.request_layout(Style::default(), None, cx), ())
}
fn prepaint(
&mut self,
_: Option<&gpui::GlobalElementId>,
_: Option<&gpui::InspectorElementId>,
_: gpui::Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
_window: &mut Window,
_cx: &mut App,
) -> Self::PrepaintState {
}
fn paint(
&mut self,
_: Option<&gpui::GlobalElementId>,
_: Option<&gpui::InspectorElementId>,
_: gpui::Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
window: &mut gpui::Window,
cx: &mut App,
) {
window.on_mouse_event({
let view = self.view.clone();
let is_resizing = view.read(cx).resizing;
move |e: &MouseMoveEvent, phase, window, cx| {
if !is_resizing {
return;
}
if !phase.bubble() {
return;
}
view.update(cx, |view, cx| view.resize(e.position, window, cx))
}
});
// When any mouse up, stop dragging
window.on_mouse_event({
let view = self.view.clone();
move |_: &MouseUpEvent, phase, window, cx| {
if phase.bubble() {
view.update(cx, |view, cx| view.done_resizing(window, cx));
}
}
})
}
}
+824 -728
View File
File diff suppressed because it is too large Load Diff
+85 -8
View File
@@ -1,7 +1,11 @@
use std::any::Any;
use std::sync::Arc;
use gpui::{
AnyElement, AnyView, App, Element, Entity, EventEmitter, FocusHandle, Focusable, Render,
SharedString, Window,
};
use gpui_base::dock::{PanelId, PanelState};
use crate::button::Button;
use crate::menu::PopupMenu;
@@ -13,14 +17,6 @@ pub enum PanelEvent {
LayoutChanged,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PanelStyle {
/// Display the TabBar when there are multiple tabs, otherwise display the simple title.
Default,
/// Always display the tab bar.
TabBar,
}
pub trait Panel: EventEmitter<PanelEvent> + Render + Focusable {
/// The name of the panel used to serialize, deserialize and identify the panel.
///
@@ -156,3 +152,84 @@ impl PartialEq for dyn PanelView {
self.view() == other.view()
}
}
#[derive(Clone)]
pub struct PanelHandle {
id: PanelId,
panel: Arc<dyn PanelView>,
}
impl PanelHandle {
pub fn new<P: Panel>(panel: Entity<P>) -> Self {
Self {
id: PanelId::from(panel.entity_id()),
panel: Arc::new(panel),
}
}
/// Recover the coop handle behind one of base's.
pub fn of(panel: &Arc<dyn gpui_base::dock::PanelView>) -> Option<&Self> {
panel.as_any().downcast_ref::<Self>()
}
/// The coop panel behind this handle.
pub fn panel(&self) -> &Arc<dyn PanelView> {
&self.panel
}
}
impl gpui_base::dock::PanelView for PanelHandle {
fn panel_name(&self, _: &App) -> &'static str {
"CoopPanel"
}
fn panel_id(&self, _: &App) -> PanelId {
self.id
}
fn closable(&self, cx: &App) -> bool {
self.panel.closable(cx)
}
fn zoomable(&self, cx: &App) -> bool {
self.panel.zoomable(cx)
}
fn visible(&self, cx: &App) -> bool {
self.panel.visible(cx)
}
fn set_active(&self, active: bool, _: &mut Window, cx: &mut App) {
self.panel.set_active(active, cx);
}
fn set_zoomed(&self, zoomed: bool, _: &mut Window, cx: &mut App) {
self.panel.set_zoomed(zoomed, cx);
}
fn on_added_to(
&self,
_group: gpui::WeakEntity<gpui_base::dock::TabGroup>,
_: &mut Window,
_: &mut App,
) {
}
fn on_removed(&self, _: &mut Window, _: &mut App) {}
fn view(&self) -> AnyView {
self.panel.view()
}
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.panel.focus_handle(cx)
}
fn dump(&self, cx: &App) -> PanelState {
PanelState::new(self.panel_name(cx))
}
fn as_any(&self) -> &dyn Any {
self
}
}
-387
View File
@@ -1,387 +0,0 @@
use std::sync::Arc;
use gpui::{
App, AppContext, Axis, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
IntoElement, ParentElement, Pixels, Render, SharedString, Styled, Subscription, WeakEntity,
Window,
};
use smallvec::SmallVec;
use theme::{AxisExt as _, Placement};
use super::{DockArea, PanelEvent};
use crate::dock::panel::{Panel, PanelView};
use crate::dock::tab_panel::TabPanel;
use crate::h_flex;
use crate::resizable::{
PANEL_MIN_SIZE, ResizablePanelEvent, ResizablePanelGroup, ResizablePanelState, ResizableState,
resizable_panel,
};
pub struct StackPanel {
pub(super) parent: Option<WeakEntity<StackPanel>>,
pub(super) axis: Axis,
focus_handle: FocusHandle,
pub(crate) panels: SmallVec<[Arc<dyn PanelView>; 2]>,
state: Entity<ResizableState>,
_subscriptions: Vec<Subscription>,
}
impl Panel for StackPanel {
fn panel_id(&self) -> SharedString {
"StackPanel".into()
}
fn title(&self, _cx: &App) -> gpui::AnyElement {
"StackPanel".into_any_element()
}
}
impl StackPanel {
pub fn new(axis: Axis, window: &mut Window, cx: &mut Context<Self>) -> Self {
let state = cx.new(|_| ResizableState::default());
// Bubble up the resize event.
let subscriptions =
vec![
cx.subscribe_in(&state, window, |_, _, _: &ResizablePanelEvent, _, cx| {
cx.emit(PanelEvent::LayoutChanged)
}),
];
Self {
axis,
parent: None,
focus_handle: cx.focus_handle(),
panels: SmallVec::new(),
state,
_subscriptions: subscriptions,
}
}
/// The first level of the stack panel is root, will not have a parent.
fn is_root(&self) -> bool {
self.parent.is_none()
}
/// Return true if self or parent only have last panel.
pub fn is_last_panel(&self, cx: &App) -> bool {
if self.panels.len() > 1 {
return false;
}
if let Some(parent) = &self.parent
&& let Some(parent) = parent.upgrade()
{
return parent.read(cx).is_last_panel(cx);
}
true
}
pub fn panels_len(&self) -> usize {
self.panels.len()
}
/// Return the index of the panel.
pub fn index_of_panel(&self, panel: Arc<dyn PanelView>) -> Option<usize> {
self.panels.iter().position(|p| p == &panel)
}
/// Add a panel at the end of the stack.
pub fn add_panel(
&mut self,
panel: Arc<dyn PanelView>,
size: Option<Pixels>,
dock_area: WeakEntity<DockArea>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.insert_panel(panel, self.panels.len(), size, dock_area, window, cx);
}
pub fn add_panel_at(
&mut self,
panel: Arc<dyn PanelView>,
placement: Placement,
size: Option<Pixels>,
dock_area: WeakEntity<DockArea>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.insert_panel_at(
panel,
self.panels_len(),
placement,
size,
dock_area,
window,
cx,
);
}
#[allow(clippy::too_many_arguments)]
pub fn insert_panel_at(
&mut self,
panel: Arc<dyn PanelView>,
ix: usize,
placement: Placement,
size: Option<Pixels>,
dock_area: WeakEntity<DockArea>,
window: &mut Window,
cx: &mut Context<Self>,
) {
match placement {
Placement::Top | Placement::Left => {
self.insert_panel_before(panel, ix, size, dock_area, window, cx)
}
Placement::Right | Placement::Bottom => {
self.insert_panel_after(panel, ix, size, dock_area, window, cx)
}
}
}
/// Insert a panel at the index.
pub fn insert_panel_before(
&mut self,
panel: Arc<dyn PanelView>,
ix: usize,
size: Option<Pixels>,
dock_area: WeakEntity<DockArea>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.insert_panel(panel, ix, size, dock_area, window, cx);
}
/// Insert a panel after the index.
pub fn insert_panel_after(
&mut self,
panel: Arc<dyn PanelView>,
ix: usize,
size: Option<Pixels>,
dock_area: WeakEntity<DockArea>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.insert_panel(panel, ix + 1, size, dock_area, window, cx);
}
fn insert_panel(
&mut self,
panel: Arc<dyn PanelView>,
ix: usize,
size: Option<Pixels>,
dock_area: WeakEntity<DockArea>,
window: &mut Window,
cx: &mut Context<Self>,
) {
// If the panel is already in the stack, return.
if self.index_of_panel(panel.clone()).is_some() {
return;
}
let view = cx.entity().clone();
window.defer(cx, {
let panel = panel.clone();
move |window, cx| {
// If the panel is a TabPanel, set its parent to this.
if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {
tab_panel.update(cx, |tab_panel, _| tab_panel.set_parent(view.downgrade()));
} else if let Ok(stack_panel) = panel.view().downcast::<Self>() {
stack_panel.update(cx, |stack_panel, _| {
stack_panel.parent = Some(view.downgrade())
});
}
// Subscribe to the panel's layout change event.
_ = dock_area.update(cx, |this, cx| {
if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {
this.subscribe_panel(&tab_panel, window, cx);
} else if let Ok(stack_panel) = panel.view().downcast::<Self>() {
this.subscribe_panel(&stack_panel, window, cx);
}
});
}
});
let ix = if ix > self.panels.len() {
self.panels.len()
} else {
ix
};
// Get avg size of all panels to insert new panel, if size is None.
let size = match size {
Some(size) => size,
None => {
let state = self.state.read(cx);
(state.container_size() / (state.sizes().len() + 1) as f32).max(PANEL_MIN_SIZE)
}
};
// Insert panel
self.panels.insert(ix, panel.clone());
// Update resizable state
self.state.update(cx, |state, cx| {
state.insert_panel(Some(size), Some(ix), cx);
});
cx.emit(PanelEvent::LayoutChanged);
cx.notify();
}
/// Remove panel from the stack.
///
/// If `ix` is not found, do nothing.
pub fn remove_panel(
&mut self,
panel: Arc<dyn PanelView>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let Some(ix) = self.index_of_panel(panel.clone()) else {
return;
};
self.panels.remove(ix);
self.state.update(cx, |state, cx| {
state.remove_panel(ix, cx);
});
cx.emit(PanelEvent::LayoutChanged);
self.remove_self_if_empty(window, cx);
}
/// Replace the old panel with the new panel at same index.
pub fn replace_panel(
&mut self,
old_panel: Arc<dyn PanelView>,
new_panel: Entity<StackPanel>,
_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.state.update(cx, |state, cx| {
state.replace_panel(ix, ResizablePanelState::default(), cx);
});
cx.emit(PanelEvent::LayoutChanged);
}
}
/// If children is empty, remove self from parent view.
pub fn remove_self_if_empty(&mut self, window: &mut Window, cx: &mut Context<Self>) {
if self.is_root() {
return;
}
if !self.panels.is_empty() {
return;
}
let view = cx.entity().clone();
if let Some(parent) = self.parent.as_ref() {
_ = parent.update(cx, |parent, cx| {
parent.remove_panel(Arc::new(view.clone()), window, cx);
});
}
cx.emit(PanelEvent::LayoutChanged);
cx.notify();
}
/// Find the first top left in the stack.
pub fn left_top_tab_panel(&self, check_parent: bool, cx: &App) -> Option<Entity<TabPanel>> {
if check_parent
&& let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade())
&& let Some(panel) = parent.read(cx).left_top_tab_panel(true, cx)
{
return Some(panel);
}
let first_panel = self.panels.first();
if let Some(view) = first_panel {
if let Ok(tab_panel) = view.view().downcast::<TabPanel>() {
Some(tab_panel)
} else if let Ok(stack_panel) = view.view().downcast::<StackPanel>() {
stack_panel.read(cx).left_top_tab_panel(false, cx)
} else {
None
}
} else {
None
}
}
/// Find the first top right in the stack.
pub fn right_top_tab_panel(&self, check_parent: bool, cx: &App) -> Option<Entity<TabPanel>> {
if check_parent
&& let Some(parent) = self.parent.as_ref().and_then(|parent| parent.upgrade())
&& let Some(panel) = parent.read(cx).right_top_tab_panel(true, cx)
{
return Some(panel);
}
let panel = if self.axis.is_vertical() {
self.panels.first()
} else {
self.panels.last()
};
if let Some(view) = panel {
if let Ok(tab_panel) = view.view().downcast::<TabPanel>() {
Some(tab_panel)
} else if let Ok(stack_panel) = view.view().downcast::<StackPanel>() {
stack_panel.read(cx).right_top_tab_panel(false, cx)
} else {
None
}
} else {
None
}
}
/// Remove all panels from the stack.
pub fn remove_all_panels(&mut self, _: &mut Window, cx: &mut Context<Self>) {
self.panels.clear();
self.state.update(cx, |state, cx| {
state.clear();
cx.notify();
});
}
/// Change the axis of the stack panel.
pub fn set_axis(&mut self, axis: Axis, _: &mut Window, cx: &mut Context<Self>) {
self.axis = axis;
cx.notify();
}
}
impl Focusable for StackPanel {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl EventEmitter<PanelEvent> for StackPanel {}
impl EventEmitter<DismissEvent> for StackPanel {}
impl Render for StackPanel {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex().size_full().overflow_hidden().child(
ResizablePanelGroup::new("stack-panel-group")
.with_state(&self.state)
.axis(self.axis)
.children(self.panels.clone().into_iter().map(|panel| {
resizable_panel()
.child(panel.view())
.visible(panel.visible(cx))
})),
)
}
}
File diff suppressed because it is too large Load Diff