Detailed changes
@@ -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": {
@@ -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": {
@@ -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": {
@@ -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": {
@@ -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": {
@@ -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": {
@@ -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": {
@@ -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": {
@@ -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(),
}
})
@@ -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)]
@@ -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(),
)
@@ -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",
+ }
};
}