First pass at the modal for setting up devcontainer from scratch.

KyleBarton created

Change summary

crates/recent_projects/src/dev_container.rs   | 127 ++++++++++++++++++++
crates/recent_projects/src/recent_projects.rs |   2 
crates/zed/src/main.rs                        |   3 
3 files changed, 128 insertions(+), 4 deletions(-)

Detailed changes

crates/recent_projects/src/dev_container.rs 🔗

@@ -1,12 +1,19 @@
 use std::path::{Path, PathBuf};
 use std::sync::Arc;
 
-use gpui::AsyncWindowContext;
+use gpui::{
+    Action, AsyncWindowContext, DismissEvent, EventEmitter, FocusHandle, Focusable, RenderOnce,
+};
 use node_runtime::NodeRuntime;
 use serde::Deserialize;
 use settings::DevContainerConnection;
 use smol::fs;
-use workspace::Workspace;
+use ui::{
+    App, Color, Context, Headline, HeadlineSize, Icon, IconName, InteractiveElement, IntoElement,
+    Label, ListItem, ListSeparator, ModalHeader, Navigable, NavigableEntry, ParentElement, Render,
+    Styled, StyledExt, Toggleable, Window, div, rems,
+};
+use workspace::{ModalView, Workspace, with_active_or_new_workspace};
 
 use crate::remote_connections::Connection;
 
@@ -275,6 +282,122 @@ pub(crate) enum DevContainerError {
     DevContainerParseFailed,
 }
 
+#[derive(PartialEq, Clone, Deserialize, Default, Action)]
+#[action(namespace = containers)]
+#[serde(deny_unknown_fields)]
+pub struct InitDevContainer;
+
+pub fn init(cx: &mut App) {
+    cx.on_action(|_: &InitDevContainer, cx| {
+        with_active_or_new_workspace(cx, move |workspace, window, cx| {
+            workspace.toggle_modal(window, cx, |window, cx| DevContainerModal::new(window, cx));
+        });
+    });
+}
+
+struct DevContainerModal {
+    focus_handle: FocusHandle,
+    search_navigable_entry: NavigableEntry,
+    other_navigable_entry: NavigableEntry,
+}
+
+impl DevContainerModal {
+    fn new(window: &mut Window, cx: &mut App) -> Self {
+        let search_navigable_entry = NavigableEntry::focusable(cx);
+        let other_navigable_entry = NavigableEntry::focusable(cx);
+        let focus_handle = cx.focus_handle();
+        DevContainerModal {
+            focus_handle,
+            search_navigable_entry,
+            other_navigable_entry,
+        }
+    }
+
+    fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
+        cx.emit(DismissEvent);
+    }
+}
+
+impl ModalView for DevContainerModal {}
+impl EventEmitter<DismissEvent> for DevContainerModal {}
+impl Focusable for DevContainerModal {
+    fn focus_handle(&self, _cx: &App) -> FocusHandle {
+        self.focus_handle.clone()
+    }
+}
+
+impl Render for DevContainerModal {
+    fn render(
+        &mut self,
+        window: &mut ui::Window,
+        cx: &mut ui::Context<Self>,
+    ) -> impl ui::IntoElement {
+        let mut view =
+            Navigable::new(
+                div()
+                    .child(div().track_focus(&self.focus_handle).child(
+                        ModalHeader::new().child(
+                            Headline::new("Create Dev Container").size(HeadlineSize::XSmall),
+                        ),
+                    ))
+                    .child(ListSeparator)
+                    .child(
+                        div()
+                            .track_focus(&self.search_navigable_entry.focus_handle)
+                            .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| {
+                                println!("action on search containers");
+                            }))
+                            .child(
+                                ListItem::new("li-search-containers")
+                                    .inset(true)
+                                    .spacing(ui::ListItemSpacing::Sparse)
+                                    .start_slot(Icon::new(IconName::Pencil).color(Color::Muted))
+                                    .toggle_state(
+                                        self.search_navigable_entry
+                                            .focus_handle
+                                            .contains_focused(window, cx),
+                                    )
+                                    .child(Label::new("Search for dev containers in registry")),
+                            ),
+                    )
+                    .child(
+                        div()
+                            .track_focus(&self.other_navigable_entry.focus_handle)
+                            .on_action(cx.listener(|this, _: &menu::Confirm, window, cx| {
+                                println!("action on other containers");
+                            }))
+                            .child(
+                                ListItem::new("li-search-containers")
+                                    .inset(true)
+                                    .spacing(ui::ListItemSpacing::Sparse)
+                                    .start_slot(Icon::new(IconName::Pencil).color(Color::Muted))
+                                    .toggle_state(
+                                        self.other_navigable_entry
+                                            .focus_handle
+                                            .contains_focused(window, cx),
+                                    )
+                                    .child(Label::new("Do another thing")),
+                            ),
+                    )
+                    .into_any_element(),
+            );
+        view = view.entry(self.search_navigable_entry.clone());
+        view = view.entry(self.other_navigable_entry.clone());
+
+        // // This is an interesting edge. Can't focus in render, or you'll just override whatever was focused before.
+        // // self.search_navigable_entry.focus_handle.focus(window, cx);
+
+        // view.render(window, cx).into_any_element()
+        div()
+            .elevation_3(cx)
+            .w(rems(34.))
+            // WHY IS THIS NEEDED FOR ACTION DISPATCH OMG
+            .key_context("ContainerModal")
+            .on_action(cx.listener(Self::dismiss))
+            .child(view.render(window, cx).into_any_element())
+    }
+}
+
 #[cfg(test)]
 mod test {
 

crates/zed/src/main.rs 🔗

@@ -33,7 +33,7 @@ use assets::Assets;
 use node_runtime::{NodeBinaryOptions, NodeRuntime};
 use parking_lot::Mutex;
 use project::{project_settings::ProjectSettings, trusted_worktrees};
-use recent_projects::{SshSettings, open_remote_project};
+use recent_projects::{SshSettings, dev_container, open_remote_project};
 use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
 use session::{AppSession, Session};
 use settings::{BaseKeymap, Settings, SettingsStore, watch_config_file};
@@ -616,6 +616,7 @@ fn main() {
         agent_ui_v2::agents_panel::init(cx);
         repl::init(app_state.fs.clone(), cx);
         recent_projects::init(cx);
+        dev_container::init(cx);
 
         load_embedded_fonts(cx);