diff --git a/Cargo.lock b/Cargo.lock index 34a1324a41..9d2c6a35fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -663,9 +663,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "bzip2-sys" @@ -750,6 +750,34 @@ dependencies = [ "winx", ] +[[package]] +name = "capture" +version = "0.1.0" +dependencies = [ + "anyhow", + "bindgen", + "block", + "byteorder", + "bytes", + "cocoa", + "core-foundation", + "core-graphics", + "foreign-types", + "futures", + "gpui", + "hmac 0.12.1", + "jwt", + "live_kit", + "log", + "media", + "objc", + "parking_lot 0.11.2", + "postage", + "serde", + "sha2 0.10.2", + "simplelog", +] + [[package]] name = "castaway" version = "0.1.2" @@ -1098,6 +1126,30 @@ dependencies = [ "workspace", ] +[[package]] +name = "contacts_status_item" +version = "0.1.0" +dependencies = [ + "anyhow", + "client", + "collections", + "editor", + "futures", + "fuzzy", + "gpui", + "language", + "log", + "menu", + "picker", + "postage", + "project", + "serde", + "settings", + "theme", + "util", + "workspace", +] + [[package]] name = "context_menu" version = "0.1.0" @@ -2213,6 +2265,7 @@ dependencies = [ "image", "lazy_static", "log", + "media", "metal", "num_cpus", "objc", @@ -2722,6 +2775,21 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41ee439ee368ba4a77ac70d04f14015415af8600d6c894dc1f11bd79758c57d5" +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64", + "crypto-common", + "digest 0.10.3", + "hmac 0.12.1", + "serde", + "serde_json", + "sha2 0.10.2", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -2892,6 +2960,20 @@ dependencies = [ "rand_chacha 0.3.1", ] +[[package]] +name = "live_kit" +version = "0.1.0" +dependencies = [ + "anyhow", + "core-foundation", + "core-graphics", + "futures", + "media", + "parking_lot 0.11.2", + "serde", + "serde_json", +] + [[package]] name = "lock_api" version = "0.4.7" @@ -3008,6 +3090,20 @@ dependencies = [ "digest 0.10.3", ] +[[package]] +name = "media" +version = "0.1.0" +dependencies = [ + "anyhow", + "bindgen", + "block", + "bytes", + "core-foundation", + "foreign-types", + "metal", + "objc", +] + [[package]] name = "memchr" version = "2.5.0" @@ -7033,7 +7129,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.53.1" +version = "0.54.1" dependencies = [ "activity_indicator", "anyhow", @@ -7052,6 +7148,7 @@ dependencies = [ "collections", "command_palette", "contacts_panel", + "contacts_status_item", "context_menu", "ctor", "diagnostics", diff --git a/assets/icons/zed_22.svg b/assets/icons/zed_22.svg new file mode 100644 index 0000000000..68e7dc8e57 --- /dev/null +++ b/assets/icons/zed_22.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 1d95c33cbf..7a25dc19d3 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -309,8 +309,7 @@ "cmd-shift-p": "command_palette::Toggle", "cmd-shift-m": "diagnostics::Deploy", "cmd-shift-e": "project_panel::ToggleFocus", - "cmd-alt-s": "workspace::SaveAll", - "shift-escape": "terminal::DeployModal" + "cmd-alt-s": "workspace::SaveAll" } }, // Bindings from Sublime Text @@ -394,10 +393,24 @@ { "context": "Workspace", "bindings": { + "shift-escape": "dock::FocusDock", "cmd-shift-c": "contacts_panel::ToggleFocus", "cmd-shift-b": "workspace::ToggleRightSidebar" } }, + { + "bindings": { + "cmd-shift-k cmd-shift-right": "dock::AnchorDockRight", + "cmd-shift-k cmd-shift-down": "dock::AnchorDockBottom", + "cmd-shift-k cmd-shift-up": "dock::ExpandDock" + } + }, + { + "context": "Dock", + "bindings": { + "shift-escape": "dock::HideDock" + } + }, { "context": "ProjectPanel", "bindings": { @@ -426,12 +439,5 @@ "cmd-v": "terminal::Paste", "cmd-k": "terminal::Clear" } - }, - { - "context": "ModalTerminal", - "bindings": { - "ctrl-cmd-space": "terminal::ShowCharacterPalette", - "shift-escape": "terminal::DeployModal" - } } ] \ No newline at end of file diff --git a/assets/settings/default.json b/assets/settings/default.json index 61af2bcaf9..d8efdc41ff 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -32,6 +32,16 @@ // 4. Save when idle for a certain amount of time: // "autosave": { "after_delay": {"milliseconds": 500} }, "autosave": "off", + // Where to place the dock by default. This setting can take three + // values: + // + // 1. Position the dock attached to the bottom of the workspace + // "default_dock_anchor": "bottom" + // 2. Position the dock to the right of the workspace like a side panel + // "default_dock_anchor": "right" + // 3. Position the dock full screen over the entire workspace" + // "default_dock_anchor": "expanded" + "default_dock_anchor": "right", // How to auto-format modified buffers when saving them. This // setting can take three values: // diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 7d4d01e8a1..8f6f4bf627 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -278,7 +278,7 @@ impl View for ActivityIndicator { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let (icon, message, action) = self.content_to_render(cx); - let mut element = MouseEventHandler::new::(0, cx, |state, cx| { + let mut element = MouseEventHandler::::new(0, cx, |state, cx| { let theme = &cx .global::() .theme diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index b0dff3e40c..bbc9b0ea7f 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -29,7 +29,7 @@ impl View for UpdateNotification { let theme = cx.global::().theme.clone(); let theme = &theme.update_notification; - MouseEventHandler::new::(0, cx, |state, cx| { + MouseEventHandler::::new(0, cx, |state, cx| { Flex::column() .with_child( Flex::row() @@ -47,7 +47,7 @@ impl View for UpdateNotification { .boxed(), ) .with_child( - MouseEventHandler::new::(0, cx, |state, _| { + MouseEventHandler::::new(0, cx, |state, _| { let style = theme.dismiss_button.style_for(state, false); Svg::new("icons/x_mark_thin_8.svg") .with_color(style.color) diff --git a/crates/capture/Cargo.toml b/crates/capture/Cargo.toml new file mode 100644 index 0000000000..f8ed31097a --- /dev/null +++ b/crates/capture/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "capture" +version = "0.1.0" +edition = "2021" +description = "An example of screen capture" + +[dependencies] +gpui = { path = "../gpui" } +live_kit = { path = "../live_kit" } +media = { path = "../media" } + +anyhow = "1.0.38" +block = "0.1" +bytes = "1.2" +byteorder = "1.4" +cocoa = "0.24" +core-foundation = "0.9.3" +core-graphics = "0.22.3" +foreign-types = "0.3" +futures = "0.3" +hmac = "0.12" +jwt = "0.16" +log = { version = "0.4.16", features = ["kv_unstable_serde"] } +objc = "0.2" +parking_lot = "0.11.1" +postage = { version = "0.4.1", features = ["futures-traits"] } +serde = { version = "1.0", features = ["derive", "rc"] } +sha2 = "0.10" +simplelog = "0.9" + +[build-dependencies] +bindgen = "0.59.2" diff --git a/crates/capture/build.rs b/crates/capture/build.rs new file mode 100644 index 0000000000..41f60ff486 --- /dev/null +++ b/crates/capture/build.rs @@ -0,0 +1,7 @@ +fn main() { + // Find WebRTC.framework as a sibling of the executable when running outside of an application bundle + println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path"); + + // Register exported Objective-C selectors, protocols, etc + println!("cargo:rustc-link-arg=-Wl,-ObjC"); +} diff --git a/crates/capture/src/live_kit_token.rs b/crates/capture/src/live_kit_token.rs new file mode 100644 index 0000000000..be4fc4f4a2 --- /dev/null +++ b/crates/capture/src/live_kit_token.rs @@ -0,0 +1,71 @@ +use anyhow::Result; +use hmac::{Hmac, Mac}; +use jwt::SignWithKey; +use serde::Serialize; +use sha2::Sha256; +use std::{ + ops::Add, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +static DEFAULT_TTL: Duration = Duration::from_secs(6 * 60 * 60); // 6 hours + +#[derive(Default, Serialize)] +#[serde(rename_all = "camelCase")] +struct ClaimGrants<'a> { + iss: &'a str, + sub: &'a str, + iat: u64, + exp: u64, + nbf: u64, + jwtid: &'a str, + video: VideoGrant<'a>, +} + +#[derive(Default, Serialize)] +#[serde(rename_all = "camelCase")] +struct VideoGrant<'a> { + room_create: Option, + room_join: Option, + room_list: Option, + room_record: Option, + room_admin: Option, + room: Option<&'a str>, + can_publish: Option, + can_subscribe: Option, + can_publish_data: Option, + hidden: Option, + recorder: Option, +} + +pub fn create_token( + api_key: &str, + secret_key: &str, + room_name: &str, + participant_name: &str, +) -> Result { + let secret_key: Hmac = Hmac::new_from_slice(secret_key.as_bytes())?; + + let now = SystemTime::now(); + + let claims = ClaimGrants { + iss: api_key, + sub: participant_name, + iat: now.duration_since(UNIX_EPOCH).unwrap().as_secs(), + exp: now + .add(DEFAULT_TTL) + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(), + nbf: 0, + jwtid: participant_name, + video: VideoGrant { + room: Some(room_name), + room_join: Some(true), + can_publish: Some(true), + can_subscribe: Some(true), + ..Default::default() + }, + }; + Ok(claims.sign_with_key(&secret_key)?) +} diff --git a/crates/capture/src/main.rs b/crates/capture/src/main.rs new file mode 100644 index 0000000000..c34f451e41 --- /dev/null +++ b/crates/capture/src/main.rs @@ -0,0 +1,143 @@ +mod live_kit_token; + +use futures::StreamExt; +use gpui::{ + actions, + elements::{Canvas, *}, + keymap::Binding, + platform::current::Surface, + Menu, MenuItem, ViewContext, +}; +use live_kit::{LocalVideoTrack, Room}; +use log::LevelFilter; +use media::core_video::CVImageBuffer; +use postage::watch; +use simplelog::SimpleLogger; +use std::sync::Arc; + +actions!(capture, [Quit]); + +fn main() { + SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); + + gpui::App::new(()).unwrap().run(|cx| { + cx.platform().activate(true); + cx.add_global_action(quit); + + cx.add_bindings([Binding::new("cmd-q", Quit, None)]); + cx.set_menus(vec![Menu { + name: "Zed", + items: vec![MenuItem::Action { + name: "Quit", + action: Box::new(Quit), + }], + }]); + + let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap(); + let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap(); + let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap(); + + cx.spawn(|mut cx| async move { + let user1_token = live_kit_token::create_token( + &live_kit_key, + &live_kit_secret, + "test-room", + "test-participant-1", + ) + .unwrap(); + let room1 = Room::new(); + room1.connect(&live_kit_url, &user1_token).await.unwrap(); + + let user2_token = live_kit_token::create_token( + &live_kit_key, + &live_kit_secret, + "test-room", + "test-participant-2", + ) + .unwrap(); + let room2 = Room::new(); + room2.connect(&live_kit_url, &user2_token).await.unwrap(); + cx.add_window(Default::default(), |cx| ScreenCaptureView::new(room2, cx)); + + let windows = live_kit::list_windows(); + let window = windows + .iter() + .find(|w| w.owner_name.as_deref() == Some("Safari")) + .unwrap(); + let track = LocalVideoTrack::screen_share_for_window(window.id); + room1.publish_video_track(&track).await.unwrap(); + }) + .detach(); + }); +} + +struct ScreenCaptureView { + image_buffer: Option, + _room: Arc, +} + +impl gpui::Entity for ScreenCaptureView { + type Event = (); +} + +impl ScreenCaptureView { + pub fn new(room: Arc, cx: &mut ViewContext) -> Self { + let mut remote_video_tracks = room.remote_video_tracks(); + cx.spawn_weak(|this, mut cx| async move { + if let Some(video_track) = remote_video_tracks.next().await { + let (mut frames_tx, mut frames_rx) = watch::channel_with(None); + video_track.add_renderer(move |frame| *frames_tx.borrow_mut() = Some(frame)); + + while let Some(frame) = frames_rx.next().await { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + this.image_buffer = frame; + cx.notify(); + }); + } else { + break; + } + } + } + }) + .detach(); + + Self { + image_buffer: None, + _room: room, + } + } +} + +impl gpui::View for ScreenCaptureView { + fn ui_name() -> &'static str { + "View" + } + + fn render(&mut self, _: &mut gpui::RenderContext) -> gpui::ElementBox { + let image_buffer = self.image_buffer.clone(); + let canvas = Canvas::new(move |bounds, _, cx| { + if let Some(image_buffer) = image_buffer.clone() { + cx.scene.push_surface(Surface { + bounds, + image_buffer, + }); + } + }); + + if let Some(image_buffer) = self.image_buffer.as_ref() { + canvas + .constrained() + .with_width(image_buffer.width() as f32) + .with_height(image_buffer.height() as f32) + .aligned() + .boxed() + } else { + canvas.boxed() + } + } +} + +fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) { + cx.platform().quit(); +} diff --git a/crates/chat_panel/src/chat_panel.rs b/crates/chat_panel/src/chat_panel.rs index 3ff7062f40..6744ae9339 100644 --- a/crates/chat_panel/src/chat_panel.rs +++ b/crates/chat_panel/src/chat_panel.rs @@ -308,7 +308,7 @@ impl ChatPanel { enum SignInPromptLabel {} Align::new( - MouseEventHandler::new::(0, cx, |mouse_state, _| { + MouseEventHandler::::new(0, cx, |mouse_state, _| { Label::new( "Sign in to use chat".to_string(), if mouse_state.hovered { diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 905aa328f2..6b512d950f 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -298,7 +298,8 @@ async fn test_host_disconnect( let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await; assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); - let (_, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx)); + let (_, workspace_b) = + cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx)); let editor_b = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "b.txt"), true, cx) @@ -2786,7 +2787,8 @@ async fn test_collaborating_with_code_actions( // Join the project as client B. let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await; - let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx)); + let (_window_b, workspace_b) = + cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx)); let editor_b = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "main.rs"), true, cx) @@ -3001,7 +3003,8 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await; - let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx)); + let (_window_b, workspace_b) = + cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx)); let editor_b = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "one.rs"), true, cx) @@ -5224,6 +5227,7 @@ impl TestServer { fs: fs.clone(), build_window_options: Default::default, initialize_workspace: |_, _, _| unimplemented!(), + default_item_factory: |_, _| unimplemented!(), }); Channel::init(&client); @@ -5459,7 +5463,9 @@ impl TestClient { cx: &mut TestAppContext, ) -> ViewHandle { let (_, root_view) = cx.add_window(|_| EmptyView); - cx.add_view(&root_view, |cx| Workspace::new(project.clone(), cx)) + cx.add_view(&root_view, |cx| { + Workspace::new(project.clone(), |_, _| unimplemented!(), cx) + }) } async fn simulate_host( diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 9b51415069..c12e68a854 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -350,7 +350,8 @@ mod tests { }); let project = Project::test(app_state.fs.clone(), [], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let editor = cx.add_view(&workspace, |cx| { let mut editor = Editor::single_line(None, cx); editor.set_text("abc", cx); diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index fde304cd35..b5460f4d06 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -276,7 +276,7 @@ impl ContactsPanel { Section::Offline => "Offline", }; let icon_size = theme.section_icon_size; - MouseEventHandler::new::(section as usize, cx, |_, _| { + MouseEventHandler::
::new(section as usize, cx, |_, _| { Flex::row() .with_child( Svg::new(if is_collapsed { @@ -375,7 +375,7 @@ impl ContactsPanel { let baseline_offset = row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.; - MouseEventHandler::new::(project_id as usize, cx, |mouse_state, cx| { + MouseEventHandler::::new(project_id as usize, cx, |mouse_state, cx| { let tree_branch = *tree_branch.style_for(mouse_state, is_selected); let row = theme.project_row.style_for(mouse_state, is_selected); @@ -424,7 +424,7 @@ impl ContactsPanel { return None; } - let button = MouseEventHandler::new::( + let button = MouseEventHandler::::new( project_id as usize, cx, |state, _| { @@ -529,7 +529,7 @@ impl ContactsPanel { enum ToggleOnline {} let project_id = project_handle.id(); - MouseEventHandler::new::(project_id, cx, |state, cx| { + MouseEventHandler::::new(project_id, cx, |state, cx| { let row = theme.project_row.style_for(state, is_selected); let mut worktree_root_names = String::new(); let project = if let Some(project) = project_handle.upgrade(cx.deref_mut()) { @@ -548,7 +548,7 @@ impl ContactsPanel { Flex::row() .with_child({ let button = - MouseEventHandler::new::(project_id, cx, |state, _| { + MouseEventHandler::::new(project_id, cx, |state, _| { let mut style = *theme.private_button.style_for(state, false); if is_going_online { style.color = theme.disabled_button.color; @@ -636,7 +636,7 @@ impl ContactsPanel { if is_incoming { row.add_children([ - MouseEventHandler::new::(user.id as usize, cx, |mouse_state, _| { + MouseEventHandler::::new(user.id as usize, cx, |mouse_state, _| { let button_style = if is_contact_request_pending { &theme.disabled_button } else { @@ -658,7 +658,7 @@ impl ContactsPanel { .contained() .with_margin_right(button_spacing) .boxed(), - MouseEventHandler::new::(user.id as usize, cx, |mouse_state, _| { + MouseEventHandler::::new(user.id as usize, cx, |mouse_state, _| { let button_style = if is_contact_request_pending { &theme.disabled_button } else { @@ -680,7 +680,7 @@ impl ContactsPanel { ]); } else { row.add_child( - MouseEventHandler::new::(user.id as usize, cx, |mouse_state, _| { + MouseEventHandler::::new(user.id as usize, cx, |mouse_state, _| { let button_style = if is_contact_request_pending { &theme.disabled_button } else { @@ -1071,7 +1071,7 @@ impl View for ContactsPanel { .boxed(), ) .with_child( - MouseEventHandler::new::(0, cx, |_, _| { + MouseEventHandler::::new(0, cx, |_, _| { Svg::new("icons/user_plus_16.svg") .with_color(theme.add_contact_button.color) .constrained() @@ -1102,35 +1102,31 @@ impl View for ContactsPanel { if info.count > 0 { Some( - MouseEventHandler::new::( - 0, - cx, - |state, cx| { - let style = - theme.invite_row.style_for(state, false).clone(); + MouseEventHandler::::new(0, cx, |state, cx| { + let style = + theme.invite_row.style_for(state, false).clone(); - let copied = - cx.read_from_clipboard().map_or(false, |item| { - item.text().as_str() == info.url.as_ref() - }); + let copied = + cx.read_from_clipboard().map_or(false, |item| { + item.text().as_str() == info.url.as_ref() + }); - Label::new( - format!( - "{} invite link ({} left)", - if copied { "Copied" } else { "Copy" }, - info.count - ), - style.label.clone(), - ) - .aligned() - .left() - .constrained() - .with_height(theme.row_height) - .contained() - .with_style(style.container) - .boxed() - }, - ) + Label::new( + format!( + "{} invite link ({} left)", + if copied { "Copied" } else { "Copy" }, + info.count + ), + style.label.clone(), + ) + .aligned() + .left() + .constrained() + .with_height(theme.row_height) + .contained() + .with_style(style.container) + .boxed() + }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, move |_, cx| { cx.write_to_clipboard(ClipboardItem::new( @@ -1247,7 +1243,8 @@ mod tests { .0 .read_with(cx, |worktree, _| worktree.id().to_proto()); - let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx)); let panel = cx.add_view(&workspace, |cx| { ContactsPanel::new( user_store.clone(), diff --git a/crates/contacts_panel/src/notifications.rs b/crates/contacts_panel/src/notifications.rs index 4cc30560d2..b9a6dba545 100644 --- a/crates/contacts_panel/src/notifications.rs +++ b/crates/contacts_panel/src/notifications.rs @@ -52,7 +52,7 @@ pub fn render_user_notification( .boxed(), ) .with_child( - MouseEventHandler::new::(user.id as usize, cx, |state, _| { + MouseEventHandler::::new(user.id as usize, cx, |state, _| { render_icon_button( theme.dismiss_button.style_for(state, false), "icons/x_mark_thin_8.svg", @@ -90,7 +90,7 @@ pub fn render_user_notification( Flex::row() .with_children(buttons.into_iter().enumerate().map( |(ix, (message, action))| { - MouseEventHandler::new::(ix, cx, |state, _| { + MouseEventHandler::