diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 3d021eda2fcf07d8724c957bc47d99d480a07bfe..8df0de797bb24ad934dcdab9f7f2b0c6ca17d095 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -172,8 +172,8 @@ use gpui::{ actions, div, img, overlay, prelude::*, px, rems, serde_json, Action, AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, IntoElement, Model, MouseDownEvent, - ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, SharedString, Stateful, Styled, - Subscription, Task, View, ViewContext, VisualContext, WeakView, + ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, ScrollHandle, SharedString, + Stateful, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, }; use project::{Fs, Project}; use serde_derive::{Deserialize, Serialize}; @@ -302,6 +302,7 @@ pub struct CollabPanel { client: Arc, project: Model, match_candidates: Vec, + scroll_handle: ScrollHandle, // list_state: ListState, subscriptions: Vec, collapsed_sections: Vec
, @@ -586,6 +587,7 @@ impl CollabPanel { project: workspace.project().clone(), subscriptions: Vec::default(), match_candidates: Vec::default(), + scroll_handle: ScrollHandle::new(), collapsed_sections: vec![Section::Offline], collapsed_channels: Vec::default(), workspace: workspace.weak_handle(), @@ -2348,48 +2350,67 @@ impl CollabPanel { } fn render_signed_in(&mut self, cx: &mut ViewContext) -> Div { - div() + dbg!(&self.scroll_handle.top_item()); + + v_stack() + .size_full() .child( div() - .m_2() - .rounded(px(2.0)) - .child(self.filter_editor.clone()), + .p_2() + .child(div().rounded(px(2.0)).child(self.filter_editor.clone())), ) .child( - List::new().children(self.entries.clone().into_iter().enumerate().map( - |(ix, entry)| { - let is_selected = self.selection == Some(ix); - match entry { - ListEntry::Header(section) => { - let is_collapsed = self.collapsed_sections.contains(§ion); - self.render_header(section, is_selected, is_collapsed, cx) - .into_any_element() - } - ListEntry::Contact { contact, calling } => self - .render_contact(&*contact, calling, is_selected, cx) - .into_any_element(), - ListEntry::ContactPlaceholder => self - .render_contact_placeholder(is_selected, cx) - .into_any_element(), - ListEntry::IncomingRequest(user) => self - .render_contact_request(user, true, is_selected, cx) - .into_any_element(), - ListEntry::OutgoingRequest(user) => self - .render_contact_request(user, false, is_selected, cx) - .into_any_element(), - ListEntry::Channel { - channel, - depth, - has_children, - } => self - .render_channel(&*channel, depth, has_children, is_selected, ix, cx) - .into_any_element(), - ListEntry::ChannelEditor { depth } => { - self.render_channel_editor(depth, cx).into_any_element() - } - } - }, - )), + v_stack() + .size_full() + .id("scroll") + .overflow_y_scroll() + .track_scroll(&self.scroll_handle) + .children( + self.entries + .clone() + .into_iter() + .enumerate() + .map(|(ix, entry)| { + let is_selected = self.selection == Some(ix); + match entry { + ListEntry::Header(section) => { + let is_collapsed = + self.collapsed_sections.contains(§ion); + self.render_header(section, is_selected, is_collapsed, cx) + .into_any_element() + } + ListEntry::Contact { contact, calling } => self + .render_contact(&*contact, calling, is_selected, cx) + .into_any_element(), + ListEntry::ContactPlaceholder => self + .render_contact_placeholder(is_selected, cx) + .into_any_element(), + ListEntry::IncomingRequest(user) => self + .render_contact_request(user, true, is_selected, cx) + .into_any_element(), + ListEntry::OutgoingRequest(user) => self + .render_contact_request(user, false, is_selected, cx) + .into_any_element(), + ListEntry::Channel { + channel, + depth, + has_children, + } => self + .render_channel( + &*channel, + depth, + has_children, + is_selected, + ix, + cx, + ) + .into_any_element(), + ListEntry::ChannelEditor { depth } => { + self.render_channel_editor(depth, cx).into_any_element() + } + } + }), + ), ) } @@ -3249,12 +3270,6 @@ impl Render for CollabPanel { } else { self.render_signed_in(cx) }) - .children(self.context_menu.as_ref().map(|(menu, position, _)| { - overlay() - .position(*position) - .anchor(gpui::AnchorCorner::TopLeft) - .child(menu.clone()) - })) } } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 3922c0c4e4c794d9d58d73982bf50fbc35a3502d..50bc678b42d88e394442231a47540ece653e796a 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -12,6 +12,7 @@ use smallvec::SmallVec; use std::{ any::{Any, TypeId}, cell::RefCell, + cmp::Ordering, fmt::Debug, mem, rc::Rc, @@ -357,6 +358,11 @@ pub trait StatefulInteractiveElement: InteractiveElement { self } + fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self { + self.interactivity().scroll_handle = Some(scroll_handle.clone()); + self + } + fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, @@ -626,6 +632,18 @@ impl Element for Div { let mut child_max = Point::default(); let content_size = if element_state.child_layout_ids.is_empty() { bounds.size + } else if let Some(scroll_handle) = self.interactivity.scroll_handle.as_ref() { + let mut state = scroll_handle.0.borrow_mut(); + state.child_bounds = Vec::with_capacity(element_state.child_layout_ids.len()); + state.bounds = bounds; + + for child_layout_id in &element_state.child_layout_ids { + let child_bounds = cx.layout_bounds(*child_layout_id); + child_min = child_min.min(&child_bounds.origin); + child_max = child_max.max(&child_bounds.lower_right()); + state.child_bounds.push(child_bounds) + } + (child_max - child_min).into() } else { for child_layout_id in &element_state.child_layout_ids { let child_bounds = cx.layout_bounds(*child_layout_id); @@ -696,6 +714,7 @@ pub struct Interactivity { pub key_context: KeyContext, pub focusable: bool, pub tracked_focus_handle: Option, + pub scroll_handle: Option, pub focus_listeners: FocusListeners, pub group: Option, pub base_style: StyleRefinement, @@ -754,6 +773,10 @@ impl Interactivity { }); } + if let Some(scroll_handle) = self.scroll_handle.as_ref() { + element_state.scroll_offset = Some(scroll_handle.0.borrow().offset.clone()); + } + let style = self.compute_style(None, &mut element_state, cx); let layout_id = f(style, cx); (layout_id, element_state) @@ -1206,6 +1229,7 @@ impl Default for Interactivity { key_context: KeyContext::default(), focusable: false, tracked_focus_handle: None, + scroll_handle: None, focus_listeners: SmallVec::default(), // scroll_offset: Point::default(), group: None, @@ -1429,3 +1453,42 @@ where self.element.children_mut() } } + +#[derive(Default)] +struct ScrollHandleState { + // not great to have the nested rc's... + offset: Rc>>, + bounds: Bounds, + child_bounds: Vec>, +} + +#[derive(Clone)] +pub struct ScrollHandle(Rc>); + +impl ScrollHandle { + pub fn new() -> Self { + Self(Rc::default()) + } + + pub fn offset(&self) -> Point { + self.0.borrow().offset.borrow().clone() + } + + pub fn top_item(&self) -> usize { + let state = self.0.borrow(); + let top = state.bounds.top() - state.offset.borrow().y; + + match state.child_bounds.binary_search_by(|bounds| { + if top < bounds.top() { + Ordering::Greater + } else if top > bounds.bottom() { + Ordering::Less + } else { + Ordering::Equal + } + }) { + Ok(ix) => ix, + Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)), + } + } +}