From 91f1be213bd182c28cf4555a52b955ca82136c49 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 18 Dec 2023 18:13:09 -0500 Subject: [PATCH 1/9] Style project share notification (#3706) This PR styles the project share notification, so we're not staring a red rectangle. Screenshot 2023-12-18 at 6 06 14 PM Release Notes: - N/A --- .../project_shared_notification.rs | 111 ++++++++++-------- 1 file changed, 63 insertions(+), 48 deletions(-) diff --git a/crates/collab_ui2/src/notifications/project_shared_notification.rs b/crates/collab_ui2/src/notifications/project_shared_notification.rs index e130f09b16..2dc0dee6f4 100644 --- a/crates/collab_ui2/src/notifications/project_shared_notification.rs +++ b/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, cx: &mut AppContext) { @@ -21,8 +22,8 @@ pub fn init(app_state: &Arc, 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) -> 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::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); + }, + ))), + ) } } From 4b74f30d0a24afde4e6f118504f5778137cccdae Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 19 Dec 2023 06:44:26 +0200 Subject: [PATCH 2/9] Properly restore termina current dir when deserializing the project --- crates/terminal_view2/src/terminal_view.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index 1ac5ba8ab5..f82a43959b 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -752,8 +752,7 @@ impl Item for TerminalView { ) -> Task>> { 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() From ba0d7e35bb74cf09833828542fbe0e0094287812 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 19 Dec 2023 10:26:06 +0100 Subject: [PATCH 3/9] Set window edited --- crates/gpui2/src/window.rs | 4 ++++ crates/workspace2/src/workspace2.rs | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 0cef164460..ca91a806ad 100644 --- a/crates/gpui2/src/window.rs +++ b/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> { self.platform .displays() diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 8cb8c3dc9a..4f911d1073 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/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) } } From ae32706cfed3b5e51f4f7a4990b8e8fc0b22399b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 19 Dec 2023 11:04:32 +0100 Subject: [PATCH 4/9] Fix tests --- crates/gpui2/src/app/test_context.rs | 7 +------ crates/gpui2/src/platform/test/window.rs | 12 +++++++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 9f0c7e6aca..c77ec29bdf 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -567,12 +567,7 @@ impl<'a> VisualTestContext<'a> { pub fn window_title(&mut self) -> Option { 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() } diff --git a/crates/gpui2/src/platform/test/window.rs b/crates/gpui2/src/platform/test/window.rs index 0f981d4478..9df513d1f7 100644 --- a/crates/gpui2/src/platform/test/window.rs +++ b/crates/gpui2/src/platform/test/window.rs @@ -21,7 +21,8 @@ pub(crate) struct TestWindowHandlers { pub struct TestWindow { pub(crate) bounds: WindowBounds, display: Rc, - pub(crate) window_title: Option, + pub(crate) title: Option, + pub(crate) edited: bool, pub(crate) input_handler: Option>>>, pub(crate) handlers: Arc>, platform: Weak, @@ -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) { From 02e53025f30efbd56b7b03f53d1aaf4237742ba1 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 19 Dec 2023 14:26:30 +0100 Subject: [PATCH 5/9] Track caller on h_stack and v_stack --- crates/gpui2/src/elements/div.rs | 2 +- crates/ui2/src/components/stack.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 1f56f44900..631ffc43c6 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1065,7 +1065,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(); } } diff --git a/crates/ui2/src/components/stack.rs b/crates/ui2/src/components/stack.rs index 6ae88cb1a2..a6321b93d7 100644 --- a/crates/ui2/src/components/stack.rs +++ b/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() } From afbc61a344856ed43c7397533c706db5727e7cfa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 19 Dec 2023 14:26:44 +0100 Subject: [PATCH 6/9] Prevent default when mousing down on a button that responds to clicks This ensures that ancestors that track focus don't accidentally steal it on mouse down, which was preventing the editor from deploying the code actions menu. --- crates/ui2/src/components/button/button_like.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/ui2/src/components/button/button_like.rs b/crates/ui2/src/components/button/button_like.rs index 54f2a0e9ea..bd4e9cd523 100644 --- a/crates/ui2/src/components/button/button_like.rs +++ b/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| { From b30fd3f5743863b8fc566ecb479f97c8f57ab30f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 19 Dec 2023 15:29:05 +0100 Subject: [PATCH 7/9] Fix janky editor scrollbar dragging We can receive multiple events before computing the next frame, and in that case we want to compute a drag delta between the position for the previous mouse event and the current one. --- crates/editor2/src/element.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 328f2f08ba..cf34863ba6 100644 --- a/crates/editor2/src/element.rs +++ b/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); From dd84993d76b9ba0e9ab8a760ae8eba0fd61bec2d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 19 Dec 2023 16:06:00 +0100 Subject: [PATCH 8/9] Maintain scroll position in CollabPanel after updating entries Co-Authored-By: Julia --- crates/collab_ui2/src/collab_panel.rs | 49 ++++++++++++++------------- crates/gpui2/src/elements/list.rs | 2 +- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index ab82310094..2179d82cf3 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/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)); } } diff --git a/crates/gpui2/src/elements/list.rs b/crates/gpui2/src/elements/list.rs index 73bd319afc..a0467aaaa3 100644 --- a/crates/gpui2/src/elements/list.rs +++ b/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, From 3e6b84a726d25df9dbcb8f298e2403b40b894771 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 19 Dec 2023 11:26:55 -0500 Subject: [PATCH 9/9] Wire up the middle mouse button to close tabs (#3714) This PR wires up the middle mouse button to close tabs. Right now we're doing this using `on_mouse_down`, but we need a way in GPUI2 to have an `on_click` for a mouse button other than the left one. Release Notes: - N/A --- crates/workspace2/src/pane.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 65e34c9fa5..e1bb006c11 100644 --- a/crates/workspace2/src/pane.rs +++ b/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(),