WIP: Panel

Marshall Bowers created

Change summary

crates/gpui3_macros/src/derive_element.rs    |  17 +-
crates/storybook2/src/ui.rs                  |   6 
crates/storybook2/src/ui/children.rs         |   7 +
crates/storybook2/src/ui/components.rs       |   3 
crates/storybook2/src/ui/components/panel.rs | 146 ++++++++++++++++++++++
crates/storybook2/src/ui/prelude.rs          |   4 
crates/storybook2/src/ui/tokens.rs           |  24 +++
crates/storybook2/src/workspace.rs           |  10 -
8 files changed, 201 insertions(+), 16 deletions(-)

Detailed changes

crates/gpui3_macros/src/derive_element.rs 🔗

@@ -27,21 +27,24 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
             fn layout(
                 &mut self,
                 state: &mut #state_type,
-                cx: &mut gpui3::ViewContext<V>,
+                cx: &mut gpui3::ViewContext<Self::State>,
             ) -> anyhow::Result<(gpui3::LayoutId, Self::FrameState)> {
-                let mut rendered_element = self.render(state, cx).into_element().into_any();
+                use gpui3::IntoAnyElement;
+
+                let mut rendered_element = self.render(cx).into_any();
                 let layout_id = rendered_element.layout(state, cx)?;
                 Ok((layout_id, rendered_element))
             }
 
             fn paint(
                 &mut self,
-                layout: &gpui3::Layout,
-                state: &mut #state_type,
+                layout: gpui3::Layout,
+                state: &mut Self::State,
                 rendered_element: &mut Self::FrameState,
-                cx: &mut gpui3::ViewContext<V>,
-            ) {
-                rendered_element.paint(layout.origin, state, cx);
+                cx: &mut gpui3::ViewContext<Self::State>,
+            ) -> anyhow::Result<()> {
+                // TODO: Where do we get the `offset` from?
+                rendered_element.paint(state, None, cx)
             }
         }
     };

crates/storybook2/src/ui.rs 🔗

@@ -1,6 +1,10 @@
-pub mod prelude;
+mod children;
 mod components;
 mod elements;
+pub mod prelude;
+mod tokens;
 
+pub use children::*;
 pub use components::*;
 pub use elements::*;
+pub use tokens::*;

crates/storybook2/src/ui/children.rs 🔗

@@ -0,0 +1,7 @@
+use std::any::Any;
+
+use gpui3::{AnyElement, ViewContext};
+
+pub type HackyChildren<S> = fn(&mut ViewContext<S>, &dyn Any) -> Vec<AnyElement<S>>;
+
+pub type HackyChildrenPayload = Box<dyn Any>;

crates/storybook2/src/ui/components/panel.rs 🔗

