Add `Terminal` component

Marshall Bowers created

Change summary

crates/storybook2/src/stories/components.rs          |  1 
crates/storybook2/src/stories/components/terminal.rs | 26 +++
crates/storybook2/src/story_selector.rs              |  2 
crates/ui2/src/components.rs                         |  2 
crates/ui2/src/components/terminal.rs                | 89 +++++++++++++
crates/ui2/src/components/workspace.rs               | 93 ++++++-------
6 files changed, 161 insertions(+), 52 deletions(-)

Detailed changes

crates/storybook2/src/stories/components/terminal.rs 🔗

@@ -0,0 +1,26 @@
+use std::marker::PhantomData;
+
+use ui::prelude::*;
+use ui::Terminal;
+
+use crate::story::Story;
+
+#[derive(Element)]
+pub struct TerminalStory<S: 'static + Send + Sync + Clone> {
+    state_type: PhantomData<S>,
+}
+
+impl<S: 'static + Send + Sync + Clone> TerminalStory<S> {
+    pub fn new() -> Self {
+        Self {
+            state_type: PhantomData,
+        }
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        Story::container(cx)
+            .child(Story::title_for::<_, Terminal<S>>(cx))
+            .child(Story::label(cx, "Default"))
+            .child(Terminal::new())
+    }
+}

crates/storybook2/src/story_selector.rs 🔗

@@ -39,6 +39,7 @@ pub enum ComponentStory {
     Panel,
     ProjectPanel,
     Tab,
+    Terminal,
     Workspace,
 }
 
@@ -54,6 +55,7 @@ impl ComponentStory {
             Self::Panel => components::panel::PanelStory::new().into_any(),
             Self::ProjectPanel => components::project_panel::ProjectPanelStory::new().into_any(),
             Self::Tab => components::tab::TabStory::new().into_any(),
+            Self::Terminal => components::terminal::TerminalStory::new().into_any(),
             Self::Workspace => components::workspace::WorkspaceStory::new().into_any(),
         }
     }

crates/ui2/src/components.rs 🔗

@@ -7,6 +7,7 @@ mod panes;
 mod project_panel;
 mod status_bar;
 mod tab;
+mod terminal;
 mod workspace;
 
 pub use assistant_panel::*;
@@ -18,4 +19,5 @@ pub use panes::*;
 pub use project_panel::*;
 pub use status_bar::*;
 pub use tab::*;
+pub use terminal::*;
 pub use workspace::*;

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

@@ -0,0 +1,89 @@
+use std::marker::PhantomData;
+use std::sync::Arc;
+
+use gpui3::{relative, rems, Size};
+
+use crate::prelude::*;
+use crate::{theme, Icon, IconButton, Pane, Tab};
+
+#[derive(Element)]
+pub struct Terminal<S: 'static + Send + Sync + Clone> {
+    state_type: PhantomData<S>,
+}
+
+impl<S: 'static + Send + Sync + Clone> Terminal<S> {
+    pub fn new() -> Self {
+        Self {
+            state_type: PhantomData,
+        }
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+
+        let can_navigate_back = true;
+        let can_navigate_forward = false;
+
+        div()
+            .flex()
+            .flex_col()
+            .w_full()
+            .child(
+                // Terminal Tabs.
+                div()
+                    .w_full()
+                    .flex()
+                    .fill(theme.middle.base.default.background)
+                    .child(
+                        div().px_1().flex().flex_none().gap_2().child(
+                            div()
+                                .flex()
+                                .items_center()
+                                .gap_px()
+                                .child(
+                                    IconButton::new(Icon::ArrowLeft).state(
+                                        InteractionState::Enabled.if_enabled(can_navigate_back),
+                                    ),
+                                )
+                                .child(IconButton::new(Icon::ArrowRight).state(
+                                    InteractionState::Enabled.if_enabled(can_navigate_forward),
+                                )),
+                        ),
+                    )
+                    .child(
+                        div().w_0().flex_1().h_full().child(
+                            div()
+                                .flex()
+                                .child(
+                                    Tab::new()
+                                        .title("zed — fish".to_string())
+                                        .icon(Icon::Terminal)
+                                        .close_side(IconSide::Right)
+                                        .current(true),
+                                )
+                                .child(
+                                    Tab::new()
+                                        .title("zed — fish".to_string())
+                                        .icon(Icon::Terminal)
+                                        .close_side(IconSide::Right)
+                                        .current(false),
+                                ),
+                        ),
+                    ),
+            )
+            // Terminal Pane.
+            .child(Pane::new(
+                ScrollState::default(),
+                Size {
+                    width: relative(1.).into(),
+                    height: rems(36.).into(),
+                },
+                |_, payload| {
+                    let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
+
+                    vec![crate::static_data::terminal_buffer(&theme).into_any()]
+                },
+                Box::new(theme),
+            ))
+    }
+}

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

@@ -4,8 +4,11 @@ use std::sync::Arc;
 use chrono::DateTime;
 use gpui3::{relative, rems, Size};
 
-use crate::{prelude::*, Pane, PaneGroup, SplitDirection};
-use crate::{theme, v_stack, Panel, PanelAllowedSides, PanelSide, ProjectPanel, StatusBar};
+use crate::prelude::*;
+use crate::{
+    theme, v_stack, Pane, PaneGroup, Panel, PanelAllowedSides, PanelSide, ProjectPanel,
+    SplitDirection, StatusBar, Terminal,
+};
 
 #[derive(Element)]
 pub struct WorkspaceElement<S: 'static + Send + Sync + Clone> {
@@ -46,13 +49,10 @@ impl<S: 'static + Send + Sync + Clone> WorkspaceElement<S> {
                                 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()
+                                    Terminal::new().into_any(), // EditorPane::new(hello_world_rust_editor_with_status_example(
+                                                                //     &theme,
+                                                                // ))
+                                                                // .into_any()
                                 ]
                             },
                             Box::new(theme.clone()),
@@ -63,15 +63,7 @@ impl<S: 'static + Send + Sync + Clone> WorkspaceElement<S> {
                                 width: relative(1.).into(),
                                 height: temp_size,
                             },
-                            |_, _| {
-                                vec![
-                                    div()
-                                        .w_full()
-                                        .fill(gpui3::rgb::<gpui3::Hsla>(0x00ff00))
-                                        .into_any(),
-                                    // Terminal::new().into_any()
-                                ]
-                            },
+                            |_, _| vec![Terminal::new().into_any()],
                             Box::new(()),
                         ),
                     ],
@@ -88,10 +80,7 @@ impl<S: 'static + Send + Sync + Clone> WorkspaceElement<S> {
                             let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
 
                             vec![
-                                div()
-                                    .w_full()
-                                    .fill(gpui3::rgb::<gpui3::Hsla>(0x0000ff))
-                                    .into_any(),
+                                Terminal::new().into_any(),
                                 //     EditorPane::new(hello_world_rust_editor_with_status_example(
                                 //     &theme,
                                 // ))
@@ -175,36 +164,36 @@ impl<S: 'static + Send + Sync + Clone> WorkspaceElement<S> {
                         .side(PanelSide::Right),
                     ),
                 // .child(
-                       //     Panel::new(
-                       //         self.right_panel_scroll_state.clone(),
-                       //         |_, payload| {
-                       //             vec![ChatPanel::new(ScrollState::default())
-                       //                 .with_messages(vec![
-                       //                     ChatMessage::new(
-                       //                         "osiewicz".to_string(),
-                       //                         "is this thing on?".to_string(),
-                       //                         DateTime::parse_from_rfc3339(
-                       //                             "2023-09-27T15:40:52.707Z",
-                       //                         )
-                       //                         .unwrap()
-                       //                         .naive_local(),
-                       //                     ),
-                       //                     ChatMessage::new(
-                       //                         "maxdeviant".to_string(),
-                       //                         "Reading you loud and clear!".to_string(),
-                       //                         DateTime::parse_from_rfc3339(
-                       //                             "2023-09-28T15:40:52.707Z",
-                       //                         )
-                       //                         .unwrap()
-                       //                         .naive_local(),
-                       //                     ),
-                       //                 ])
-                       //                 .into_any()]
-                       //         },
-                       //         Box::new(()),
-                       //     )
-                       //     .side(PanelSide::Right),
-                       // ),
+                //     Panel::new(
+                //         self.right_panel_scroll_state.clone(),
+                //         |_, payload| {
+                //             vec![ChatPanel::new(ScrollState::default())
+                //                 .with_messages(vec![
+                //                     ChatMessage::new(
+                //                         "osiewicz".to_string(),
+                //                         "is this thing on?".to_string(),
+                //                         DateTime::parse_from_rfc3339(
+                //                             "2023-09-27T15:40:52.707Z",
+                //                         )
+                //                         .unwrap()
+                //                         .naive_local(),
+                //                     ),
+                //                     ChatMessage::new(
+                //                         "maxdeviant".to_string(),
+                //                         "Reading you loud and clear!".to_string(),
+                //                         DateTime::parse_from_rfc3339(
+                //                             "2023-09-28T15:40:52.707Z",
+                //                         )
+                //                         .unwrap()
+                //                         .naive_local(),
+                //                     ),
+                //                 ])
+                //                 .into_any()]
+                //         },
+                //         Box::new(()),
+                //     )
+                //     .side(PanelSide::Right),
+                // ),
             )
             .child(StatusBar::new())
         // An example of a toast is below