From 3074455386126085ecd8f68d9ecc8dddfece51b0 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 16 Aug 2023 16:56:00 -0700 Subject: [PATCH 1/5] WIP --- crates/collab/src/tests/channel_tests.rs | 13 +++++++++++++ styles/src/style_tree/collab_modals.ts | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index d778b6a472..a250f59a21 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -770,6 +770,19 @@ async fn test_call_from_channel( }); } +#[gpui::test] +async fn test_lost_channel_creation( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + // Invite a member + // Create a new sub channel + // Member accepts invite + // Make sure that member can see new channel + todo!(); +} + #[derive(Debug, PartialEq)] struct ExpectedChannel { depth: usize, diff --git a/styles/src/style_tree/collab_modals.ts b/styles/src/style_tree/collab_modals.ts index 4bdeb45f9c..0f50e01a39 100644 --- a/styles/src/style_tree/collab_modals.ts +++ b/styles/src/style_tree/collab_modals.ts @@ -76,7 +76,8 @@ export default function channel_modal(): any { }, }, - max_height: 400, + // FIXME: due to a bug in the picker's size calculation, this must be 600 + max_height: 600, max_width: 540, title: { ...text(theme.middle, "sans", "on", { size: "lg" }), From 2f1614705569567b3e18331c4de18f8fe68df4af Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 16 Aug 2023 19:47:54 -0700 Subject: [PATCH 2/5] Fix dock resizing --- crates/ai/src/assistant.rs | 6 +- crates/collab_ui/src/collab_panel.rs | 4 +- crates/gpui/src/app.rs | 8 ++ crates/gpui/src/elements.rs | 17 ++- crates/gpui/src/elements/component.rs | 5 +- crates/gpui/src/elements/resizable.rs | 147 +++++++++++++++++---- crates/project_panel/src/project_panel.rs | 4 +- crates/terminal_view/src/terminal_panel.rs | 6 +- crates/workspace/src/dock.rs | 18 +-- crates/workspace/src/workspace.rs | 7 +- 10 files changed, 170 insertions(+), 52 deletions(-) diff --git a/crates/ai/src/assistant.rs b/crates/ai/src/assistant.rs index e0fe41aebe..70473cbc7f 100644 --- a/crates/ai/src/assistant.rs +++ b/crates/ai/src/assistant.rs @@ -726,10 +726,10 @@ impl Panel for AssistantPanel { } } - fn set_size(&mut self, size: f32, cx: &mut ViewContext) { + fn set_size(&mut self, size: Option, cx: &mut ViewContext) { match self.position(cx) { - DockPosition::Left | DockPosition::Right => self.width = Some(size), - DockPosition::Bottom => self.height = Some(size), + DockPosition::Left | DockPosition::Right => self.width = size, + DockPosition::Bottom => self.height = size, } cx.notify(); } diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 4f0a61bf6a..b31ea0fbf2 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2391,8 +2391,8 @@ impl Panel for CollabPanel { .unwrap_or_else(|| settings::get::(cx).default_width) } - fn set_size(&mut self, size: f32, cx: &mut ViewContext) { - self.width = Some(size); + fn set_size(&mut self, size: Option, cx: &mut ViewContext) { + self.width = size; self.serialize(cx); cx.notify(); } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 8e6d43a45d..b08d9501f6 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -577,6 +577,14 @@ impl AppContext { } } + pub fn optional_global(&self) -> Option<&T> { + if let Some(global) = self.globals.get(&TypeId::of::()) { + Some(global.downcast_ref().unwrap()) + } else { + None + } + } + pub fn upgrade(&self) -> App { App(self.weak_self.as_ref().unwrap().upgrade().unwrap()) } diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index b8af978658..f1be9b34ae 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -186,16 +186,27 @@ pub trait Element: 'static { Tooltip::new::(id, text, action, style, self.into_any(), cx) } - fn resizable( + /// Uses the the given element to calculate resizes for the given tag + fn provide_resize_bounds(self) -> BoundsProvider + where + Self: 'static + Sized, + { + BoundsProvider::<_, Tag>::new(self.into_any()) + } + + /// Calls the given closure with the new size of the element whenever the + /// handle is dragged. This will be calculated in relation to the bounds + /// provided by the given tag + fn resizable( self, side: HandleSide, size: f32, - on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext), + on_resize: impl 'static + FnMut(&mut V, Option, &mut ViewContext), ) -> Resizable where Self: 'static + Sized, { - Resizable::new(self.into_any(), side, size, on_resize) + Resizable::new::(self.into_any(), side, size, on_resize) } fn mouse(self, region_id: usize) -> MouseEventHandler diff --git a/crates/gpui/src/elements/component.rs b/crates/gpui/src/elements/component.rs index 1c4359e2c3..2f9cc6cce6 100644 --- a/crates/gpui/src/elements/component.rs +++ b/crates/gpui/src/elements/component.rs @@ -82,6 +82,9 @@ impl + 'static> Element for ComponentAdapter { view: &V, cx: &ViewContext, ) -> serde_json::Value { - element.debug(view, cx) + serde_json::json!({ + "type": "ComponentAdapter", + "child": element.debug(view, cx), + }) } } diff --git a/crates/gpui/src/elements/resizable.rs b/crates/gpui/src/elements/resizable.rs index 0b1d94f8f8..37e40d6584 100644 --- a/crates/gpui/src/elements/resizable.rs +++ b/crates/gpui/src/elements/resizable.rs @@ -1,14 +1,14 @@ use std::{cell::RefCell, rc::Rc}; +use collections::HashMap; use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; use crate::{ geometry::rect::RectF, platform::{CursorStyle, MouseButton}, - scene::MouseDrag, - AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, - SizeConstraint, View, ViewContext, + AnyElement, AppContext, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, + SizeConstraint, TypeTag, View, ViewContext, }; #[derive(Copy, Clone, Debug)] @@ -27,15 +27,6 @@ impl HandleSide { } } - /// 'before' is in reference to the standard english document ordering of left-to-right - /// then top-to-bottom - fn before_content(self) -> bool { - match self { - HandleSide::Left | HandleSide::Top => true, - HandleSide::Right | HandleSide::Bottom => false, - } - } - fn relevant_component(&self, vector: Vector2F) -> f32 { match self.axis() { Axis::Horizontal => vector.x(), @@ -43,14 +34,6 @@ impl HandleSide { } } - fn compute_delta(&self, e: MouseDrag) -> f32 { - if self.before_content() { - self.relevant_component(e.prev_mouse_position) - self.relevant_component(e.position) - } else { - self.relevant_component(e.position) - self.relevant_component(e.prev_mouse_position) - } - } - fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF { match self { HandleSide::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)), @@ -69,21 +52,29 @@ impl HandleSide { } } +fn get_bounds(tag: TypeTag, cx: &AppContext) -> Option<&(RectF, RectF)> +where +{ + cx.optional_global::() + .and_then(|map| map.0.get(&tag)) +} + pub struct Resizable { child: AnyElement, + tag: TypeTag, handle_side: HandleSide, handle_size: f32, - on_resize: Rc)>>, + on_resize: Rc, &mut ViewContext)>>, } const DEFAULT_HANDLE_SIZE: f32 = 4.0; impl Resizable { - pub fn new( + pub fn new( child: AnyElement, handle_side: HandleSide, size: f32, - on_resize: impl 'static + FnMut(&mut V, f32, &mut ViewContext), + on_resize: impl 'static + FnMut(&mut V, Option, &mut ViewContext), ) -> Self { let child = match handle_side.axis() { Axis::Horizontal => child.constrained().with_max_width(size), @@ -94,6 +85,7 @@ impl Resizable { Self { child, handle_side, + tag: TypeTag::new::(), handle_size: DEFAULT_HANDLE_SIZE, on_resize: Rc::new(RefCell::new(on_resize)), } @@ -139,6 +131,14 @@ impl Element for Resizable { handle_region, ) .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere + .on_click(MouseButton::Left, { + let on_resize = self.on_resize.clone(); + move |click, v, cx| { + if click.click_count == 2 { + on_resize.borrow_mut()(v, None, cx); + } + } + }) .on_drag(MouseButton::Left, { let bounds = bounds.clone(); let side = self.handle_side; @@ -146,16 +146,30 @@ impl Element for Resizable { let min_size = side.relevant_component(constraint.min); let max_size = side.relevant_component(constraint.max); let on_resize = self.on_resize.clone(); + let tag = self.tag; move |event, view: &mut V, cx| { if event.end { return; } - let new_size = min_size - .max(prev_size + side.compute_delta(event)) - .min(max_size) - .round(); + + let Some((bounds, _)) = get_bounds(tag, cx) else { + return; + }; + + let new_size_raw = match side { + // Handle on top side of element => Element is on bottom + HandleSide::Top => bounds.height() + bounds.origin_y() - event.position.y(), + // Handle on right side of element => Element is on left + HandleSide::Right => event.position.x() - bounds.lower_left().x(), + // Handle on left side of element => Element is on the right + HandleSide::Left => bounds.width() + bounds.origin_x() - event.position.x(), + // Handle on bottom side of element => Element is on the top + HandleSide::Bottom => event.position.y() - bounds.lower_left().y(), + }; + + let new_size = min_size.max(new_size_raw).min(max_size).round(); if new_size != prev_size { - on_resize.borrow_mut()(view, new_size, cx); + on_resize.borrow_mut()(view, Some(new_size), cx); } } }), @@ -201,3 +215,80 @@ impl Element for Resizable { }) } } + +#[derive(Debug, Default)] +struct ProviderMap(HashMap); + +pub struct BoundsProvider { + child: AnyElement, + phantom: std::marker::PhantomData

