Add `Pane` and `PaneGroup` components

Marshall Bowers created

Change summary

crates/storybook2/src/storybook2.rs    |   2 
crates/ui2/src/components.rs           |   2 
crates/ui2/src/components/panes.rs     | 131 +++++++++++++++++++++++++++
crates/ui2/src/components/workspace.rs | 133 ++++++++++++++++-----------
4 files changed, 211 insertions(+), 57 deletions(-)

Detailed changes

crates/ui2/src/components.rs 🔗

@@ -3,6 +3,7 @@ mod buffer;
 mod icon_button;
 mod list;
 mod panel;
+mod panes;
 mod project_panel;
 mod status_bar;
 mod workspace;
@@ -12,6 +13,7 @@ pub use buffer::*;
 pub use icon_button::*;
 pub use list::*;
 pub use panel::*;
+pub use panes::*;
 pub use project_panel::*;
 pub use status_bar::*;
 pub use workspace::*;

crates/ui2/src/components/panes.rs 🔗

@@ -0,0 +1,131 @@
+use std::marker::PhantomData;
+
+use gpui3::{hsla, Hsla, Length, Size};
+
+use crate::prelude::*;
+use crate::theme;
+
+#[derive(Default, PartialEq)]
+pub enum SplitDirection {
+    #[default]
+    Horizontal,
+    Vertical,
+}
+
+#[derive(Element)]
+pub struct Pane<S: 'static + Send + Sync> {
+    state_type: PhantomData<S>,
+    scroll_state: ScrollState,
+    size: Size<Length>,
+    fill: Hsla,
+    children: HackyChildren<S>,
+    payload: HackyChildrenPayload,
+}
+
+impl<S: 'static + Send + Sync> Pane<S> {
+    pub fn new(
+        scroll_state: ScrollState,
+        size: Size<Length>,
+        children: HackyChildren<S>,
+        payload: HackyChildrenPayload,
+    ) -> Self {
+        // Fill is only here for debugging purposes, remove before release
+        let system_color = SystemColor::new();
+
+        Self {
+            state_type: PhantomData,
+            scroll_state,
+            size,
+            fill: hsla(0.3, 0.3, 0.3, 1.),
+            // fill: system_color.transparent,
+            children,
+            payload,
+        }
+    }
+
+    pub fn fill(mut self, fill: Hsla) -> Self {
+        self.fill = fill;
+        self
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+
+        div()
+            .flex()
+            .flex_initial()
+            .fill(self.fill)
+            .w(self.size.width)
+            .h(self.size.height)
+            .overflow_y_scroll(self.scroll_state.clone())
+            .children_any((self.children)(cx, self.payload.as_ref()))
+    }
+}
+
+#[derive(Element)]
+pub struct PaneGroup<S: 'static + Send + Sync> {
+    state_type: PhantomData<S>,
+    groups: Vec<PaneGroup<S>>,
+    panes: Vec<Pane<S>>,
+    split_direction: SplitDirection,
+}
+
+impl<S: 'static + Send + Sync> PaneGroup<S> {
+    pub fn new_groups(groups: Vec<PaneGroup<S>>, split_direction: SplitDirection) -> Self {
+        Self {
+            state_type: PhantomData,
+            groups,
+            panes: Vec::new(),
+            split_direction,
+        }
+    }
+
+    pub fn new_panes(panes: Vec<Pane<S>>, split_direction: SplitDirection) -> Self {
+        Self {
+            state_type: PhantomData,
+            groups: Vec::new(),
+            panes,
+            split_direction,
+        }
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+
+        if !self.panes.is_empty() {
+            let el = div()
+                .flex()
+                .flex_1()
+                .gap_px()
+                .w_full()
+                .h_full()
+                .fill(theme.lowest.base.default.background)
+                .children(self.panes.iter_mut().map(|pane| pane.render(cx)));
+
+            if self.split_direction == SplitDirection::Horizontal {
+                return el;
+            } else {
+                return el.flex_col();
+            }
+        }
+
+        if !self.groups.is_empty() {
+            let el = div()
+                .flex()
+                .flex_1()
+                .gap_px()
+                .w_full()
+                .h_full()
+                .fill(theme.lowest.base.default.background)
+                .children(self.groups.iter_mut().map(|group| group.render(cx)));
+
+            if self.split_direction == SplitDirection::Horizontal {
+                return el;
+            } else {
+                return el.flex_col();
+            }
+        }
+
+        unreachable!()
+    }
+}

crates/ui2/src/components/workspace.rs 🔗

@@ -4,7 +4,7 @@ use std::sync::Arc;
 use chrono::DateTime;
 use gpui3::{relative, rems, Size};
 
-use crate::prelude::*;
+use crate::{prelude::*, Pane, PaneGroup, SplitDirection};
 use crate::{theme, v_stack, Panel, PanelAllowedSides, PanelSide, ProjectPanel, StatusBar};
 
 #[derive(Element)]
