@@ -3961,8 +3961,14 @@ impl ProjectPanel {
linear_color_stop(shadow_color_bottom, 0.),
));
+ let id: ElementId = if is_sticky {
+ SharedString::from(format!("project_panel_sticky_item_{}", entry_id.to_usize())).into()
+ } else {
+ (entry_id.to_proto() as usize).into()
+ };
+
div()
- .id(entry_id.to_proto() as usize)
+ .id(id.clone())
.relative()
.group(GROUP_NAME)
.cursor_pointer()
@@ -3973,6 +3979,9 @@ impl ProjectPanel {
.border_color(border_color)
.hover(|style| style.bg(bg_hover_color).border_color(border_hover_color))
.when(show_sticky_shadow, |this| this.child(sticky_shadow))
+ .when(is_sticky, |this| {
+ this.block_mouse_except_scroll()
+ })
.when(!is_sticky, |this| {
this
.when(is_highlighted && folded_directory_drag_target.is_none(), |this| this.border_color(transparent_white()).bg(item_colors.drag_over))
@@ -4183,6 +4192,16 @@ impl ProjectPanel {
.unwrap_or(ScrollStrategy::Top);
this.scroll_handle.scroll_to_item(index, strategy);
cx.notify();
+ // move down by 1px so that clicked item
+ // don't count as sticky anymore
+ cx.on_next_frame(window, |_, window, cx| {
+ cx.on_next_frame(window, |this, _, cx| {
+ let mut offset = this.scroll_handle.offset();
+ offset.y += px(1.);
+ this.scroll_handle.set_offset(offset);
+ cx.notify();
+ });
+ });
return;
}
}
@@ -4201,7 +4220,7 @@ impl ProjectPanel {
}),
)
.child(
- ListItem::new(entry_id.to_proto() as usize)
+ ListItem::new(id)
.indent_level(depth)
.indent_step_size(px(settings.indent_size))
.spacing(match settings.entry_spacing {
@@ -149,47 +149,7 @@ where
) -> AnyElement {
let entries = (self.compute_fn)(visible_range.clone(), window, cx);
- struct StickyAnchor<T> {
- entry: T,
- index: usize,
- }
-
- let mut sticky_anchor = None;
- let mut last_item_is_drifting = false;
-
- let mut iter = entries.iter().enumerate().peekable();
- while let Some((ix, current_entry)) = iter.next() {
- let depth = current_entry.depth();
-
- if depth < ix {
- sticky_anchor = Some(StickyAnchor {
- entry: current_entry.clone(),
- index: visible_range.start + ix,
- });
- break;
- }
-
- if let Some(&(_next_ix, next_entry)) = iter.peek() {
- let next_depth = next_entry.depth();
- let next_item_outdented = next_depth + 1 == depth;
-
- let depth_same_as_index = depth == ix;
- let depth_greater_than_index = depth == ix + 1;
-
- if next_item_outdented && (depth_same_as_index || depth_greater_than_index) {
- if depth_greater_than_index {
- last_item_is_drifting = true;
- }
- sticky_anchor = Some(StickyAnchor {
- entry: current_entry.clone(),
- index: visible_range.start + ix,
- });
- break;
- }
- }
- }
-
- let Some(sticky_anchor) = sticky_anchor else {
+ let Some(sticky_anchor) = find_sticky_anchor(&entries, visible_range.start) else {
return StickyItemsElement {
drifting_element: None,
drifting_decoration: None,
@@ -203,23 +163,21 @@ where
let mut elements = (self.render_fn)(sticky_anchor.entry, window, cx);
let items_count = elements.len();
- let indents: SmallVec<[usize; 8]> = {
- elements
- .iter()
- .enumerate()
- .map(|(ix, _)| anchor_depth.saturating_sub(items_count.saturating_sub(ix)))
- .collect()
- };
+ let indents: SmallVec<[usize; 8]> = (0..items_count)
+ .map(|ix| anchor_depth.saturating_sub(items_count.saturating_sub(ix)))
+ .collect();
let mut last_decoration_element = None;
let mut rest_decoration_elements = SmallVec::new();
- let available_space = size(
- AvailableSpace::Definite(bounds.size.width),
+ let expanded_width = bounds.size.width + scroll_offset.x.abs();
+
+ let decor_available_space = size(
+ AvailableSpace::Definite(expanded_width),
AvailableSpace::Definite(bounds.size.height),
);
- let drifting_y_offset = if last_item_is_drifting {
+ let drifting_y_offset = if sticky_anchor.drifting {
let scroll_top = -scroll_offset.y;
let anchor_top = item_height * (sticky_anchor.index + 1);
let sticky_area_height = item_height * items_count;
@@ -228,7 +186,7 @@ where
Pixels::ZERO
};
- let (drifting_indent, rest_indents) = if last_item_is_drifting && !indents.is_empty() {
+ let (drifting_indent, rest_indents) = if sticky_anchor.drifting && !indents.is_empty() {
let last = indents[indents.len() - 1];
let rest: SmallVec<[usize; 8]> = indents[..indents.len() - 1].iter().copied().collect();
(Some(last), rest)
@@ -236,11 +194,14 @@ where
(None, indents)
};
+ let base_origin = bounds.origin - point(px(0.), scroll_offset.y);
+
for decoration in &self.decorations {
if let Some(drifting_indent) = drifting_indent {
let drifting_indent_vec: SmallVec<[usize; 8]> =
[drifting_indent].into_iter().collect();
- let sticky_origin = bounds.origin - scroll_offset
+
+ let sticky_origin = base_origin
+ point(px(0.), item_height * rest_indents.len() + drifting_y_offset);
let decoration_bounds = Bounds::new(sticky_origin, bounds.size);
@@ -252,13 +213,13 @@ where
window,
cx,
);
- drifting_dec.layout_as_root(available_space, window, cx);
+ drifting_dec.layout_as_root(decor_available_space, window, cx);
drifting_dec.prepaint_at(sticky_origin, window, cx);
last_decoration_element = Some(drifting_dec);
}
if !rest_indents.is_empty() {
- let decoration_bounds = Bounds::new(bounds.origin - scroll_offset, bounds.size);
+ let decoration_bounds = Bounds::new(base_origin, bounds.size);
let mut rest_dec = decoration.as_ref().compute(
&rest_indents,
decoration_bounds,
@@ -267,46 +228,45 @@ where
window,
cx,
);
- rest_dec.layout_as_root(available_space, window, cx);
+ rest_dec.layout_as_root(decor_available_space, window, cx);
rest_dec.prepaint_at(bounds.origin, window, cx);
rest_decoration_elements.push(rest_dec);
}
}
let (mut drifting_element, mut rest_elements) =
- if last_item_is_drifting && !elements.is_empty() {
+ if sticky_anchor.drifting && !elements.is_empty() {
let last = elements.pop().unwrap();
(Some(last), elements)
} else {
(None, elements)
};
- for (ix, element) in rest_elements.iter_mut().enumerate() {
- let sticky_origin = bounds.origin - scroll_offset + point(px(0.), item_height * ix);
- let element_available_space = size(
- AvailableSpace::Definite(bounds.size.width),
- AvailableSpace::Definite(item_height),
- );
-
- element.layout_as_root(element_available_space, window, cx);
- element.prepaint_at(sticky_origin, window, cx);
- }
+ let element_available_space = size(
+ AvailableSpace::Definite(expanded_width),
+ AvailableSpace::Definite(item_height),
+ );
+ // order of prepaint is important here
+ // mouse events checks hitboxes in reverse insertion order
if let Some(ref mut drifting_element) = drifting_element {
- let sticky_origin = bounds.origin - scroll_offset
+ let sticky_origin = base_origin
+ point(
px(0.),
item_height * rest_elements.len() + drifting_y_offset,
);
- let element_available_space = size(
- AvailableSpace::Definite(bounds.size.width),
- AvailableSpace::Definite(item_height),
- );
drifting_element.layout_as_root(element_available_space, window, cx);
drifting_element.prepaint_at(sticky_origin, window, cx);
}
+ for (ix, element) in rest_elements.iter_mut().enumerate() {
+ let sticky_origin = base_origin + point(px(0.), item_height * ix);
+
+ element.layout_as_root(element_available_space, window, cx);
+ element.prepaint_at(sticky_origin, window, cx);
+ }
+
StickyItemsElement {
drifting_element,
drifting_decoration: last_decoration_element,
@@ -317,6 +277,48 @@ where
}
}
+struct StickyAnchor<T> {
+ entry: T,
+ index: usize,
+ drifting: bool,
+}
+
+fn find_sticky_anchor<T: StickyCandidate + Clone>(
+ entries: &SmallVec<[T; 8]>,
+ visible_range_start: usize,
+) -> Option<StickyAnchor<T>> {
+ let mut iter = entries.iter().enumerate().peekable();
+ while let Some((ix, current_entry)) = iter.next() {
+ let depth = current_entry.depth();
+
+ if depth < ix {
+ return Some(StickyAnchor {
+ entry: current_entry.clone(),
+ index: visible_range_start + ix,
+ drifting: false,
+ });
+ }
+
+ if let Some(&(_next_ix, next_entry)) = iter.peek() {
+ let next_depth = next_entry.depth();
+ let next_item_outdented = next_depth + 1 == depth;
+
+ let depth_same_as_index = depth == ix;
+ let depth_greater_than_index = depth == ix + 1;
+
+ if next_item_outdented && (depth_same_as_index || depth_greater_than_index) {
+ return Some(StickyAnchor {
+ entry: current_entry.clone(),
+ index: visible_range_start + ix,
+ drifting: depth_greater_than_index,
+ });
+ }
+ }
+ }
+
+ None
+}
+
/// A decoration for a [`StickyItems`]. This can be used for various things,
/// such as rendering indent guides, or other visual effects.
pub trait StickyItemsDecoration {