@@ -0,0 +1,146 @@
+use std::marker::PhantomData;
+
+use gpui3::AbsoluteLength;
+
+use crate::themes::rose_pine_dawn;
+use crate::ui::prelude::*;
+use crate::ui::{token, v_stack};
+
+#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
+pub enum PanelAllowedSides {
+    LeftOnly,
+    RightOnly,
+    BottomOnly,
+    #[default]
+    LeftAndRight,
+    All,
+}
+
+impl PanelAllowedSides {
+    /// Return a `HashSet` that contains the allowable `PanelSide`s.
+    pub fn allowed_sides(&self) -> HashSet<PanelSide> {
+        match self {
+            Self::LeftOnly => HashSet::from_iter([PanelSide::Left]),
+            Self::RightOnly => HashSet::from_iter([PanelSide::Right]),
+            Self::BottomOnly => HashSet::from_iter([PanelSide::Bottom]),
+            Self::LeftAndRight => HashSet::from_iter([PanelSide::Left, PanelSide::Right]),
+            Self::All => HashSet::from_iter([PanelSide::Left, PanelSide::Right, PanelSide::Bottom]),
+        }
+    }
+}
+
+#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
+pub enum PanelSide {
+    #[default]
+    Left,
+    Right,
+    Bottom,
+}
+
+use std::collections::HashSet;
+
+#[derive(Element)]
+pub struct Panel {
+    scroll_state: ScrollState,
+    current_side: PanelSide,
+    /// Defaults to PanelAllowedSides::LeftAndRight
+    allowed_sides: PanelAllowedSides,
+    initial_width: AbsoluteLength,
+    width: Option<AbsoluteLength>,
+    // children: HackyChildren<V>,
+    // payload: HackyChildrenPayload,
+}
+
+impl Panel {
+    pub fn new(
+        scroll_state: ScrollState,
+        // children: HackyChildren<S>,
+        // payload: HackyChildrenPayload,
+    ) -> Self {
+        let token = token();
+
+        Self {
+            scroll_state,
+            current_side: PanelSide::default(),
+            allowed_sides: PanelAllowedSides::default(),
+            initial_width: token.default_panel_size,
+            width: None,
+            // children,
+            // payload,
+        }
+    }
+
+    pub fn initial_width(mut self, initial_width: AbsoluteLength) -> Self {
+        self.initial_width = initial_width;
+        self
+    }
+
+    pub fn width(mut self, width: AbsoluteLength) -> Self {
+        self.width = Some(width);
+        self
+    }
+
+    pub fn allowed_sides(mut self, allowed_sides: PanelAllowedSides) -> Self {
+        self.allowed_sides = allowed_sides;
+        self
+    }
+
+    pub fn side(mut self, side: PanelSide) -> Self {
+        let allowed_sides = self.allowed_sides.allowed_sides();
+
+        if allowed_sides.contains(&side) {
+            self.current_side = side;
+        } else {
+            panic!(
+                "The panel side {:?} was not added as allowed before it was set.",
+                side
+            );
+        }
+        self
+    }
+
+    fn render<S: 'static + Send + Sync>(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let token = token();
+        let theme = rose_pine_dawn();
+
+        let panel_base;
+        let current_width = self.width.unwrap_or(self.initial_width);
+
+        match self.current_side {
+            PanelSide::Left => {
+                panel_base = v_stack()
+                    .flex_initial()
+                    .h_full()
+                    // .w(current_width)
+                    .w_4()
+                    .fill(theme.middle.base.default.background)
+                    .border_r()
+                    .border_color(theme.middle.base.default.border);
+            }
+            PanelSide::Right => {
+                panel_base = v_stack()
+                    .flex_initial()
+                    .h_full()
+                    // .w(current_width)
+                    .w_4()
+                    .fill(theme.middle.base.default.background)
+                    .border_l()
+                    .border_color(theme.middle.base.default.border);
+            }
+            PanelSide::Bottom => {
+                panel_base = v_stack()
+                    .flex_initial()
+                    .w_full()
+                    // .h(current_width)
+                    .h_4()
+                    .fill(theme.middle.base.default.background)
+                    .border_t()
+                    .border_color(theme.middle.base.default.border);
+            }
+        }
+
+        panel_base
+
+        // panel_base.children_any((self.children)(cx, self.payload.as_ref()))
+    }
+}

crates/storybook2/src/ui/prelude.rs 🔗

@@ -1 +1,3 @@
-pub use gpui3::StyleHelpers;
+pub use gpui3::{Element, ScrollState, StyleHelpers, ViewContext};
+
+pub use crate::ui::{HackyChildren, HackyChildrenPayload};

crates/storybook2/src/ui/tokens.rs 🔗

@@ -0,0 +1,24 @@
+use gpui3::{hsla, rems, AbsoluteLength, Hsla};
+
+#[derive(Clone, Copy)]
+pub struct Token {
+    pub list_indent_depth: AbsoluteLength,
+    pub default_panel_size: AbsoluteLength,
+    pub state_hover_background: Hsla,
+    pub state_active_background: Hsla,
+}
+
+impl Default for Token {
+    fn default() -> Self {
+        Self {
+            list_indent_depth: AbsoluteLength::Rems(rems(0.5)),
+            default_panel_size: AbsoluteLength::Rems(rems(16.)),
+            state_hover_background: hsla(0.0, 0.0, 0.0, 0.08),
+            state_active_background: hsla(0.0, 0.0, 0.0, 0.16),
+        }
+    }
+}
+
+pub fn token() -> Token {
+    Token::default()
+}

crates/storybook2/src/workspace.rs 🔗

@@ -1,4 +1,5 @@
-use crate::ui::Stack;
+use crate::ui::prelude::*;
+use crate::ui::{Panel, Stack};
 use crate::{
     collab_panel::{collab_panel, CollabPanel},
     theme::theme,
@@ -33,12 +34,7 @@ impl Workspace {
             .size_full()
             .v_stack()
             .fill(theme.lowest.base.default.background)
-            .child(
-                div()
-                    .size_full()
-                    .flex()
-                    .fill(theme.middle.positive.default.background),
-            )
+            .child(Panel::new(ScrollState::default()))
             .child(
                 div()
                     .size_full()