add gpui skills
This commit is contained in:
705
.agents/skills/gpui-element/references/advanced-patterns.md
Normal file
705
.agents/skills/gpui-element/references/advanced-patterns.md
Normal file
@@ -0,0 +1,705 @@
|
||||
# Advanced Element Patterns
|
||||
|
||||
Advanced techniques and patterns for implementing sophisticated GPUI elements.
|
||||
|
||||
## Custom Layout Algorithms
|
||||
|
||||
Implementing custom layout algorithms not supported by GPUI's built-in layouts.
|
||||
|
||||
### Masonry Layout (Pinterest-Style)
|
||||
|
||||
```rust
|
||||
pub struct MasonryLayout {
|
||||
id: ElementId,
|
||||
columns: usize,
|
||||
gap: Pixels,
|
||||
children: Vec<AnyElement>,
|
||||
}
|
||||
|
||||
struct MasonryLayoutState {
|
||||
column_layouts: Vec<Vec<LayoutId>>,
|
||||
column_heights: Vec<Pixels>,
|
||||
}
|
||||
|
||||
struct MasonryPaintState {
|
||||
child_bounds: Vec<Bounds<Pixels>>,
|
||||
}
|
||||
|
||||
impl Element for MasonryLayout {
|
||||
type RequestLayoutState = MasonryLayoutState;
|
||||
type PrepaintState = MasonryPaintState;
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) -> (LayoutId, MasonryLayoutState) {
|
||||
// Initialize columns
|
||||
let mut columns: Vec<Vec<LayoutId>> = vec![Vec::new(); self.columns];
|
||||
let mut column_heights = vec![px(0.); self.columns];
|
||||
|
||||
// Distribute children across columns
|
||||
for child in &mut self.children {
|
||||
let (child_layout_id, _) = child.request_layout(
|
||||
global_id,
|
||||
inspector_id,
|
||||
window,
|
||||
cx
|
||||
);
|
||||
|
||||
let child_size = window.layout_bounds(child_layout_id).size;
|
||||
|
||||
// Find shortest column
|
||||
let min_column_idx = column_heights
|
||||
.iter()
|
||||
.enumerate()
|
||||
.min_by(|a, b| a.1.partial_cmp(b.1).unwrap())
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
// Add child to shortest column
|
||||
columns[min_column_idx].push(child_layout_id);
|
||||
column_heights[min_column_idx] += child_size.height + self.gap;
|
||||
}
|
||||
|
||||
// Calculate total layout size
|
||||
let column_width = px(200.); // Fixed column width
|
||||
let total_width = column_width * self.columns as f32
|
||||
+ self.gap * (self.columns - 1) as f32;
|
||||
let total_height = column_heights.iter()
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap())
|
||||
.copied()
|
||||
.unwrap_or(px(0.));
|
||||
|
||||
let layout_id = window.request_layout(
|
||||
Style {
|
||||
size: size(total_width, total_height),
|
||||
..default()
|
||||
},
|
||||
columns.iter().flatten().copied().collect(),
|
||||
cx
|
||||
);
|
||||
|
||||
(layout_id, MasonryLayoutState {
|
||||
column_layouts: columns,
|
||||
column_heights,
|
||||
})
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
layout_state: &mut MasonryLayoutState,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) -> MasonryPaintState {
|
||||
let column_width = px(200.);
|
||||
let mut child_bounds = Vec::new();
|
||||
|
||||
// Position children in columns
|
||||
for (col_idx, column) in layout_state.column_layouts.iter().enumerate() {
|
||||
let x_offset = bounds.left()
|
||||
+ (column_width + self.gap) * col_idx as f32;
|
||||
let mut y_offset = bounds.top();
|
||||
|
||||
for (child_idx, layout_id) in column.iter().enumerate() {
|
||||
let child_size = window.layout_bounds(*layout_id).size;
|
||||
let child_bound = Bounds::new(
|
||||
point(x_offset, y_offset),
|
||||
size(column_width, child_size.height)
|
||||
);
|
||||
|
||||
self.children[child_idx].prepaint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
child_bound,
|
||||
window,
|
||||
cx
|
||||
);
|
||||
|
||||
child_bounds.push(child_bound);
|
||||
y_offset += child_size.height + self.gap;
|
||||
}
|
||||
}
|
||||
|
||||
MasonryPaintState { child_bounds }
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_layout_state: &mut MasonryLayoutState,
|
||||
paint_state: &mut MasonryPaintState,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) {
|
||||
for (child, bounds) in self.children.iter_mut().zip(&paint_state.child_bounds) {
|
||||
child.paint(global_id, inspector_id, *bounds, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Circular Layout
|
||||
|
||||
```rust
|
||||
pub struct CircularLayout {
|
||||
id: ElementId,
|
||||
radius: Pixels,
|
||||
children: Vec<AnyElement>,
|
||||
}
|
||||
|
||||
impl Element for CircularLayout {
|
||||
type RequestLayoutState = Vec<LayoutId>;
|
||||
type PrepaintState = Vec<Bounds<Pixels>>;
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) -> (LayoutId, Vec<LayoutId>) {
|
||||
let child_layouts: Vec<_> = self.children
|
||||
.iter_mut()
|
||||
.map(|child| child.request_layout(global_id, inspector_id, window, cx).0)
|
||||
.collect();
|
||||
|
||||
let diameter = self.radius * 2.;
|
||||
let layout_id = window.request_layout(
|
||||
Style {
|
||||
size: size(diameter, diameter),
|
||||
..default()
|
||||
},
|
||||
child_layouts.clone(),
|
||||
cx
|
||||
);
|
||||
|
||||
(layout_id, child_layouts)
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
layout_ids: &mut Vec<LayoutId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) -> Vec<Bounds<Pixels>> {
|
||||
let center = bounds.center();
|
||||
let angle_step = 2.0 * std::f32::consts::PI / self.children.len() as f32;
|
||||
|
||||
let mut child_bounds = Vec::new();
|
||||
|
||||
for (i, (child, layout_id)) in self.children.iter_mut()
|
||||
.zip(layout_ids.iter())
|
||||
.enumerate()
|
||||
{
|
||||
let angle = angle_step * i as f32;
|
||||
let child_size = window.layout_bounds(*layout_id).size;
|
||||
|
||||
// Position child on circle
|
||||
let x = center.x + self.radius * angle.cos() - child_size.width / 2.;
|
||||
let y = center.y + self.radius * angle.sin() - child_size.height / 2.;
|
||||
|
||||
let child_bound = Bounds::new(point(x, y), child_size);
|
||||
|
||||
child.prepaint(global_id, inspector_id, child_bound, window, cx);
|
||||
child_bounds.push(child_bound);
|
||||
}
|
||||
|
||||
child_bounds
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
_bounds: Bounds<Pixels>,
|
||||
_layout_ids: &mut Vec<LayoutId>,
|
||||
child_bounds: &mut Vec<Bounds<Pixels>>,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) {
|
||||
for (child, bounds) in self.children.iter_mut().zip(child_bounds) {
|
||||
child.paint(global_id, inspector_id, *bounds, window, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Element Composition with Traits
|
||||
|
||||
Create reusable behaviors via traits for element composition.
|
||||
|
||||
### Hoverable Trait
|
||||
|
||||
```rust
|
||||
pub trait Hoverable: Element {
|
||||
fn on_hover<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&mut Window, &mut App) + 'static;
|
||||
|
||||
fn on_hover_end<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&mut Window, &mut App) + 'static;
|
||||
}
|
||||
|
||||
// Implementation for custom element
|
||||
pub struct HoverableElement {
|
||||
id: ElementId,
|
||||
content: AnyElement,
|
||||
hover_handlers: Vec<Box<dyn Fn(&mut Window, &mut App)>>,
|
||||
hover_end_handlers: Vec<Box<dyn Fn(&mut Window, &mut App)>>,
|
||||
was_hovered: bool,
|
||||
}
|
||||
|
||||
impl Hoverable for HoverableElement {
|
||||
fn on_hover<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&mut Window, &mut App) + 'static
|
||||
{
|
||||
self.hover_handlers.push(Box::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
fn on_hover_end<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&mut Window, &mut App) + 'static
|
||||
{
|
||||
self.hover_end_handlers.push(Box::new(f));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for HoverableElement {
|
||||
type RequestLayoutState = LayoutId;
|
||||
type PrepaintState = Hitbox;
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_global_id: Option<&GlobalElementId>,
|
||||
_inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_layout: &mut LayoutId,
|
||||
hitbox: &mut Hitbox,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) {
|
||||
let is_hovered = hitbox.is_hovered(window);
|
||||
|
||||
// Trigger hover events
|
||||
if is_hovered && !self.was_hovered {
|
||||
for handler in &self.hover_handlers {
|
||||
handler(window, cx);
|
||||
}
|
||||
} else if !is_hovered && self.was_hovered {
|
||||
for handler in &self.hover_end_handlers {
|
||||
handler(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
self.was_hovered = is_hovered;
|
||||
|
||||
// Paint content
|
||||
self.content.paint(bounds, window, cx);
|
||||
}
|
||||
|
||||
// ... other methods
|
||||
}
|
||||
```
|
||||
|
||||
### Clickable Trait
|
||||
|
||||
```rust
|
||||
pub trait Clickable: Element {
|
||||
fn on_click<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&MouseUpEvent, &mut Window, &mut App) + 'static;
|
||||
|
||||
fn on_double_click<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&MouseUpEvent, &mut Window, &mut App) + 'static;
|
||||
}
|
||||
|
||||
pub struct ClickableElement {
|
||||
id: ElementId,
|
||||
content: AnyElement,
|
||||
click_handlers: Vec<Box<dyn Fn(&MouseUpEvent, &mut Window, &mut App)>>,
|
||||
double_click_handlers: Vec<Box<dyn Fn(&MouseUpEvent, &mut Window, &mut App)>>,
|
||||
last_click_time: Option<Instant>,
|
||||
}
|
||||
|
||||
impl Clickable for ClickableElement {
|
||||
fn on_click<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&MouseUpEvent, &mut Window, &mut App) + 'static
|
||||
{
|
||||
self.click_handlers.push(Box::new(f));
|
||||
self
|
||||
}
|
||||
|
||||
fn on_double_click<F>(&mut self, f: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&MouseUpEvent, &mut Window, &mut App) + 'static
|
||||
{
|
||||
self.double_click_handlers.push(Box::new(f));
|
||||
self
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Async Element Updates
|
||||
|
||||
Elements that update based on async operations.
|
||||
|
||||
```rust
|
||||
pub struct AsyncElement {
|
||||
id: ElementId,
|
||||
state: Entity<AsyncState>,
|
||||
loading: bool,
|
||||
data: Option<String>,
|
||||
}
|
||||
|
||||
pub struct AsyncState {
|
||||
loading: bool,
|
||||
data: Option<String>,
|
||||
}
|
||||
|
||||
impl Element for AsyncElement {
|
||||
type RequestLayoutState = ();
|
||||
type PrepaintState = Hitbox;
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_global_id: Option<&GlobalElementId>,
|
||||
_inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_layout: &mut (),
|
||||
hitbox: &mut Hitbox,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) {
|
||||
// Display loading or data
|
||||
if self.loading {
|
||||
// Paint loading indicator
|
||||
self.paint_loading(bounds, window, cx);
|
||||
} else if let Some(data) = &self.data {
|
||||
// Paint data
|
||||
self.paint_data(data, bounds, window, cx);
|
||||
}
|
||||
|
||||
// Trigger async update on click
|
||||
window.on_mouse_event({
|
||||
let state = self.state.clone();
|
||||
let hitbox = hitbox.clone();
|
||||
|
||||
move |event: &MouseUpEvent, phase, window, cx| {
|
||||
if hitbox.is_hovered(window) && phase.bubble() {
|
||||
// Spawn async task
|
||||
cx.spawn({
|
||||
let state = state.clone();
|
||||
async move {
|
||||
// Perform async operation
|
||||
let result = fetch_data_async().await;
|
||||
|
||||
// Update state on completion
|
||||
state.update(cx, |state, cx| {
|
||||
state.loading = false;
|
||||
state.data = Some(result);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
}).detach();
|
||||
|
||||
// Set loading state immediately
|
||||
state.update(cx, |state, cx| {
|
||||
state.loading = true;
|
||||
cx.notify();
|
||||
});
|
||||
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ... other methods
|
||||
}
|
||||
|
||||
async fn fetch_data_async() -> String {
|
||||
// Simulate async operation
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
"Data loaded!".to_string()
|
||||
}
|
||||
```
|
||||
|
||||
## Element Memoization
|
||||
|
||||
Optimize performance by memoizing expensive element computations.
|
||||
|
||||
```rust
|
||||
pub struct MemoizedElement<T: PartialEq + Clone + 'static> {
|
||||
id: ElementId,
|
||||
value: T,
|
||||
render_fn: Box<dyn Fn(&T) -> AnyElement>,
|
||||
cached_element: Option<AnyElement>,
|
||||
last_value: Option<T>,
|
||||
}
|
||||
|
||||
impl<T: PartialEq + Clone + 'static> MemoizedElement<T> {
|
||||
pub fn new<F>(id: ElementId, value: T, render_fn: F) -> Self
|
||||
where
|
||||
F: Fn(&T) -> AnyElement + 'static,
|
||||
{
|
||||
Self {
|
||||
id,
|
||||
value,
|
||||
render_fn: Box::new(render_fn),
|
||||
cached_element: None,
|
||||
last_value: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq + Clone + 'static> Element for MemoizedElement<T> {
|
||||
type RequestLayoutState = LayoutId;
|
||||
type PrepaintState = ();
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) -> (LayoutId, LayoutId) {
|
||||
// Check if value changed
|
||||
if self.last_value.as_ref() != Some(&self.value) || self.cached_element.is_none() {
|
||||
// Recompute element
|
||||
self.cached_element = Some((self.render_fn)(&self.value));
|
||||
self.last_value = Some(self.value.clone());
|
||||
}
|
||||
|
||||
// Request layout for cached element
|
||||
let (layout_id, _) = self.cached_element
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.request_layout(global_id, inspector_id, window, cx);
|
||||
|
||||
(layout_id, layout_id)
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_layout_id: &mut LayoutId,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) -> () {
|
||||
self.cached_element
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.prepaint(global_id, inspector_id, bounds, window, cx);
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_layout_id: &mut LayoutId,
|
||||
_: &mut (),
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) {
|
||||
self.cached_element
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.paint(global_id, inspector_id, bounds, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
MemoizedElement::new(
|
||||
ElementId::Name("memoized".into()),
|
||||
self.expensive_value.clone(),
|
||||
|value| {
|
||||
// Expensive rendering function only called when value changes
|
||||
div().child(format!("Computed: {}", value))
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Virtual List Pattern
|
||||
|
||||
Efficiently render large lists by only rendering visible items.
|
||||
|
||||
```rust
|
||||
pub struct VirtualList {
|
||||
id: ElementId,
|
||||
item_count: usize,
|
||||
item_height: Pixels,
|
||||
viewport_height: Pixels,
|
||||
scroll_offset: Pixels,
|
||||
render_item: Box<dyn Fn(usize) -> AnyElement>,
|
||||
}
|
||||
|
||||
struct VirtualListState {
|
||||
visible_range: Range<usize>,
|
||||
visible_item_layouts: Vec<LayoutId>,
|
||||
}
|
||||
|
||||
impl Element for VirtualList {
|
||||
type RequestLayoutState = VirtualListState;
|
||||
type PrepaintState = Hitbox;
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) -> (LayoutId, VirtualListState) {
|
||||
// Calculate visible range
|
||||
let start_idx = (self.scroll_offset / self.item_height).floor() as usize;
|
||||
let end_idx = ((self.scroll_offset + self.viewport_height) / self.item_height)
|
||||
.ceil() as usize;
|
||||
let visible_range = start_idx..end_idx.min(self.item_count);
|
||||
|
||||
// Request layout only for visible items
|
||||
let visible_item_layouts: Vec<_> = visible_range.clone()
|
||||
.map(|i| {
|
||||
let mut item = (self.render_item)(i);
|
||||
item.request_layout(global_id, inspector_id, window, cx).0
|
||||
})
|
||||
.collect();
|
||||
|
||||
let total_height = self.item_height * self.item_count as f32;
|
||||
let layout_id = window.request_layout(
|
||||
Style {
|
||||
size: size(relative(1.0), self.viewport_height),
|
||||
overflow: Overflow::Hidden,
|
||||
..default()
|
||||
},
|
||||
visible_item_layouts.clone(),
|
||||
cx
|
||||
);
|
||||
|
||||
(layout_id, VirtualListState {
|
||||
visible_range,
|
||||
visible_item_layouts,
|
||||
})
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_global_id: Option<&GlobalElementId>,
|
||||
_inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut VirtualListState,
|
||||
window: &mut Window,
|
||||
_cx: &mut App
|
||||
) -> Hitbox {
|
||||
// Prepaint visible items at correct positions
|
||||
for (i, layout_id) in state.visible_item_layouts.iter().enumerate() {
|
||||
let item_idx = state.visible_range.start + i;
|
||||
let y = item_idx as f32 * self.item_height - self.scroll_offset;
|
||||
let item_bounds = Bounds::new(
|
||||
point(bounds.left(), bounds.top() + y),
|
||||
size(bounds.width(), self.item_height)
|
||||
);
|
||||
|
||||
// Prepaint if visible
|
||||
if item_bounds.intersects(&bounds) {
|
||||
// Prepaint item...
|
||||
}
|
||||
}
|
||||
|
||||
window.insert_hitbox(bounds, HitboxBehavior::Normal)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
_global_id: Option<&GlobalElementId>,
|
||||
_inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
state: &mut VirtualListState,
|
||||
hitbox: &mut Hitbox,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) {
|
||||
// Paint visible items
|
||||
for (i, _layout_id) in state.visible_item_layouts.iter().enumerate() {
|
||||
let item_idx = state.visible_range.start + i;
|
||||
let y = item_idx as f32 * self.item_height - self.scroll_offset;
|
||||
let item_bounds = Bounds::new(
|
||||
point(bounds.left(), bounds.top() + y),
|
||||
size(bounds.width(), self.item_height)
|
||||
);
|
||||
|
||||
if item_bounds.intersects(&bounds) {
|
||||
let mut item = (self.render_item)(item_idx);
|
||||
item.paint(item_bounds, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle scroll
|
||||
window.on_mouse_event({
|
||||
let hitbox = hitbox.clone();
|
||||
let total_height = self.item_height * self.item_count as f32;
|
||||
|
||||
move |event: &ScrollWheelEvent, phase, window, cx| {
|
||||
if hitbox.is_hovered(window) && phase.bubble() {
|
||||
self.scroll_offset -= event.delta.y;
|
||||
self.scroll_offset = self.scroll_offset
|
||||
.max(px(0.))
|
||||
.min(total_height - self.viewport_height);
|
||||
cx.notify();
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Usage: Efficiently render 10,000 items
|
||||
let virtual_list = VirtualList {
|
||||
id: ElementId::Name("large-list".into()),
|
||||
item_count: 10_000,
|
||||
item_height: px(40.),
|
||||
viewport_height: px(400.),
|
||||
scroll_offset: px(0.),
|
||||
render_item: Box::new(|index| {
|
||||
div().child(format!("Item {}", index))
|
||||
}),
|
||||
};
|
||||
```
|
||||
|
||||
These advanced patterns enable sophisticated element implementations while maintaining performance and code quality.
|
||||
Reference in New Issue
Block a user