mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-10 05:37:29 +03:00
Introduce Orientation concept to List
When the Orientation is Bottom, we paint elements from the bottom of the list when underflowing and express scroll position relative to the bottom. In either orientation, when inserting elements outside the visible area, we adjust the scroll position as needed to keep the visible elements stable. Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com> Co-Authored-By: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
parent
897826f710
commit
77c7fa53da
@ -19,11 +19,18 @@ pub struct List {
|
||||
#[derive(Clone)]
|
||||
pub struct ListState(Arc<Mutex<StateInner>>);
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum Orientation {
|
||||
Top,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
struct StateInner {
|
||||
last_layout_width: f32,
|
||||
elements: Vec<ElementBox>,
|
||||
heights: SumTree<ElementHeight>,
|
||||
scroll_top: f32,
|
||||
scroll_position: f32,
|
||||
orientation: Orientation,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -69,6 +76,11 @@ impl Element for List {
|
||||
item_constraint.min.set_y(0.);
|
||||
item_constraint.max.set_y(f32::INFINITY);
|
||||
|
||||
let size = constraint.max;
|
||||
|
||||
let visible_top = state.scroll_top(size.y());
|
||||
let visible_bottom = visible_top + size.y();
|
||||
|
||||
if state.last_layout_width == constraint.max.x() {
|
||||
let mut old_heights = state.heights.cursor::<PendingCount, ElementHeightSummary>();
|
||||
let mut new_heights = old_heights.slice(&PendingCount(1), sum_tree::Bias::Left, &());
|
||||
@ -78,6 +90,21 @@ impl Element for List {
|
||||
let size =
|
||||
state.elements[old_heights.sum_start().count].layout(item_constraint, cx);
|
||||
new_heights.push(ElementHeight::Ready(size.y()), &());
|
||||
|
||||
// Adjust scroll position to keep visible elements stable
|
||||
match state.orientation {
|
||||
Orientation::Top => {
|
||||
if new_heights.summary().height < visible_top {
|
||||
state.scroll_position += size.y();
|
||||
}
|
||||
}
|
||||
Orientation::Bottom => {
|
||||
if new_heights.summary().height - size.y() > visible_bottom {
|
||||
state.scroll_position += size.y();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
old_heights.next(&());
|
||||
} else {
|
||||
new_heights.push_tree(
|
||||
@ -102,7 +129,7 @@ impl Element for List {
|
||||
state.last_layout_width = constraint.max.x();
|
||||
}
|
||||
|
||||
(constraint.max, ())
|
||||
(size, ())
|
||||
}
|
||||
|
||||
fn paint(&mut self, bounds: RectF, _: &mut (), cx: &mut PaintContext) {
|
||||
@ -115,8 +142,15 @@ impl Element for List {
|
||||
cursor.seek(&Count(visible_range.start), Bias::Right, &());
|
||||
cursor.sum_start().0
|
||||
};
|
||||
if state.orientation == Orientation::Bottom
|
||||
&& bounds.height() > state.heights.summary().height
|
||||
{
|
||||
item_top += bounds.height() - state.heights.summary().height;
|
||||
}
|
||||
let scroll_top = state.scroll_top(bounds.height());
|
||||
|
||||
for element in &mut state.elements[visible_range] {
|
||||
let origin = bounds.origin() + vec2f(0., item_top - state.scroll_top);
|
||||
let origin = bounds.origin() + vec2f(0., item_top - scroll_top);
|
||||
element.paint(origin, cx);
|
||||
item_top += element.size().y();
|
||||
}
|
||||
@ -167,20 +201,21 @@ impl Element for List {
|
||||
json!({
|
||||
"visible_range": visible_range,
|
||||
"visible_elements": visible_elements,
|
||||
"scroll_top": state.scroll_top,
|
||||
"scroll_position": state.scroll_position,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ListState {
|
||||
pub fn new(elements: Vec<ElementBox>) -> Self {
|
||||
pub fn new(elements: Vec<ElementBox>, orientation: Orientation) -> Self {
|
||||
let mut heights = SumTree::new();
|
||||
heights.extend(elements.iter().map(|_| ElementHeight::Pending), &());
|
||||
Self(Arc::new(Mutex::new(StateInner {
|
||||
last_layout_width: 0.,
|
||||
elements,
|
||||
heights,
|
||||
scroll_top: 0.,
|
||||
scroll_position: 0.,
|
||||
orientation,
|
||||
})))
|
||||
}
|
||||
|
||||
@ -215,9 +250,9 @@ impl ListState {
|
||||
impl StateInner {
|
||||
fn visible_range(&self, height: f32) -> Range<usize> {
|
||||
let mut cursor = self.heights.cursor::<Height, Count>();
|
||||
cursor.seek(&Height(self.scroll_top), Bias::Right, &());
|
||||
cursor.seek(&Height(self.scroll_top(height)), Bias::Right, &());
|
||||
let start_ix = cursor.sum_start().0;
|
||||
cursor.seek(&Height(self.scroll_top + height), Bias::Left, &());
|
||||
cursor.seek(&Height(self.scroll_top(height) + height), Bias::Left, &());
|
||||
let end_ix = cursor.sum_start().0;
|
||||
start_ix..self.elements.len().min(end_ix + 1)
|
||||
}
|
||||
@ -235,12 +270,24 @@ impl StateInner {
|
||||
}
|
||||
|
||||
let scroll_max = (self.heights.summary().height - height).max(0.);
|
||||
self.scroll_top = (self.scroll_top - delta.y()).max(0.).min(scroll_max);
|
||||
|
||||
let delta_y = match self.orientation {
|
||||
Orientation::Top => -delta.y(),
|
||||
Orientation::Bottom => delta.y(),
|
||||
};
|
||||
self.scroll_position = (self.scroll_position + delta_y).max(0.).min(scroll_max);
|
||||
cx.notify();
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn scroll_top(&self, height: f32) -> f32 {
|
||||
match self.orientation {
|
||||
Orientation::Top => self.scroll_position,
|
||||
Orientation::Bottom => {
|
||||
(self.heights.summary().height - height - self.scroll_position).max(0.)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ElementHeight {
|
||||
@ -329,7 +376,7 @@ mod tests {
|
||||
fn test_layout(cx: &mut crate::MutableAppContext) {
|
||||
let mut presenter = cx.build_presenter(0, 20.0);
|
||||
let mut layout_cx = presenter.layout_cx(cx);
|
||||
let state = ListState::new(vec![item(20.), item(30.), item(10.)]);
|
||||
let state = ListState::new(vec![item(20.), item(30.), item(10.)], Orientation::Top);
|
||||
let mut list = List::new(state.clone()).boxed();
|
||||
|
||||
let size = list.layout(
|
||||
|
@ -38,7 +38,7 @@ impl ChatPanel {
|
||||
let mut this = Self {
|
||||
channel_list,
|
||||
active_channel: None,
|
||||
messages: ListState::new(Vec::new()),
|
||||
messages: ListState::new(Vec::new(), Orientation::Bottom),
|
||||
input_editor,
|
||||
settings,
|
||||
};
|
||||
@ -82,6 +82,7 @@ impl ChatPanel {
|
||||
.cursor::<(), ()>()
|
||||
.map(|m| self.render_message(m))
|
||||
.collect(),
|
||||
Orientation::Bottom,
|
||||
);
|
||||
self.active_channel = Some((channel, subscription));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user