, +} + +impl BoundsProvider { + pub fn new(child: AnyElement) -> Self { + Self { + child, + phantom: std::marker::PhantomData, + } + } +} + +impl Element for BoundsProvider { + type LayoutState = (); + + type PaintState = (); + + fn layout( + &mut self, + constraint: crate::SizeConstraint, + view: &mut V, + cx: &mut crate::LayoutContext, + ) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) { + (self.child.layout(constraint, view, cx), ()) + } + + fn paint( + &mut self, + scene: &mut crate::SceneBuilder, + bounds: pathfinder_geometry::rect::RectF, + visible_bounds: pathfinder_geometry::rect::RectF, + _: &mut Self::LayoutState, + view: &mut V, + cx: &mut crate::PaintContext, + ) -> Self::PaintState { + cx.update_default_global::(|map, _| { + map.0.insert(TypeTag::new::

(), (bounds, visible_bounds)); + }); + + self.child + .paint(scene, bounds.origin(), visible_bounds, view, cx) + } + + fn rect_for_text_range( + &self, + range_utf16: std::ops::Range, + _: pathfinder_geometry::rect::RectF, + _: pathfinder_geometry::rect::RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + view: &V, + cx: &crate::ViewContext, + ) -> Option { + self.child.rect_for_text_range(range_utf16, view, cx) + } + + fn debug( + &self, + _: pathfinder_geometry::rect::RectF, + _: &Self::LayoutState, + _: &Self::PaintState, + view: &V, + cx: &crate::ViewContext, + ) -> serde_json::Value { + serde_json::json!({ + "type": "Provider", + "providing": format!("{:?}", TypeTag::new::

()), + "child": self.child.debug(view, cx), + }) + } +} diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 7a3e405e58..9fbbd3408f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1651,8 +1651,8 @@ impl workspace::dock::Panel for ProjectPanel { .unwrap_or_else(|| settings::get::(cx).default_width) } - fn set_size(&mut self, size: f32, cx: &mut ViewContext) { - self.width = Some(size); + fn set_size(&mut self, size: Option, cx: &mut ViewContext) { + self.width = size; self.serialize(cx); cx.notify(); } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 7141cda172..472e748359 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -362,10 +362,10 @@ impl Panel for TerminalPanel { } } - fn set_size(&mut self, size: f32, cx: &mut ViewContext) { + fn set_size(&mut self, size: Option, cx: &mut ViewContext) { match self.position(cx) { - DockPosition::Left | DockPosition::Right => self.width = Some(size), - DockPosition::Bottom => self.height = Some(size), + DockPosition::Left | DockPosition::Right => self.width = size, + DockPosition::Bottom => self.height = size, } self.serialize(cx); cx.notify(); diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 55233c0836..3d40c8c420 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,4 +1,4 @@ -use crate::{StatusItemView, Workspace}; +use crate::{StatusItemView, Workspace, WorkspaceBounds}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyViewHandle, AppContext, @@ -13,7 +13,7 @@ pub trait Panel: View { fn position_is_valid(&self, position: DockPosition) -> bool; fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext); fn size(&self, cx: &WindowContext) -> f32; - fn set_size(&mut self, size: f32, cx: &mut ViewContext); + fn set_size(&mut self, size: Option, cx: &mut ViewContext); fn icon_path(&self, cx: &WindowContext) -> Option<&'static str>; fn icon_tooltip(&self) -> (String, Option>); fn icon_label(&self, _: &WindowContext) -> Option { @@ -50,7 +50,7 @@ pub trait PanelHandle { fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext); fn set_active(&self, active: bool, cx: &mut WindowContext); fn size(&self, cx: &WindowContext) -> f32; - fn set_size(&self, size: f32, cx: &mut WindowContext); + fn set_size(&self, size: Option, cx: &mut WindowContext); fn icon_path(&self, cx: &WindowContext) -> Option<&'static str>; fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option>); fn icon_label(&self, cx: &WindowContext) -> Option; @@ -82,7 +82,7 @@ where self.read(cx).size(cx) } - fn set_size(&self, size: f32, cx: &mut WindowContext) { + fn set_size(&self, size: Option, cx: &mut WindowContext) { self.update(cx, |this, cx| this.set_size(size, cx)) } @@ -373,7 +373,7 @@ impl Dock { } } - pub fn resize_active_panel(&mut self, size: f32, cx: &mut ViewContext) { + pub fn resize_active_panel(&mut self, size: Option, cx: &mut ViewContext) { if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { entry.panel.set_size(size, cx); cx.notify(); @@ -386,7 +386,7 @@ impl Dock { .into_any() .contained() .with_style(self.style(cx)) - .resizable( + .resizable::( self.position.to_resize_handle_side(), active_entry.panel.size(cx), |_, _, _| {}, @@ -423,7 +423,7 @@ impl View for Dock { ChildView::new(active_entry.panel.as_any(), cx) .contained() .with_style(style) - .resizable( + .resizable::( self.position.to_resize_handle_side(), active_entry.panel.size(cx), |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), @@ -700,8 +700,8 @@ pub mod test { self.size } - fn set_size(&mut self, size: f32, _: &mut ViewContext) { - self.size = size; + fn set_size(&mut self, size: Option, _: &mut ViewContext) { + self.size = size.unwrap_or(300.); } fn icon_path(&self, _: &WindowContext) -> Option<&'static str> { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 79f38f8e30..79b701e015 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -553,6 +553,8 @@ struct FollowerState { items_by_leader_view_id: HashMap>, } +enum WorkspaceBounds {} + impl Workspace { pub fn new( workspace_id: WorkspaceId, @@ -3776,6 +3778,7 @@ impl View for Workspace { })) .with_children(self.render_notifications(&theme.workspace, cx)), )) + .provide_resize_bounds::() .flex(1.0, true), ) .with_child(ChildView::new(&self.status_bar, cx)) @@ -4859,7 +4862,9 @@ mod tests { panel_1.size(cx) ); - left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx)); + left_dock.update(cx, |left_dock, cx| { + left_dock.resize_active_panel(Some(1337.), cx) + }); assert_eq!( workspace .right_dock() From 05becc75d1919588e45b9ee5e3a1810415681c25 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 16 Aug 2023 19:51:41 -0700 Subject: [PATCH 3/5] Collapse offline section by default --- crates/collab_ui/src/collab_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index b31ea0fbf2..0e7bd5f929 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -397,7 +397,7 @@ impl CollabPanel { project: workspace.project().clone(), subscriptions: Vec::default(), match_candidates: Vec::default(), - collapsed_sections: Vec::default(), + collapsed_sections: vec![Section::Offline], workspace: workspace.weak_handle(), client: workspace.app_state().client.clone(), context_menu_on_selected: true, From 5bc481112eccfe187103f1b504ff28b401cdb44f Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 16 Aug 2023 20:05:21 -0700 Subject: [PATCH 4/5] Add test for lost channel update --- crates/collab/src/tests/channel_tests.rs | 97 +++++++++++++++++++++++- 1 file changed, 93 insertions(+), 4 deletions(-) diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index a250f59a21..06cf3607c0 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -776,11 +776,100 @@ async fn test_lost_channel_creation( cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, ) { + deterministic.forbid_parking(); + let mut server = TestServer::start(&deterministic).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + + server + .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + + let channel_id = server.make_channel("x", (&client_a, cx_a), &mut []).await; + // Invite a member - // Create a new sub channel - // Member accepts invite - // Make sure that member can see new channel - todo!(); + client_a + .channel_store() + .update(cx_a, |channel_store, cx| { + channel_store.invite_member(channel_id, client_b.user_id().unwrap(), false, cx) + }) + .await + .unwrap(); + + deterministic.run_until_parked(); + + // Sanity check + assert_channel_invitations( + client_b.channel_store(), + cx_b, + &[ExpectedChannel { + depth: 0, + id: channel_id, + name: "x".to_string(), + user_is_admin: false, + }], + ); + + let subchannel_id = client_a + .channel_store() + .update(cx_a, |channel_store, cx| { + channel_store.create_channel("subchannel", Some(channel_id), cx) + }) + .await + .unwrap(); + + deterministic.run_until_parked(); + + // Make sure A sees their new channel + assert_channels( + client_a.channel_store(), + cx_a, + &[ + ExpectedChannel { + depth: 0, + id: channel_id, + name: "x".to_string(), + user_is_admin: true, + }, + ExpectedChannel { + depth: 1, + id: subchannel_id, + name: "subchannel".to_string(), + user_is_admin: true, + }, + ], + ); + + // Accept the invite + client_b + .channel_store() + .update(cx_b, |channel_store, _| { + channel_store.respond_to_channel_invite(channel_id, true) + }) + .await + .unwrap(); + + deterministic.run_until_parked(); + + // B should now see the channel + assert_channels( + client_b.channel_store(), + cx_b, + &[ + ExpectedChannel { + depth: 0, + id: channel_id, + name: "x".to_string(), + user_is_admin: false, + }, + ExpectedChannel { + depth: 1, + id: subchannel_id, + name: "subchannel".to_string(), + user_is_admin: false, + }, + ], + ); } #[derive(Debug, PartialEq)] From 75679291a9a7353d7ddc444e460163c881806430 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 17 Aug 2023 00:55:11 -0700 Subject: [PATCH 5/5] Add fix for lost channel update bug --- crates/client/src/channel_store.rs | 2 ++ crates/collab/src/db.rs | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/client/src/channel_store.rs b/crates/client/src/channel_store.rs index e2c18a63a9..03d334a9de 100644 --- a/crates/client/src/channel_store.rs +++ b/crates/client/src/channel_store.rs @@ -441,10 +441,12 @@ impl ChannelStore { for channel in payload.channels { if let Some(existing_channel) = self.channels_by_id.get_mut(&channel.id) { + // FIXME: We may be missing a path for this existing channel in certain cases let existing_channel = Arc::make_mut(existing_channel); existing_channel.name = channel.name; continue; } + self.channels_by_id.insert( channel.id, Arc::new(Channel { diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 64349123af..b457c4c116 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -3650,7 +3650,11 @@ impl Database { let ancestor_ids = self.get_channel_ancestors(id, tx).await?; let user_ids = channel_member::Entity::find() .distinct() - .filter(channel_member::Column::ChannelId.is_in(ancestor_ids.iter().copied())) + .filter( + channel_member::Column::ChannelId + .is_in(ancestor_ids.iter().copied()) + .and(channel_member::Column::Accepted.eq(true)), + ) .select_only() .column(channel_member::Column::UserId) .into_values::<_, QueryUserIds>()