project_panel: Fix sticky items horizontal scroll and hover propagation (#34367)
Release Notes: - Fixed horizontal scrolling not working for sticky items in the Project Panel. - Fixed issue where hovering over the last sticky item in the Project Panel showed a hovered state on the entry behind it. - Improved behavior when clicking a sticky item in the Project Panel so it scrolls just enough for the item to no longer be sticky.
This commit is contained in:
parent
8f6b9f0d65
commit
1cadff9311
2 changed files with 95 additions and 74 deletions
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue