@@ -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<Client>,
project: Model<Project>,
match_candidates: Vec<StringMatchCandidate>,
+ scroll_handle: ScrollHandle,
// list_state: ListState<Self>,
subscriptions: Vec<Subscription>,
collapsed_sections: Vec<Section>,
@@ -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<Self>) -> 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())
- }))
}
}
@@ -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<FocusHandle>,
+ pub scroll_handle: Option<ScrollHandle>,
pub focus_listeners: FocusListeners,
pub group: Option<SharedString>,
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<RefCell<Point<Pixels>>>,
+ bounds: Bounds<Pixels>,
+ child_bounds: Vec<Bounds<Pixels>>,
+}
+
+#[derive(Clone)]
+pub struct ScrollHandle(Rc<RefCell<ScrollHandleState>>);
+
+impl ScrollHandle {
+ pub fn new() -> Self {
+ Self(Rc::default())
+ }
+
+ pub fn offset(&self) -> Point<Pixels> {
+ 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)),
+ }
+ }
+}