Merge branch 'main' into fix-panel-resize

Max Brunsfeld created

Change summary

crates/collab_ui2/src/collab_panel.rs                              |  49 
crates/collab_ui2/src/notifications/project_shared_notification.rs | 111 
crates/editor2/src/element.rs                                      |  12 
crates/gpui2/src/app/test_context.rs                               |   7 
crates/gpui2/src/elements/div.rs                                   |   2 
crates/gpui2/src/elements/list.rs                                  |   2 
crates/gpui2/src/platform/test/window.rs                           |  12 
crates/gpui2/src/window.rs                                         |   4 
crates/terminal_view2/src/terminal_view.rs                         |   3 
crates/ui2/src/components/button/button_like.rs                    |  11 
crates/ui2/src/components/stack.rs                                 |   2 
crates/workspace2/src/pane.rs                                      |   8 
crates/workspace2/src/workspace2.rs                                |   3 
13 files changed, 131 insertions(+), 95 deletions(-)

Detailed changes

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -962,15 +962,12 @@ impl CollabPanel {
             self.entries.push(ListEntry::ContactPlaceholder);
         }
 
-        self.list_state.reset(self.entries.len());
-
         if select_same_item {
             if let Some(prev_selected_entry) = prev_selected_entry {
                 self.selection.take();
                 for (ix, entry) in self.entries.iter().enumerate() {
                     if *entry == prev_selected_entry {
                         self.selection = Some(ix);
-                        self.scroll_to_item(ix);
                         break;
                     }
                 }
@@ -980,49 +977,53 @@ impl CollabPanel {
                 if self.entries.is_empty() {
                     None
                 } else {
-                    let ix = prev_selection.min(self.entries.len() - 1);
-                    self.scroll_to_item(ix);
-                    Some(ix)
+                    Some(prev_selection.min(self.entries.len() - 1))
                 }
             });
         }
 
+        let old_scroll_top = self.list_state.logical_scroll_top();
+        self.list_state.reset(self.entries.len());
+
         if scroll_to_top {
-            self.scroll_to_item(0)
+            self.list_state.scroll_to(ListOffset::default());
         } else {
-            let ListOffset {
-                item_ix: old_index,
-                offset_in_item: old_offset,
-            } = self.list_state.logical_scroll_top();
             // Attempt to maintain the same scroll position.
-            if let Some(old_top_entry) = old_entries.get(old_index) {
-                let (new_index, new_offset) = self
+            if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
+                let new_scroll_top = self
                     .entries
                     .iter()
                     .position(|entry| entry == old_top_entry)
-                    .map(|item_ix| (item_ix, old_offset))
+                    .map(|item_ix| ListOffset {
+                        item_ix,
+                        offset_in_item: old_scroll_top.offset_in_item,
+                    })
                     .or_else(|| {
-                        let entry_after_old_top = old_entries.get(old_index + 1)?;
+                        let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?;
                         let item_ix = self
                             .entries
                             .iter()
                             .position(|entry| entry == entry_after_old_top)?;
-                        Some((item_ix, px(0.)))
+                        Some(ListOffset {
+                            item_ix,
+                            offset_in_item: Pixels::ZERO,
+                        })
                     })
                     .or_else(|| {
-                        let entry_before_old_top = old_entries.get(old_index.saturating_sub(1))?;
+                        let entry_before_old_top =
+                            old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?;
                         let item_ix = self
                             .entries
                             .iter()
                             .position(|entry| entry == entry_before_old_top)?;
-                        Some((item_ix, px(0.)))
-                    })
-                    .unwrap_or_else(|| (old_index, old_offset));
+                        Some(ListOffset {
+                            item_ix,
+                            offset_in_item: Pixels::ZERO,
+                        })
+                    });
 
-                self.list_state.scroll_to(ListOffset {
-                    item_ix: new_index,
-                    offset_in_item: new_offset,
-                });
+                self.list_state
+                    .scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
             }
         }
 

crates/collab_ui2/src/notifications/project_shared_notification.rs 🔗

@@ -3,11 +3,12 @@ use call::{room, ActiveCall};
 use client::User;
 use collections::HashMap;
 use gpui::{
-    px, AppContext, Div, Element, ParentElement, Render, RenderOnce, Size, Styled, ViewContext,
-    VisualContext,
+    img, px, AppContext, Div, ParentElement, Render, Size, Styled, ViewContext, VisualContext,
 };
+use settings::Settings;
 use std::sync::{Arc, Weak};
-use ui::{h_stack, v_stack, Avatar, Button, Clickable, Label};
+use theme::ThemeSettings;
+use ui::{h_stack, prelude::*, v_stack, Button, Label};
 use workspace::AppState;
 
 pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
@@ -21,8 +22,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
             worktree_root_names,
         } => {
             let window_size = Size {
-                width: px(380.),
-                height: px(64.),
+                width: px(400.),
+                height: px(96.),
             };
 
             for screen in cx.displays() {
@@ -116,56 +117,70 @@ impl ProjectSharedNotification {
             });
         }
     }
-
-    fn render_owner(&self) -> impl Element {
-        h_stack()
-            .child(Avatar::new(self.owner.avatar_uri.clone()))
-            .child(
-                v_stack()
-                    .child(Label::new(self.owner.github_login.clone()))
-                    .child(Label::new(format!(
-                        "is sharing a project in Zed{}",
-                        if self.worktree_root_names.is_empty() {
-                            ""
-                        } else {
-                            ":"
-                        }
-                    )))
-                    .children(if self.worktree_root_names.is_empty() {
-                        None
-                    } else {
-                        Some(Label::new(self.worktree_root_names.join(", ")))
-                    }),
-            )
-    }
-
-    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> impl Element {
-        let this = cx.view().clone();
-        v_stack()
-            .child(Button::new("open", "Open").render(cx).on_click({
-                let this = this.clone();
-                move |_, cx| {
-                    this.update(cx, |this, cx| this.join(cx));
-                }
-            }))
-            .child(
-                Button::new("dismiss", "Dismiss")
-                    .render(cx)
-                    .on_click(move |_, cx| {
-                        this.update(cx, |this, cx| this.dismiss(cx));
-                    }),
-            )
-    }
 }
 
 impl Render for ProjectSharedNotification {
     type Element = Div;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        // TODO: Is there a better place for us to initialize the font?
+        let (ui_font, ui_font_size) = {
+            let theme_settings = ThemeSettings::get_global(cx);
+            (
+                theme_settings.ui_font.family.clone(),
+                theme_settings.ui_font_size.clone(),
+            )
+        };
+
+        cx.set_rem_size(ui_font_size);
+
         h_stack()
+            .font(ui_font)
+            .text_ui()
+            .justify_between()
             .size_full()
-            .bg(gpui::red())
-            .child(self.render_owner())
-            .child(self.render_buttons(cx))
+            .elevation_3(cx)
+            .p_2()
+            .gap_2()
+            .child(
+                h_stack()
+                    .gap_2()
+                    .child(
+                        img(self.owner.avatar_uri.clone())
+                            .w_16()
+                            .h_16()
+                            .rounded_full(),
+                    )
+                    .child(
+                        v_stack()
+                            .child(Label::new(self.owner.github_login.clone()))
+                            .child(Label::new(format!(
+                                "is sharing a project in Zed{}",
+                                if self.worktree_root_names.is_empty() {
+                                    ""
+                                } else {
+                                    ":"
+                                }
+                            )))
+                            .children(if self.worktree_root_names.is_empty() {
+                                None
+                            } else {
+                                Some(Label::new(self.worktree_root_names.join(", ")))
+                            }),
+                    ),
+            )
+            .child(
+                v_stack()
+                    .child(Button::new("open", "Open").on_click(cx.listener(
+                        move |this, _event, cx| {
+                            this.join(cx);
+                        },
+                    )))
+                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
+                        move |this, _event, cx| {
+                            this.dismiss(cx);
+                        },
+                    ))),
+            )
     }
 }

crates/editor2/src/element.rs 🔗

@@ -1351,7 +1351,7 @@ impl EditorElement {
             ));
         }
 
-        let mouse_position = cx.mouse_position();
+        let mut mouse_position = cx.mouse_position();
         if track_bounds.contains(&mouse_position) {
             cx.set_cursor_style(CursorStyle::Arrow);
         }
@@ -1377,6 +1377,8 @@ impl EditorElement {
                             }
                             editor.set_scroll_position(position, cx);
                         }
