diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 15869fc5acc4d1fac8777cf957ed7676f4f1a426..6144612cd7bd9ed16159028d5bbf0086ca409a6b 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -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 { diff --git a/crates/ui/src/components/sticky_items.rs b/crates/ui/src/components/sticky_items.rs index 218f7aae3510213afeed9d80a28428ce9c0df28a..ca8b336a5aa97101f29d394399f36bda2fcc44b9 100644 --- a/crates/ui/src/components/sticky_items.rs +++ b/crates/ui/src/components/sticky_items.rs @@ -149,47 +149,7 @@ where ) -> AnyElement { let entries = (self.compute_fn)(visible_range.clone(), window, cx); - struct StickyAnchor { - 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 { + entry: T, + index: usize, + drifting: bool, +} + +fn find_sticky_anchor( + entries: &SmallVec<[T; 8]>, + visible_range_start: usize, +) -> Option> { + 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 {