Get the channel select looking good

Max Brunsfeld and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

gpui/src/app.rs                          |  52 ++++++--
gpui/src/elements/mouse_event_handler.rs |   9 
gpui/src/elements/uniform_list.rs        |   8 
gpui/src/views.rs                        |   6 
gpui/src/views/select.rs                 | 145 ++++++++++++++++++++++++-
zed/assets/themes/_base.toml             |  37 +++++
zed/src/chat_panel.rs                    | 130 ++++++++++++++++-------
zed/src/theme.rs                         |  35 +++++
zed/src/workspace/pane.rs                |   4 
zed/src/workspace/sidebar.rs             |   4 
10 files changed, 348 insertions(+), 82 deletions(-)

Detailed changes

gpui/src/app.rs 🔗

@@ -294,7 +294,9 @@ impl App {
         let platform = self.0.borrow().foreground_platform.clone();
         platform.run(Box::new(move || {
             let mut cx = self.0.borrow_mut();
-            on_finish_launching(&mut *cx);
+            let cx = &mut *cx;
+            crate::views::init(cx);
+            on_finish_launching(cx);
         }))
     }
 
@@ -1306,7 +1308,7 @@ impl MutableAppContext {
 
     pub fn element_state<Tag: 'static, T: 'static + Default>(
         &mut self,
-        id: usize,
+        id: ElementStateId,
     ) -> ElementStateHandle<T> {
         let key = (TypeId::of::<Tag>(), id);
         self.cx
@@ -1699,7 +1701,7 @@ pub struct AppContext {
     models: HashMap<usize, Box<dyn AnyModel>>,
     views: HashMap<(usize, usize), Box<dyn AnyView>>,
     windows: HashMap<usize, Window>,
-    element_states: HashMap<(TypeId, usize), Box<dyn Any>>,
+    element_states: HashMap<(TypeId, ElementStateId), Box<dyn Any>>,
     background: Arc<executor::Background>,
     ref_counts: Arc<Mutex<RefCounts>>,
     font_cache: Arc<FontCache>,
@@ -2977,6 +2979,10 @@ impl<T: View> WeakViewHandle<T> {
         }
     }
 
+    pub fn id(&self) -> usize {
+        self.view_id
+    }
+
     pub fn upgrade(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
         if cx.ref_counts.lock().is_entity_alive(self.view_id) {
             Some(ViewHandle::new(
@@ -3000,15 +3006,30 @@ impl<T> Clone for WeakViewHandle<T> {
     }
 }
 
+#[derive(Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ElementStateId(usize, usize);
+
+impl From<usize> for ElementStateId {
+    fn from(id: usize) -> Self {
+        Self(id, 0)
+    }
+}
+
+impl From<(usize, usize)> for ElementStateId {
+    fn from(id: (usize, usize)) -> Self {
+        Self(id.0, id.1)
+    }
+}
+
 pub struct ElementStateHandle<T> {
     value_type: PhantomData<T>,
     tag_type_id: TypeId,
-    id: usize,
+    id: ElementStateId,
     ref_counts: Weak<Mutex<RefCounts>>,
 }
 
 impl<T: 'static> ElementStateHandle<T> {
-    fn new(tag_type_id: TypeId, id: usize, ref_counts: &Arc<Mutex<RefCounts>>) -> Self {
+    fn new(tag_type_id: TypeId, id: ElementStateId, ref_counts: &Arc<Mutex<RefCounts>>) -> Self {
         ref_counts.lock().inc_element_state(tag_type_id, id);
         Self {
             value_type: PhantomData,
@@ -3128,10 +3149,10 @@ impl Drop for Subscription {
 #[derive(Default)]
 struct RefCounts {
     entity_counts: HashMap<usize, usize>,
-    element_state_counts: HashMap<(TypeId, usize), usize>,
+    element_state_counts: HashMap<(TypeId, ElementStateId), usize>,
     dropped_models: HashSet<usize>,
     dropped_views: HashSet<(usize, usize)>,
-    dropped_element_states: HashSet<(TypeId, usize)>,
+    dropped_element_states: HashSet<(TypeId, ElementStateId)>,
 }
 
 impl RefCounts {
@@ -3155,11 +3176,14 @@ impl RefCounts {
         }
     }
 
-    fn inc_element_state(&mut self, tag_type_id: TypeId, id: usize) {
-        *self
-            .element_state_counts
-            .entry((tag_type_id, id))
-            .or_insert(0) += 1;
+    fn inc_element_state(&mut self, tag_type_id: TypeId, id: ElementStateId) {
+        match self.element_state_counts.entry((tag_type_id, id)) {
+            Entry::Occupied(mut entry) => *entry.get_mut() += 1,
+            Entry::Vacant(entry) => {
+                entry.insert(1);
+                self.dropped_element_states.remove(&(tag_type_id, id));
+            }
+        }
     }
 
     fn dec_model(&mut self, model_id: usize) {
@@ -3180,7 +3204,7 @@ impl RefCounts {
         }
     }
 
-    fn dec_element_state(&mut self, tag_type_id: TypeId, id: usize) {
+    fn dec_element_state(&mut self, tag_type_id: TypeId, id: ElementStateId) {
         let key = (tag_type_id, id);
         let count = self.element_state_counts.get_mut(&key).unwrap();
         *count -= 1;
@@ -3199,7 +3223,7 @@ impl RefCounts {
     ) -> (
         HashSet<usize>,
         HashSet<(usize, usize)>,
-        HashSet<(TypeId, usize)>,
+        HashSet<(TypeId, ElementStateId)>,
     ) {
         let mut dropped_models = HashSet::new();
         let mut dropped_views = HashSet::new();

gpui/src/elements/mouse_event_handler.rs 🔗

@@ -3,8 +3,8 @@ use std::ops::DerefMut;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     platform::CursorStyle,
-    CursorStyleHandle, DebugContext, Element, ElementBox, ElementStateHandle, Event, EventContext,
-    LayoutContext, MutableAppContext, PaintContext, SizeConstraint,
+    CursorStyleHandle, DebugContext, Element, ElementBox, ElementStateHandle, ElementStateId,
+    Event, EventContext, LayoutContext, MutableAppContext, PaintContext, SizeConstraint,
 };
 use serde_json::json;
 
@@ -25,13 +25,14 @@ pub struct MouseState {
 }
 
 impl MouseEventHandler {
-    pub fn new<Tag, F, C>(id: usize, cx: &mut C, render_child: F) -> Self
+    pub fn new<Tag, F, C, Id>(id: Id, cx: &mut C, render_child: F) -> Self
     where
         Tag: 'static,
         F: FnOnce(&MouseState, &mut C) -> ElementBox,
         C: DerefMut<Target = MutableAppContext>,
+        Id: Into<ElementStateId>,
     {
-        let state_handle = cx.element_state::<Tag, _>(id);
+        let state_handle = cx.element_state::<Tag, _>(id.into());
         let child = state_handle.update(cx, |state, cx| render_child(state, cx));
         Self {
             state: state_handle,

gpui/src/elements/uniform_list.rs 🔗

@@ -5,7 +5,7 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{self, json},
-    AppContext, ElementBox,
+    ElementBox, MutableAppContext,
 };
 use json::ToJson;
 use parking_lot::Mutex;
@@ -38,7 +38,7 @@ pub struct LayoutState {
 
 pub struct UniformList<F>
 where
-    F: Fn(Range<usize>, &mut Vec<ElementBox>, &AppContext),
+    F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut MutableAppContext),
 {
     state: UniformListState,
     item_count: usize,
@@ -47,7 +47,7 @@ where
 
 impl<F> UniformList<F>
 where
-    F: Fn(Range<usize>, &mut Vec<ElementBox>, &AppContext),
+    F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut MutableAppContext),
 {
     pub fn new(state: UniformListState, item_count: usize, append_items: F) -> Self {
         Self {
@@ -102,7 +102,7 @@ where
 
 impl<F> Element for UniformList<F>
 where
-    F: Fn(Range<usize>, &mut Vec<ElementBox>, &AppContext),
+    F: Fn(Range<usize>, &mut Vec<ElementBox>, &mut MutableAppContext),
 {
     type LayoutState = LayoutState;
     type PaintState = ();

gpui/src/views.rs 🔗

@@ -1,3 +1,7 @@
 mod select;
 
-pub use select::*;
+pub use select::{ItemType, Select, SelectStyle};
+
+pub fn init(cx: &mut super::MutableAppContext) {
+    select::init(cx);
+}

gpui/src/views/select.rs 🔗

@@ -1,14 +1,79 @@
-use crate::{elements::*, Entity, RenderContext, View};
-use std::ops::Range;
+use crate::{
+    action, elements::*, AppContext, Entity, MutableAppContext, RenderContext, View, ViewContext,
+    WeakViewHandle,
+};
 
 pub struct Select {
-    selected_ix: Option<usize>,
-    render_selected_element: Box<dyn FnMut()>,
-    render_elements: Box<dyn FnMut(Range<usize>, &mut RenderContext<Self>)>,
+    handle: WeakViewHandle<Self>,
+    render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> ElementBox>,
+    selected_item_ix: usize,
+    item_count: usize,
+    is_open: bool,
+    list_state: UniformListState,
+    style: SelectStyle,
 }
 
+#[derive(Clone, Default)]
+pub struct SelectStyle {
+    pub header: ContainerStyle,
+    pub menu: ContainerStyle,
+}
+
+pub enum ItemType {
+    Header,
+    Selected,
+    Unselected,
+}
+
+action!(ToggleSelect);
+action!(SelectItem, usize);
+
 pub enum Event {}
 
+pub fn init(cx: &mut MutableAppContext) {
+    cx.add_action(Select::toggle);
+    cx.add_action(Select::select_item);
+}
+
+impl Select {
+    pub fn new<F: 'static + Fn(usize, ItemType, bool, &AppContext) -> ElementBox>(
+        item_count: usize,
+        cx: &mut ViewContext<Self>,
+        render_item: F,
+    ) -> Self {
+        Self {
+            handle: cx.handle().downgrade(),
+            render_item: Box::new(render_item),
+            selected_item_ix: 0,
+            item_count,
+            is_open: false,
+            list_state: UniformListState::default(),
+            style: Default::default(),
+        }
+    }
+
+    pub fn with_style(mut self, style: &SelectStyle) -> Self {
+        self.style = style.clone();
+        self
+    }
+
+    pub fn set_item_count(&mut self, count: usize, cx: &mut ViewContext<Self>) {
+        self.item_count = count;
+        cx.notify();
+    }
+
+    fn toggle(&mut self, _: &ToggleSelect, cx: &mut ViewContext<Self>) {
+        self.is_open = !self.is_open;
+        cx.notify();
+    }
+
+    fn select_item(&mut self, action: &SelectItem, cx: &mut ViewContext<Self>) {
+        self.selected_item_ix = action.0;
+        self.is_open = false;
+        cx.notify();
+    }
+}
+
 impl Entity for Select {
     type Event = Event;
 }
@@ -19,6 +84,74 @@ impl View for Select {
     }
 
     fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        todo!()
+        if self.item_count == 0 {
+            return Empty::new().boxed();
+        }
+
+        enum Header {}
+        enum Item {}
+
+        let mut result = Flex::column().with_child(
+            MouseEventHandler::new::<Header, _, _, _>(self.handle.id(), cx, |mouse_state, cx| {
+                Container::new((self.render_item)(
+                    self.selected_item_ix,
+                    ItemType::Header,
+                    mouse_state.hovered,
+                    cx,
+                ))
+                .with_style(&self.style.header)
+                .boxed()
+            })
+            .on_click(move |cx| cx.dispatch_action(ToggleSelect))
+            .boxed(),
+        );
+        if self.is_open {
+            let handle = self.handle.clone();
+            result.add_child(
+                Overlay::new(
+                    Container::new(
+                        ConstrainedBox::new(
+                            UniformList::new(
+                                self.list_state.clone(),
+                                self.item_count,
+                                move |mut range, items, mut cx| {
+                                    let handle = handle.upgrade(cx).unwrap();
+                                    let this = handle.read(cx);
+                                    let selected_item_ix = this.selected_item_ix;
+                                    range.end = range.end.min(this.item_count);
+                                    items.extend(range.map(|ix| {
+                                        MouseEventHandler::new::<Item, _, _, _>(
+                                            (handle.id(), ix),
+                                            &mut cx,
+                                            |mouse_state, cx| {
+                                                (handle.read(*cx).render_item)(
+                                                    ix,
+                                                    if ix == selected_item_ix {
+                                                        ItemType::Selected
+                                                    } else {
+                                                        ItemType::Unselected
+                                                    },
+                                                    mouse_state.hovered,
+                                                    cx,
+                                                )
+                                            },
+                                        )
+                                        .on_click(move |cx| cx.dispatch_action(SelectItem(ix)))
+                                        .boxed()
+                                    }))
+                                },
+                            )
+                            .boxed(),
+                        )
+                        .with_max_height(200.)
+                        .boxed(),
+                    )
+                    .with_style(&self.style.menu)
+                    .boxed(),
+                )
+                .boxed(),
+            )
+        }
+        result.boxed()
     }
 }

zed/assets/themes/_base.toml 🔗

@@ -26,15 +26,42 @@ color = "$text.2.color"
 color = "$text.0.color"
 
 [chat_panel]
-padding = { top = 10.0, bottom = 10.0, left = 10.0, right = 10.0 }
 channel_name = { extends = "$text.0", weight = "bold" }
-channel_name_hash = { text = "$text.2", padding.right = 5.0 }
+channel_name_hash = { text = "$text.2", padding.right = 5 }
 
 [chat_panel.message]
 body = "$text.1"
-sender.margin.right = 10.0
-sender.text = { extends = "$text.0", weight = "bold" }
-timestamp.text = "$text.2"
+sender = { extends = "$text.0", weight = "bold", margin.right = 10.0 }
+timestamp = "$text.2"
+padding = { top = 10, bottom = 10, left = 10, right = 10 }
+
+[chat_panel.channel_select.item]
+padding = { top = 4, bottom = 4, left = 4, right = 4 }
+name = "$text.1"
+hash = { extends = "$text.2", margin.right = 5.0 }
+
+[chat_panel.channel_select.hovered_item]
+extends = "$chat_panel.channel_select.item"
+background = "$surface.2"
+corner_radius = 6.0
+
+[chat_panel.channel_select.active_item]
+extends = "$chat_panel.channel_select.item"
+name = "$text.0"
+
+[chat_panel.channel_select.hovered_active_item]
+extends = "$chat_panel.channel_select.hovered_item"
+name = "$text.0"
+
+[chat_panel.channel_select.header]
+extends = "$chat_panel.channel_select.active_item"
+padding.bottom = 0
+
+[chat_panel.channel_select.menu]
+padding = { top = 4, bottom = 4, left = 4, right = 4 }
+corner_radius = 6.0
+border = { color = "#000000", width = 1.0 }
+background = "$surface.0"
 
 [selector]
 background = "$surface.2"

zed/src/chat_panel.rs 🔗

@@ -1,12 +1,17 @@
 use crate::{
     channel::{Channel, ChannelEvent, ChannelList, ChannelMessage},
     editor::Editor,
+    theme,
     util::ResultExt,
     Settings,
 };
 use gpui::{
-    action, elements::*, keymap::Binding, Entity, ModelHandle, MutableAppContext, RenderContext,
-    Subscription, View, ViewContext, ViewHandle,
+    action,
+    elements::*,
+    keymap::Binding,
+    views::{ItemType, Select, SelectStyle},
+    AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription, View,
+    ViewContext, ViewHandle,
 };
 use postage::watch;
 use time::{OffsetDateTime, UtcOffset};
@@ -16,6 +21,7 @@ pub struct ChatPanel {
     active_channel: Option<(ModelHandle<Channel>, Subscription)>,
     message_list: ListState,
     input_editor: ViewHandle<Editor>,
+    channel_select: ViewHandle<Select>,
     settings: watch::Receiver<Settings>,
 }
 
@@ -38,11 +44,33 @@ impl ChatPanel {
         cx: &mut ViewContext<Self>,
     ) -> Self {
         let input_editor = cx.add_view(|cx| Editor::auto_height(settings.clone(), cx));
+        let channel_select = cx.add_view(|cx| {
+            let channel_list = channel_list.clone();
+            let theme = &settings.borrow().theme.chat_panel.channel_select;
+            Select::new(0, cx, {
+                let settings = settings.clone();
+                move |ix, item_type, is_hovered, cx| {
+                    Self::render_channel_name(
+                        &channel_list,
+                        ix,
+                        item_type,
+                        is_hovered,
+                        &settings.borrow().theme.chat_panel.channel_select,
+                        cx,
+                    )
+                }
+            })
+            .with_style(&SelectStyle {
+                header: theme.header.container.clone(),
+                menu: theme.menu.clone(),
+            })
+        });
         let mut this = Self {
             channel_list,
             active_channel: Default::default(),
             message_list: ListState::new(0, Orientation::Bottom),
             input_editor,
+            channel_select,
             settings,
         };
 
@@ -56,23 +84,33 @@ impl ChatPanel {
     }
 
     fn init_active_channel(&mut self, cx: &mut ViewContext<Self>) {
-        if self.active_channel.is_none() {
-            let channel = self.channel_list.update(cx, |list, cx| {
-                if let Some(channel_id) = list
-                    .available_channels()
-                    .and_then(|channels| channels.first())
-                    .map(|details| details.id)
-                {
-                    return list.get_channel(channel_id, cx);
+        let (active_channel, channel_count) = self.channel_list.update(cx, |list, cx| {
+            let channel_count;
+            let mut active_channel = None;
+
+            if let Some(available_channels) = list.available_channels() {
+                channel_count = available_channels.len();
+                if self.active_channel.is_none() {
+                    if let Some(channel_id) = available_channels.first().map(|channel| channel.id) {
+                        active_channel = list.get_channel(channel_id, cx);
+                    }
                 }
-                None
-            });
-            if let Some(channel) = channel {
-                self.set_active_channel(channel, cx);
+            } else {
+                channel_count = 0;
             }
-        } else if self.channel_list.read(cx).available_channels().is_none() {
+
+            (active_channel, channel_count)
+        });
+
+        if let Some(active_channel) = active_channel {
+            self.set_active_channel(active_channel, cx);
+        } else {
             self.active_channel = None;
         }
+
+        self.channel_select.update(cx, |select, cx| {
+            select.set_item_count(channel_count, cx);
+        });
     }
 
     fn set_active_channel(&mut self, channel: ModelHandle<Channel>, cx: &mut ViewContext<Self>) {
@@ -106,28 +144,6 @@ impl ChatPanel {
         cx.notify();
     }
 
-    fn render_channel_name(&self, cx: &mut RenderContext<Self>) -> ElementBox {
-        let settings = self.settings.borrow();
-        let theme = &settings.theme.chat_panel;
-        if let Some((channel, _)) = self.active_channel.as_ref() {
-            let channel = channel.read(cx);
-            Flex::row()
-                .with_child(
-                    Container::new(
-                        Label::new("#".to_string(), theme.channel_name_hash.label.clone()).boxed(),
-                    )
-                    .with_style(&theme.channel_name_hash.container)
-                    .boxed(),
-                )
-                .with_child(
-                    Label::new(channel.name().to_string(), theme.channel_name.clone()).boxed(),
-                )
-                .boxed()
-        } else {
-            Empty::new().boxed()
-        }
-    }
-
     fn render_active_channel_messages(&self, cx: &mut RenderContext<Self>) -> ElementBox {
         let messages = if let Some((channel, _)) = self.active_channel.as_ref() {
             let channel = channel.read(cx);
@@ -155,7 +171,7 @@ impl ChatPanel {
                         Container::new(
                             Label::new(
                                 message.sender.github_login.clone(),
-                                theme.sender.label.clone(),
+                                theme.sender.text.clone(),
                             )
                             .boxed(),
                         )
@@ -166,7 +182,7 @@ impl ChatPanel {
                         Container::new(
                             Label::new(
                                 format_timestamp(message.timestamp, now),
-                                theme.timestamp.label.clone(),
+                                theme.timestamp.text.clone(),
                             )
                             .boxed(),
                         )
@@ -185,6 +201,36 @@ impl ChatPanel {
             .boxed()
     }
 
+    fn render_channel_name(
+        channel_list: &ModelHandle<ChannelList>,
+        ix: usize,
+        item_type: ItemType,
+        is_hovered: bool,
+        theme: &theme::ChannelSelect,
+        cx: &AppContext,
+    ) -> ElementBox {
+        let channel = &channel_list.read(cx).available_channels().unwrap()[ix];
+        let theme = match (item_type, is_hovered) {
+            (ItemType::Header, _) => &theme.header,
+            (ItemType::Selected, false) => &theme.active_item,
+            (ItemType::Selected, true) => &theme.hovered_active_item,
+            (ItemType::Unselected, false) => &theme.item,
+            (ItemType::Unselected, true) => &theme.hovered_item,
+        };
+        Container::new(
+            Flex::row()
+                .with_child(
+                    Container::new(Label::new("#".to_string(), theme.hash.text.clone()).boxed())
+                        .with_style(&theme.hash.container)
+                        .boxed(),
+                )
+                .with_child(Label::new(channel.name.clone(), theme.name.clone()).boxed())
+                .boxed(),
+        )
+        .with_style(&theme.container)
+        .boxed()
+    }
+
     fn send(&mut self, _: &Send, cx: &mut ViewContext<Self>) {
         if let Some((channel, _)) = self.active_channel.as_ref() {
             let body = self.input_editor.update(cx, |editor, cx| {
@@ -224,7 +270,11 @@ impl View for ChatPanel {
         let theme = &self.settings.borrow().theme;
         Container::new(
             Flex::column()
-                .with_child(self.render_channel_name(cx))
+                .with_child(
+                    Container::new(ChildView::new(self.channel_select.id()).boxed())
+                        .with_style(&theme.chat_panel.channel_select.container)
+                        .boxed(),
+                )
                 .with_child(self.render_active_channel_messages(cx))
                 .with_child(self.render_input_box())
                 .boxed(),

zed/src/theme.rs 🔗

@@ -68,15 +68,34 @@ pub struct ChatPanel {
     #[serde(flatten)]
     pub container: ContainerStyle,
     pub message: ChatMessage,
-    pub channel_name: TextStyle,
-    pub channel_name_hash: ContainedLabel,
+    pub channel_select: ChannelSelect,
 }
 
 #[derive(Deserialize)]
 pub struct ChatMessage {
     pub body: TextStyle,
-    pub sender: ContainedLabel,
-    pub timestamp: ContainedLabel,
+    pub sender: ContainedText,
+    pub timestamp: ContainedText,
+}
+
+#[derive(Deserialize)]
+pub struct ChannelSelect {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub header: ChannelName,
+    pub item: ChannelName,
+    pub active_item: ChannelName,
+    pub hovered_item: ChannelName,
+    pub hovered_active_item: ChannelName,
+    pub menu: ContainerStyle,
+}
+
+#[derive(Deserialize)]
+pub struct ChannelName {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    pub hash: ContainedText,
+    pub name: TextStyle,
 }
 
 #[derive(Deserialize)]
@@ -90,6 +109,14 @@ pub struct Selector {
     pub active_item: ContainedLabel,
 }
 
+#[derive(Deserialize)]
+pub struct ContainedText {
+    #[serde(flatten)]
+    pub container: ContainerStyle,
+    #[serde(flatten)]
+    pub text: TextStyle,
+}
+
 #[derive(Deserialize)]
 pub struct ContainedLabel {
     #[serde(flatten)]

zed/src/workspace/pane.rs 🔗

@@ -193,7 +193,7 @@ impl Pane {
             row.add_child(
                 Expanded::new(
                     1.0,
-                    MouseEventHandler::new::<Tab, _, _>(item.id(), cx, |mouse_state, cx| {
+                    MouseEventHandler::new::<Tab, _, _, _>(item.id(), cx, |mouse_state, cx| {
                         let title = item.title(cx);
 
                         let mut border = border.clone();
@@ -317,7 +317,7 @@ impl Pane {
             let close_color = current_color.unwrap_or(theme.workspace.tab.icon_close);
             let icon = Svg::new("icons/x.svg").with_color(close_color);
 
-            MouseEventHandler::new::<TabCloseButton, _, _>(item_id, cx, |mouse_state, _| {
+            MouseEventHandler::new::<TabCloseButton, _, _, _>(item_id, cx, |mouse_state, _| {
                 if mouse_state.hovered {
                     Container::new(icon.with_color(Color::white()).boxed())
                         .with_background_color(if mouse_state.clicked {

zed/src/workspace/sidebar.rs 🔗

@@ -76,7 +76,7 @@ impl Sidebar {
                         &settings.theme.workspace.sidebar_icon
                     };
                     enum SidebarButton {}
-                    MouseEventHandler::new::<SidebarButton, _, _>(item.view.id(), cx, |_, _| {
+                    MouseEventHandler::new::<SidebarButton, _, _, _>(item.view.id(), cx, |_, _| {
                         ConstrainedBox::new(
                             Align::new(
                                 ConstrainedBox::new(
@@ -133,7 +133,7 @@ impl Sidebar {
     ) -> ElementBox {
         let width = self.width.clone();
         let side = self.side;
-        MouseEventHandler::new::<Self, _, _>(self.side.id(), &mut cx, |_, _| {
+        MouseEventHandler::new::<Self, _, _, _>(self.side.id(), &mut cx, |_, _| {
             Container::new(Empty::new().boxed())
                 .with_style(&settings.theme.workspace.sidebar.resize_handle)
                 .boxed()