+
+                        mouse_position = event.position;
                         cx.stop_propagation();
                     } else {
                         editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
@@ -1392,6 +1394,10 @@ impl EditorElement {
             cx.on_mouse_event({
                 let editor = self.editor.clone();
                 move |event: &MouseUpEvent, phase, cx| {
+                    if phase == DispatchPhase::Capture {
+                        return;
+                    }
+
                     editor.update(cx, |editor, cx| {
                         editor.scroll_manager.set_is_dragging_scrollbar(false, cx);
                         cx.stop_propagation();
@@ -1402,6 +1408,10 @@ impl EditorElement {
             cx.on_mouse_event({
                 let editor = self.editor.clone();
                 move |event: &MouseDownEvent, phase, cx| {
+                    if phase == DispatchPhase::Capture {
+                        return;
+                    }
+
                     editor.update(cx, |editor, cx| {
                         if track_bounds.contains(&event.position) {
                             editor.scroll_manager.set_is_dragging_scrollbar(true, cx);

crates/gpui2/src/app/test_context.rs 🔗

@@ -567,12 +567,7 @@ impl<'a> VisualTestContext<'a> {
     pub fn window_title(&mut self) -> Option<String> {
         self.cx
             .update_window(self.window, |_, cx| {
-                cx.window
-                    .platform_window
-                    .as_test()
-                    .unwrap()
-                    .window_title
-                    .clone()
+                cx.window.platform_window.as_test().unwrap().title.clone()
             })
             .unwrap()
     }

crates/gpui2/src/elements/div.rs 🔗

@@ -1061,7 +1061,7 @@ impl Interactivity {
                         {
                             cx.focus(&focus_handle);
                             // If there is a parent that is also focusable, prevent it
-                            // from trasferring focus because we already did so.
+                            // from transferring focus because we already did so.
                             cx.prevent_default();
                         }
                     }

crates/gpui2/src/elements/list.rs 🔗

@@ -293,7 +293,7 @@ impl std::fmt::Debug for ListItem {
     }
 }
 
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, Default)]
 pub struct ListOffset {
     pub item_ix: usize,
     pub offset_in_item: Pixels,

crates/gpui2/src/platform/test/window.rs 🔗

@@ -21,7 +21,8 @@ pub(crate) struct TestWindowHandlers {
 pub struct TestWindow {
     pub(crate) bounds: WindowBounds,
     display: Rc<dyn PlatformDisplay>,
-    pub(crate) window_title: Option<String>,
+    pub(crate) title: Option<String>,
+    pub(crate) edited: bool,
     pub(crate) input_handler: Option<Arc<Mutex<Box<dyn PlatformInputHandler>>>>,
     pub(crate) handlers: Arc<Mutex<TestWindowHandlers>>,
     platform: Weak<TestPlatform>,
@@ -41,7 +42,8 @@ impl TestWindow {
             input_handler: None,
             sprite_atlas: Arc::new(TestAtlas::new()),
             handlers: Default::default(),
-            window_title: Default::default(),
+            title: Default::default(),
+            edited: false,
         }
     }
 }
@@ -109,11 +111,11 @@ impl PlatformWindow for TestWindow {
     }
 
     fn set_title(&mut self, title: &str) {
-        self.window_title = Some(title.to_owned());
+        self.title = Some(title.to_owned());
     }
 
-    fn set_edited(&mut self, _edited: bool) {
-        unimplemented!()
+    fn set_edited(&mut self, edited: bool) {
+        self.edited = edited;
     }
 
     fn show_character_palette(&self) {

crates/gpui2/src/window.rs 🔗

@@ -773,6 +773,10 @@ impl<'a> WindowContext<'a> {
         self.window.platform_window.set_title(title);
     }
 
+    pub fn set_window_edited(&mut self, edited: bool) {
+        self.window.platform_window.set_edited(edited);
+    }
+
     pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
         self.platform
             .displays()

crates/terminal_view2/src/terminal_view.rs 🔗

@@ -752,8 +752,7 @@ impl Item for TerminalView {
     ) -> Task<anyhow::Result<View<Self>>> {
         let window = cx.window_handle();
         cx.spawn(|pane, mut cx| async move {
-            let cwd = None;
-            TERMINAL_DB
+            let cwd = TERMINAL_DB
                 .get_working_directory(item_id, workspace_id)
                 .log_err()
                 .flatten()

crates/ui2/src/components/button/button_like.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{relative, DefiniteLength};
+use gpui::{relative, DefiniteLength, MouseButton};
 use gpui::{rems, transparent_black, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful};
 use smallvec::SmallVec;
 
@@ -372,10 +372,11 @@ impl RenderOnce for ButtonLike {
             .when_some(
                 self.on_click.filter(|_| !self.disabled),
                 |this, on_click| {
-                    this.on_click(move |event, cx| {
-                        cx.stop_propagation();
-                        (on_click)(event, cx)
-                    })
+                    this.on_mouse_down(MouseButton::Left, |_, cx| cx.prevent_default())
+                        .on_click(move |event, cx| {
+                            cx.stop_propagation();
+                            (on_click)(event, cx)
+                        })
                 },
             )
             .when_some(self.tooltip, |this, tooltip| {

crates/ui2/src/components/stack.rs 🔗

@@ -5,6 +5,7 @@ use crate::StyledExt;
 /// Horizontally stacks elements.
 ///
 /// Sets `flex()`, `flex_row()`, `items_center()`
+#[track_caller]
 pub fn h_stack() -> Div {
     div().h_flex()
 }
@@ -12,6 +13,7 @@ pub fn h_stack() -> Div {
 /// Vertically stacks elements.
 ///
 /// Sets `flex()`, `flex_col()`
+#[track_caller]
 pub fn v_stack() -> Div {
     div().v_flex()
 }

crates/workspace2/src/pane.rs 🔗

@@ -1493,6 +1493,14 @@ impl Pane {
             .on_click(
                 cx.listener(move |pane: &mut Self, _, cx| pane.activate_item(ix, true, true, cx)),
             )
+            // TODO: This should be a click listener with the middle mouse button instead of a mouse down listener.
+            .on_mouse_down(
+                MouseButton::Middle,
+                cx.listener(move |pane, _event, cx| {
+                    pane.close_item_by_id(item_id, SaveIntent::Close, cx)
+                        .detach_and_log_err(cx);
+                }),
+            )
             .on_drag(
                 DraggedTab {
                     pane: cx.view().clone(),

crates/workspace2/src/workspace2.rs 🔗

@@ -2520,8 +2520,7 @@ impl Workspace {
                 .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
         if is_edited != self.window_edited {
             self.window_edited = is_edited;
-            // todo!()
-            // cx.set_window_edited(self.window_edited)
+            cx.set_window_edited(self.window_edited)
         }
     }