diff --git a/assets/icons/at-sign.svg b/assets/icons/at-sign.svg
new file mode 100644
index 0000000000..5adac38f62
--- /dev/null
+++ b/assets/icons/at-sign.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/bell-off.svg b/assets/icons/bell-off.svg
new file mode 100644
index 0000000000..db1021f2d3
--- /dev/null
+++ b/assets/icons/bell-off.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/bell-ring.svg b/assets/icons/bell-ring.svg
new file mode 100644
index 0000000000..da51fdc5be
--- /dev/null
+++ b/assets/icons/bell-ring.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/bell.svg b/assets/icons/bell.svg
index ea1c6dd42e..4c7d5472db 100644
--- a/assets/icons/bell.svg
+++ b/assets/icons/bell.svg
@@ -1,8 +1 @@
-
+
\ No newline at end of file
diff --git a/assets/icons/mail-open.svg b/assets/icons/mail-open.svg
new file mode 100644
index 0000000000..b63915bd73
--- /dev/null
+++ b/assets/icons/mail-open.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/crates/db2/src/db2.rs b/crates/db2/src/db2.rs
index fe79dfbb0c..e052d59d12 100644
--- a/crates/db2/src/db2.rs
+++ b/crates/db2/src/db2.rs
@@ -190,138 +190,142 @@ where
.detach()
}
-// #[cfg(test)]
-// mod tests {
-// use std::thread;
+#[cfg(test)]
+mod tests {
+ use std::thread;
-// use sqlez::domain::Domain;
-// use sqlez_macros::sql;
-// use tempdir::TempDir;
+ use sqlez::domain::Domain;
+ use sqlez_macros::sql;
+ use tempdir::TempDir;
-// use crate::open_db;
+ use crate::open_db;
-// // Test bad migration panics
-// #[gpui::test]
-// #[should_panic]
-// async fn test_bad_migration_panics() {
-// enum BadDB {}
+ // Test bad migration panics
+ #[gpui2::test]
+ #[should_panic]
+ async fn test_bad_migration_panics() {
+ enum BadDB {}
-// impl Domain for BadDB {
-// fn name() -> &'static str {
-// "db_tests"
-// }
+ impl Domain for BadDB {
+ fn name() -> &'static str {
+ "db_tests"
+ }
-// fn migrations() -> &'static [&'static str] {
-// &[
-// sql!(CREATE TABLE test(value);),
-// // failure because test already exists
-// sql!(CREATE TABLE test(value);),
-// ]
-// }
-// }
+ fn migrations() -> &'static [&'static str] {
+ &[
+ sql!(CREATE TABLE test(value);),
+ // failure because test already exists
+ sql!(CREATE TABLE test(value);),
+ ]
+ }
+ }
-// let tempdir = TempDir::new("DbTests").unwrap();
-// let _bad_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
-// }
+ let tempdir = TempDir::new("DbTests").unwrap();
+ let _bad_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
+ }
-// /// Test that DB exists but corrupted (causing recreate)
-// #[gpui::test]
-// async fn test_db_corruption() {
-// enum CorruptedDB {}
+ /// Test that DB exists but corrupted (causing recreate)
+ #[gpui2::test]
+ async fn test_db_corruption(cx: &mut gpui2::TestAppContext) {
+ cx.executor().allow_parking();
-// impl Domain for CorruptedDB {
-// fn name() -> &'static str {
-// "db_tests"
-// }
+ enum CorruptedDB {}
-// fn migrations() -> &'static [&'static str] {
-// &[sql!(CREATE TABLE test(value);)]
-// }
-// }
+ impl Domain for CorruptedDB {
+ fn name() -> &'static str {
+ "db_tests"
+ }
-// enum GoodDB {}
+ fn migrations() -> &'static [&'static str] {
+ &[sql!(CREATE TABLE test(value);)]
+ }
+ }
-// impl Domain for GoodDB {
-// fn name() -> &'static str {
-// "db_tests" //Notice same name
-// }
+ enum GoodDB {}
-// fn migrations() -> &'static [&'static str] {
-// &[sql!(CREATE TABLE test2(value);)] //But different migration
-// }
-// }
+ impl Domain for GoodDB {
+ fn name() -> &'static str {
+ "db_tests" //Notice same name
+ }
-// let tempdir = TempDir::new("DbTests").unwrap();
-// {
-// let corrupt_db =
-// open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
-// assert!(corrupt_db.persistent());
-// }
+ fn migrations() -> &'static [&'static str] {
+ &[sql!(CREATE TABLE test2(value);)] //But different migration
+ }
+ }
-// let good_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
-// assert!(
-// good_db.select_row::("SELECT * FROM test2").unwrap()()
-// .unwrap()
-// .is_none()
-// );
-// }
+ let tempdir = TempDir::new("DbTests").unwrap();
+ {
+ let corrupt_db =
+ open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
+ assert!(corrupt_db.persistent());
+ }
-// /// Test that DB exists but corrupted (causing recreate)
-// #[gpui::test(iterations = 30)]
-// async fn test_simultaneous_db_corruption() {
-// enum CorruptedDB {}
+ let good_db = open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
+ assert!(
+ good_db.select_row::("SELECT * FROM test2").unwrap()()
+ .unwrap()
+ .is_none()
+ );
+ }
-// impl Domain for CorruptedDB {
-// fn name() -> &'static str {
-// "db_tests"
-// }
+ /// Test that DB exists but corrupted (causing recreate)
+ #[gpui2::test(iterations = 30)]
+ async fn test_simultaneous_db_corruption(cx: &mut gpui2::TestAppContext) {
+ cx.executor().allow_parking();
-// fn migrations() -> &'static [&'static str] {
-// &[sql!(CREATE TABLE test(value);)]
-// }
-// }
+ enum CorruptedDB {}
-// enum GoodDB {}
+ impl Domain for CorruptedDB {
+ fn name() -> &'static str {
+ "db_tests"
+ }
-// impl Domain for GoodDB {
-// fn name() -> &'static str {
-// "db_tests" //Notice same name
-// }
+ fn migrations() -> &'static [&'static str] {
+ &[sql!(CREATE TABLE test(value);)]
+ }
+ }
-// fn migrations() -> &'static [&'static str] {
-// &[sql!(CREATE TABLE test2(value);)] //But different migration
-// }
-// }
+ enum GoodDB {}
-// let tempdir = TempDir::new("DbTests").unwrap();
-// {
-// // Setup the bad database
-// let corrupt_db =
-// open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
-// assert!(corrupt_db.persistent());
-// }
+ impl Domain for GoodDB {
+ fn name() -> &'static str {
+ "db_tests" //Notice same name
+ }
-// // Try to connect to it a bunch of times at once
-// let mut guards = vec![];
-// for _ in 0..10 {
-// let tmp_path = tempdir.path().to_path_buf();
-// let guard = thread::spawn(move || {
-// let good_db = smol::block_on(open_db::(
-// tmp_path.as_path(),
-// &util::channel::ReleaseChannel::Dev,
-// ));
-// assert!(
-// good_db.select_row::("SELECT * FROM test2").unwrap()()
-// .unwrap()
-// .is_none()
-// );
-// });
+ fn migrations() -> &'static [&'static str] {
+ &[sql!(CREATE TABLE test2(value);)] //But different migration
+ }
+ }
-// guards.push(guard);
-// }
+ let tempdir = TempDir::new("DbTests").unwrap();
+ {
+ // Setup the bad database
+ let corrupt_db =
+ open_db::(tempdir.path(), &util::channel::ReleaseChannel::Dev).await;
+ assert!(corrupt_db.persistent());
+ }
-// for guard in guards.into_iter() {
-// assert!(guard.join().is_ok());
-// }
-// }
-// }
+ // Try to connect to it a bunch of times at once
+ let mut guards = vec![];
+ for _ in 0..10 {
+ let tmp_path = tempdir.path().to_path_buf();
+ let guard = thread::spawn(move || {
+ let good_db = smol::block_on(open_db::(
+ tmp_path.as_path(),
+ &util::channel::ReleaseChannel::Dev,
+ ));
+ assert!(
+ good_db.select_row::("SELECT * FROM test2").unwrap()()
+ .unwrap()
+ .is_none()
+ );
+ });
+
+ guards.push(guard);
+ }
+
+ for guard in guards.into_iter() {
+ assert!(guard.join().is_ok());
+ }
+ }
+}
diff --git a/crates/db2/src/kvp.rs b/crates/db2/src/kvp.rs
index 254d91689d..b4445e3586 100644
--- a/crates/db2/src/kvp.rs
+++ b/crates/db2/src/kvp.rs
@@ -31,32 +31,32 @@ impl KeyValueStore {
}
}
-// #[cfg(test)]
-// mod tests {
-// use crate::kvp::KeyValueStore;
+#[cfg(test)]
+mod tests {
+ use crate::kvp::KeyValueStore;
-// #[gpui::test]
-// async fn test_kvp() {
-// let db = KeyValueStore(crate::open_test_db("test_kvp").await);
+ #[gpui2::test]
+ async fn test_kvp() {
+ let db = KeyValueStore(crate::open_test_db("test_kvp").await);
-// assert_eq!(db.read_kvp("key-1").unwrap(), None);
+ assert_eq!(db.read_kvp("key-1").unwrap(), None);
-// db.write_kvp("key-1".to_string(), "one".to_string())
-// .await
-// .unwrap();
-// assert_eq!(db.read_kvp("key-1").unwrap(), Some("one".to_string()));
+ db.write_kvp("key-1".to_string(), "one".to_string())
+ .await
+ .unwrap();
+ assert_eq!(db.read_kvp("key-1").unwrap(), Some("one".to_string()));
-// db.write_kvp("key-1".to_string(), "one-2".to_string())
-// .await
-// .unwrap();
-// assert_eq!(db.read_kvp("key-1").unwrap(), Some("one-2".to_string()));
+ db.write_kvp("key-1".to_string(), "one-2".to_string())
+ .await
+ .unwrap();
+ assert_eq!(db.read_kvp("key-1").unwrap(), Some("one-2".to_string()));
-// db.write_kvp("key-2".to_string(), "two".to_string())
-// .await
-// .unwrap();
-// assert_eq!(db.read_kvp("key-2").unwrap(), Some("two".to_string()));
+ db.write_kvp("key-2".to_string(), "two".to_string())
+ .await
+ .unwrap();
+ assert_eq!(db.read_kvp("key-2").unwrap(), Some("two".to_string()));
-// db.delete_kvp("key-1".to_string()).await.unwrap();
-// assert_eq!(db.read_kvp("key-1").unwrap(), None);
-// }
-// }
+ db.delete_kvp("key-1".to_string()).await.unwrap();
+ assert_eq!(db.read_kvp("key-1").unwrap(), None);
+ }
+}
diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs
index 4890b79a9a..a92dbd6ff9 100644
--- a/crates/gpui2/src/element.rs
+++ b/crates/gpui2/src/element.rs
@@ -198,14 +198,19 @@ impl AnyElement {
pub trait Component {
fn render(self) -> AnyElement;
- fn when(mut self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
+ fn map(self, f: impl FnOnce(Self) -> U) -> U
+ where
+ Self: Sized,
+ U: Component,
+ {
+ f(self)
+ }
+
+ fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
where
Self: Sized,
{
- if condition {
- self = then(self);
- }
- self
+ self.map(|this| if condition { then(this) } else { this })
}
}
diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs
index 2897c9f38e..37b4d64345 100644
--- a/crates/gpui2/src/window.rs
+++ b/crates/gpui2/src/window.rs
@@ -6,8 +6,9 @@ use crate::{
Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent,
MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite,
PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
- SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task,
- Underline, UnderlineStyle, View, VisualContext, WeakView, WindowOptions, SUBPIXEL_VARIANTS,
+ SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
+ TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
+ WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Result};
use collections::HashMap;
@@ -56,6 +57,7 @@ pub enum DispatchPhase {
Capture,
}
+type AnyObserver = Box bool + 'static>;
type AnyListener = Box;
type AnyKeyListener = Box<
dyn Fn(
@@ -187,6 +189,10 @@ pub struct Window {
default_prevented: bool,
mouse_position: Point,
scale_factor: f32,
+ bounds: WindowBounds,
+ bounds_observers: SubscriberSet<(), AnyObserver>,
+ active: bool,
+ activation_observers: SubscriberSet<(), AnyObserver>,
pub(crate) scene_builder: SceneBuilder,
pub(crate) dirty: bool,
pub(crate) last_blur: Option