wip

Nathan Sobo created

Change summary

crates/breadcrumbs/src/breadcrumbs.rs               |   6 
crates/collab/src/tests/integration_tests.rs        |   5 
crates/collab_ui/src/collab_titlebar_item.rs        | 132 ++++++++------
crates/collab_ui/src/collaborator_list_popover.rs   |  33 +-
crates/collab_ui/src/contact_finder.rs              |   4 
crates/collab_ui/src/contact_list.rs                |  66 +++---
crates/collab_ui/src/contact_notification.rs        |   2 
crates/collab_ui/src/contacts_popover.rs            |   6 
crates/collab_ui/src/face_pile.rs                   |  27 +-
crates/collab_ui/src/incoming_call_notification.rs  |  16 
crates/collab_ui/src/notifications.rs               |  10 
crates/collab_ui/src/project_shared_notification.rs |  14 
crates/collab_ui/src/sharing_status_indicator.rs    |   8 
crates/gpui/src/app/window.rs                       |   4 
14 files changed, 176 insertions(+), 157 deletions(-)

Detailed changes

crates/breadcrumbs/src/breadcrumbs.rs 🔗

@@ -72,14 +72,14 @@ impl View for Breadcrumbs {
                 .boxed();
         }
 
-        MouseEventHandler::<Breadcrumbs>::new(0, cx, |state, _| {
+        MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
             let style = style.style_for(state, false);
             crumbs.with_style(style.container).boxed()
         })
-        .on_click(MouseButton::Left, |_, cx| {
+        .on_click(MouseButton::Left, |_, _, cx| {
             cx.dispatch_action(outline::Toggle);
         })
-        .with_tooltip::<Breadcrumbs, _>(
+        .with_tooltip::<Breadcrumbs>(
             0,
             "Show symbol outline".to_owned(),
             Some(Box::new(outline::Toggle)),

crates/collab/src/tests/integration_tests.rs 🔗

@@ -1467,7 +1467,8 @@ async fn test_host_disconnect(
     deterministic.run_until_parked();
     assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 
-    let (_, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+    let (window_id_b, workspace_b) =
+        cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
     let editor_b = workspace_b
         .update(cx_b, |workspace, cx| {
             workspace.open_path((worktree_id, "b.txt"), None, true, cx)
@@ -1490,7 +1491,7 @@ async fn test_host_disconnect(
     assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
 
     // Ensure client B's edited state is reset and that the whole window is blurred.
-    cx_b.read_window(|cx| {
+    cx_b.read_window(window_id_b, |cx| {
         assert_eq!(cx.focused_view_id(), None);
     });
     assert!(!cx_b.is_window_edited(workspace_b.window_id()));

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -16,8 +16,8 @@ use gpui::{
     impl_internal_actions,
     json::{self, ToJson},
     platform::{CursorStyle, MouseButton},
-    AppContext, Entity, ImageData, ModelHandle, Subscription, View, ViewContext, ViewHandle,
-    WeakViewHandle,
+    AppContext, Entity, ImageData, ModelHandle, SceneBuilder, Subscription, View, ViewContext,
+    ViewHandle, WeakViewHandle,
 };
 use settings::Settings;
 use std::{ops::Range, sync::Arc};
@@ -68,7 +68,7 @@ impl View for CollabTitlebarItem {
         "CollabTitlebarItem"
     }
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
         let workspace = if let Some(workspace) = self.workspace.upgrade(cx) {
             workspace
         } else {
@@ -326,7 +326,7 @@ impl CollabTitlebarItem {
         &self,
         theme: &Theme,
         cx: &mut ViewContext<Self>,
-    ) -> ElementBox {
+    ) -> ElementBox<Self> {
         let titlebar = &theme.workspace.titlebar;
 
         let badge = if self
@@ -352,7 +352,7 @@ impl CollabTitlebarItem {
 
         Stack::new()
             .with_child(
-                MouseEventHandler::<ToggleContactsMenu>::new(0, cx, |state, _| {
+                MouseEventHandler::<ToggleContactsMenu, Self>::new(0, cx, |state, _| {
                     let style = titlebar
                         .toggle_contacts_button
                         .style_for(state, self.contacts_popover.is_some());
@@ -369,10 +369,10 @@ impl CollabTitlebarItem {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(ToggleContactsMenu);
                 })
-                .with_tooltip::<ToggleContactsMenu, _>(
+                .with_tooltip::<ToggleContactsMenu>(
                     0,
                     "Show contacts menu".into(),
                     Some(Box::new(ToggleContactsMenu)),
@@ -391,7 +391,7 @@ impl CollabTitlebarItem {
         theme: &Theme,
         room: &ModelHandle<Room>,
         cx: &mut ViewContext<Self>,
-    ) -> ElementBox {
+    ) -> ElementBox<Self> {
         let icon;
         let tooltip;
         if room.read(cx).is_screen_sharing() {
@@ -403,7 +403,7 @@ impl CollabTitlebarItem {
         }
 
         let titlebar = &theme.workspace.titlebar;
-        MouseEventHandler::<ToggleScreenSharing>::new(0, cx, |state, _| {
+        MouseEventHandler::<ToggleScreenSharing, Self>::new(0, cx, |state, _| {
             let style = titlebar.call_control.style_for(state, false);
             Svg::new(icon)
                 .with_color(style.color)
@@ -418,10 +418,10 @@ impl CollabTitlebarItem {
                 .boxed()
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, cx| {
+        .on_click(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_action(ToggleScreenSharing);
         })
-        .with_tooltip::<ToggleScreenSharing, _>(
+        .with_tooltip::<ToggleScreenSharing>(
             0,
             tooltip.into(),
             Some(Box::new(ToggleScreenSharing)),
@@ -437,7 +437,7 @@ impl CollabTitlebarItem {
         workspace: &ViewHandle<Workspace>,
         theme: &Theme,
         cx: &mut ViewContext<Self>,
-    ) -> Option<ElementBox> {
+    ) -> Option<ElementBox<Self>> {
         let project = workspace.read(cx).project();
         if project.read(cx).is_remote() {
             return None;
@@ -457,7 +457,7 @@ impl CollabTitlebarItem {
         Some(
             Stack::new()
                 .with_child(
-                    MouseEventHandler::<ShareUnshare>::new(0, cx, |state, _| {
+                    MouseEventHandler::<ShareUnshare, Self>::new(0, cx, |state, _| {
                         //TODO: Ensure this button has consistant width for both text variations
                         let style = titlebar
                             .share_button
@@ -468,14 +468,14 @@ impl CollabTitlebarItem {
                             .boxed()
                     })
                     .with_cursor_style(CursorStyle::PointingHand)
-                    .on_click(MouseButton::Left, move |_, cx| {
+                    .on_click(MouseButton::Left, move |_, _, cx| {
                         if is_shared {
                             cx.dispatch_action(UnshareProject);
                         } else {
                             cx.dispatch_action(ShareProject);
                         }
                     })
-                    .with_tooltip::<ShareUnshare, _>(
+                    .with_tooltip::<ShareUnshare>(
                         0,
                         tooltip.to_owned(),
                         None,
@@ -491,12 +491,16 @@ impl CollabTitlebarItem {
         )
     }
 
-    fn render_user_menu_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> ElementBox {
+    fn render_user_menu_button(
+        &self,
+        theme: &Theme,
+        cx: &mut ViewContext<Self>,
+    ) -> ElementBox<Self> {
         let titlebar = &theme.workspace.titlebar;
 
         Stack::new()
             .with_child(
-                MouseEventHandler::<ToggleUserMenu>::new(0, cx, |state, _| {
+                MouseEventHandler::<ToggleUserMenu, Self>::new(0, cx, |state, _| {
                     let style = titlebar.call_control.style_for(state, false);
                     Svg::new("icons/ellipsis_14.svg")
                         .with_color(style.color)
@@ -511,10 +515,10 @@ impl CollabTitlebarItem {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(ToggleUserMenu);
                 })
-                .with_tooltip::<ToggleUserMenu, _>(
+                .with_tooltip::<ToggleUserMenu>(
                     0,
                     "Toggle user menu".to_owned(),
                     Some(Box::new(ToggleUserMenu)),
@@ -535,9 +539,9 @@ impl CollabTitlebarItem {
             .boxed()
     }
 
-    fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> ElementBox {
+    fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
         let titlebar = &theme.workspace.titlebar;
-        MouseEventHandler::<SignIn>::new(0, cx, |state, _| {
+        MouseEventHandler::<SignIn, Self>::new(0, cx, |state, _| {
             let style = titlebar.sign_in_prompt.style_for(state, false);
             Label::new("Sign In", style.text.clone())
                 .contained()
@@ -545,7 +549,7 @@ impl CollabTitlebarItem {
                 .boxed()
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, cx| {
+        .on_click(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_action(SignIn);
         })
         .boxed()
@@ -555,7 +559,7 @@ impl CollabTitlebarItem {
         &'a self,
         _theme: &'a theme::Titlebar,
         cx: &'a ViewContext<Self>,
-    ) -> Option<ElementBox> {
+    ) -> Option<ElementBox<Self>> {
         self.contacts_popover.as_ref().map(|popover| {
             Overlay::new(ChildView::new(popover, cx).boxed())
                 .with_fit_mode(OverlayFitMode::SwitchAnchor)
@@ -574,7 +578,7 @@ impl CollabTitlebarItem {
         theme: &Theme,
         room: &ModelHandle<Room>,
         cx: &mut ViewContext<Self>,
-    ) -> Vec<ElementBox> {
+    ) -> Vec<ElementBox<Self>> {
         let mut participants = room
             .read(cx)
             .remote_participants()
@@ -616,7 +620,7 @@ impl CollabTitlebarItem {
         user: &Arc<User>,
         peer_id: PeerId,
         cx: &mut ViewContext<Self>,
-    ) -> ElementBox {
+    ) -> ElementBox<Self> {
         let replica_id = workspace.read(cx).project().read(cx).replica_id();
         Container::new(self.render_face_pile(
             user,
@@ -640,7 +644,7 @@ impl CollabTitlebarItem {
         workspace: &ViewHandle<Workspace>,
         theme: &Theme,
         cx: &mut ViewContext<Self>,
-    ) -> ElementBox {
+    ) -> ElementBox<Self> {
         let project_id = workspace.read(cx).project().read(cx).remote_id();
         let room = ActiveCall::global(cx).read(cx).room();
         let is_being_followed = workspace.read(cx).is_being_followed(peer_id);
@@ -753,41 +757,42 @@ impl CollabTitlebarItem {
 
         if let Some(location) = location {
             if let Some(replica_id) = replica_id {
-                content =
-                    MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| {
-                        content
-                    })
-                    .with_cursor_style(CursorStyle::PointingHand)
-                    .on_click(MouseButton::Left, move |_, cx| {
-                        cx.dispatch_action(ToggleFollow(peer_id))
-                    })
-                    .with_tooltip::<ToggleFollow, _>(
-                        peer_id.as_u64() as usize,
-                        if is_being_followed {
-                            format!("Unfollow {}", user.github_login)
-                        } else {
-                            format!("Follow {}", user.github_login)
-                        },
-                        Some(Box::new(FollowNextCollaborator)),
-                        theme.tooltip.clone(),
-                        cx,
-                    )
-                    .boxed();
+                content = MouseEventHandler::<ToggleFollow, Self>::new(
+                    replica_id.into(),
+                    cx,
+                    move |_, _| content,
+                )
+                .with_cursor_style(CursorStyle::PointingHand)
+                .on_click(MouseButton::Left, move |_, _, cx| {
+                    cx.dispatch_action(ToggleFollow(peer_id))
+                })
+                .with_tooltip::<ToggleFollow>(
+                    peer_id.as_u64() as usize,
+                    if is_being_followed {
+                        format!("Unfollow {}", user.github_login)
+                    } else {
+                        format!("Follow {}", user.github_login)
+                    },
+                    Some(Box::new(FollowNextCollaborator)),
+                    theme.tooltip.clone(),
+                    cx,
+                )
+                .boxed();
             } else if let ParticipantLocation::SharedProject { project_id } = location {
                 let user_id = user.id;
-                content = MouseEventHandler::<JoinProject>::new(
+                content = MouseEventHandler::<JoinProject, Self>::new(
                     peer_id.as_u64() as usize,
                     cx,
                     move |_, _| content,
                 )
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(JoinProject {
                         project_id,
                         follow_user_id: user_id,
                     })
                 })
-                .with_tooltip::<JoinProject, _>(
+                .with_tooltip::<JoinProject>(
                     peer_id.as_u64() as usize,
                     format!("Follow {} into external project", user.github_login),
                     Some(Box::new(FollowNextCollaborator)),
@@ -823,7 +828,7 @@ impl CollabTitlebarItem {
         avatar: Arc<ImageData>,
         avatar_style: AvatarStyle,
         background_color: Color,
-    ) -> ElementBox {
+    ) -> ElementBox<Self> {
         Image::from_data(avatar)
             .with_style(avatar_style.image)
             .aligned()
@@ -841,7 +846,7 @@ impl CollabTitlebarItem {
         &self,
         status: &client::Status,
         cx: &mut ViewContext<Self>,
-    ) -> Option<ElementBox> {
+    ) -> Option<ElementBox<Self>> {
         enum ConnectionStatusButton {}
 
         let theme = &cx.global::<Settings>().theme.clone();
@@ -867,7 +872,7 @@ impl CollabTitlebarItem {
                 .boxed(),
             ),
             client::Status::UpgradeRequired => Some(
-                MouseEventHandler::<ConnectionStatusButton>::new(0, cx, |_, _| {
+                MouseEventHandler::<ConnectionStatusButton, Self>::new(0, cx, |_, _| {
                     Label::new(
                         "Please update Zed to collaborate",
                         theme.workspace.titlebar.outdated_warning.text.clone(),
@@ -878,7 +883,7 @@ impl CollabTitlebarItem {
                     .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, cx| {
+                .on_click(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(auto_update::Check);
                 })
                 .boxed(),
@@ -898,7 +903,7 @@ impl AvatarRibbon {
     }
 }
 
-impl Element for AvatarRibbon {
+impl Element<CollabTitlebarItem> for AvatarRibbon {
     type LayoutState = ();
 
     type PaintState = ();
@@ -906,17 +911,20 @@ impl Element for AvatarRibbon {
     fn layout(
         &mut self,
         constraint: gpui::SizeConstraint,
-        _: &mut gpui::LayoutContext,
+        _: &mut CollabTitlebarItem,
+        _: &mut ViewContext<CollabTitlebarItem>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         (constraint.max, ())
     }
 
     fn paint(
         &mut self,
-        bounds: gpui::geometry::rect::RectF,
-        _: gpui::geometry::rect::RectF,
+        scene: &mut SceneBuilder,
+        bounds: RectF,
+        _: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut gpui::PaintContext,
+        _: &mut CollabTitlebarItem,
+        _: &mut ViewContext<CollabTitlebarItem>,
     ) -> Self::PaintState {
         let mut path = PathBuilder::new();
         path.reset(bounds.lower_left());
@@ -937,17 +945,19 @@ impl Element for AvatarRibbon {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &gpui::MeasurementContext,
+        _: &CollabTitlebarItem,
+        _: &ViewContext<CollabTitlebarItem>,
     ) -> Option<RectF> {
         None
     }
 
     fn debug(
         &self,
-        bounds: gpui::geometry::rect::RectF,
+        bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &gpui::DebugContext,
+        _: &CollabTitlebarItem,
+        _: &ViewContext<CollabTitlebarItem>,
     ) -> gpui::json::Value {
         json::json!({
             "type": "AvatarRibbon",

crates/collab_ui/src/collaborator_list_popover.rs 🔗

@@ -30,10 +30,10 @@ impl View for CollaboratorListPopover {
         "CollaboratorListPopover"
     }
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
         let theme = cx.global::<Settings>().theme.clone();
 
-        MouseEventHandler::<Self>::new(0, cx, |_, _| {
+        MouseEventHandler::<Self, Self>::new(0, cx, |_, _| {
             List::new(self.list_state.clone())
                 .contained()
                 .with_style(theme.contacts_popover.container) //TODO: Change the name of this theme key
@@ -42,7 +42,7 @@ impl View for CollaboratorListPopover {
                 .with_height(theme.contacts_popover.height)
                 .boxed()
         })
-        .on_down_out(MouseButton::Left, move |_, cx| {
+        .on_down_out(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_action(ToggleCollaboratorList);
         })
         .boxed()
@@ -117,7 +117,7 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
     icon_action: IA,
     icon_tooltip: String,
     cx: &mut ViewContext<CollaboratorListPopover>,
-) -> ElementBox {
+) -> ElementBox<CollaboratorListPopover> {
     enum Username {}
     enum UsernameTooltip {}
     enum Icon {}
@@ -127,19 +127,20 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
     let username_theme = theme.contact_list.contact_username.text.clone();
     let tooltip_theme = theme.tooltip.clone();
 
-    let username = MouseEventHandler::<Username>::new(index, cx, |_, _| {
-        Label::new(username.to_owned(), username_theme.clone()).boxed()
-    })
-    .on_click(MouseButton::Left, move |_, cx| {
-        if let Some(username_action) = username_action.clone() {
-            cx.dispatch_action(username_action);
-        }
-    });
+    let username =
+        MouseEventHandler::<Username, CollaboratorListPopover>::new(index, cx, |_, _| {
+            Label::new(username.to_owned(), username_theme.clone()).boxed()
+        })
+        .on_click(MouseButton::Left, move |_, _, cx| {
+            if let Some(username_action) = username_action.clone() {
+                cx.dispatch_action(username_action);
+            }
+        });
 
     Flex::row()
         .with_child(if let Some(username_tooltip) = username_tooltip {
             username
-                .with_tooltip::<UsernameTooltip, _>(
+                .with_tooltip::<UsernameTooltip>(
                     index,
                     username_tooltip,
                     None,
@@ -151,11 +152,11 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
             username.boxed()
         })
         .with_child(
-            MouseEventHandler::<Icon>::new(index, cx, |_, _| icon.boxed())
-                .on_click(MouseButton::Left, move |_, cx| {
+            MouseEventHandler::<Icon, CollaboratorListPopover>::new(index, cx, |_, _| icon.boxed())
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(icon_action.clone())
                 })
-                .with_tooltip::<IconTooltip, _>(index, icon_tooltip, None, tooltip_theme, cx)
+                .with_tooltip::<IconTooltip>(index, icon_tooltip, None, tooltip_theme, cx)
                 .boxed(),
         )
         .boxed()

crates/collab_ui/src/contact_finder.rs 🔗

@@ -32,7 +32,7 @@ impl View for ContactFinder {
         "ContactFinder"
     }
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
         ChildView::new(&self.picker, cx).boxed()
     }
 
@@ -104,7 +104,7 @@ impl PickerDelegate for ContactFinder {
         mouse_state: &mut MouseState,
         selected: bool,
         cx: &gpui::AppContext,
-    ) -> ElementBox {
+    ) -> ElementBox<Picker<Self>> {
         let theme = &cx.global::<Settings>().theme;
         let user = &self.potential_contacts[ix];
         let request_status = self.user_store.read(cx).contact_request_status(user);

crates/collab_ui/src/contact_list.rs 🔗

@@ -159,7 +159,7 @@ pub enum Event {
 pub struct ContactList {
     entries: Vec<ContactEntry>,
     match_candidates: Vec<StringMatchCandidate>,
-    list_state: ListState,
+    list_state: ListState<Self>,
     project: ModelHandle<Project>,
     user_store: ModelHandle<UserStore>,
     filter_editor: ViewHandle<Editor>,
@@ -202,7 +202,7 @@ impl ContactList {
         })
         .detach();
 
-        let list_state = ListState::new(0, Orientation::Top, 1000., move |this, ix, cx| {
+        let list_state = ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
             let theme = cx.global::<Settings>().theme.clone();
             let is_selected = this.selection == Some(ix);
             let current_project_id = this.project.read(cx).remote_id();
@@ -748,7 +748,7 @@ impl ContactList {
         is_pending: bool,
         is_selected: bool,
         theme: &theme::ContactList,
-    ) -> ElementBox {
+    ) -> ElementBox<Self> {
         Flex::row()
             .with_children(user.avatar.clone().map(|avatar| {
                 Image::from_data(avatar)
@@ -800,7 +800,7 @@ impl ContactList {
         is_selected: bool,
         theme: &theme::ContactList,
         cx: &mut ViewContext<Self>,
-    ) -> ElementBox {
+    ) -> ElementBox<Self> {
         let font_cache = cx.font_cache();
         let host_avatar_height = theme
             .contact_avatar
@@ -819,7 +819,7 @@ impl ContactList {
             worktree_root_names.join(", ")
         };
 
-        MouseEventHandler::<JoinProject>::new(project_id as usize, cx, |mouse_state, _| {
+        MouseEventHandler::<JoinProject, Self>::new(project_id as usize, cx, |mouse_state, _| {
             let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
             let row = theme.project_row.style_for(mouse_state, is_selected);
 
@@ -827,7 +827,7 @@ impl ContactList {
                 .with_child(
                     Stack::new()
                         .with_child(
-                            Canvas::new(move |bounds, _, cx| {
+                            Canvas::new(move |scene, bounds, _, _, cx| {
                                 let start_x = bounds.min_x() + (bounds.width() / 2.)
                                     - (tree_branch.width / 2.);
                                 let end_x = bounds.max_x();
@@ -882,7 +882,7 @@ impl ContactList {
         } else {
             CursorStyle::Arrow
         })
-        .on_click(MouseButton::Left, move |_, cx| {
+        .on_click(MouseButton::Left, move |_, _, cx| {
             if !is_current {
                 cx.dispatch_global_action(JoinProject {
                     project_id,
@@ -899,7 +899,7 @@ impl ContactList {
         is_selected: bool,
         theme: &theme::ContactList,
         cx: &mut ViewContext<Self>,
-    ) -> ElementBox {
+    ) -> ElementBox<Self> {
         let font_cache = cx.font_cache();
         let host_avatar_height = theme
             .contact_avatar
@@ -913,7 +913,7 @@ impl ContactList {
         let baseline_offset =
             row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
 
-        MouseEventHandler::<OpenSharedScreen>::new(
+        MouseEventHandler::<OpenSharedScreen, Self>::new(
             peer_id.as_u64() as usize,
             cx,
             |mouse_state, _| {
@@ -924,7 +924,7 @@ impl ContactList {
                     .with_child(
                         Stack::new()
                             .with_child(
-                                Canvas::new(move |bounds, _, cx| {
+                                Canvas::new(move |scene, bounds, _, _, cx| {
                                     let start_x = bounds.min_x() + (bounds.width() / 2.)
                                         - (tree_branch.width / 2.);
                                     let end_x = bounds.max_x();
@@ -988,7 +988,7 @@ impl ContactList {
             },
         )
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, cx| {
+        .on_click(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_action(OpenSharedScreen { peer_id });
         })
         .boxed()
@@ -1000,7 +1000,7 @@ impl ContactList {
         is_selected: bool,
         is_collapsed: bool,
         cx: &mut ViewContext<Self>,
-    ) -> ElementBox {
+    ) -> ElementBox<Self> {
         enum Header {}
         enum LeaveCallContactList {}
 
@@ -1015,14 +1015,14 @@ impl ContactList {
         };
         let leave_call = if section == Section::ActiveCall {
             Some(
-                MouseEventHandler::<LeaveCallContactList>::new(0, cx, |state, _| {
+                MouseEventHandler::<LeaveCallContactList, Self>::new(0, cx, |state, _| {
                     let style = theme.leave_call.style_for(state, false);
                     Label::new("Leave Call", style.text.clone())
                         .contained()
                         .with_style(style.container)
                         .boxed()
                 })
-                .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(LeaveCall))
+                .on_click(MouseButton::Left, |_, _, cx| cx.dispatch_action(LeaveCall))
                 .aligned()
                 .boxed(),
             )
@@ -1031,7 +1031,7 @@ impl ContactList {
         };
 
         let icon_size = theme.section_icon_size;
-        MouseEventHandler::<Header>::new(section as usize, cx, |_, _| {
+        MouseEventHandler::<Header, Self>::new(section as usize, cx, |_, _| {
             Flex::row()
                 .with_child(
                     Svg::new(if is_collapsed {
@@ -1065,7 +1065,7 @@ impl ContactList {
                 .boxed()
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, cx| {
+        .on_click(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_action(ToggleExpanded(section))
         })
         .boxed()
@@ -1078,14 +1078,14 @@ impl ContactList {
         theme: &theme::ContactList,
         is_selected: bool,
         cx: &mut ViewContext<Self>,
-    ) -> ElementBox {
+    ) -> ElementBox<Self> {
         let online = contact.online;
         let busy = contact.busy || calling;
         let user_id = contact.user.id;
         let github_login = contact.user.github_login.clone();
         let initial_project = project.clone();
         let mut element =
-            MouseEventHandler::<Contact>::new(contact.user.id as usize, cx, |_, cx| {
+            MouseEventHandler::<Contact, Self>::new(contact.user.id as usize, cx, |_, cx| {
                 Flex::row()
                     .with_children(contact.user.avatar.clone().map(|avatar| {
                         let status_badge = if contact.online {
@@ -1128,7 +1128,7 @@ impl ContactList {
                         .boxed(),
                     )
                     .with_child(
-                        MouseEventHandler::<Cancel>::new(
+                        MouseEventHandler::<Cancel, Self>::new(
                             contact.user.id as usize,
                             cx,
                             |mouse_state, _| {
@@ -1142,7 +1142,7 @@ impl ContactList {
                         )
                         .with_padding(Padding::uniform(2.))
                         .with_cursor_style(CursorStyle::PointingHand)
-                        .on_click(MouseButton::Left, move |_, cx| {
+                        .on_click(MouseButton::Left, move |_, _, cx| {
                             cx.dispatch_action(RemoveContact {
                                 user_id,
                                 github_login: github_login.clone(),
@@ -1172,7 +1172,7 @@ impl ContactList {
                     )
                     .boxed()
             })
-            .on_click(MouseButton::Left, move |_, cx| {
+            .on_click(MouseButton::Left, move |_, _, cx| {
                 if online && !busy {
                     cx.dispatch_action(Call {
                         recipient_user_id: user_id,
@@ -1195,7 +1195,7 @@ impl ContactList {
         is_incoming: bool,
         is_selected: bool,
         cx: &mut ViewContext<Self>,
-    ) -> ElementBox {
+    ) -> ElementBox<Self> {
         enum Decline {}
         enum Accept {}
         enum Cancel {}
@@ -1228,7 +1228,7 @@ impl ContactList {
 
         if is_incoming {
             row.add_children([
-                MouseEventHandler::<Decline>::new(user.id as usize, cx, |mouse_state, _| {
+                MouseEventHandler::<Decline, Self>::new(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
@@ -1239,7 +1239,7 @@ impl ContactList {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(RespondToContactRequest {
                         user_id,
                         accept: false,
@@ -1248,7 +1248,7 @@ impl ContactList {
                 .contained()
                 .with_margin_right(button_spacing)
                 .boxed(),
-                MouseEventHandler::<Accept>::new(user.id as usize, cx, |mouse_state, _| {
+                MouseEventHandler::<Accept, Self>::new(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
@@ -1260,7 +1260,7 @@ impl ContactList {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(RespondToContactRequest {
                         user_id,
                         accept: true,
@@ -1270,7 +1270,7 @@ impl ContactList {
             ]);
         } else {
             row.add_child(
-                MouseEventHandler::<Cancel>::new(user.id as usize, cx, |mouse_state, _| {
+                MouseEventHandler::<Cancel, Self>::new(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
@@ -1283,7 +1283,7 @@ impl ContactList {
                 })
                 .with_padding(Padding::uniform(2.))
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(RemoveContact {
                         user_id,
                         github_login: github_login.clone(),
@@ -1331,7 +1331,7 @@ impl View for ContactList {
         cx
     }
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
         enum AddContact {}
         let theme = cx.global::<Settings>().theme.clone();
 
@@ -1346,7 +1346,7 @@ impl View for ContactList {
                             .boxed(),
                     )
                     .with_child(
-                        MouseEventHandler::<AddContact>::new(0, cx, |_, _| {
+                        MouseEventHandler::<AddContact, Self>::new(0, cx, |_, _| {
                             render_icon_button(
                                 &theme.contact_list.add_contact_button,
                                 "icons/user_plus_16.svg",
@@ -1354,10 +1354,10 @@ impl View for ContactList {
                             .boxed()
                         })
                         .with_cursor_style(CursorStyle::PointingHand)
-                        .on_click(MouseButton::Left, |_, cx| {
+                        .on_click(MouseButton::Left, |_, _, cx| {
                             cx.dispatch_action(contacts_popover::ToggleContactFinder)
                         })
-                        .with_tooltip::<AddContact, _>(
+                        .with_tooltip::<AddContact>(
                             0,
                             "Search for new contact".into(),
                             None,
@@ -1387,7 +1387,7 @@ impl View for ContactList {
     }
 }
 
-fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Element {
+fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Element<ContactList> {
     Svg::new(svg_path)
         .with_color(style.color)
         .constrained()

crates/collab_ui/src/contact_notification.rs 🔗

@@ -42,7 +42,7 @@ impl View for ContactNotification {
         "ContactNotification"
     }
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
         match self.kind {
             ContactEventKind::Requested => render_user_notification(
                 self.user.clone(),

crates/collab_ui/src/contacts_popover.rs 🔗

@@ -91,14 +91,14 @@ impl View for ContactsPopover {
         "ContactsPopover"
     }
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
         let theme = cx.global::<Settings>().theme.clone();
         let child = match &self.child {
             Child::ContactList(child) => ChildView::new(child, cx),
             Child::ContactFinder(child) => ChildView::new(child, cx),
         };
 
-        MouseEventHandler::<ContactsPopover>::new(0, cx, |_, _| {
+        MouseEventHandler::<ContactsPopover, Self>::new(0, cx, |_, _| {
             Flex::column()
                 .with_child(child.flex(1., true).boxed())
                 .contained()
@@ -108,7 +108,7 @@ impl View for ContactsPopover {
                 .with_height(theme.contacts_popover.height)
                 .boxed()
         })
-        .on_down_out(MouseButton::Left, move |_, cx| {
+        .on_down_out(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_action(ToggleContactsMenu);
         })
         .boxed()

crates/collab_ui/src/face_pile.rs 🔗

@@ -7,13 +7,14 @@ use gpui::{
     },
     json::ToJson,
     serde_json::{self, json},
-    Axis, DebugContext, Element, ElementBox, MeasurementContext, PaintContext, SceneBuilder,
-    ViewContext,
+    Axis, Element, ElementBox, SceneBuilder, ViewContext,
 };
 
+use crate::CollabTitlebarItem;
+
 pub(crate) struct FacePile {
     overlap: f32,
-    faces: Vec<ElementBox>,
+    faces: Vec<ElementBox<CollabTitlebarItem>>,
 }
 
 impl FacePile {
@@ -25,15 +26,15 @@ impl FacePile {
     }
 }
 
-impl<V: View> Element<V> for FacePile {
+impl Element<CollabTitlebarItem> for FacePile {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: gpui::SizeConstraint,
-        view: &mut Self,
-        cx: &mut ViewContext<Self>,
+        view: &mut CollabTitlebarItem,
+        cx: &mut ViewContext<CollabTitlebarItem>,
     ) -> (Vector2F, Self::LayoutState) {
         debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
 
@@ -52,8 +53,8 @@ impl<V: View> Element<V> for FacePile {
         bounds: RectF,
         visible_bounds: RectF,
         _layout: &mut Self::LayoutState,
-        view: &mut Self,
-        cx: &mut ViewContext<Self>,
+        view: &mut CollabTitlebarItem,
+        cx: &mut ViewContext<CollabTitlebarItem>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
@@ -79,7 +80,8 @@ impl<V: View> Element<V> for FacePile {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &MeasurementContext,
+        _: &CollabTitlebarItem,
+        _: &ViewContext<CollabTitlebarItem>,
     ) -> Option<RectF> {
         None
     }
@@ -89,7 +91,8 @@ impl<V: View> Element<V> for FacePile {
         bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &DebugContext,
+        _: &CollabTitlebarItem,
+        _: &ViewContext<CollabTitlebarItem>,
     ) -> serde_json::Value {
         json!({
             "type": "FacePile",
@@ -98,8 +101,8 @@ impl<V: View> Element<V> for FacePile {
     }
 }
 
-impl Extend<ElementBox<Self>> for FacePile {
-    fn extend<T: IntoIterator<Item = ElementBox<Self>>>(&mut self, children: T) {
+impl Extend<ElementBox<CollabTitlebarItem>> for FacePile {
+    fn extend<T: IntoIterator<Item = ElementBox<CollabTitlebarItem>>>(&mut self, children: T) {
         self.faces.extend(children);
     }
 }

crates/collab_ui/src/incoming_call_notification.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{
     geometry::{rect::RectF, vector::vec2f},
     impl_internal_actions,
     platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
-    AppContext, Entity, View, ViewContext,
+    AppContext, ElementBox, Entity, View, ViewContext,
 };
 use settings::Settings;
 use util::ResultExt;
@@ -99,7 +99,7 @@ impl IncomingCallNotification {
         }
     }
 
-    fn render_caller(&self, cx: &mut ViewContext<Self>) -> ElementBox {
+    fn render_caller(&self, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
         let theme = &cx.global::<Settings>().theme.incoming_call_notification;
         let default_project = proto::ParticipantProject::default();
         let initial_project = self
@@ -165,13 +165,13 @@ impl IncomingCallNotification {
             .boxed()
     }
 
-    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> ElementBox {
+    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
         enum Accept {}
         enum Decline {}
 
         Flex::column()
             .with_child(
-                MouseEventHandler::<Accept>::new(0, cx, |_, cx| {
+                MouseEventHandler::<Accept, Self>::new(0, cx, |_, cx| {
                     let theme = &cx.global::<Settings>().theme.incoming_call_notification;
                     Label::new("Accept", theme.accept_button.text.clone())
                         .aligned()
@@ -180,14 +180,14 @@ impl IncomingCallNotification {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, cx| {
+                .on_click(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(RespondToCall { accept: true });
                 })
                 .flex(1., true)
                 .boxed(),
             )
             .with_child(
-                MouseEventHandler::<Decline>::new(0, cx, |_, cx| {
+                MouseEventHandler::<Decline, Self>::new(0, cx, |_, cx| {
                     let theme = &cx.global::<Settings>().theme.incoming_call_notification;
                     Label::new("Decline", theme.decline_button.text.clone())
                         .aligned()
@@ -196,7 +196,7 @@ impl IncomingCallNotification {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, cx| {
+                .on_click(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(RespondToCall { accept: false });
                 })
                 .flex(1., true)
@@ -222,7 +222,7 @@ impl View for IncomingCallNotification {
         "IncomingCallNotification"
     }
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
         let background = cx
             .global::<Settings>()
             .theme

crates/collab_ui/src/notifications.rs 🔗

@@ -17,7 +17,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
     dismiss_action: A,
     buttons: Vec<(&'static str, Box<dyn Action>)>,
     cx: &mut ViewContext<V>,
-) -> ElementBox {
+) -> ElementBox<V> {
     let theme = cx.global::<Settings>().theme.clone();
     let theme = &theme.contact_notification;
 
@@ -51,7 +51,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
                     .boxed(),
                 )
                 .with_child(
-                    MouseEventHandler::<Dismiss>::new(user.id as usize, cx, |state, _| {
+                    MouseEventHandler::<Dismiss, V>::new(user.id as usize, cx, |state, _| {
                         let style = theme.dismiss_button.style_for(state, false);
                         Svg::new("icons/x_mark_8.svg")
                             .with_color(style.color)
@@ -67,7 +67,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
                     })
                     .with_cursor_style(CursorStyle::PointingHand)
                     .with_padding(Padding::uniform(5.))
-                    .on_click(MouseButton::Left, move |_, cx| {
+                    .on_click(MouseButton::Left, move |_, _, cx| {
                         cx.dispatch_any_action(dismiss_action.boxed_clone())
                     })
                     .aligned()
@@ -96,7 +96,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
                 Flex::row()
                     .with_children(buttons.into_iter().enumerate().map(
                         |(ix, (message, action))| {
-                            MouseEventHandler::<Button>::new(ix, cx, |state, _| {
+                            MouseEventHandler::<Button, V>::new(ix, cx, |state, _| {
                                 let button = theme.button.style_for(state, false);
                                 Label::new(message, button.text.clone())
                                     .contained()
@@ -104,7 +104,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
                                     .boxed()
                             })
                             .with_cursor_style(CursorStyle::PointingHand)
-                            .on_click(MouseButton::Left, move |_, cx| {
+                            .on_click(MouseButton::Left, move |_, _, cx| {
                                 cx.dispatch_any_action(action.boxed_clone())
                             })
                             .boxed()

crates/collab_ui/src/project_shared_notification.rs 🔗

@@ -104,7 +104,7 @@ impl ProjectSharedNotification {
         cx.remove_window(window_id);
     }
 
-    fn render_owner(&self, cx: &mut ViewContext<Self>) -> ElementBox {
+    fn render_owner(&self, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
         let theme = &cx.global::<Settings>().theme.project_shared_notification;
         Flex::row()
             .with_children(self.owner.avatar.clone().map(|avatar| {
@@ -164,7 +164,7 @@ impl ProjectSharedNotification {
             .boxed()
     }
 
-    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> ElementBox {
+    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
         enum Open {}
         enum Dismiss {}
 
@@ -173,7 +173,7 @@ impl ProjectSharedNotification {
 
         Flex::column()
             .with_child(
-                MouseEventHandler::<Open>::new(0, cx, |_, cx| {
+                MouseEventHandler::<Open, Self>::new(0, cx, |_, cx| {
                     let theme = &cx.global::<Settings>().theme.project_shared_notification;
                     Label::new("Open", theme.open_button.text.clone())
                         .aligned()
@@ -182,7 +182,7 @@ impl ProjectSharedNotification {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(JoinProject {
                         project_id,
                         follow_user_id: owner_user_id,
@@ -192,7 +192,7 @@ impl ProjectSharedNotification {
                 .boxed(),
             )
             .with_child(
-                MouseEventHandler::<Dismiss>::new(0, cx, |_, cx| {
+                MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, cx| {
                     let theme = &cx.global::<Settings>().theme.project_shared_notification;
                     Label::new("Dismiss", theme.dismiss_button.text.clone())
                         .aligned()
@@ -201,7 +201,7 @@ impl ProjectSharedNotification {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, cx| {
+                .on_click(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(DismissProject);
                 })
                 .flex(1., true)
@@ -227,7 +227,7 @@ impl View for ProjectSharedNotification {
         "ProjectSharedNotification"
     }
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::ElementBox<Self> {
         let background = cx
             .global::<Settings>()
             .theme

crates/collab_ui/src/sharing_status_indicator.rs 🔗

@@ -40,13 +40,13 @@ impl View for SharingStatusIndicator {
         "SharingStatusIndicator"
     }
 
-    fn render(&mut self, cx: &mut ViewContext<'_, Self>) -> ElementBox {
-        let color = match cx.appearance {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> ElementBox<Self> {
+        let color = match cx.window_appearance() {
             Appearance::Light | Appearance::VibrantLight => Color::black(),
             Appearance::Dark | Appearance::VibrantDark => Color::white(),
         };
 
-        MouseEventHandler::<Self>::new(0, cx, |_, _| {
+        MouseEventHandler::<Self, Self>::new(0, cx, |_, _| {
             Svg::new("icons/disable_screen_sharing_12.svg")
                 .with_color(color)
                 .constrained()
@@ -54,7 +54,7 @@ impl View for SharingStatusIndicator {
                 .aligned()
                 .boxed()
         })
-        .on_click(MouseButton::Left, |_, cx| {
+        .on_click(MouseButton::Left, |_, _, cx| {
             cx.dispatch_action(ToggleScreenSharing);
         })
         .boxed()

crates/gpui/src/app/window.rs 🔗

@@ -805,6 +805,10 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> {
         self.window.platform_window.bounds()
     }
 
+    pub fn window_appearance(&self) -> Appearance {
+        self.window.appearance
+    }
+
     pub fn window_display_uuid(&self) -> Option<Uuid> {
         self.window.platform_window.screen().display_uuid()
     }