Add a ScrollHandle to gpui2 for the collab panel
This commit is contained in:
@@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user