Make Modals dismissable in theory

Conrad Irwin created

Change summary

crates/go_to_line2/src/go_to_line.rs | 14 ++++-
crates/menu2/src/menu2.rs            |  2 
crates/workspace2/src/modal_layer.rs | 75 ++++++++++++++++-------------
crates/workspace2/src/workspace2.rs  |  8 +-
4 files changed, 57 insertions(+), 42 deletions(-)

Detailed changes

crates/go_to_line2/src/go_to_line.rs 🔗

@@ -7,7 +7,7 @@ use text::Point;
 use theme::ActiveTheme;
 use ui::{h_stack, modal, v_stack, Label, LabelColor};
 use util::paths::FILE_ROW_COLUMN_DELIMITER;
-use workspace::ModalRegistry;
+use workspace::{ModalRegistry, Modal, ModalEvent};
 
 actions!(Toggle);
 
@@ -33,7 +33,7 @@ pub enum Event {
 }
 
 impl EventEmitter for GoToLine {
-    type Event = ModalEvent;
+    type Event = Event;
 }
 
 impl GoToLine {
@@ -100,7 +100,7 @@ impl GoToLine {
         cx.emit(Event::Dismissed);
     }
 
-    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
+    fn confirm(&mut self, _: &menu::Confirm, _cx: &mut ViewContext<Self>) {
         // // if let Some(point) = self.point_from_query(cx) {
         // //     self.active_editor.update(cx, |active_editor, cx| {
         // //         let snapshot = active_editor.snapshot(cx).display_snapshot;
@@ -119,6 +119,14 @@ impl GoToLine {
     }
 }
 
+impl Modal for GoToLine {
+    fn to_modal_event(&self, e: &Self::Event) -> Option<ModalEvent> {
+        match e {
+            Event::Dismissed => Some(ModalEvent::Dismissed),
+        }
+    }
+}
+
 impl Render for GoToLine {
     type Element = Div<Self>;
 

crates/menu2/src/menu2.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{actions, ctor};
+use gpui::actions;
 
 // todo!(remove this)
 // https://github.com/rust-lang/rust/issues/47384

crates/workspace2/src/modal_layer.rs 🔗

@@ -1,7 +1,7 @@
 use crate::Workspace;
 use gpui::{
-    div, px, AnyView, AppContext, Component, Div, ParentElement, Render, StatelessInteractive,
-    Styled, View, ViewContext, EventEmitter,
+    div, px, AnyView, AppContext, Component, Div, EventEmitter, ParentElement, Render,
+    StatelessInteractive, Styled, Subscription, View, ViewContext, WeakView,
 };
 use std::{any::TypeId, sync::Arc};
 use ui::v_stack;
@@ -10,11 +10,10 @@ pub struct ModalRegistry {
     registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>,
 }
 
-pub trait Modal {}
-
-#[derive(Clone)]
 pub struct ModalLayer {
+    workspace: WeakView<Workspace>,
     open_modal: Option<AnyView>,
+    subscription: Option<Subscription>,
 }
 
 pub fn init_modal_registry(cx: &mut AppContext) {
@@ -23,23 +22,19 @@ pub fn init_modal_registry(cx: &mut AppContext) {
     });
 }
 
-struct ToggleModal {
-    name: String,
-}
-
-pub enum ModalEvents {
-    Dismissed
+pub enum ModalEvent {
+    Dismissed,
 }
 
-trait Modal: EventEmitter + Render {
-    fn to_modal_events(&Self::Event) -> Option<ModalEvents>;
+pub trait Modal: EventEmitter + Render {
+    fn to_modal_event(&self, _: &Self::Event) -> Option<ModalEvent>;
 }
 
 impl ModalRegistry {
     pub fn register_modal<A: 'static, V, B>(&mut self, action: A, build_view: B)
     where
         V: Modal,
-        B: Fn(&Workspace, &mut ViewContext<Workspace>) -> Option<View<V>> + 'static,
+        B: Fn(&mut Workspace, &mut ViewContext<Workspace>) -> Option<View<V>> + 'static,
     {
         let build_view = Arc::new(build_view);
 
@@ -48,35 +43,47 @@ impl ModalRegistry {
             Box::new(move |mut div| {
                 let build_view = build_view.clone();
 
-                div.on_action(
-                    move |workspace: &mut Workspace, event: &A, cx: &mut ViewContext<Workspace>| {
-                        let Some(new_modal) = (build_view)(workspace, cx) else {
-                            return;
+                div.on_action(move |workspace, event: &A, cx| {
+                    let Some(new_modal) =
+                        (build_view)(workspace, cx) else {
+                            return
                         };
-                        workspace.modal_layer.update(cx, |modal_layer, _| {
-                            modal_layer.open_modal = Some(new_modal.into());
-                        });
-                        cx.subscribe(new_modal, |e, modal, cx| {
-                            match modal.to_modal_events(e) {
-                                Some(Dismissed) =>
-                                dismissed -> whatever
-                            }
-                        })
-
-                        cx.notify();
-                    },
-                )
+                    workspace.modal_layer.update(cx, |modal_layer, cx| {
+                        modal_layer.show_modal(new_modal, cx);
+                    })
+                })
             }),
         ));
     }
 }
 
 impl ModalLayer {
-    pub fn new() -> Self {
-        Self { open_modal: None }
+    pub fn new(workspace: WeakView<Workspace>) -> Self {
+        Self {
+            workspace,
+            open_modal: None,
+            subscription: None,
+        }
+    }
+
+    pub fn show_modal<V: Modal>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Self>) {
+        self.subscription = Some(cx.subscribe(&new_modal, |this, modal, e, cx| {
+            match modal.read(cx).to_modal_event(e) {
+                Some(ModalEvent::Dismissed) => this.hide_modal(cx),
+                None => {}
+            }
+        }));
+        self.open_modal = Some(new_modal.into());
+        cx.notify();
+    }
+
+    pub fn hide_modal(&mut self, cx: &mut ViewContext<Self>) {
+        self.open_modal.take();
+        self.subscription.take();
+        cx.notify();
     }
 
-    pub fn render(&self, workspace: &Workspace, cx: &ViewContext<Workspace>) -> Div<Workspace> {
+    pub fn render(&self, cx: &ViewContext<Workspace>) -> Div<Workspace> {
         let mut parent = div().relative().size_full();
 
         for (_, action) in cx.global::<ModalRegistry>().registered_modals.iter() {

crates/workspace2/src/workspace2.rs 🔗

@@ -46,8 +46,7 @@ use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings,
 use itertools::Itertools;
 use language2::LanguageRegistry;
 use lazy_static::lazy_static;
-pub use modal_layer::ModalRegistry;
-use modal_layer::{init_modal_registry, ModalLayer};
+pub use modal_layer::*;
 use node_runtime::NodeRuntime;
 use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
 pub use pane::*;
@@ -697,7 +696,8 @@ impl Workspace {
             status_bar
         });
 
-        let modal_layer = cx.build_view(|cx| ModalLayer::new());
+        let workspace_handle = cx.view().downgrade();
+        let modal_layer = cx.build_view(|cx| ModalLayer::new(workspace_handle));
 
         // todo!()
         // cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
@@ -3709,7 +3709,7 @@ impl Render for Workspace {
             .child(
                 self.modal_layer
                     .read(cx)
-                    .render(self, cx)
+                    .render(cx)
                     .relative()
                     .flex_1()
                     .w_full()