project panel: Add indent guides for sticky items (#34092)
- Adds new trait `StickyItemsDecoration` in `sticky_items` which is implemented by `IndentGuides` from `indent_guides`. <img width="347" alt="image" src="https://github.com/user-attachments/assets/577748bc-13f6-41b8-9266-6a0b72349a18" /> Release Notes: - N/A
This commit is contained in:
parent
ad8b823555
commit
3a247ee947
6 changed files with 580 additions and 400 deletions
|
@ -506,35 +506,6 @@ pub trait UniformListDecoration {
|
||||||
) -> AnyElement;
|
) -> AnyElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for implementing top slots in a [`UniformList`].
|
|
||||||
/// Top slots are elements that appear at the top of the list and can adjust
|
|
||||||
/// the visible range of list items.
|
|
||||||
pub trait UniformListTopSlot {
|
|
||||||
/// Returns elements to render at the top slot for the given visible range.
|
|
||||||
fn compute(
|
|
||||||
&mut self,
|
|
||||||
visible_range: Range<usize>,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> SmallVec<[AnyElement; 8]>;
|
|
||||||
|
|
||||||
/// Layout and prepaint the top slot elements.
|
|
||||||
fn prepaint(
|
|
||||||
&self,
|
|
||||||
elements: &mut SmallVec<[AnyElement; 8]>,
|
|
||||||
bounds: Bounds<Pixels>,
|
|
||||||
item_height: Pixels,
|
|
||||||
scroll_offset: Point<Pixels>,
|
|
||||||
padding: crate::Edges<Pixels>,
|
|
||||||
can_scroll_horizontally: bool,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Paint the top slot elements.
|
|
||||||
fn paint(&self, elements: &mut SmallVec<[AnyElement; 8]>, window: &mut Window, cx: &mut App);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UniformList {
|
impl UniformList {
|
||||||
/// Selects a specific list item for measurement.
|
/// Selects a specific list item for measurement.
|
||||||
pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
|
pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
|
||||||
|
|
|
@ -4584,53 +4584,52 @@ impl OutlinePanel {
|
||||||
.track_scroll(self.scroll_handle.clone())
|
.track_scroll(self.scroll_handle.clone())
|
||||||
.when(show_indent_guides, |list| {
|
.when(show_indent_guides, |list| {
|
||||||
list.with_decoration(
|
list.with_decoration(
|
||||||
ui::indent_guides(
|
ui::indent_guides(px(indent_size), IndentGuideColors::panel(cx))
|
||||||
cx.entity().clone(),
|
.with_compute_indents_fn(
|
||||||
px(indent_size),
|
cx.entity().clone(),
|
||||||
IndentGuideColors::panel(cx),
|
|outline_panel, range, _, _| {
|
||||||
|outline_panel, range, _, _| {
|
let entries = outline_panel.cached_entries.get(range);
|
||||||
let entries = outline_panel.cached_entries.get(range);
|
if let Some(entries) = entries {
|
||||||
if let Some(entries) = entries {
|
entries.into_iter().map(|item| item.depth).collect()
|
||||||
entries.into_iter().map(|item| item.depth).collect()
|
} else {
|
||||||
} else {
|
smallvec::SmallVec::new()
|
||||||
smallvec::SmallVec::new()
|
}
|
||||||
}
|
},
|
||||||
},
|
)
|
||||||
)
|
.with_render_fn(
|
||||||
.with_render_fn(
|
cx.entity().clone(),
|
||||||
cx.entity().clone(),
|
move |outline_panel, params, _, _| {
|
||||||
move |outline_panel, params, _, _| {
|
const LEFT_OFFSET: Pixels = px(14.);
|
||||||
const LEFT_OFFSET: Pixels = px(14.);
|
|
||||||
|
|
||||||
let indent_size = params.indent_size;
|
let indent_size = params.indent_size;
|
||||||
let item_height = params.item_height;
|
let item_height = params.item_height;
|
||||||
let active_indent_guide_ix = find_active_indent_guide_ix(
|
let active_indent_guide_ix = find_active_indent_guide_ix(
|
||||||
outline_panel,
|
outline_panel,
|
||||||
¶ms.indent_guides,
|
¶ms.indent_guides,
|
||||||
);
|
);
|
||||||
|
|
||||||
params
|
params
|
||||||
.indent_guides
|
.indent_guides
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(ix, layout)| {
|
.map(|(ix, layout)| {
|
||||||
let bounds = Bounds::new(
|
let bounds = Bounds::new(
|
||||||
point(
|
point(
|
||||||
layout.offset.x * indent_size + LEFT_OFFSET,
|
layout.offset.x * indent_size + LEFT_OFFSET,
|
||||||
layout.offset.y * item_height,
|
layout.offset.y * item_height,
|
||||||
),
|
),
|
||||||
size(px(1.), layout.length * item_height),
|
size(px(1.), layout.length * item_height),
|
||||||
);
|
);
|
||||||
ui::RenderedIndentGuide {
|
ui::RenderedIndentGuide {
|
||||||
bounds,
|
bounds,
|
||||||
layout,
|
layout,
|
||||||
is_active: active_indent_guide_ix == Some(ix),
|
is_active: active_indent_guide_ix == Some(ix),
|
||||||
hitbox: None,
|
hitbox: None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
|
@ -3947,7 +3947,7 @@ impl ProjectPanel {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let shadow_color_top = hsla(0.0, 0.0, 0.0, 0.15);
|
let shadow_color_top = hsla(0.0, 0.0, 0.0, 0.1);
|
||||||
let shadow_color_bottom = hsla(0.0, 0.0, 0.0, 0.);
|
let shadow_color_bottom = hsla(0.0, 0.0, 0.0, 0.);
|
||||||
let sticky_shadow = div()
|
let sticky_shadow = div()
|
||||||
.absolute()
|
.absolute()
|
||||||
|
@ -4176,6 +4176,16 @@ impl ProjectPanel {
|
||||||
}
|
}
|
||||||
} else if kind.is_dir() {
|
} else if kind.is_dir() {
|
||||||
this.marked_entries.clear();
|
this.marked_entries.clear();
|
||||||
|
if is_sticky {
|
||||||
|
if let Some((_, _, index)) = this.index_for_entry(entry_id, worktree_id) {
|
||||||
|
let strategy = sticky_index
|
||||||
|
.map(ScrollStrategy::ToPosition)
|
||||||
|
.unwrap_or(ScrollStrategy::Top);
|
||||||
|
this.scroll_handle.scroll_to_item(index, strategy);
|
||||||
|
cx.notify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if event.modifiers().alt {
|
if event.modifiers().alt {
|
||||||
this.toggle_expand_all(entry_id, window, cx);
|
this.toggle_expand_all(entry_id, window, cx);
|
||||||
} else {
|
} else {
|
||||||
|
@ -4188,16 +4198,6 @@ impl ProjectPanel {
|
||||||
let allow_preview = preview_tabs_enabled && click_count == 1;
|
let allow_preview = preview_tabs_enabled && click_count == 1;
|
||||||
this.open_entry(entry_id, focus_opened_item, allow_preview, cx);
|
this.open_entry(entry_id, focus_opened_item, allow_preview, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_sticky {
|
|
||||||
if let Some((_, _, index)) = this.index_for_entry(entry_id, worktree_id) {
|
|
||||||
let strategy = sticky_index
|
|
||||||
.map(ScrollStrategy::ToPosition)
|
|
||||||
.unwrap_or(ScrollStrategy::Top);
|
|
||||||
this.scroll_handle.scroll_to_item(index, strategy);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
@ -5167,52 +5167,51 @@ impl Render for ProjectPanel {
|
||||||
})
|
})
|
||||||
.when(show_indent_guides, |list| {
|
.when(show_indent_guides, |list| {
|
||||||
list.with_decoration(
|
list.with_decoration(
|
||||||
ui::indent_guides(
|
ui::indent_guides(px(indent_size), IndentGuideColors::panel(cx))
|
||||||
cx.entity().clone(),
|
.with_compute_indents_fn(
|
||||||
px(indent_size),
|
cx.entity().clone(),
|
||||||
IndentGuideColors::panel(cx),
|
|this, range, window, cx| {
|
||||||
|this, range, window, cx| {
|
let mut items =
|
||||||
let mut items =
|
SmallVec::with_capacity(range.end - range.start);
|
||||||
SmallVec::with_capacity(range.end - range.start);
|
this.iter_visible_entries(
|
||||||
this.iter_visible_entries(
|
range,
|
||||||
range,
|
window,
|
||||||
window,
|
cx,
|
||||||
cx,
|
|entry, _, entries, _, _| {
|
||||||
|entry, _, entries, _, _| {
|
let (depth, _) =
|
||||||
let (depth, _) = Self::calculate_depth_and_difference(
|
Self::calculate_depth_and_difference(
|
||||||
entry, entries,
|
entry, entries,
|
||||||
);
|
);
|
||||||
items.push(depth);
|
items.push(depth);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
items
|
items
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.on_click(cx.listener(
|
.on_click(cx.listener(
|
||||||
|this, active_indent_guide: &IndentGuideLayout, window, cx| {
|
|this, active_indent_guide: &IndentGuideLayout, window, cx| {
|
||||||
if window.modifiers().secondary() {
|
if window.modifiers().secondary() {
|
||||||
let ix = active_indent_guide.offset.y;
|
let ix = active_indent_guide.offset.y;
|
||||||
let Some((target_entry, worktree)) = maybe!({
|
let Some((target_entry, worktree)) = maybe!({
|
||||||
let (worktree_id, entry) = this.entry_at_index(ix)?;
|
let (worktree_id, entry) =
|
||||||
let worktree = this
|
this.entry_at_index(ix)?;
|
||||||
.project
|
let worktree = this
|
||||||
.read(cx)
|
.project
|
||||||
.worktree_for_id(worktree_id, cx)?;
|
.read(cx)
|
||||||
let target_entry = worktree
|
.worktree_for_id(worktree_id, cx)?;
|
||||||
.read(cx)
|
let target_entry = worktree
|
||||||
.entry_for_path(&entry.path.parent()?)?;
|
.read(cx)
|
||||||
Some((target_entry, worktree))
|
.entry_for_path(&entry.path.parent()?)?;
|
||||||
}) else {
|
Some((target_entry, worktree))
|
||||||
return;
|
}) else {
|
||||||
};
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
this.collapse_entry(target_entry.clone(), worktree, cx);
|
this.collapse_entry(target_entry.clone(), worktree, cx);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.with_render_fn(
|
.with_render_fn(cx.entity().clone(), move |this, params, _, cx| {
|
||||||
cx.entity().clone(),
|
|
||||||
move |this, params, _, cx| {
|
|
||||||
const LEFT_OFFSET: Pixels = px(14.);
|
const LEFT_OFFSET: Pixels = px(14.);
|
||||||
const PADDING_Y: Pixels = px(4.);
|
const PADDING_Y: Pixels = px(4.);
|
||||||
const HITBOX_OVERDRAW: Pixels = px(3.);
|
const HITBOX_OVERDRAW: Pixels = px(3.);
|
||||||
|
@ -5260,12 +5259,11 @@ impl Render for ProjectPanel {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.when(show_sticky_scroll, |list| {
|
.when(show_sticky_scroll, |list| {
|
||||||
list.with_decoration(ui::sticky_items(
|
let sticky_items = ui::sticky_items(
|
||||||
cx.entity().clone(),
|
cx.entity().clone(),
|
||||||
|this, range, window, cx| {
|
|this, range, window, cx| {
|
||||||
let mut items = SmallVec::with_capacity(range.end - range.start);
|
let mut items = SmallVec::with_capacity(range.end - range.start);
|
||||||
|
@ -5286,7 +5284,40 @@ impl Render for ProjectPanel {
|
||||||
|this, marker_entry, window, cx| {
|
|this, marker_entry, window, cx| {
|
||||||
this.render_sticky_entries(marker_entry, window, cx)
|
this.render_sticky_entries(marker_entry, window, cx)
|
||||||
},
|
},
|
||||||
))
|
);
|
||||||
|
list.with_decoration(if show_indent_guides {
|
||||||
|
sticky_items.with_decoration(
|
||||||
|
ui::indent_guides(px(indent_size), IndentGuideColors::panel(cx))
|
||||||
|
.with_render_fn(cx.entity().clone(), move |_, params, _, _| {
|
||||||
|
const LEFT_OFFSET: Pixels = px(14.);
|
||||||
|
|
||||||
|
let indent_size = params.indent_size;
|
||||||
|
let item_height = params.item_height;
|
||||||
|
|
||||||
|
params
|
||||||
|
.indent_guides
|
||||||
|
.into_iter()
|
||||||
|
.map(|layout| {
|
||||||
|
let bounds = Bounds::new(
|
||||||
|
point(
|
||||||
|
layout.offset.x * indent_size + LEFT_OFFSET,
|
||||||
|
layout.offset.y * item_height,
|
||||||
|
),
|
||||||
|
size(px(1.), layout.length * item_height),
|
||||||
|
);
|
||||||
|
ui::RenderedIndentGuide {
|
||||||
|
bounds,
|
||||||
|
layout,
|
||||||
|
is_active: false,
|
||||||
|
hitbox: None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
sticky_items
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.size_full()
|
.size_full()
|
||||||
.with_sizing_behavior(ListSizingBehavior::Infer)
|
.with_sizing_behavior(ListSizingBehavior::Infer)
|
||||||
|
|
|
@ -55,23 +55,27 @@ impl Render for IndentGuidesStory {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.with_sizing_behavior(gpui::ListSizingBehavior::Infer)
|
.with_sizing_behavior(gpui::ListSizingBehavior::Infer)
|
||||||
.with_decoration(ui::indent_guides(
|
.with_decoration(
|
||||||
cx.entity().clone(),
|
ui::indent_guides(
|
||||||
px(16.),
|
px(16.),
|
||||||
ui::IndentGuideColors {
|
ui::IndentGuideColors {
|
||||||
default: Color::Info.color(cx),
|
default: Color::Info.color(cx),
|
||||||
hover: Color::Accent.color(cx),
|
hover: Color::Accent.color(cx),
|
||||||
active: Color::Accent.color(cx),
|
active: Color::Accent.color(cx),
|
||||||
},
|
},
|
||||||
|this, range, _cx, _context| {
|
)
|
||||||
this.depths
|
.with_compute_indents_fn(
|
||||||
.iter()
|
cx.entity().clone(),
|
||||||
.skip(range.start)
|
|this, range, _cx, _context| {
|
||||||
.take(range.end - range.start)
|
this.depths
|
||||||
.cloned()
|
.iter()
|
||||||
.collect()
|
.skip(range.start)
|
||||||
},
|
.take(range.end - range.start)
|
||||||
)),
|
.cloned()
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use std::{cmp::Ordering, ops::Range, rc::Rc};
|
use std::{cmp::Ordering, ops::Range, rc::Rc};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{AnyElement, App, Bounds, Entity, Hsla, Point, fill, point, size};
|
||||||
AnyElement, App, Bounds, Entity, Hsla, Point, UniformListDecoration, fill, point, size,
|
use gpui::{DispatchPhase, Hitbox, HitboxBehavior, MouseButton, MouseDownEvent, MouseMoveEvent};
|
||||||
};
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
@ -32,7 +31,8 @@ impl IndentGuideColors {
|
||||||
pub struct IndentGuides {
|
pub struct IndentGuides {
|
||||||
colors: IndentGuideColors,
|
colors: IndentGuideColors,
|
||||||
indent_size: Pixels,
|
indent_size: Pixels,
|
||||||
compute_indents_fn: Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> SmallVec<[usize; 64]>>,
|
compute_indents_fn:
|
||||||
|
Option<Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> SmallVec<[usize; 64]>>>,
|
||||||
render_fn: Option<
|
render_fn: Option<
|
||||||
Box<
|
Box<
|
||||||
dyn Fn(
|
dyn Fn(
|
||||||
|
@ -45,25 +45,11 @@ pub struct IndentGuides {
|
||||||
on_click: Option<Rc<dyn Fn(&IndentGuideLayout, &mut Window, &mut App)>>,
|
on_click: Option<Rc<dyn Fn(&IndentGuideLayout, &mut Window, &mut App)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn indent_guides<V: Render>(
|
pub fn indent_guides(indent_size: Pixels, colors: IndentGuideColors) -> IndentGuides {
|
||||||
entity: Entity<V>,
|
|
||||||
indent_size: Pixels,
|
|
||||||
colors: IndentGuideColors,
|
|
||||||
compute_indents_fn: impl Fn(
|
|
||||||
&mut V,
|
|
||||||
Range<usize>,
|
|
||||||
&mut Window,
|
|
||||||
&mut Context<V>,
|
|
||||||
) -> SmallVec<[usize; 64]>
|
|
||||||
+ 'static,
|
|
||||||
) -> IndentGuides {
|
|
||||||
let compute_indents_fn = Box::new(move |range, window: &mut Window, cx: &mut App| {
|
|
||||||
entity.update(cx, |this, cx| compute_indents_fn(this, range, window, cx))
|
|
||||||
});
|
|
||||||
IndentGuides {
|
IndentGuides {
|
||||||
colors,
|
colors,
|
||||||
indent_size,
|
indent_size,
|
||||||
compute_indents_fn,
|
compute_indents_fn: None,
|
||||||
render_fn: None,
|
render_fn: None,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
}
|
}
|
||||||
|
@ -79,6 +65,25 @@ impl IndentGuides {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the function that computes indents for uniform list decoration.
|
||||||
|
pub fn with_compute_indents_fn<V: Render>(
|
||||||
|
mut self,
|
||||||
|
entity: Entity<V>,
|
||||||
|
compute_indents_fn: impl Fn(
|
||||||
|
&mut V,
|
||||||
|
Range<usize>,
|
||||||
|
&mut Window,
|
||||||
|
&mut Context<V>,
|
||||||
|
) -> SmallVec<[usize; 64]>
|
||||||
|
+ 'static,
|
||||||
|
) -> Self {
|
||||||
|
let compute_indents_fn = Box::new(move |range, window: &mut Window, cx: &mut App| {
|
||||||
|
entity.update(cx, |this, cx| compute_indents_fn(this, range, window, cx))
|
||||||
|
});
|
||||||
|
self.compute_indents_fn = Some(compute_indents_fn);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets a custom callback that will be called when the indent guides need to be rendered.
|
/// Sets a custom callback that will be called when the indent guides need to be rendered.
|
||||||
pub fn with_render_fn<V: Render>(
|
pub fn with_render_fn<V: Render>(
|
||||||
mut self,
|
mut self,
|
||||||
|
@ -97,6 +102,53 @@ impl IndentGuides {
|
||||||
self.render_fn = Some(Box::new(render_fn));
|
self.render_fn = Some(Box::new(render_fn));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_from_layout(
|
||||||
|
&self,
|
||||||
|
indent_guides: SmallVec<[IndentGuideLayout; 12]>,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
item_height: Pixels,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> AnyElement {
|
||||||
|
let mut indent_guides = if let Some(ref custom_render) = self.render_fn {
|
||||||
|
let params = RenderIndentGuideParams {
|
||||||
|
indent_guides,
|
||||||
|
indent_size: self.indent_size,
|
||||||
|
item_height,
|
||||||
|
};
|
||||||
|
custom_render(params, window, cx)
|
||||||
|
} else {
|
||||||
|
indent_guides
|
||||||
|
.into_iter()
|
||||||
|
.map(|layout| RenderedIndentGuide {
|
||||||
|
bounds: Bounds::new(
|
||||||
|
point(
|
||||||
|
layout.offset.x * self.indent_size,
|
||||||
|
layout.offset.y * item_height,
|
||||||
|
),
|
||||||
|
size(px(1.), layout.length * item_height),
|
||||||
|
),
|
||||||
|
layout,
|
||||||
|
is_active: false,
|
||||||
|
hitbox: None,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
for guide in &mut indent_guides {
|
||||||
|
guide.bounds.origin += bounds.origin;
|
||||||
|
if let Some(hitbox) = guide.hitbox.as_mut() {
|
||||||
|
hitbox.origin += bounds.origin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let indent_guides = IndentGuidesElement {
|
||||||
|
indent_guides: Rc::new(indent_guides),
|
||||||
|
colors: self.colors.clone(),
|
||||||
|
on_hovered_indent_guide_click: self.on_click.clone(),
|
||||||
|
};
|
||||||
|
indent_guides.into_any_element()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parameters for rendering indent guides.
|
/// Parameters for rendering indent guides.
|
||||||
|
@ -136,9 +188,7 @@ pub struct IndentGuideLayout {
|
||||||
|
|
||||||
/// Implements the necessary functionality for rendering indent guides inside a uniform list.
|
/// Implements the necessary functionality for rendering indent guides inside a uniform list.
|
||||||
mod uniform_list {
|
mod uniform_list {
|
||||||
use gpui::{
|
use gpui::UniformListDecoration;
|
||||||
DispatchPhase, Hitbox, HitboxBehavior, MouseButton, MouseDownEvent, MouseMoveEvent,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -161,227 +211,212 @@ mod uniform_list {
|
||||||
if includes_trailing_indent {
|
if includes_trailing_indent {
|
||||||
visible_range.end += 1;
|
visible_range.end += 1;
|
||||||
}
|
}
|
||||||
let visible_entries = &(self.compute_indents_fn)(visible_range.clone(), window, cx);
|
let Some(ref compute_indents_fn) = self.compute_indents_fn else {
|
||||||
|
panic!("compute_indents_fn is required for UniformListDecoration");
|
||||||
|
};
|
||||||
|
let visible_entries = &compute_indents_fn(visible_range.clone(), window, cx);
|
||||||
let indent_guides = compute_indent_guides(
|
let indent_guides = compute_indent_guides(
|
||||||
&visible_entries,
|
&visible_entries,
|
||||||
visible_range.start,
|
visible_range.start,
|
||||||
includes_trailing_indent,
|
includes_trailing_indent,
|
||||||
);
|
);
|
||||||
let mut indent_guides = if let Some(ref custom_render) = self.render_fn {
|
self.render_from_layout(indent_guides, bounds, item_height, window, cx)
|
||||||
let params = RenderIndentGuideParams {
|
|
||||||
indent_guides,
|
|
||||||
indent_size: self.indent_size,
|
|
||||||
item_height,
|
|
||||||
};
|
|
||||||
custom_render(params, window, cx)
|
|
||||||
} else {
|
|
||||||
indent_guides
|
|
||||||
.into_iter()
|
|
||||||
.map(|layout| RenderedIndentGuide {
|
|
||||||
bounds: Bounds::new(
|
|
||||||
point(
|
|
||||||
layout.offset.x * self.indent_size,
|
|
||||||
layout.offset.y * item_height,
|
|
||||||
),
|
|
||||||
size(px(1.), layout.length * item_height),
|
|
||||||
),
|
|
||||||
layout,
|
|
||||||
is_active: false,
|
|
||||||
hitbox: None,
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
};
|
|
||||||
for guide in &mut indent_guides {
|
|
||||||
guide.bounds.origin += bounds.origin;
|
|
||||||
if let Some(hitbox) = guide.hitbox.as_mut() {
|
|
||||||
hitbox.origin += bounds.origin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let indent_guides = IndentGuidesElement {
|
|
||||||
indent_guides: Rc::new(indent_guides),
|
|
||||||
colors: self.colors.clone(),
|
|
||||||
on_hovered_indent_guide_click: self.on_click.clone(),
|
|
||||||
};
|
|
||||||
indent_guides.into_any_element()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct IndentGuidesElement {
|
/// Implements the necessary functionality for rendering indent guides inside a sticky items.
|
||||||
colors: IndentGuideColors,
|
mod sticky_items {
|
||||||
indent_guides: Rc<SmallVec<[RenderedIndentGuide; 12]>>,
|
use crate::StickyItemsDecoration;
|
||||||
on_hovered_indent_guide_click:
|
|
||||||
Option<Rc<dyn Fn(&IndentGuideLayout, &mut Window, &mut App)>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum IndentGuidesElementPrepaintState {
|
use super::*;
|
||||||
Static,
|
|
||||||
Interactive {
|
|
||||||
hitboxes: Rc<SmallVec<[Hitbox; 12]>>,
|
|
||||||
on_hovered_indent_guide_click: Rc<dyn Fn(&IndentGuideLayout, &mut Window, &mut App)>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Element for IndentGuidesElement {
|
impl StickyItemsDecoration for IndentGuides {
|
||||||
type RequestLayoutState = ();
|
fn compute(
|
||||||
type PrepaintState = IndentGuidesElementPrepaintState;
|
&self,
|
||||||
|
indents: &SmallVec<[usize; 8]>,
|
||||||
fn id(&self) -> Option<ElementId> {
|
bounds: Bounds<Pixels>,
|
||||||
None
|
_scroll_offset: Point<Pixels>,
|
||||||
}
|
item_height: Pixels,
|
||||||
|
|
||||||
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_layout(
|
|
||||||
&mut self,
|
|
||||||
_id: Option<&gpui::GlobalElementId>,
|
|
||||||
_inspector_id: Option<&gpui::InspectorElementId>,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
) -> AnyElement {
|
||||||
(window.request_layout(gpui::Style::default(), [], cx), ())
|
let indent_guides = compute_indent_guides(&indents, 0, false);
|
||||||
|
self.render_from_layout(indent_guides, bounds, item_height, window, cx)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn prepaint(
|
struct IndentGuidesElement {
|
||||||
&mut self,
|
colors: IndentGuideColors,
|
||||||
_id: Option<&gpui::GlobalElementId>,
|
indent_guides: Rc<SmallVec<[RenderedIndentGuide; 12]>>,
|
||||||
_inspector_id: Option<&gpui::InspectorElementId>,
|
on_hovered_indent_guide_click: Option<Rc<dyn Fn(&IndentGuideLayout, &mut Window, &mut App)>>,
|
||||||
_bounds: Bounds<Pixels>,
|
}
|
||||||
_request_layout: &mut Self::RequestLayoutState,
|
|
||||||
window: &mut Window,
|
enum IndentGuidesElementPrepaintState {
|
||||||
_cx: &mut App,
|
Static,
|
||||||
) -> Self::PrepaintState {
|
Interactive {
|
||||||
if let Some(on_hovered_indent_guide_click) = self.on_hovered_indent_guide_click.clone()
|
hitboxes: Rc<SmallVec<[Hitbox; 12]>>,
|
||||||
{
|
on_hovered_indent_guide_click: Rc<dyn Fn(&IndentGuideLayout, &mut Window, &mut App)>,
|
||||||
let hitboxes = self
|
},
|
||||||
.indent_guides
|
}
|
||||||
.as_ref()
|
|
||||||
.iter()
|
impl Element for IndentGuidesElement {
|
||||||
.map(|guide| {
|
type RequestLayoutState = ();
|
||||||
window.insert_hitbox(
|
type PrepaintState = IndentGuidesElementPrepaintState;
|
||||||
guide.hitbox.unwrap_or(guide.bounds),
|
|
||||||
HitboxBehavior::Normal,
|
fn id(&self) -> Option<ElementId> {
|
||||||
)
|
None
|
||||||
})
|
}
|
||||||
.collect();
|
|
||||||
Self::PrepaintState::Interactive {
|
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
|
||||||
hitboxes: Rc::new(hitboxes),
|
None
|
||||||
on_hovered_indent_guide_click,
|
}
|
||||||
}
|
|
||||||
} else {
|
fn request_layout(
|
||||||
Self::PrepaintState::Static
|
&mut self,
|
||||||
|
_id: Option<&gpui::GlobalElementId>,
|
||||||
|
_inspector_id: Option<&gpui::InspectorElementId>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||||
|
(window.request_layout(gpui::Style::default(), [], cx), ())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepaint(
|
||||||
|
&mut self,
|
||||||
|
_id: Option<&gpui::GlobalElementId>,
|
||||||
|
_inspector_id: Option<&gpui::InspectorElementId>,
|
||||||
|
_bounds: Bounds<Pixels>,
|
||||||
|
_request_layout: &mut Self::RequestLayoutState,
|
||||||
|
window: &mut Window,
|
||||||
|
_cx: &mut App,
|
||||||
|
) -> Self::PrepaintState {
|
||||||
|
if let Some(on_hovered_indent_guide_click) = self.on_hovered_indent_guide_click.clone() {
|
||||||
|
let hitboxes = self
|
||||||
|
.indent_guides
|
||||||
|
.as_ref()
|
||||||
|
.iter()
|
||||||
|
.map(|guide| {
|
||||||
|
window
|
||||||
|
.insert_hitbox(guide.hitbox.unwrap_or(guide.bounds), HitboxBehavior::Normal)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Self::PrepaintState::Interactive {
|
||||||
|
hitboxes: Rc::new(hitboxes),
|
||||||
|
on_hovered_indent_guide_click,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Self::PrepaintState::Static
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn paint(
|
fn paint(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&gpui::GlobalElementId>,
|
_id: Option<&gpui::GlobalElementId>,
|
||||||
_inspector_id: Option<&gpui::InspectorElementId>,
|
_inspector_id: Option<&gpui::InspectorElementId>,
|
||||||
_bounds: Bounds<Pixels>,
|
_bounds: Bounds<Pixels>,
|
||||||
_request_layout: &mut Self::RequestLayoutState,
|
_request_layout: &mut Self::RequestLayoutState,
|
||||||
prepaint: &mut Self::PrepaintState,
|
prepaint: &mut Self::PrepaintState,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
_cx: &mut App,
|
_cx: &mut App,
|
||||||
) {
|
) {
|
||||||
let current_view = window.current_view();
|
let current_view = window.current_view();
|
||||||
|
|
||||||
match prepaint {
|
match prepaint {
|
||||||
IndentGuidesElementPrepaintState::Static => {
|
IndentGuidesElementPrepaintState::Static => {
|
||||||
for indent_guide in self.indent_guides.as_ref() {
|
for indent_guide in self.indent_guides.as_ref() {
|
||||||
let fill_color = if indent_guide.is_active {
|
let fill_color = if indent_guide.is_active {
|
||||||
self.colors.active
|
self.colors.active
|
||||||
} else {
|
} else {
|
||||||
self.colors.default
|
self.colors.default
|
||||||
};
|
};
|
||||||
|
|
||||||
window.paint_quad(fill(indent_guide.bounds, fill_color));
|
window.paint_quad(fill(indent_guide.bounds, fill_color));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
IndentGuidesElementPrepaintState::Interactive {
|
}
|
||||||
hitboxes,
|
IndentGuidesElementPrepaintState::Interactive {
|
||||||
on_hovered_indent_guide_click,
|
hitboxes,
|
||||||
} => {
|
on_hovered_indent_guide_click,
|
||||||
window.on_mouse_event({
|
} => {
|
||||||
let hitboxes = hitboxes.clone();
|
window.on_mouse_event({
|
||||||
let indent_guides = self.indent_guides.clone();
|
let hitboxes = hitboxes.clone();
|
||||||
let on_hovered_indent_guide_click = on_hovered_indent_guide_click.clone();
|
let indent_guides = self.indent_guides.clone();
|
||||||
move |event: &MouseDownEvent, phase, window, cx| {
|
let on_hovered_indent_guide_click = on_hovered_indent_guide_click.clone();
|
||||||
if phase == DispatchPhase::Bubble && event.button == MouseButton::Left {
|
move |event: &MouseDownEvent, phase, window, cx| {
|
||||||
let mut active_hitbox_ix = None;
|
if phase == DispatchPhase::Bubble && event.button == MouseButton::Left {
|
||||||
for (i, hitbox) in hitboxes.iter().enumerate() {
|
let mut active_hitbox_ix = None;
|
||||||
if hitbox.is_hovered(window) {
|
for (i, hitbox) in hitboxes.iter().enumerate() {
|
||||||
active_hitbox_ix = Some(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(active_hitbox_ix) = active_hitbox_ix else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let active_indent_guide = &indent_guides[active_hitbox_ix].layout;
|
|
||||||
on_hovered_indent_guide_click(active_indent_guide, window, cx);
|
|
||||||
|
|
||||||
cx.stop_propagation();
|
|
||||||
window.prevent_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let mut hovered_hitbox_id = None;
|
|
||||||
for (i, hitbox) in hitboxes.iter().enumerate() {
|
|
||||||
window.set_cursor_style(gpui::CursorStyle::PointingHand, hitbox);
|
|
||||||
let indent_guide = &self.indent_guides[i];
|
|
||||||
let fill_color = if hitbox.is_hovered(window) {
|
|
||||||
hovered_hitbox_id = Some(hitbox.id);
|
|
||||||
self.colors.hover
|
|
||||||
} else if indent_guide.is_active {
|
|
||||||
self.colors.active
|
|
||||||
} else {
|
|
||||||
self.colors.default
|
|
||||||
};
|
|
||||||
|
|
||||||
window.paint_quad(fill(indent_guide.bounds, fill_color));
|
|
||||||
}
|
|
||||||
|
|
||||||
window.on_mouse_event({
|
|
||||||
let prev_hovered_hitbox_id = hovered_hitbox_id;
|
|
||||||
let hitboxes = hitboxes.clone();
|
|
||||||
move |_: &MouseMoveEvent, phase, window, cx| {
|
|
||||||
let mut hovered_hitbox_id = None;
|
|
||||||
for hitbox in hitboxes.as_ref() {
|
|
||||||
if hitbox.is_hovered(window) {
|
if hitbox.is_hovered(window) {
|
||||||
hovered_hitbox_id = Some(hitbox.id);
|
active_hitbox_ix = Some(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if phase == DispatchPhase::Capture {
|
|
||||||
// If the hovered hitbox has changed, we need to re-paint the indent guides.
|
let Some(active_hitbox_ix) = active_hitbox_ix else {
|
||||||
match (prev_hovered_hitbox_id, hovered_hitbox_id) {
|
return;
|
||||||
(Some(prev_id), Some(id)) => {
|
};
|
||||||
if prev_id != id {
|
|
||||||
cx.notify(current_view)
|
let active_indent_guide = &indent_guides[active_hitbox_ix].layout;
|
||||||
}
|
on_hovered_indent_guide_click(active_indent_guide, window, cx);
|
||||||
}
|
|
||||||
(None, Some(_)) => cx.notify(current_view),
|
cx.stop_propagation();
|
||||||
(Some(_), None) => cx.notify(current_view),
|
window.prevent_default();
|
||||||
(None, None) => {}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
let mut hovered_hitbox_id = None;
|
||||||
|
for (i, hitbox) in hitboxes.iter().enumerate() {
|
||||||
|
window.set_cursor_style(gpui::CursorStyle::PointingHand, hitbox);
|
||||||
|
let indent_guide = &self.indent_guides[i];
|
||||||
|
let fill_color = if hitbox.is_hovered(window) {
|
||||||
|
hovered_hitbox_id = Some(hitbox.id);
|
||||||
|
self.colors.hover
|
||||||
|
} else if indent_guide.is_active {
|
||||||
|
self.colors.active
|
||||||
|
} else {
|
||||||
|
self.colors.default
|
||||||
|
};
|
||||||
|
|
||||||
|
window.paint_quad(fill(indent_guide.bounds, fill_color));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.on_mouse_event({
|
||||||
|
let prev_hovered_hitbox_id = hovered_hitbox_id;
|
||||||
|
let hitboxes = hitboxes.clone();
|
||||||
|
move |_: &MouseMoveEvent, phase, window, cx| {
|
||||||
|
let mut hovered_hitbox_id = None;
|
||||||
|
for hitbox in hitboxes.as_ref() {
|
||||||
|
if hitbox.is_hovered(window) {
|
||||||
|
hovered_hitbox_id = Some(hitbox.id);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
if phase == DispatchPhase::Capture {
|
||||||
}
|
// If the hovered hitbox has changed, we need to re-paint the indent guides.
|
||||||
|
match (prev_hovered_hitbox_id, hovered_hitbox_id) {
|
||||||
|
(Some(prev_id), Some(id)) => {
|
||||||
|
if prev_id != id {
|
||||||
|
cx.notify(current_view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, Some(_)) => cx.notify(current_view),
|
||||||
|
(Some(_), None) => cx.notify(current_view),
|
||||||
|
(None, None) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoElement for IndentGuidesElement {
|
impl IntoElement for IndentGuidesElement {
|
||||||
type Element = Self;
|
type Element = Self;
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
fn into_element(self) -> Self::Element {
|
||||||
self
|
self
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::{ops::Range, rc::Rc};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, AvailableSpace, Bounds, Context, Element, ElementId, Entity, GlobalElementId,
|
AnyElement, App, AvailableSpace, Bounds, Context, Element, ElementId, Entity, GlobalElementId,
|
||||||
InspectorElementId, IntoElement, LayoutId, Pixels, Point, Render, Style, UniformListDecoration,
|
InspectorElementId, IntoElement, LayoutId, Pixels, Point, Render, Style, UniformListDecoration,
|
||||||
Window, point, size,
|
Window, point, px, size,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
@ -11,10 +11,10 @@ pub trait StickyCandidate {
|
||||||
fn depth(&self) -> usize;
|
fn depth(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct StickyItems<T> {
|
pub struct StickyItems<T> {
|
||||||
compute_fn: Rc<dyn Fn(Range<usize>, &mut Window, &mut App) -> SmallVec<[T; 8]>>,
|
compute_fn: Rc<dyn Fn(Range<usize>, &mut Window, &mut App) -> SmallVec<[T; 8]>>,
|
||||||
render_fn: Rc<dyn Fn(T, &mut Window, &mut App) -> SmallVec<[AnyElement; 8]>>,
|
render_fn: Rc<dyn Fn(T, &mut Window, &mut App) -> SmallVec<[AnyElement; 8]>>,
|
||||||
|
decorations: Vec<Box<dyn StickyItemsDecoration>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sticky_items<V, T>(
|
pub fn sticky_items<V, T>(
|
||||||
|
@ -44,11 +44,26 @@ where
|
||||||
StickyItems {
|
StickyItems {
|
||||||
compute_fn,
|
compute_fn,
|
||||||
render_fn,
|
render_fn,
|
||||||
|
decorations: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> StickyItems<T>
|
||||||
|
where
|
||||||
|
T: StickyCandidate + Clone + 'static,
|
||||||
|
{
|
||||||
|
/// Adds a decoration element to the sticky items.
|
||||||
|
pub fn with_decoration(mut self, decoration: impl StickyItemsDecoration + 'static) -> Self {
|
||||||
|
self.decorations.push(Box::new(decoration));
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StickyItemsElement {
|
struct StickyItemsElement {
|
||||||
elements: SmallVec<[AnyElement; 8]>,
|
drifting_element: Option<AnyElement>,
|
||||||
|
drifting_decoration: Option<AnyElement>,
|
||||||
|
rest_elements: SmallVec<[AnyElement; 8]>,
|
||||||
|
rest_decorations: SmallVec<[AnyElement; 1]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoElement for StickyItemsElement {
|
impl IntoElement for StickyItemsElement {
|
||||||
|
@ -103,8 +118,16 @@ impl Element for StickyItemsElement {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
// reverse so that last item is bottom most among sticky items
|
if let Some(ref mut drifting_element) = self.drifting_element {
|
||||||
for item in self.elements.iter_mut().rev() {
|
drifting_element.paint(window, cx);
|
||||||
|
}
|
||||||
|
if let Some(ref mut drifting_decoration) = self.drifting_decoration {
|
||||||
|
drifting_decoration.paint(window, cx);
|
||||||
|
}
|
||||||
|
for item in self.rest_elements.iter_mut().rev() {
|
||||||
|
item.paint(window, cx);
|
||||||
|
}
|
||||||
|
for item in self.rest_decorations.iter_mut() {
|
||||||
item.paint(window, cx);
|
item.paint(window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,11 +148,14 @@ where
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let entries = (self.compute_fn)(visible_range.clone(), window, cx);
|
let entries = (self.compute_fn)(visible_range.clone(), window, cx);
|
||||||
let mut elements = SmallVec::new();
|
|
||||||
|
|
||||||
let mut anchor_entry = None;
|
struct StickyAnchor<T> {
|
||||||
|
entry: T,
|
||||||
|
index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sticky_anchor = None;
|
||||||
let mut last_item_is_drifting = false;
|
let mut last_item_is_drifting = false;
|
||||||
let mut anchor_index = None;
|
|
||||||
|
|
||||||
let mut iter = entries.iter().enumerate().peekable();
|
let mut iter = entries.iter().enumerate().peekable();
|
||||||
while let Some((ix, current_entry)) = iter.next() {
|
while let Some((ix, current_entry)) = iter.next() {
|
||||||
|
@ -137,7 +163,10 @@ where
|
||||||
let index_in_range = ix;
|
let index_in_range = ix;
|
||||||
|
|
||||||
if current_depth < index_in_range {
|
if current_depth < index_in_range {
|
||||||
anchor_entry = Some(current_entry.clone());
|
sticky_anchor = Some(StickyAnchor {
|
||||||
|
entry: current_entry.clone(),
|
||||||
|
index: visible_range.start + ix,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,44 +175,155 @@ where
|
||||||
|
|
||||||
if next_depth < current_depth && next_depth < index_in_range {
|
if next_depth < current_depth && next_depth < index_in_range {
|
||||||
last_item_is_drifting = true;
|
last_item_is_drifting = true;
|
||||||
anchor_index = Some(visible_range.start + ix);
|
sticky_anchor = Some(StickyAnchor {
|
||||||
anchor_entry = Some(current_entry.clone());
|
entry: current_entry.clone(),
|
||||||
|
index: visible_range.start + ix,
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(anchor_entry) = anchor_entry {
|
let Some(sticky_anchor) = sticky_anchor else {
|
||||||
elements = (self.render_fn)(anchor_entry, window, cx);
|
return StickyItemsElement {
|
||||||
let items_count = elements.len();
|
drifting_element: None,
|
||||||
|
drifting_decoration: None,
|
||||||
|
rest_elements: SmallVec::new(),
|
||||||
|
rest_decorations: SmallVec::new(),
|
||||||
|
}
|
||||||
|
.into_any_element();
|
||||||
|
};
|
||||||
|
|
||||||
for (ix, element) in elements.iter_mut().enumerate() {
|
let anchor_depth = sticky_anchor.entry.depth();
|
||||||
let mut item_y_offset = None;
|
let mut elements = (self.render_fn)(sticky_anchor.entry, window, cx);
|
||||||
if ix == items_count - 1 && last_item_is_drifting {
|
let items_count = elements.len();
|
||||||
if let Some(anchor_index) = anchor_index {
|
|
||||||
let scroll_top = -scroll_offset.y;
|
|
||||||
let anchor_top = item_height * anchor_index;
|
|
||||||
let sticky_area_height = item_height * items_count;
|
|
||||||
item_y_offset =
|
|
||||||
Some((anchor_top - scroll_top - sticky_area_height).min(Pixels::ZERO));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let sticky_origin = bounds.origin
|
let indents: SmallVec<[usize; 8]> = {
|
||||||
+ point(
|
elements
|
||||||
-scroll_offset.x,
|
.iter()
|
||||||
-scroll_offset.y + item_height * ix + item_y_offset.unwrap_or(Pixels::ZERO),
|
.enumerate()
|
||||||
);
|
.map(|(ix, _)| anchor_depth.saturating_sub(items_count.saturating_sub(ix)))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
let available_space = size(
|
let mut last_decoration_element = None;
|
||||||
AvailableSpace::Definite(bounds.size.width),
|
let mut rest_decoration_elements = SmallVec::new();
|
||||||
AvailableSpace::Definite(item_height),
|
|
||||||
|
let available_space = size(
|
||||||
|
AvailableSpace::Definite(bounds.size.width),
|
||||||
|
AvailableSpace::Definite(bounds.size.height),
|
||||||
|
);
|
||||||
|
|
||||||
|
let drifting_y_offset = if last_item_is_drifting {
|
||||||
|
let scroll_top = -scroll_offset.y;
|
||||||
|
let anchor_top = item_height * sticky_anchor.index;
|
||||||
|
let sticky_area_height = item_height * items_count;
|
||||||
|
(anchor_top - scroll_top - sticky_area_height).min(Pixels::ZERO)
|
||||||
|
} else {
|
||||||
|
Pixels::ZERO
|
||||||
|
};
|
||||||
|
|
||||||
|
let (drifting_indent, rest_indents) = if last_item_is_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)
|
||||||
|
} else {
|
||||||
|
(None, indents)
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
+ point(px(0.), item_height * rest_indents.len() + drifting_y_offset);
|
||||||
|
let decoration_bounds = Bounds::new(sticky_origin, bounds.size);
|
||||||
|
|
||||||
|
let mut drifting_dec = decoration.as_ref().compute(
|
||||||
|
&drifting_indent_vec,
|
||||||
|
decoration_bounds,
|
||||||
|
scroll_offset,
|
||||||
|
item_height,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
);
|
);
|
||||||
element.layout_as_root(available_space, window, cx);
|
drifting_dec.layout_as_root(available_space, window, cx);
|
||||||
element.prepaint_at(sticky_origin, 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 mut rest_dec = decoration.as_ref().compute(
|
||||||
|
&rest_indents,
|
||||||
|
decoration_bounds,
|
||||||
|
scroll_offset,
|
||||||
|
item_height,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
rest_dec.layout_as_root(available_space, window, cx);
|
||||||
|
rest_dec.prepaint_at(bounds.origin, window, cx);
|
||||||
|
rest_decoration_elements.push(rest_dec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StickyItemsElement { elements }.into_any_element()
|
let (mut drifting_element, mut rest_elements) =
|
||||||
|
if last_item_is_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref mut drifting_element) = drifting_element {
|
||||||
|
let sticky_origin = bounds.origin - scroll_offset
|
||||||
|
+ 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
StickyItemsElement {
|
||||||
|
drifting_element,
|
||||||
|
drifting_decoration: last_decoration_element,
|
||||||
|
rest_elements,
|
||||||
|
rest_decorations: rest_decoration_elements,
|
||||||
|
}
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A decoration for a [`StickyItems`]. This can be used for various things,
|
||||||
|
/// such as rendering indent guides, or other visual effects.
|
||||||
|
pub trait StickyItemsDecoration {
|
||||||
|
/// Compute the decoration element, given the visible range of list items,
|
||||||
|
/// the bounds of the list, and the height of each item.
|
||||||
|
fn compute(
|
||||||
|
&self,
|
||||||
|
indents: &SmallVec<[usize; 8]>,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
scroll_offset: Point<Pixels>,
|
||||||
|
item_height: Pixels,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> AnyElement;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue