Add components example (#2846)

Mikayla Maki created

This PR is a continuation of the components UI exploration I've been
doing. It adds an example to the GPUI examples page and totally
restructures the generics on our MouseEventHandler.

Release Note:
- N/A

Change summary

crates/activity_indicator/src/activity_indicator.rs    |   2 
crates/ai/src/assistant.rs                             |  18 
crates/auto_update/src/update_notification.rs          |   4 
crates/breadcrumbs/src/breadcrumbs.rs                  |   2 
crates/collab_ui/src/collab_titlebar_item.rs           |  30 
crates/collab_ui/src/contact_list.rs                   |  20 
crates/collab_ui/src/contacts_popover.rs               |   2 
crates/collab_ui/src/incoming_call_notification.rs     |   4 
crates/collab_ui/src/notifications.rs                  |   4 
crates/collab_ui/src/project_shared_notification.rs    |   4 
crates/collab_ui/src/sharing_status_indicator.rs       |   2 
crates/context_menu/src/context_menu.rs                |   4 
crates/copilot/src/sign_in.rs                          |   2 
crates/copilot_button/src/copilot_button.rs            |   2 
crates/diagnostics/src/items.rs                        |   4 
crates/drag_and_drop/src/drag_and_drop.rs              |   6 
crates/editor/src/editor.rs                            |  10 
crates/editor/src/element.rs                           |   2 
crates/editor/src/hover_popover.rs                     |   4 
crates/feedback/src/deploy_feedback_button.rs          |   2 
crates/feedback/src/feedback_info_text.rs              |   2 
crates/feedback/src/submit_feedback_button.rs          |   2 
crates/gpui/examples/components.rs                     | 335 ++++++++++++
crates/gpui/src/app.rs                                 |  38 +
crates/gpui/src/elements.rs                            |   4 
crates/gpui/src/elements/container.rs                  |   9 
crates/gpui/src/elements/mouse_event_handler.rs        |  63 +
crates/gpui/src/elements/tooltip.rs                    |   2 
crates/gpui/src/fonts.rs                               |   7 
crates/gpui/src/platform.rs                            |  11 
crates/gpui/src/scene/mouse_region.rs                  |  46 
crates/gpui/src/views/select.rs                        |   4 
crates/language_selector/src/active_buffer_language.rs |   2 
crates/language_tools/src/lsp_log.rs                   |   8 
crates/language_tools/src/syntax_tree_view.rs          |   8 
crates/picker/src/picker.rs                            |   2 
crates/project_panel/src/project_panel.rs              |   6 
crates/search/src/buffer_search.rs                     |   8 
crates/search/src/project_search.rs                    |   8 
crates/theme/src/ui.rs                                 |  14 
crates/vcs_menu/src/lib.rs                             |   2 
crates/workspace/src/dock.rs                           |   3 
crates/workspace/src/notifications.rs                  |   4 
crates/workspace/src/pane.rs                           |  10 
crates/workspace/src/pane/dragged_item_receiver.rs     |   4 
crates/workspace/src/pane_group.rs                     |   2 
crates/workspace/src/shared_screen.rs                  |   2 
crates/workspace/src/toolbar.rs                        |   2 
crates/workspace/src/workspace.rs                      |   4 
49 files changed, 585 insertions(+), 155 deletions(-)

Detailed changes

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -318,7 +318,7 @@ impl View for ActivityIndicator {
             on_click,
         } = self.content_to_render(cx);
 
-        let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
+        let mut element = MouseEventHandler::new::<Self, _>(0, cx, |state, cx| {
             let theme = &theme::current(cx).workspace.status_bar.lsp_status;
             let style = if state.hovered() && on_click.is_some() {
                 theme.hovered.as_ref().unwrap_or(&theme.default)

crates/ai/src/assistant.rs 🔗

@@ -348,7 +348,7 @@ impl AssistantPanel {
         enum History {}
         let theme = theme::current(cx);
         let tooltip_style = theme::current(cx).tooltip.clone();
-        MouseEventHandler::<History, _>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<History, _>(0, cx, |state, _| {
             let style = theme.assistant.hamburger_button.style_for(state);
             Svg::for_style(style.icon.clone())
                 .contained()
@@ -380,7 +380,7 @@ impl AssistantPanel {
     fn render_split_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
         let theme = theme::current(cx);
         let tooltip_style = theme::current(cx).tooltip.clone();
-        MouseEventHandler::<Split, _>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<Split, _>(0, cx, |state, _| {
             let style = theme.assistant.split_button.style_for(state);
             Svg::for_style(style.icon.clone())
                 .contained()
@@ -404,7 +404,7 @@ impl AssistantPanel {
     fn render_assist_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
         let theme = theme::current(cx);
         let tooltip_style = theme::current(cx).tooltip.clone();
-        MouseEventHandler::<Assist, _>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<Assist, _>(0, cx, |state, _| {
             let style = theme.assistant.assist_button.style_for(state);
             Svg::for_style(style.icon.clone())
                 .contained()
@@ -422,7 +422,7 @@ impl AssistantPanel {
     fn render_quote_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
         let theme = theme::current(cx);
         let tooltip_style = theme::current(cx).tooltip.clone();
-        MouseEventHandler::<QuoteSelection, _>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<QuoteSelection, _>(0, cx, |state, _| {
             let style = theme.assistant.quote_button.style_for(state);
             Svg::for_style(style.icon.clone())
                 .contained()
@@ -450,7 +450,7 @@ impl AssistantPanel {
     fn render_plus_button(cx: &mut ViewContext<Self>) -> impl Element<Self> {
         let theme = theme::current(cx);
         let tooltip_style = theme::current(cx).tooltip.clone();
-        MouseEventHandler::<NewConversation, _>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<NewConversation, _>(0, cx, |state, _| {
             let style = theme.assistant.plus_button.style_for(state);
             Svg::for_style(style.icon.clone())
                 .contained()
@@ -480,7 +480,7 @@ impl AssistantPanel {
             &theme.assistant.zoom_in_button
         };
 
-        MouseEventHandler::<ToggleZoomButton, _>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<ToggleZoomButton, _>(0, cx, |state, _| {
             let style = style.style_for(state);
             Svg::for_style(style.icon.clone())
                 .contained()
@@ -506,7 +506,7 @@ impl AssistantPanel {
     ) -> impl Element<Self> {
         let conversation = &self.saved_conversations[index];
         let path = conversation.path.clone();
-        MouseEventHandler::<SavedConversationMetadata, _>::new(index, cx, move |state, cx| {
+        MouseEventHandler::new::<SavedConversationMetadata, _>(index, cx, move |state, cx| {
             let style = &theme::current(cx).assistant.saved_conversation;
             Flex::row()
                 .with_child(
@@ -1818,7 +1818,7 @@ impl ConversationEditor {
                             let theme = theme::current(cx);
                             let style = &theme.assistant;
                             let message_id = message.id;
-                            let sender = MouseEventHandler::<Sender, _>::new(
+                            let sender = MouseEventHandler::new::<Sender, _>(
                                 message_id.0,
                                 cx,
                                 |state, _| match message.role {
@@ -2044,7 +2044,7 @@ impl ConversationEditor {
     ) -> impl Element<Self> {
         enum Model {}
 
-        MouseEventHandler::<Model, _>::new(0, cx, |state, cx| {
+        MouseEventHandler::new::<Model, _>(0, cx, |state, cx| {
             let style = style.model.style_for(state);
             Label::new(self.conversation.read(cx).model.clone(), style.text.clone())
                 .contained()

crates/auto_update/src/update_notification.rs 🔗

@@ -31,7 +31,7 @@ impl View for UpdateNotification {
 
         let app_name = cx.global::<ReleaseChannel>().display_name();
 
-        MouseEventHandler::<ViewReleaseNotes, _>::new(0, cx, |state, cx| {
+        MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {
             Flex::column()
                 .with_child(
                     Flex::row()
@@ -48,7 +48,7 @@ impl View for UpdateNotification {
                             .flex(1., true),
                         )
                         .with_child(
-                            MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
+                            MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
                                 let style = theme.dismiss_button.style_for(state);
                                 Svg::new("icons/x_mark_8.svg")
                                     .with_color(style.color)

crates/breadcrumbs/src/breadcrumbs.rs 🔗

@@ -82,7 +82,7 @@ impl View for Breadcrumbs {
                 .into_any();
         }
 
-        MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<Breadcrumbs, _>(0, cx, |state, _| {
             let style = style.style_for(state);
             crumbs.with_style(style.container)
         })

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -226,7 +226,7 @@ impl CollabTitlebarItem {
         let mut ret = Flex::row().with_child(
             Stack::new()
                 .with_child(
-                    MouseEventHandler::<ToggleProjectMenu, Self>::new(0, cx, |mouse_state, cx| {
+                    MouseEventHandler::new::<ToggleProjectMenu, _>(0, cx, |mouse_state, cx| {
                         let style = project_style
                             .in_state(self.project_popover.is_some())
                             .style_for(mouse_state);
@@ -266,7 +266,7 @@ impl CollabTitlebarItem {
                     .with_child(
                         Stack::new()
                             .with_child(
-                                MouseEventHandler::<ToggleVcsMenu, Self>::new(
+                                MouseEventHandler::new::<ToggleVcsMenu, _>(
                                     0,
                                     cx,
                                     |mouse_state, cx| {
@@ -398,7 +398,7 @@ impl CollabTitlebarItem {
         self.branch_popover.as_ref().map(|child| {
             let theme = theme::current(cx).clone();
             let child = ChildView::new(child, cx);
-            let child = MouseEventHandler::<BranchList, Self>::new(0, cx, |_, _| {
+            let child = MouseEventHandler::new::<BranchList, _>(0, cx, |_, _| {
                 child
                     .flex(1., true)
                     .contained()
@@ -433,7 +433,7 @@ impl CollabTitlebarItem {
         self.project_popover.as_ref().map(|child| {
             let theme = theme::current(cx).clone();
             let child = ChildView::new(child, cx);
-            let child = MouseEventHandler::<RecentProjects, Self>::new(0, cx, |_, _| {
+            let child = MouseEventHandler::new::<RecentProjects, _>(0, cx, |_, _| {
                 child
                     .flex(1., true)
                     .contained()
@@ -560,7 +560,7 @@ impl CollabTitlebarItem {
 
         Stack::new()
             .with_child(
-                MouseEventHandler::<ToggleContactsMenu, Self>::new(0, cx, |state, _| {
+                MouseEventHandler::new::<ToggleContactsMenu, _>(0, cx, |state, _| {
                     let style = titlebar
                         .toggle_contacts_button
                         .in_state(self.contacts_popover.is_some())
@@ -610,7 +610,7 @@ impl CollabTitlebarItem {
 
         let active = room.read(cx).is_screen_sharing();
         let titlebar = &theme.titlebar;
-        MouseEventHandler::<ToggleScreenSharing, Self>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<ToggleScreenSharing, _>(0, cx, |state, _| {
             let style = titlebar
                 .screen_share_button
                 .in_state(active)
@@ -659,7 +659,7 @@ impl CollabTitlebarItem {
         }
 
         let titlebar = &theme.titlebar;
-        MouseEventHandler::<ToggleMute, Self>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<ToggleMute, _>(0, cx, |state, _| {
             let style = titlebar
                 .toggle_microphone_button
                 .in_state(is_muted)
@@ -712,7 +712,7 @@ impl CollabTitlebarItem {
         }
 
         let titlebar = &theme.titlebar;
-        MouseEventHandler::<ToggleDeafen, Self>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<ToggleDeafen, _>(0, cx, |state, _| {
             let style = titlebar
                 .toggle_speakers_button
                 .in_state(is_deafened)
@@ -747,7 +747,7 @@ impl CollabTitlebarItem {
         let tooltip = "Leave call";
 
         let titlebar = &theme.titlebar;
-        MouseEventHandler::<LeaveCall, Self>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<LeaveCall, _>(0, cx, |state, _| {
             let style = titlebar.leave_call_button.style_for(state);
             Svg::new(icon)
                 .with_color(style.color)
@@ -801,7 +801,7 @@ impl CollabTitlebarItem {
         Some(
             Stack::new()
                 .with_child(
-                    MouseEventHandler::<ShareUnshare, Self>::new(0, cx, |state, _| {
+                    MouseEventHandler::new::<ShareUnshare, _>(0, cx, |state, _| {
                         //TODO: Ensure this button has consistent width for both text variations
                         let style = titlebar.share_button.inactive_state().style_for(state);
                         Label::new(label, style.text.clone())
@@ -847,7 +847,7 @@ impl CollabTitlebarItem {
         let avatar_style = &user_menu_button_style.avatar;
         Stack::new()
             .with_child(
-                MouseEventHandler::<ToggleUserMenu, Self>::new(0, cx, |state, _| {
+                MouseEventHandler::new::<ToggleUserMenu, _>(0, cx, |state, _| {
                     let style = user_menu_button_style
                         .user_menu
                         .inactive_state()
@@ -907,7 +907,7 @@ impl CollabTitlebarItem {
 
     fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
         let titlebar = &theme.titlebar;
-        MouseEventHandler::<SignIn, Self>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<SignIn, _>(0, cx, |state, _| {
             let style = titlebar.sign_in_button.inactive_state().style_for(state);
             Label::new("Sign In", style.text.clone())
                 .contained()
@@ -1142,7 +1142,7 @@ impl CollabTitlebarItem {
             if let Some(replica_id) = replica_id {
                 enum ToggleFollow {}
 
-                content = MouseEventHandler::<ToggleFollow, Self>::new(
+                content = MouseEventHandler::new::<ToggleFollow, _>(
                     replica_id.into(),
                     cx,
                     move |_, _| content,
@@ -1173,7 +1173,7 @@ impl CollabTitlebarItem {
                 enum JoinProject {}
 
                 let user_id = user.id;
-                content = MouseEventHandler::<JoinProject, Self>::new(
+                content = MouseEventHandler::new::<JoinProject, _>(
                     peer_id.as_u64() as usize,
                     cx,
                     move |_, _| content,
@@ -1261,7 +1261,7 @@ impl CollabTitlebarItem {
                     .into_any(),
             ),
             client::Status::UpgradeRequired => Some(
-                MouseEventHandler::<ConnectionStatusButton, Self>::new(0, cx, |_, _| {
+                MouseEventHandler::new::<ConnectionStatusButton, _>(0, cx, |_, _| {
                     Label::new(
                         "Please update Zed to collaborate",
                         theme.titlebar.outdated_warning.text.clone(),

crates/collab_ui/src/contact_list.rs 🔗

@@ -810,7 +810,7 @@ impl ContactList {
             worktree_root_names.join(", ")
         };
 
-        MouseEventHandler::<JoinProject, Self>::new(project_id as usize, cx, |mouse_state, _| {
+        MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, _| {
             let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
             let row = theme
                 .project_row
@@ -904,7 +904,7 @@ impl ContactList {
         let baseline_offset =
             row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
 
-        MouseEventHandler::<OpenSharedScreen, Self>::new(
+        MouseEventHandler::new::<OpenSharedScreen, _>(
             peer_id.as_u64() as usize,
             cx,
             |mouse_state, _| {
@@ -1006,7 +1006,7 @@ impl ContactList {
         };
         let leave_call = if section == Section::ActiveCall {
             Some(
-                MouseEventHandler::<LeaveCallContactList, Self>::new(0, cx, |state, _| {
+                MouseEventHandler::new::<LeaveCallContactList, _>(0, cx, |state, _| {
                     let style = theme.leave_call.style_for(state);
                     Label::new("Leave Call", style.text.clone())
                         .contained()
@@ -1024,7 +1024,7 @@ impl ContactList {
         };
 
         let icon_size = theme.section_icon_size;
-        MouseEventHandler::<Header, Self>::new(section as usize, cx, |_, _| {
+        MouseEventHandler::new::<Header, _>(section as usize, cx, |_, _| {
             Flex::row()
                 .with_child(
                     Svg::new(if is_collapsed {
@@ -1075,7 +1075,7 @@ impl ContactList {
         let github_login = contact.user.github_login.clone();
         let initial_project = project.clone();
         let mut event_handler =
-            MouseEventHandler::<Contact, Self>::new(contact.user.id as usize, cx, |_, cx| {
+            MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |_, cx| {
                 Flex::row()
                     .with_children(contact.user.avatar.clone().map(|avatar| {
                         let status_badge = if contact.online {
@@ -1114,7 +1114,7 @@ impl ContactList {
                         .flex(1., true),
                     )
                     .with_child(
-                        MouseEventHandler::<Cancel, Self>::new(
+                        MouseEventHandler::new::<Cancel, _>(
                             contact.user.id as usize,
                             cx,
                             |mouse_state, _| {
@@ -1208,7 +1208,7 @@ impl ContactList {
 
         if is_incoming {
             row.add_child(
-                MouseEventHandler::<Decline, Self>::new(user.id as usize, cx, |mouse_state, _| {
+                MouseEventHandler::new::<Decline, _>(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
@@ -1231,7 +1231,7 @@ impl ContactList {
             );
 
             row.add_child(
-                MouseEventHandler::<Accept, Self>::new(user.id as usize, cx, |mouse_state, _| {
+                MouseEventHandler::new::<Accept, _>(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
@@ -1254,7 +1254,7 @@ impl ContactList {
             );
         } else {
             row.add_child(
-                MouseEventHandler::<Cancel, Self>::new(user.id as usize, cx, |mouse_state, _| {
+                MouseEventHandler::new::<Cancel, _>(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
@@ -1333,7 +1333,7 @@ impl View for ContactList {
                             .flex(1., true),
                     )
                     .with_child(
-                        MouseEventHandler::<AddContact, Self>::new(0, cx, |_, _| {
+                        MouseEventHandler::new::<AddContact, _>(0, cx, |_, _| {
                             render_icon_button(
                                 &theme.contact_list.add_contact_button,
                                 "icons/user_plus_16.svg",

crates/collab_ui/src/contacts_popover.rs 🔗

@@ -113,7 +113,7 @@ impl View for ContactsPopover {
             Child::ContactFinder(child) => ChildView::new(child, cx),
         };
 
-        MouseEventHandler::<ContactsPopover, Self>::new(0, cx, |_, _| {
+        MouseEventHandler::new::<ContactsPopover, _>(0, cx, |_, _| {
             Flex::column()
                 .with_child(child.flex(1., true))
                 .contained()

crates/collab_ui/src/incoming_call_notification.rs 🔗

@@ -173,7 +173,7 @@ impl IncomingCallNotification {
         let theme = theme::current(cx);
         Flex::column()
             .with_child(
-                MouseEventHandler::<Accept, Self>::new(0, cx, |_, _| {
+                MouseEventHandler::new::<Accept, _>(0, cx, |_, _| {
                     let theme = &theme.incoming_call_notification;
                     Label::new("Accept", theme.accept_button.text.clone())
                         .aligned()
@@ -187,7 +187,7 @@ impl IncomingCallNotification {
                 .flex(1., true),
             )
             .with_child(
-                MouseEventHandler::<Decline, Self>::new(0, cx, |_, _| {
+                MouseEventHandler::new::<Decline, _>(0, cx, |_, _| {
                     let theme = &theme.incoming_call_notification;
                     Label::new("Decline", theme.decline_button.text.clone())
                         .aligned()

crates/collab_ui/src/notifications.rs 🔗

@@ -52,7 +52,7 @@ where
                     .flex(1., true),
                 )
                 .with_child(
-                    MouseEventHandler::<Dismiss, V>::new(user.id as usize, cx, |state, _| {
+                    MouseEventHandler::new::<Dismiss, _>(user.id as usize, cx, |state, _| {
                         let style = theme.dismiss_button.style_for(state);
                         Svg::new("icons/x_mark_8.svg")
                             .with_color(style.color)
@@ -92,7 +92,7 @@ where
                 Flex::row()
                     .with_children(buttons.into_iter().enumerate().map(
                         |(ix, (message, handler))| {
-                            MouseEventHandler::<Button, V>::new(ix, cx, |state, _| {
+                            MouseEventHandler::new::<Button, _>(ix, cx, |state, _| {
                                 let button = theme.button.style_for(state);
                                 Label::new(message, button.text.clone())
                                     .contained()

crates/collab_ui/src/project_shared_notification.rs 🔗

@@ -170,7 +170,7 @@ impl ProjectSharedNotification {
         let theme = theme::current(cx);
         Flex::column()
             .with_child(
-                MouseEventHandler::<Open, Self>::new(0, cx, |_, _| {
+                MouseEventHandler::new::<Open, _>(0, cx, |_, _| {
                     let theme = &theme.project_shared_notification;
                     Label::new("Open", theme.open_button.text.clone())
                         .aligned()
@@ -182,7 +182,7 @@ impl ProjectSharedNotification {
                 .flex(1., true),
             )
             .with_child(
-                MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, _| {
+                MouseEventHandler::new::<Dismiss, _>(0, cx, |_, _| {
                     let theme = &theme.project_shared_notification;
                     Label::new("Dismiss", theme.dismiss_button.text.clone())
                         .aligned()

crates/collab_ui/src/sharing_status_indicator.rs 🔗

@@ -47,7 +47,7 @@ impl View for SharingStatusIndicator {
             Appearance::Dark | Appearance::VibrantDark => Color::white(),
         };
 
-        MouseEventHandler::<Self, Self>::new(0, cx, |_, _| {
+        MouseEventHandler::new::<Self, _>(0, cx, |_, _| {
             Svg::new("icons/disable_screen_sharing_12.svg")
                 .with_color(color)
                 .constrained()

crates/context_menu/src/context_menu.rs 🔗

@@ -439,14 +439,14 @@ impl ContextMenu {
 
         let style = theme::current(cx).context_menu.clone();
 
-        MouseEventHandler::<Menu, ContextMenu>::new(0, cx, |_, cx| {
+        MouseEventHandler::new::<Menu, _>(0, cx, |_, cx| {
             Flex::column()
                 .with_children(self.items.iter().enumerate().map(|(ix, item)| {
                     match item {
                         ContextMenuItem::Item { label, action } => {
                             let action = action.clone();
                             let view_id = self.parent_view_id;
-                            MouseEventHandler::<MenuItem, ContextMenu>::new(ix, cx, |state, _| {
+                            MouseEventHandler::new::<MenuItem, _>(ix, cx, |state, _| {
                                 let style = style.item.in_state(self.selected_index == Some(ix));
                                 let style = style.style_for(state);
                                 let keystroke = match &action {

crates/copilot/src/sign_in.rs 🔗

@@ -113,7 +113,7 @@ impl CopilotCodeVerification {
 
         let device_code_style = &style.auth.prompting.device_code;
 
-        MouseEventHandler::<Self, _>::new(0, cx, |state, _cx| {
+        MouseEventHandler::new::<Self, _>(0, cx, |state, _cx| {
             Flex::row()
                 .with_child(
                     Label::new(data.user_code.clone(), device_code_style.text.clone())

crates/copilot_button/src/copilot_button.rs 🔗

@@ -62,7 +62,7 @@ impl View for CopilotButton {
 
         Stack::new()
             .with_child(
-                MouseEventHandler::<Self, _>::new(0, cx, {
+                MouseEventHandler::new::<Self, _>(0, cx, {
                     let theme = theme.clone();
                     let status = status.clone();
                     move |state, _cx| {

crates/diagnostics/src/items.rs 🔗

@@ -94,7 +94,7 @@ impl View for DiagnosticIndicator {
         let tooltip_style = theme::current(cx).tooltip.clone();
         let in_progress = !self.in_progress_checks.is_empty();
         let mut element = Flex::row().with_child(
-            MouseEventHandler::<Summary, _>::new(0, cx, |state, cx| {
+            MouseEventHandler::new::<Summary, _>(0, cx, |state, cx| {
                 let theme = theme::current(cx);
                 let style = theme
                     .workspace
@@ -195,7 +195,7 @@ impl View for DiagnosticIndicator {
         } else if let Some(diagnostic) = &self.current_diagnostic {
             let message_style = style.diagnostic_message.clone();
             element.add_child(
-                MouseEventHandler::<Message, _>::new(1, cx, |state, _| {
+                MouseEventHandler::new::<Message, _>(1, cx, |state, _| {
                     Label::new(
                         diagnostic.message.split('\n').next().unwrap().to_string(),
                         message_style.style_for(state).text.clone(),

crates/drag_and_drop/src/drag_and_drop.rs 🔗

@@ -202,7 +202,7 @@ impl<V: View> DragAndDrop<V> {
                         let position = (position - region_offset).round();
                         Some(
                             Overlay::new(
-                                MouseEventHandler::<DraggedElementHandler, V>::new(
+                                MouseEventHandler::new::<DraggedElementHandler, _>(
                                     0,
                                     cx,
                                     |_, cx| render(payload, cx),
@@ -235,7 +235,7 @@ impl<V: View> DragAndDrop<V> {
                     }
 
                     State::Canceled => Some(
-                        MouseEventHandler::<DraggedElementHandler, V>::new(0, cx, |_, _| {
+                        MouseEventHandler::new::<DraggedElementHandler, _>(0, cx, |_, _| {
                             Empty::new().constrained().with_width(0.).with_height(0.)
                         })
                         .on_up(MouseButton::Left, |_, _, cx| {
@@ -301,7 +301,7 @@ pub trait Draggable<V: View> {
         Self: Sized;
 }
 
-impl<Tag, V: View> Draggable<V> for MouseEventHandler<Tag, V> {
+impl<V: View> Draggable<V> for MouseEventHandler<V> {
     fn as_draggable<D: View, P: Any>(
         self,
         payload: P,

crates/editor/src/editor.rs 🔗

@@ -867,7 +867,7 @@ impl CompletionsMenu {
                     let completion = &completions[mat.candidate_id];
                     let item_ix = start_ix + ix;
                     items.push(
-                        MouseEventHandler::<CompletionTag, _>::new(
+                        MouseEventHandler::new::<CompletionTag, _>(
                             mat.candidate_id,
                             cx,
                             |state, _| {
@@ -1044,7 +1044,7 @@ impl CodeActionsMenu {
                 for (ix, action) in actions[range].iter().enumerate() {
                     let item_ix = start_ix + ix;
                     items.push(
-                        MouseEventHandler::<ActionTag, _>::new(item_ix, cx, |state, _| {
+                        MouseEventHandler::new::<ActionTag, _>(item_ix, cx, |state, _| {
                             let item_style = if item_ix == selected_item {
                                 style.autocomplete.selected_item
                             } else if state.hovered() {
@@ -3547,7 +3547,7 @@ impl Editor {
         if self.available_code_actions.is_some() {
             enum CodeActions {}
             Some(
-                MouseEventHandler::<CodeActions, _>::new(0, cx, |state, _| {
+                MouseEventHandler::new::<CodeActions, _>(0, cx, |state, _| {
                     Svg::new("icons/bolt_8.svg").with_color(
                         style
                             .code_actions
@@ -3594,7 +3594,7 @@ impl Editor {
                 fold_data
                     .map(|(fold_status, buffer_row, active)| {
                         (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
-                            MouseEventHandler::<FoldIndicators, _>::new(
+                            MouseEventHandler::new::<FoldIndicators, _>(
                                 ix as usize,
                                 cx,
                                 |mouse_state, _| {
@@ -8663,7 +8663,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
         let font_size = (style.text_scale_factor * settings.buffer_font_size(cx)).round();
         let anchor_x = cx.anchor_x;
         enum BlockContextToolip {}
-        MouseEventHandler::<BlockContext, _>::new(cx.block_id, cx, |_, _| {
+        MouseEventHandler::new::<BlockContext, _>(cx.block_id, cx, |_, _| {
             Flex::column()
                 .with_children(highlighted_lines.iter().map(|(line, highlights)| {
                     Label::new(

crates/editor/src/element.rs 🔗

@@ -1637,7 +1637,7 @@ impl EditorElement {
                         let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
 
                         enum JumpIcon {}
-                        MouseEventHandler::<JumpIcon, _>::new((*id).into(), cx, |state, _| {
+                        MouseEventHandler::new::<JumpIcon, _>((*id).into(), cx, |state, _| {
                             let style = style.jump_icon.style_for(state);
                             Svg::new("icons/arrow_up_right_8.svg")
                                 .with_color(style.color)

crates/editor/src/hover_popover.rs 🔗

@@ -565,7 +565,7 @@ impl InfoPopover {
             )
         });
 
-        MouseEventHandler::<InfoPopover, _>::new(0, cx, |_, cx| {
+        MouseEventHandler::new::<InfoPopover, _>(0, cx, |_, cx| {
             let mut region_id = 0;
             let view_id = cx.view_id();
 
@@ -654,7 +654,7 @@ impl DiagnosticPopover {
 
         let tooltip_style = theme::current(cx).tooltip.clone();
 
-        MouseEventHandler::<DiagnosticPopover, _>::new(0, cx, |_, _| {
+        MouseEventHandler::new::<DiagnosticPopover, _>(0, cx, |_, _| {
             text.with_soft_wrap(true)
                 .contained()
                 .with_style(container_style)

crates/feedback/src/deploy_feedback_button.rs 🔗

@@ -35,7 +35,7 @@ impl View for DeployFeedbackButton {
         let theme = theme::current(cx).clone();
         Stack::new()
             .with_child(
-                MouseEventHandler::<Self, Self>::new(0, cx, |state, _| {
+                MouseEventHandler::new::<Self, _>(0, cx, |state, _| {
                     let style = &theme
                         .workspace
                         .status_bar

crates/feedback/src/feedback_info_text.rs 🔗

@@ -41,7 +41,7 @@ impl View for FeedbackInfoText {
                 .aligned(),
             )
             .with_child(
-                MouseEventHandler::<OpenZedCommunityRepo, Self>::new(0, cx, |state, _| {
+                MouseEventHandler::new::<OpenZedCommunityRepo, _>(0, cx, |state, _| {
                     let contained_text = if state.hovered() {
                         &theme.feedback.link_text_hover
                     } else {

crates/feedback/src/submit_feedback_button.rs 🔗

@@ -52,7 +52,7 @@ impl View for SubmitFeedbackButton {
             .map_or(true, |i| i.read(cx).allow_submission);
 
         enum SubmitFeedbackButton {}
-        MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<SubmitFeedbackButton, _>(0, cx, |state, _| {
             let text;
             let style = if allow_submission {
                 text = "Submit as Markdown";

crates/gpui/examples/components.rs 🔗

@@ -0,0 +1,335 @@
+use button_component::Button;
+
+use component::AdaptComponent;
+use gpui::{
+    color::Color,
+    elements::{ContainerStyle, Flex, Label, ParentElement},
+    fonts::{self, TextStyle},
+    platform::WindowOptions,
+    AnyElement, App, Element, Entity, View, ViewContext,
+};
+use log::LevelFilter;
+use pathfinder_geometry::vector::vec2f;
+use simplelog::SimpleLogger;
+use theme::Toggleable;
+use toggleable_button::ToggleableButton;
+
+fn main() {
+    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
+
+    App::new(()).unwrap().run(|cx| {
+        cx.platform().activate(true);
+        cx.add_window(WindowOptions::with_bounds(vec2f(300., 200.)), |_| {
+            TestView {
+                count: 0,
+                is_doubling: false,
+            }
+        });
+    });
+}
+
+pub struct TestView {
+    count: usize,
+    is_doubling: bool,
+}
+
+impl TestView {
+    fn increase_count(&mut self) {
+        if self.is_doubling {
+            self.count *= 2;
+        } else {
+            self.count += 1;
+        }
+    }
+}
+
+impl Entity for TestView {
+    type Event = ();
+}
+
+type ButtonStyle = ContainerStyle;
+
+impl View for TestView {
+    fn ui_name() -> &'static str {
+        "TestView"
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
+        fonts::with_font_cache(cx.font_cache.to_owned(), || {
+            Flex::column()
+                .with_child(Label::new(
+                    format!("Count: {}", self.count),
+                    TextStyle::for_color(Color::red()),
+                ))
+                .with_child(
+                    Button::new(move |_, v: &mut Self, cx| {
+                        v.increase_count();
+                        cx.notify();
+                    })
+                    .with_text(
+                        "Hello from a counting BUTTON",
+                        TextStyle::for_color(Color::blue()),
+                    )
+                    .with_style(ButtonStyle::fill(Color::yellow()))
+                    .into_element(),
+                )
+                .with_child(
+                    ToggleableButton::new(self.is_doubling, move |_, v: &mut Self, cx| {
+                        v.is_doubling = !v.is_doubling;
+                        cx.notify();
+                    })
+                    .with_text("Double the count?", TextStyle::for_color(Color::black()))
+                    .with_style(Toggleable {
+                        inactive: ButtonStyle::fill(Color::red()),
+                        active: ButtonStyle::fill(Color::green()),
+                    })
+                    .into_element(),
+                )
+                .expanded()
+                .contained()
+                .with_background_color(Color::white())
+                .into_any()
+        })
+    }
+}
+
+mod theme {
+    pub struct Toggleable<T> {
+        pub inactive: T,
+        pub active: T,
+    }
+
+    impl<T> Toggleable<T> {
+        pub fn style_for(&self, active: bool) -> &T {
+            if active {
+                &self.active
+            } else {
+                &self.inactive
+            }
+        }
+    }
+}
+
+// Component creation:
+mod toggleable_button {
+    use gpui::{
+        elements::{ContainerStyle, LabelStyle},
+        scene::MouseClick,
+        EventContext, View,
+    };
+
+    use crate::{button_component::Button, component::Component, theme::Toggleable};
+
+    pub struct ToggleableButton<V: View> {
+        active: bool,
+        style: Option<Toggleable<ContainerStyle>>,
+        button: Button<V>,
+    }
+
+    impl<V: View> ToggleableButton<V> {
+        pub fn new<F>(active: bool, on_click: F) -> Self
+        where
+            F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
+        {
+            Self {
+                active,
+                button: Button::new(on_click),
+                style: None,
+            }
+        }
+
+        pub fn with_text(self, text: &str, style: impl Into<LabelStyle>) -> ToggleableButton<V> {
+            ToggleableButton {
+                active: self.active,
+                style: self.style,
+                button: self.button.with_text(text, style),
+            }
+        }
+
+        pub fn with_style(self, style: Toggleable<ContainerStyle>) -> ToggleableButton<V> {
+            ToggleableButton {
+                active: self.active,
+                style: Some(style),
+                button: self.button,
+            }
+        }
+    }
+
+    impl<V: View> Component for ToggleableButton<V> {
+        type View = V;
+
+        fn render(
+            self,
+            v: &mut Self::View,
+            cx: &mut gpui::ViewContext<Self::View>,
+        ) -> gpui::AnyElement<V> {
+            let button = if let Some(style) = self.style {
+                self.button.with_style(*style.style_for(self.active))
+            } else {
+                self.button
+            };
+            button.render(v, cx)
+        }
+    }
+}
+
+mod button_component {
+
+    use gpui::{
+        elements::{ContainerStyle, Label, LabelStyle, MouseEventHandler},
+        platform::MouseButton,
+        scene::MouseClick,
+        AnyElement, Element, EventContext, TypeTag, View, ViewContext,
+    };
+
+    use crate::component::Component;
+
+    type ClickHandler<V> = Box<dyn Fn(MouseClick, &mut V, &mut EventContext<V>)>;
+
+    pub struct Button<V: View> {
+        click_handler: ClickHandler<V>,
+        tag: TypeTag,
+        contents: Option<AnyElement<V>>,
+        style: Option<ContainerStyle>,
+    }
+
+    impl<V: View> Button<V> {
+        pub fn new<F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static>(handler: F) -> Self {
+            Self {
+                click_handler: Box::new(handler),
+                tag: TypeTag::new::<F>(),
+                style: None,
+                contents: None,
+            }
+        }
+
+        pub fn with_text(mut self, text: &str, style: impl Into<LabelStyle>) -> Self {
+            self.contents = Some(Label::new(text.to_string(), style).into_any());
+            self
+        }
+
+        pub fn _with_contents<E: Element<V>>(mut self, contents: E) -> Self {
+            self.contents = Some(contents.into_any());
+            self
+        }
+
+        pub fn with_style(mut self, style: ContainerStyle) -> Self {
+            self.style = Some(style);
+            self
+        }
+    }
+
+    impl<V: View> Component for Button<V> {
+        type View = V;
+
+        fn render(self, _: &mut Self::View, cx: &mut ViewContext<V>) -> AnyElement<Self::View> {
+            let click_handler = self.click_handler;
+
+            let result = MouseEventHandler::new_dynamic(self.tag, 0, cx, |_, _| {
+                self.contents
+                    .unwrap_or_else(|| gpui::elements::Empty::new().into_any())
+            })
+            .on_click(MouseButton::Left, move |click, v, cx| {
+                click_handler(click, v, cx);
+            })
+            .contained();
+
+            let result = if let Some(style) = self.style {
+                result.with_style(style)
+            } else {
+                result
+            };
+
+            result.into_any()
+        }
+    }
+}
+
+mod component {
+
+    use gpui::{AnyElement, Element, View, ViewContext};
+    use pathfinder_geometry::vector::Vector2F;
+
+    // Public API:
+    pub trait Component {
+        type View: View;
+
+        fn render(
+            self,
+            v: &mut Self::View,
+            cx: &mut ViewContext<Self::View>,
+        ) -> AnyElement<Self::View>;
+    }
+
+    pub struct ComponentAdapter<E> {
+        component: Option<E>,
+    }
+
+    impl<E> ComponentAdapter<E> {
+        pub fn new(e: E) -> Self {
+            Self { component: Some(e) }
+        }
+    }
+
+    pub trait AdaptComponent<C: Component>: Sized {
+        fn into_element(self) -> ComponentAdapter<Self> {
+            ComponentAdapter::new(self)
+        }
+    }
+
+    impl<C: Component> AdaptComponent<C> for C {}
+
+    impl<C: Component + 'static> Element<C::View> for ComponentAdapter<C> {
+        type LayoutState = AnyElement<C::View>;
+
+        type PaintState = ();
+
+        fn layout(
+            &mut self,
+            constraint: gpui::SizeConstraint,
+            view: &mut C::View,
+            cx: &mut gpui::LayoutContext<C::View>,
+        ) -> (Vector2F, Self::LayoutState) {
+            let component = self.component.take().unwrap();
+            let mut element = component.render(view, cx.view_context());
+            let constraint = element.layout(constraint, view, cx);
+            (constraint, element)
+        }
+
+        fn paint(
+            &mut self,
+            scene: &mut gpui::SceneBuilder,
+            bounds: gpui::geometry::rect::RectF,
+            visible_bounds: gpui::geometry::rect::RectF,
+            layout: &mut Self::LayoutState,
+            view: &mut C::View,
+            cx: &mut gpui::PaintContext<C::View>,
+        ) -> Self::PaintState {
+            layout.paint(scene, bounds.origin(), visible_bounds, view, cx)
+        }
+
+        fn rect_for_text_range(
+            &self,
+            _: std::ops::Range<usize>,
+            _: gpui::geometry::rect::RectF,
+            _: gpui::geometry::rect::RectF,
+            _: &Self::LayoutState,
+            _: &Self::PaintState,
+            _: &C::View,
+            _: &ViewContext<C::View>,
+        ) -> Option<gpui::geometry::rect::RectF> {
+            todo!()
+        }
+
+        fn debug(
+            &self,
+            _: gpui::geometry::rect::RectF,
+            _: &Self::LayoutState,
+            _: &Self::PaintState,
+            _: &C::View,
+            _: &ViewContext<C::View>,
+        ) -> serde_json::Value {
+            todo!()
+        }
+    }
+}

crates/gpui/src/app.rs 🔗

@@ -3280,7 +3280,11 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
     }
 
     pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
-        let region_id = MouseRegionId::new::<Tag>(self.view_id, region_id);
+        self.mouse_state_dynamic(TypeTag::new::<Tag>(), region_id)
+    }
+
+    pub fn mouse_state_dynamic(&self, tag: TypeTag, region_id: usize) -> MouseState {
+        let region_id = MouseRegionId::new(tag, self.view_id, region_id);
         MouseState {
             hovered: self.window.hovered_region_ids.contains(&region_id),
             clicked: if let Some((clicked_region_id, button)) = self.window.clicked_region {
@@ -3321,6 +3325,36 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
     }
 }
 
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct TypeTag {
+    tag: TypeId,
+    #[cfg(debug_assertions)]
+    tag_type_name: &'static str,
+}
+
+impl TypeTag {
+    pub fn new<Tag: 'static>() -> Self {
+        Self {
+            tag: TypeId::of::<Tag>(),
+            #[cfg(debug_assertions)]
+            tag_type_name: std::any::type_name::<Tag>(),
+        }
+    }
+
+    pub fn dynamic(tag: TypeId, #[cfg(debug_assertions)] type_name: &'static str) -> Self {
+        Self {
+            tag,
+            #[cfg(debug_assertions)]
+            tag_type_name: type_name,
+        }
+    }
+
+    #[cfg(debug_assertions)]
+    pub(crate) fn type_name(&self) -> &'static str {
+        self.tag_type_name
+    }
+}
+
 impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
     fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
         BorrowAppContext::read_with(&*self.window_context, f)
@@ -5171,7 +5205,7 @@ mod tests {
             fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
                 enum Handler {}
                 let mouse_down_count = self.mouse_down_count.clone();
-                MouseEventHandler::<Handler, _>::new(0, cx, |_, _| Empty::new())
+                MouseEventHandler::new::<Handler, _>(0, cx, |_, _| Empty::new())
                     .on_down(MouseButton::Left, move |_, _, _| {
                         mouse_down_count.fetch_add(1, SeqCst);
                     })

crates/gpui/src/elements.rs 🔗

@@ -193,11 +193,11 @@ pub trait Element<V: View>: 'static {
         Resizable::new(self.into_any(), side, size, on_resize)
     }
 
-    fn mouse<Tag>(self, region_id: usize) -> MouseEventHandler<Tag, V>
+    fn mouse<Tag: 'static>(self, region_id: usize) -> MouseEventHandler<V>
     where
         Self: Sized,
     {
-        MouseEventHandler::for_child(self.into_any(), region_id)
+        MouseEventHandler::for_child::<Tag>(self.into_any(), region_id)
     }
 }
 

crates/gpui/src/elements/container.rs 🔗

@@ -38,6 +38,15 @@ pub struct ContainerStyle {
     pub cursor: Option<CursorStyle>,
 }
 
+impl ContainerStyle {
+    pub fn fill(color: Color) -> Self {
+        Self {
+            background_color: Some(color),
+            ..Default::default()
+        }
+    }
+}
+
 pub struct Container<V: View> {
     child: AnyElement<V>,
     style: ContainerStyle,

crates/gpui/src/elements/mouse_event_handler.rs 🔗

@@ -11,12 +11,12 @@ use crate::{
         MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
     },
     AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
-    SceneBuilder, SizeConstraint, View, ViewContext,
+    SceneBuilder, SizeConstraint, TypeTag, View, ViewContext,
 };
 use serde_json::json;
-use std::{marker::PhantomData, ops::Range};
+use std::ops::Range;
 
-pub struct MouseEventHandler<Tag: 'static, V: View> {
+pub struct MouseEventHandler<V: View> {
     child: AnyElement<V>,
     region_id: usize,
     cursor_style: Option<CursorStyle>,
@@ -26,13 +26,13 @@ pub struct MouseEventHandler<Tag: 'static, V: View> {
     notify_on_click: bool,
     above: bool,
     padding: Padding,
-    _tag: PhantomData<Tag>,
+    tag: TypeTag,
 }
 
 /// Element which provides a render_child callback with a MouseState and paints a mouse
 /// region under (or above) it for easy mouse event handling.
-impl<Tag, V: View> MouseEventHandler<Tag, V> {
-    pub fn for_child(child: impl Element<V>, region_id: usize) -> Self {
+impl<V: View> MouseEventHandler<V> {
+    pub fn for_child<Tag: 'static>(child: impl Element<V>, region_id: usize) -> Self {
         Self {
             child: child.into_any(),
             region_id,
@@ -43,16 +43,19 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
             hoverable: false,
             above: false,
             padding: Default::default(),
-            _tag: PhantomData,
+            tag: TypeTag::new::<Tag>(),
         }
     }
 
-    pub fn new<E, F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
+    pub fn new<Tag: 'static, E>(
+        region_id: usize,
+        cx: &mut ViewContext<V>,
+        render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
+    ) -> Self
     where
         E: Element<V>,
-        F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> E,
     {
-        let mut mouse_state = cx.mouse_state::<Tag>(region_id);
+        let mut mouse_state = cx.mouse_state_dynamic(TypeTag::new::<Tag>(), region_id);
         let child = render_child(&mut mouse_state, cx).into_any();
         let notify_on_hover = mouse_state.accessed_hovered();
         let notify_on_click = mouse_state.accessed_clicked();
@@ -66,19 +69,46 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
             hoverable: true,
             above: false,
             padding: Default::default(),
-            _tag: PhantomData,
+            tag: TypeTag::new::<Tag>(),
+        }
+    }
+
+    pub fn new_dynamic(
+        tag: TypeTag,
+        region_id: usize,
+        cx: &mut ViewContext<V>,
+        render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> AnyElement<V>,
+    ) -> Self {
+        let mut mouse_state = cx.mouse_state_dynamic(tag, region_id);
+        let child = render_child(&mut mouse_state, cx);
+        let notify_on_hover = mouse_state.accessed_hovered();
+        let notify_on_click = mouse_state.accessed_clicked();
+        Self {
+            child,
+            region_id,
+            cursor_style: None,
+            handlers: Default::default(),
+            notify_on_hover,
+            notify_on_click,
+            hoverable: true,
+            above: false,
+            padding: Default::default(),
+            tag,
         }
     }
 
     /// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
     /// for drag and drop handling and similar events which should be captured before the child
     /// gets the opportunity
-    pub fn above<D, F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
+    pub fn above<Tag: 'static, D>(
+        region_id: usize,
+        cx: &mut ViewContext<V>,
+        render_child: impl FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
+    ) -> Self
     where
         D: Element<V>,
-        F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> D,
     {
-        let mut handler = Self::new(region_id, cx, render_child);
+        let mut handler = Self::new::<Tag, _>(region_id, cx, render_child);
         handler.above = true;
         handler
     }
@@ -223,7 +253,8 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
             });
         }
         scene.push_mouse_region(
-            MouseRegion::from_handlers::<Tag>(
+            MouseRegion::from_handlers(
+                self.tag,
                 cx.view_id(),
                 self.region_id,
                 hit_bounds,
@@ -236,7 +267,7 @@ impl<Tag, V: View> MouseEventHandler<Tag, V> {
     }
 }
 
-impl<Tag, V: View> Element<V> for MouseEventHandler<Tag, V> {
+impl<V: View> Element<V> for MouseEventHandler<V> {
     type LayoutState = ();
     type PaintState = ();
 

crates/gpui/src/elements/tooltip.rs 🔗

@@ -95,7 +95,7 @@ impl<V: View> Tooltip<V> {
         } else {
             None
         };
-        let child = MouseEventHandler::<MouseEventHandlerState<Tag>, _>::new(id, cx, |_, _| child)
+        let child = MouseEventHandler::new::<MouseEventHandlerState<Tag>, _>(id, cx, |_, _| child)
             .on_hover(move |e, _, cx| {
                 let position = e.position;
                 if e.started {

crates/gpui/src/fonts.rs 🔗

@@ -72,6 +72,13 @@ pub struct TextStyle {
 }
 
 impl TextStyle {
+    pub fn for_color(color: Color) -> Self {
+        Self {
+            color,
+            ..Default::default()
+        }
+    }
+
     pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
         TextStyle {
             color: refinement.color.unwrap_or(self.color),

crates/gpui/src/platform.rs 🔗

@@ -24,6 +24,7 @@ use crate::{
 use anyhow::{anyhow, bail, Result};
 use async_task::Runnable;
 pub use event::*;
+use pathfinder_geometry::vector::vec2f;
 use postage::oneshot;
 use schemars::JsonSchema;
 use serde::Deserialize;
@@ -180,6 +181,16 @@ pub struct WindowOptions<'a> {
     pub screen: Option<Rc<dyn Screen>>,
 }
 
+impl<'a> WindowOptions<'a> {
+    pub fn with_bounds(bounds: Vector2F) -> Self {
+        Self {
+            bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), bounds)),
+            center: true,
+            ..Default::default()
+        }
+    }
+}
+
 #[derive(Debug)]
 pub struct TitlebarOptions<'a> {
     pub title: Option<&'a str>,

crates/gpui/src/scene/mouse_region.rs 🔗

@@ -1,13 +1,10 @@
-use crate::{platform::MouseButton, window::WindowContext, EventContext, View, ViewContext};
+use crate::{
+    platform::MouseButton, window::WindowContext, EventContext, TypeTag, View, ViewContext,
+};
 use collections::HashMap;
 use pathfinder_geometry::rect::RectF;
 use smallvec::SmallVec;
-use std::{
-    any::{Any, TypeId},
-    fmt::Debug,
-    mem::Discriminant,
-    rc::Rc,
-};
+use std::{any::Any, fmt::Debug, mem::Discriminant, rc::Rc};
 
 use super::{
     mouse_event::{
@@ -33,14 +30,27 @@ impl MouseRegion {
     /// should pass a different (consistent) region_id. If you have one big region that covers your
     /// whole component, just pass the view_id again.
     pub fn new<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
-        Self::from_handlers::<Tag>(view_id, region_id, bounds, Default::default())
+        Self::from_handlers(
+            TypeTag::new::<Tag>(),
+            view_id,
+            region_id,
+            bounds,
+            Default::default(),
+        )
     }
 
     pub fn handle_all<Tag: 'static>(view_id: usize, region_id: usize, bounds: RectF) -> Self {
-        Self::from_handlers::<Tag>(view_id, region_id, bounds, HandlerSet::capture_all())
+        Self::from_handlers(
+            TypeTag::new::<Tag>(),
+            view_id,
+            region_id,
+            bounds,
+            HandlerSet::capture_all(),
+        )
     }
 
-    pub fn from_handlers<Tag: 'static>(
+    pub fn from_handlers(
+        tag: TypeTag,
         view_id: usize,
         region_id: usize,
         bounds: RectF,
@@ -49,10 +59,8 @@ impl MouseRegion {
         Self {
             id: MouseRegionId {
                 view_id,
-                tag: TypeId::of::<Tag>(),
+                tag,
                 region_id,
-                #[cfg(debug_assertions)]
-                tag_type_name: std::any::type_name::<Tag>(),
             },
             bounds,
             handlers,
@@ -180,20 +188,16 @@ impl MouseRegion {
 #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
 pub struct MouseRegionId {
     view_id: usize,
-    tag: TypeId,
+    tag: TypeTag,
     region_id: usize,
-    #[cfg(debug_assertions)]
-    tag_type_name: &'static str,
 }
 
 impl MouseRegionId {
-    pub(crate) fn new<Tag: 'static>(view_id: usize, region_id: usize) -> Self {
+    pub(crate) fn new(tag: TypeTag, view_id: usize, region_id: usize) -> Self {
         MouseRegionId {
             view_id,
             region_id,
-            tag: TypeId::of::<Tag>(),
-            #[cfg(debug_assertions)]
-            tag_type_name: std::any::type_name::<Tag>(),
+            tag,
         }
     }
 
@@ -203,7 +207,7 @@ impl MouseRegionId {
 
     #[cfg(debug_assertions)]
     pub fn tag_type_name(&self) -> &'static str {
-        self.tag_type_name
+        self.tag.type_name()
     }
 }
 

crates/gpui/src/views/select.rs 🔗

@@ -106,7 +106,7 @@ impl View for Select {
             Default::default()
         };
         let mut result = Flex::column().with_child(
-            MouseEventHandler::<Header, _>::new(self.handle.id(), cx, |mouse_state, cx| {
+            MouseEventHandler::new::<Header, _>(self.handle.id(), cx, |mouse_state, cx| {
                 (self.render_item)(
                     self.selected_item_ix,
                     ItemType::Header,
@@ -130,7 +130,7 @@ impl View for Select {
                         let selected_item_ix = this.selected_item_ix;
                         range.end = range.end.min(this.item_count);
                         items.extend(range.map(|ix| {
-                            MouseEventHandler::<Item, _>::new(ix, cx, |mouse_state, cx| {
+                            MouseEventHandler::new::<Item, _>(ix, cx, |mouse_state, cx| {
                                 (this.render_item)(
                                     ix,
                                     if ix == selected_item_ix {

crates/language_selector/src/active_buffer_language.rs 🔗

@@ -53,7 +53,7 @@ impl View for ActiveBufferLanguage {
                 "Unknown".to_string()
             };
 
-            MouseEventHandler::<Self, Self>::new(0, cx, |state, cx| {
+            MouseEventHandler::new::<Self, _>(0, cx, |state, cx| {
                 let theme = &theme::current(cx).workspace.status_bar;
                 let style = theme.active_language.style_for(state);
                 Label::new(active_language_text, style.text.clone())

crates/language_tools/src/lsp_log.rs 🔗

@@ -573,7 +573,7 @@ impl View for LspLogToolbarItemView {
             .with_children(if self.menu_open {
                 Some(
                     Overlay::new(
-                        MouseEventHandler::<Menu, _>::new(0, cx, move |_, cx| {
+                        MouseEventHandler::new::<Menu, _>(0, cx, move |_, cx| {
                             Flex::column()
                                 .with_children(menu_rows.into_iter().map(|row| {
                                     Self::render_language_server_menu_item(
@@ -672,7 +672,7 @@ impl LspLogToolbarItemView {
         cx: &mut ViewContext<Self>,
     ) -> impl Element<Self> {
         enum ToggleMenu {}
-        MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, cx| {
+        MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, cx| {
             let label: Cow<str> = current_server
                 .and_then(|row| {
                     let worktree = row.worktree.read(cx);
@@ -728,7 +728,7 @@ impl LspLogToolbarItemView {
                 .with_height(theme.toolbar_dropdown_menu.row_height)
             })
             .with_child(
-                MouseEventHandler::<ActivateLog, _>::new(id.0, cx, move |state, _| {
+                MouseEventHandler::new::<ActivateLog, _>(id.0, cx, move |state, _| {
                     let style = theme
                         .toolbar_dropdown_menu
                         .item
@@ -746,7 +746,7 @@ impl LspLogToolbarItemView {
                 }),
             )
             .with_child(
-                MouseEventHandler::<ActivateRpcTrace, _>::new(id.0, cx, move |state, cx| {
+                MouseEventHandler::new::<ActivateRpcTrace, _>(id.0, cx, move |state, cx| {
                     let style = theme
                         .toolbar_dropdown_menu
                         .item

crates/language_tools/src/syntax_tree_view.rs 🔗

@@ -389,7 +389,7 @@ impl View for SyntaxTreeView {
         {
             let layer = layer.clone();
             let theme = editor_theme.clone();
-            return MouseEventHandler::<Self, Self>::new(0, cx, move |state, cx| {
+            return MouseEventHandler::new::<Self, _>(0, cx, move |state, cx| {
                 let list_hovered = state.hovered();
                 UniformList::new(
                     self.list_state.clone(),
@@ -505,7 +505,7 @@ impl SyntaxTreeToolbarItemView {
                 .with_child(Self::render_header(&theme, &active_layer, cx))
                 .with_children(self.menu_open.then(|| {
                     Overlay::new(
-                        MouseEventHandler::<Menu, _>::new(0, cx, move |_, cx| {
+                        MouseEventHandler::new::<Menu, _>(0, cx, move |_, cx| {
                             Flex::column()
                                 .with_children(active_buffer.syntax_layers().enumerate().map(
                                     |(ix, layer)| {
@@ -564,7 +564,7 @@ impl SyntaxTreeToolbarItemView {
         cx: &mut ViewContext<Self>,
     ) -> impl Element<Self> {
         enum ToggleMenu {}
-        MouseEventHandler::<ToggleMenu, Self>::new(0, cx, move |state, _| {
+        MouseEventHandler::new::<ToggleMenu, _>(0, cx, move |state, _| {
             let style = theme.toolbar_dropdown_menu.header.style_for(state);
             Flex::row()
                 .with_child(
@@ -596,7 +596,7 @@ impl SyntaxTreeToolbarItemView {
         cx: &mut ViewContext<Self>,
     ) -> impl Element<Self> {
         enum ActivateLayer {}
-        MouseEventHandler::<ActivateLayer, _>::new(layer_ix, cx, move |state, _| {
+        MouseEventHandler::new::<ActivateLayer, _>(layer_ix, cx, move |state, _| {
             let is_selected = layer.node() == active_layer.node();
             let style = theme
                 .toolbar_dropdown_menu

crates/picker/src/picker.rs 🔗

@@ -112,7 +112,7 @@ impl<D: PickerDelegate> View for Picker<D> {
                             let selected_ix = this.delegate.selected_index();
                             range.end = cmp::min(range.end, this.delegate.match_count());
                             items.extend(range.map(move |ix| {
-                                MouseEventHandler::<D, _>::new(ix, cx, |state, cx| {
+                                MouseEventHandler::new::<D, _>(ix, cx, |state, cx| {
                                     this.delegate.render_match(ix, state, ix == selected_ix, cx)
                                 })
                                 // Capture mouse events

crates/project_panel/src/project_panel.rs 🔗

@@ -1407,7 +1407,7 @@ impl ProjectPanel {
 
         let show_editor = details.is_editing && !details.is_processing;
 
-        MouseEventHandler::<Self, _>::new(entry_id.to_usize(), cx, |state, cx| {
+        MouseEventHandler::new::<Self, _>(entry_id.to_usize(), cx, |state, cx| {
             let mut style = entry_style
                 .in_state(details.is_selected)
                 .style_for(state)
@@ -1519,7 +1519,7 @@ impl View for ProjectPanel {
         if has_worktree {
             Stack::new()
                 .with_child(
-                    MouseEventHandler::<ProjectPanel, _>::new(0, cx, |_, cx| {
+                    MouseEventHandler::new::<ProjectPanel, _>(0, cx, |_, cx| {
                         UniformList::new(
                             self.list.clone(),
                             self.visible_entries
@@ -1563,7 +1563,7 @@ impl View for ProjectPanel {
         } else {
             Flex::column()
                 .with_child(
-                    MouseEventHandler::<Self, _>::new(2, cx, {
+                    MouseEventHandler::new::<Self, _>(2, cx, {
                         let button_style = theme.open_project_button.clone();
                         let context_menu_item_style = theme::current(cx).context_menu.item.clone();
                         move |state, cx| {

crates/search/src/buffer_search.rs 🔗

@@ -416,7 +416,7 @@ impl BufferSearchBar {
         let tooltip_style = theme::current(cx).tooltip.clone();
         let is_active = self.search_options.contains(option);
         Some(
-            MouseEventHandler::<Self, _>::new(option.bits as usize, cx, |state, cx| {
+            MouseEventHandler::new::<Self, _>(option.bits as usize, cx, |state, cx| {
                 let theme = theme::current(cx);
                 let style = theme
                     .search
@@ -463,7 +463,7 @@ impl BufferSearchBar {
         let tooltip_style = theme::current(cx).tooltip.clone();
 
         enum NavButton {}
-        MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
+        MouseEventHandler::new::<NavButton, _>(direction as usize, cx, |state, cx| {
             let theme = theme::current(cx);
             let style = theme.search.option_button.inactive_state().style_for(state);
             Label::new(icon, style.text.clone())
@@ -497,7 +497,7 @@ impl BufferSearchBar {
         let action_type_id = 0_usize;
 
         enum ActionButton {}
-        MouseEventHandler::<ActionButton, _>::new(action_type_id, cx, |state, cx| {
+        MouseEventHandler::new::<ActionButton, _>(action_type_id, cx, |state, cx| {
             let theme = theme::current(cx);
             let style = theme.search.action_button.style_for(state);
             Label::new(icon, style.text.clone())
@@ -527,7 +527,7 @@ impl BufferSearchBar {
         let tooltip_style = theme::current(cx).tooltip.clone();
 
         enum CloseButton {}
-        MouseEventHandler::<CloseButton, _>::new(0, cx, |state, _| {
+        MouseEventHandler::new::<CloseButton, _>(0, cx, |state, _| {
             let style = theme.dismiss_button.style_for(state);
             Svg::new("icons/x_mark_8.svg")
                 .with_color(style.color)

crates/search/src/project_search.rs 🔗

@@ -328,7 +328,7 @@ impl View for ProjectSearchView {
                 editor.set_placeholder_text(new_placeholder_text, cx);
             });
 
-            MouseEventHandler::<Status, _>::new(0, cx, |_, _| {
+            MouseEventHandler::new::<Status, _>(0, cx, |_, _| {
                 Label::new(text, theme.search.results_status.clone())
                     .aligned()
                     .contained()
@@ -1103,7 +1103,7 @@ impl ProjectSearchBar {
         let tooltip_style = theme::current(cx).tooltip.clone();
 
         enum NavButton {}
-        MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
+        MouseEventHandler::new::<NavButton, _>(direction as usize, cx, |state, cx| {
             let theme = theme::current(cx);
             let style = theme.search.option_button.inactive_state().style_for(state);
             Label::new(icon, style.text.clone())
@@ -1134,7 +1134,7 @@ impl ProjectSearchBar {
     ) -> AnyElement<Self> {
         let tooltip_style = theme::current(cx).tooltip.clone();
         let is_active = self.is_option_enabled(option, cx);
-        MouseEventHandler::<Self, _>::new(option.bits as usize, cx, |state, cx| {
+        MouseEventHandler::new::<Self, _>(option.bits as usize, cx, |state, cx| {
             let theme = theme::current(cx);
             let style = theme
                 .search
@@ -1170,7 +1170,7 @@ impl ProjectSearchBar {
 
         let region_id = 3;
 
-        MouseEventHandler::<Self, _>::new(region_id, cx, |state, cx| {
+        MouseEventHandler::new::<Self, _>(region_id, cx, |state, cx| {
             let theme = theme::current(cx);
             let style = theme
                 .search

crates/theme/src/ui.rs 🔗

@@ -34,7 +34,7 @@ pub fn checkbox<Tag, V, F>(
     id: usize,
     cx: &mut ViewContext<V>,
     change: F,
-) -> MouseEventHandler<Tag, V>
+) -> MouseEventHandler<V>
 where
     Tag: 'static,
     V: View,
@@ -43,7 +43,7 @@ where
     let label = Label::new(label, style.label.text.clone())
         .contained()
         .with_style(style.label.container);
-    checkbox_with_label(label, style, checked, id, cx, change)
+    checkbox_with_label::<Tag, _, _, _>(label, style, checked, id, cx, change)
 }
 
 pub fn checkbox_with_label<Tag, D, V, F>(
@@ -53,14 +53,14 @@ pub fn checkbox_with_label<Tag, D, V, F>(
     id: usize,
     cx: &mut ViewContext<V>,
     change: F,
-) -> MouseEventHandler<Tag, V>
+) -> MouseEventHandler<V>
 where
     Tag: 'static,
     D: Element<V>,
     V: View,
     F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
 {
-    MouseEventHandler::new(id, cx, |state, _| {
+    MouseEventHandler::new::<Tag, _>(id, cx, |state, _| {
         let indicator = if checked {
             svg(&style.icon)
         } else {
@@ -143,14 +143,14 @@ pub fn cta_button<Tag, L, V, F>(
     style: &ButtonStyle,
     cx: &mut ViewContext<V>,
     f: F,
-) -> MouseEventHandler<Tag, V>
+) -> MouseEventHandler<V>
 where
     Tag: 'static,
     L: Into<Cow<'static, str>>,
     V: View,
     F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
 {
-    MouseEventHandler::<Tag, V>::new(0, cx, |state, _| {
+    MouseEventHandler::new::<Tag, _>(0, cx, |state, _| {
         let style = style.style_for(state);
         Label::new(label, style.text.to_owned())
             .aligned()
@@ -205,7 +205,7 @@ where
                 ))
                 .with_child(
                     // FIXME: Get a better tag type
-                    MouseEventHandler::<Tag, V>::new(999999, cx, |state, _cx| {
+                    MouseEventHandler::new::<Tag, _>(999999, cx, |state, _cx| {
                         let style = style.close_icon.style_for(state);
                         icon(style)
                     })

crates/vcs_menu/src/lib.rs 🔗

@@ -295,7 +295,7 @@ impl PickerDelegate for BranchListDelegate {
             let style = theme.picker.footer.clone();
             enum BranchCreateButton {}
             Some(
-                Flex::row().with_child(MouseEventHandler::<BranchCreateButton, _>::new(0, cx, |state, _| {
+                Flex::row().with_child(MouseEventHandler::new::<BranchCreateButton, _>(0, cx, |state, _| {
                     let style = style.style_for(state);
                     Label::new("Create branch", style.label.clone())
                         .contained()

crates/workspace/src/dock.rs 🔗

@@ -497,9 +497,8 @@ impl View for PanelButtons {
                     };
                     Stack::new()
                         .with_child(
-                            MouseEventHandler::<Self, _>::new(panel_ix, cx, |state, cx| {
+                            MouseEventHandler::new::<Self, _>(panel_ix, cx, |state, cx| {
                                 let style = button_style.in_state(is_active);
-
                                 let style = style.style_for(state);
                                 Flex::row()
                                     .with_child(

crates/workspace/src/notifications.rs 🔗

@@ -290,7 +290,7 @@ pub mod simple_message_notification {
                                 .flex(1., true),
                         )
                         .with_child(
-                            MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
+                            MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
                                 let style = theme.dismiss_button.style_for(state);
                                 Svg::new("icons/x_mark_8.svg")
                                     .with_color(style.color)
@@ -319,7 +319,7 @@ pub mod simple_message_notification {
                 .with_children({
                     click_message
                         .map(|click_message| {
-                            MouseEventHandler::<MessageNotificationTag, _>::new(
+                            MouseEventHandler::new::<MessageNotificationTag, _>(
                                 0,
                                 cx,
                                 |state, _| {

crates/workspace/src/pane.rs 🔗

@@ -1211,7 +1211,7 @@ impl Pane {
 
                             enum Tab {}
                             let mouse_event_handler =
-                                MouseEventHandler::<Tab, Pane>::new(ix, cx, |_, cx| {
+                                MouseEventHandler::new::<Tab, _>(ix, cx, |_, cx| {
                                     Self::render_tab(
                                         &item,
                                         pane.clone(),
@@ -1420,7 +1420,7 @@ impl Pane {
             let item_id = item.id();
             enum TabCloseButton {}
             let icon = Svg::new("icons/x_mark_8.svg");
-            MouseEventHandler::<TabCloseButton, _>::new(item_id, cx, |mouse_state, _| {
+            MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state, _| {
                 if mouse_state.hovered() {
                     icon.with_color(tab_style.icon_close_active)
                 } else {
@@ -1485,7 +1485,7 @@ impl Pane {
     ) -> AnyElement<Pane> {
         enum TabBarButton {}
 
-        let mut button = MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
+        let mut button = MouseEventHandler::new::<TabBarButton, _>(index, cx, |mouse_state, cx| {
             let theme = &settings::get::<ThemeSettings>(cx).theme.workspace.tab_bar;
             let style = theme.pane_button.in_state(is_active).style_for(mouse_state);
             Svg::new(icon)
@@ -1547,7 +1547,7 @@ impl View for Pane {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
         enum MouseNavigationHandler {}
 
-        MouseEventHandler::<MouseNavigationHandler, _>::new(0, cx, |_, cx| {
+        MouseEventHandler::new::<MouseNavigationHandler, _>(0, cx, |_, cx| {
             let active_item_index = self.active_item_index;
 
             if let Some(active_item) = self.active_item() {
@@ -1559,7 +1559,7 @@ impl View for Pane {
 
                         enum TabBarEventHandler {}
                         stack.add_child(
-                            MouseEventHandler::<TabBarEventHandler, _>::new(0, cx, |_, _| {
+                            MouseEventHandler::new::<TabBarEventHandler, _>(0, cx, |_, _| {
                                 Empty::new()
                                     .contained()
                                     .with_style(theme.workspace.tab_bar.container)

crates/workspace/src/pane/dragged_item_receiver.rs 🔗

@@ -19,7 +19,7 @@ pub fn dragged_item_receiver<Tag, D, F>(
     split_margin: Option<f32>,
     cx: &mut ViewContext<Pane>,
     render_child: F,
-) -> MouseEventHandler<Tag, Pane>
+) -> MouseEventHandler<Pane>
 where
     Tag: 'static,
     D: Element<Pane>,
@@ -39,7 +39,7 @@ where
         None
     };
 
-    let mut handler = MouseEventHandler::<Tag, _>::above(region_id, cx, |state, cx| {
+    let mut handler = MouseEventHandler::above::<Tag, _>(region_id, cx, |state, cx| {
         // Observing hovered will cause a render when the mouse enters regardless
         // of if mouse position was accessed before
         let drag_position = if state.hovered() { drag_position } else { None };

crates/workspace/src/pane_group.rs 🔗

@@ -212,7 +212,7 @@ impl Member {
                                 let leader_user_id = leader.user.id;
                                 let app_state = Arc::downgrade(app_state);
                                 Some(
-                                    MouseEventHandler::<FollowIntoExternalProject, _>::new(
+                                    MouseEventHandler::new::<FollowIntoExternalProject, _>(
                                         pane.id(),
                                         cx,
                                         |_, _| {

crates/workspace/src/shared_screen.rs 🔗

@@ -72,7 +72,7 @@ impl View for SharedScreen {
         enum Focus {}
 
         let frame = self.frame.clone();
-        MouseEventHandler::<Focus, _>::new(0, cx, |_, cx| {
+        MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| {
             Canvas::new(move |scene, bounds, _, _, _| {
                 if let Some(frame) = frame.clone() {
                     let size = constrain_size_preserving_aspect_ratio(

crates/workspace/src/toolbar.rs 🔗

@@ -223,7 +223,7 @@ fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>
     action_name: &'static str,
     cx: &mut ViewContext<Toolbar>,
 ) -> AnyElement<Toolbar> {
-    MouseEventHandler::<A, _>::new(0, cx, |state, _| {
+    MouseEventHandler::new::<A, _>(0, cx, |state, _| {
         let style = if enabled {
             style.style_for(state)
         } else {

crates/workspace/src/workspace.rs 🔗

@@ -2560,7 +2560,7 @@ impl Workspace {
         };
 
         enum TitleBar {}
-        MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
+        MouseEventHandler::new::<TitleBar, _>(0, cx, |_, cx| {
             Stack::new()
                 .with_children(
                     self.titlebar_item
@@ -2649,7 +2649,7 @@ impl Workspace {
         if self.project.read(cx).is_read_only() {
             enum DisconnectedOverlay {}
             Some(
-                MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
+                MouseEventHandler::new::<DisconnectedOverlay, _>(0, cx, |_, cx| {
                     let theme = &theme::current(cx);
                     Label::new(
                         "Your connection to the remote project has been lost.",