Start on workspace notifications

Nathan Sobo created

Change summary

assets/themes/cave-dark.json         | 13 ++++++
assets/themes/cave-light.json        | 13 ++++++
assets/themes/dark.json              | 13 ++++++
assets/themes/light.json             | 13 ++++++
assets/themes/solarized-dark.json    | 13 ++++++
assets/themes/solarized-light.json   | 13 ++++++
assets/themes/sulphurpool-dark.json  | 13 ++++++
assets/themes/sulphurpool-light.json | 13 ++++++
crates/chat_panel/src/chat_panel.rs  |  2 
crates/theme/src/theme.rs            |  9 ++++
crates/workspace/src/workspace.rs    | 58 ++++++++++++++++++++++++++++++
styles/src/styleTree/workspace.ts    |  8 ++++
12 files changed, 180 insertions(+), 1 deletion(-)

Detailed changes

assets/themes/cave-dark.json 🔗

@@ -470,6 +470,19 @@
       "color": "#efecf4",
       "size": 14,
       "background": "#000000aa"
+    },
+    "notification": {
+      "margin": {
+        "top": 10
+      }
+    },
+    "notifications": {
+      "width": 256,
+      "margin": {
+        "right": 10,
+        "bottom": 10
+      },
+      "background": "#ff0000"
     }
   },
   "editor": {

assets/themes/cave-light.json 🔗

@@ -470,6 +470,19 @@
       "color": "#19171c",
       "size": 14,
       "background": "#000000aa"
+    },
+    "notification": {
+      "margin": {
+        "top": 10
+      }
+    },
+    "notifications": {
+      "width": 256,
+      "margin": {
+        "right": 10,
+        "bottom": 10
+      },
+      "background": "#ff0000"
     }
   },
   "editor": {

assets/themes/dark.json 🔗

@@ -470,6 +470,19 @@
       "color": "#ffffff",
       "size": 14,
       "background": "#000000aa"
+    },
+    "notification": {
+      "margin": {
+        "top": 10
+      }
+    },
+    "notifications": {
+      "width": 256,
+      "margin": {
+        "right": 10,
+        "bottom": 10
+      },
+      "background": "#ff0000"
     }
   },
   "editor": {

assets/themes/light.json 🔗

@@ -470,6 +470,19 @@
       "color": "#000000",
       "size": 14,
       "background": "#000000aa"
+    },
+    "notification": {
+      "margin": {
+        "top": 10
+      }
+    },
+    "notifications": {
+      "width": 256,
+      "margin": {
+        "right": 10,
+        "bottom": 10
+      },
+      "background": "#ff0000"
     }
   },
   "editor": {

assets/themes/solarized-dark.json 🔗

@@ -470,6 +470,19 @@
       "color": "#fdf6e3",
       "size": 14,
       "background": "#000000aa"
+    },
+    "notification": {
+      "margin": {
+        "top": 10
+      }
+    },
+    "notifications": {
+      "width": 256,
+      "margin": {
+        "right": 10,
+        "bottom": 10
+      },
+      "background": "#ff0000"
     }
   },
   "editor": {

assets/themes/solarized-light.json 🔗

@@ -470,6 +470,19 @@
       "color": "#002b36",
       "size": 14,
       "background": "#000000aa"
+    },
+    "notification": {
+      "margin": {
+        "top": 10
+      }
+    },
+    "notifications": {
+      "width": 256,
+      "margin": {
+        "right": 10,
+        "bottom": 10
+      },
+      "background": "#ff0000"
     }
   },
   "editor": {

assets/themes/sulphurpool-dark.json 🔗

@@ -470,6 +470,19 @@
       "color": "#f5f7ff",
       "size": 14,
       "background": "#000000aa"
+    },
+    "notification": {
+      "margin": {
+        "top": 10
+      }
+    },
+    "notifications": {
+      "width": 256,
+      "margin": {
+        "right": 10,
+        "bottom": 10
+      },
+      "background": "#ff0000"
     }
   },
   "editor": {

assets/themes/sulphurpool-light.json 🔗

@@ -470,6 +470,19 @@
       "color": "#202746",
       "size": 14,
       "background": "#000000aa"
+    },
+    "notification": {
+      "margin": {
+        "top": 10
+      }
+    },
+    "notifications": {
+      "width": 256,
+      "margin": {
+        "right": 10,
+        "bottom": 10
+      },
+      "background": "#ff0000"
     }
   },
   "editor": {

crates/chat_panel/src/chat_panel.rs 🔗

@@ -69,7 +69,7 @@ impl ChatPanel {
             .with_style(move |cx| {
                 let theme = &cx.global::<Settings>().theme.chat_panel.channel_select;
                 SelectStyle {
-                    header: theme.header.container.clone(),
+                    header: theme.header.container,
                     menu: theme.menu.clone(),
                 }
             })

crates/theme/src/theme.rs 🔗

@@ -45,6 +45,8 @@ pub struct Workspace {
     pub toolbar: Toolbar,
     pub disconnected_overlay: ContainedText,
     pub modal: ContainerStyle,
+    pub notification: ContainerStyle,
+    pub notifications: Notifications,
 }
 
 #[derive(Clone, Deserialize, Default)]
@@ -109,6 +111,13 @@ pub struct Toolbar {
     pub item_spacing: f32,
 }
 
+#[derive(Clone, Deserialize, Default)]
+pub struct Notifications {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub width: f32,
+}
+
 #[derive(Clone, Deserialize, Default)]
 pub struct Search {
     #[serde(flatten)]

crates/workspace/src/workspace.rs 🔗

@@ -604,6 +604,24 @@ impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
     }
 }
 
+pub trait Notification: View {}
+
+pub trait NotificationHandle {
+    fn to_any(&self) -> AnyViewHandle;
+}
+
+impl<T: Notification> NotificationHandle for ViewHandle<T> {
+    fn to_any(&self) -> AnyViewHandle {
+        self.into()
+    }
+}
+
+impl Into<AnyViewHandle> for &dyn NotificationHandle {
+    fn into(self) -> AnyViewHandle {
+        self.to_any()
+    }
+}
+
 #[derive(Clone)]
 pub struct WorkspaceParams {
     pub project: ModelHandle<Project>,
@@ -683,6 +701,7 @@ pub struct Workspace {
     panes: Vec<ViewHandle<Pane>>,
     active_pane: ViewHandle<Pane>,
     status_bar: ViewHandle<StatusBar>,
+    notifications: Vec<Box<dyn NotificationHandle>>,
     project: ModelHandle<Project>,
     leader_state: LeaderState,
     follower_states_by_leader: FollowerStatesByLeader,
@@ -791,6 +810,7 @@ impl Workspace {
             panes: vec![pane.clone()],
             active_pane: pane.clone(),
             status_bar,
+            notifications: Default::default(),
             client: params.client.clone(),
             remote_entity_subscription: None,
             user_store: params.user_store.clone(),
@@ -971,6 +991,15 @@ impl Workspace {
         }
     }
 
+    pub fn show_notification<V: Notification>(
+        &mut self,
+        notification: ViewHandle<V>,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.notifications.push(Box::new(notification));
+        cx.notify();
+    }
+
     pub fn items<'a>(
         &'a self,
         cx: &'a AppContext,
@@ -1703,6 +1732,34 @@ impl Workspace {
         }
     }
 
+    fn render_notifications(
+        &self,
+        theme: &theme::Workspace,
+        cx: &mut RenderContext<Self>,
+    ) -> Option<ElementBox> {
+        if self.notifications.is_empty() {
+            None
+        } else {
+            Some(
+                Flex::column()
+                    .with_children(self.notifications.iter().map(|notification| {
+                        ChildView::new(notification.as_ref())
+                            .contained()
+                            .with_style(theme.notification)
+                            .boxed()
+                    }))
+                    .constrained()
+                    .with_width(250.)
+                    .contained()
+                    .with_style(theme.notifications.container)
+                    .aligned()
+                    .bottom()
+                    .right()
+                    .boxed(),
+            )
+        }
+    }
+
     // RPC handlers
 
     async fn handle_follow(
@@ -2037,6 +2094,7 @@ impl View for Workspace {
                                     .top()
                                     .boxed()
                             }))
+                            .with_children(self.render_notifications(&theme.workspace, cx))
                             .flex(1.0, true)
                             .boxed(),
                     )

styles/src/styleTree/workspace.ts 🔗

@@ -146,5 +146,13 @@ export default function workspace(theme: Theme) {
       ...text(theme, "sans", "active"),
       background: "#000000aa",
     },
+    notification: {
+      margin: { top: 10 },
+    },
+    notifications: {
+      width: 256,
+      margin: { right: 10, bottom: 10 },
+      background: "#ff0000",
+    }
   };
 }