@@ -30,62 +30,81 @@ impl<S: 'static + Send + Sync + Clone> WorkspaceElement<S> {
     pub fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
         let theme = theme(cx).clone();
 
-        // let temp_size = rems(36.).into();
+        let temp_size = rems(36.).into();
 
-        // let root_group = PaneGroup::new_groups(
-        //     vec![
-        //         PaneGroup::new_panes(
-        //             vec![
-        //                 Pane::new(
-        //                     ScrollState::default(),
-        //                     Size {
-        //                         width: relative(1.).into(),
-        //                         height: temp_size,
-        //                     },
-        //                     |_, payload| {
-        //                         let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
+        let root_group = PaneGroup::new_groups(
+            vec![
+                PaneGroup::new_panes(
+                    vec![
+                        Pane::new(
+                            ScrollState::default(),
+                            Size {
+                                width: relative(1.).into(),
+                                height: temp_size,
+                            },
+                            |_, payload| {
+                                let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
 
-        //                         vec![EditorPane::new(hello_world_rust_editor_with_status_example(
-        //                             &theme,
-        //                         ))
-        //                         .into_any()]
-        //                     },
-        //                     Box::new(theme.clone()),
-        //                 ),
-        //                 Pane::new(
-        //                     ScrollState::default(),
-        //                     Size {
-        //                         width: relative(1.).into(),
-        //                         height: temp_size,
-        //                     },
-        //                     |_, _| vec![Terminal::new().into_any()],
-        //                     Box::new(()),
-        //                 ),
-        //             ],
-        //             SplitDirection::Vertical,
-        //         ),
-        //         PaneGroup::new_panes(
-        //             vec![Pane::new(
-        //                 ScrollState::default(),
-        //                 Size {
-        //                     width: relative(1.).into(),
-        //                     height: relative(1.).into(),
-        //                 },
-        //                 |_, payload| {
-        //                     let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
+                                vec![
+                                    div()
+                                        .w_full()
+                                        .fill(gpui3::rgb::<gpui3::Hsla>(0xff0000))
+                                        .into_any(), // EditorPane::new(hello_world_rust_editor_with_status_example(
+                                                     //     &theme,
+                                                     // ))
+                                                     // .into_any()
+                                ]
+                            },
+                            Box::new(theme.clone()),
+                        ),
+                        Pane::new(
+                            ScrollState::default(),
+                            Size {
+                                width: relative(1.).into(),
+                                height: temp_size,
+                            },
+                            |_, _| {
+                                vec![
+                                    div()
+                                        .w_full()
+                                        .fill(gpui3::rgb::<gpui3::Hsla>(0x00ff00))
+                                        .into_any(),
+                                    // Terminal::new().into_any()
+                                ]
+                            },
+                            Box::new(()),
+                        ),
+                    ],
+                    SplitDirection::Vertical,
+                ),
+                PaneGroup::new_panes(
+                    vec![Pane::new(
+                        ScrollState::default(),
+                        Size {
+                            width: relative(1.).into(),
+                            height: relative(1.).into(),
+                        },
+                        |_, payload| {
+                            let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
 
-        //                     vec![EditorPane::new(hello_world_rust_editor_with_status_example(
-        //                         &theme,
-        //                     ))
-        //                     .into_any()]
-        //                 },
-        //                 Box::new(theme.clone()),
-        //             )],
-        //             SplitDirection::Vertical,
-        //         ),
-        //     ],
-        //     SplitDirection::Horizontal,
-        // );
+                            vec![
+                                div()
+                                    .w_full()
+                                    .fill(gpui3::rgb::<gpui3::Hsla>(0x0000ff))
+                                    .into_any(),
+                                //     EditorPane::new(hello_world_rust_editor_with_status_example(
+                                //     &theme,
+                                // ))
+                                // .into_any()
+                            ]
+                        },
+                        Box::new(theme.clone()),
+                    )],
+                    SplitDirection::Vertical,
+                ),
+            ],
+            SplitDirection::Horizontal,
+        );
 
         div()
             .relative()
@@ -130,7 +149,8 @@ impl<S: 'static + Send + Sync + Clone> WorkspaceElement<S> {
                                     .flex_1()
                                     // CSS Hack: Flex 1 has to have a set height to properly fill the space
                                     // Or it will give you a height of 0
-                                    .h_px(), // .child(root_group),
+                                    .h_px()
+                                    .child(root_group),
                             )
                             .child(
                                 Panel::new(
@@ -153,7 +173,8 @@ impl<S: 'static + Send + Sync + Clone> WorkspaceElement<S> {
                             Box::new(()),
                         )
                         .side(PanelSide::Right),
-                    ), // .child(
+                    ),
+                // .child(
                        //     Panel::new(
                        //         self.right_panel_scroll_state.clone(),
                        //         |_, payload| {