All skills
lassejlv avatar

/build-gpui-apps

@52f4cfd
by Lasselassejlv/skills50 stars
1

Build, scaffold, refactor, debug, review, and validate native Rust desktop applications with GPUI. Recommend GPUI Kit and ask before adopting it; use gpui_kit imports after agreement, or preserve the chosen upstream GPUI stack. Includes the merged GPUI Kit component and design skills, full application/Base/Component/Shell documentation, coding and design guides, state, actions, async, input, accessibility, motion, themes, native integration, packaging, auto updates, testing, and production delivery. Use paper-to-gpui when the primary task is faithfully translating a selected Paper.design frame into an existing view.

Use this Skill: https://skilld.dev/gh/lassejlv/skills/build-gpui-apps

This session only. Nothing lands on disk.

referencesgpui-kitgpuielement-advanced.md

≈5.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Advanced Element Patterns

Contents: Custom Layout Algorithms · Element Composition with Traits · Async Element Updates · Element Memoization · Virtual List Pattern

Custom Layout Algorithms

Implementing custom layout algorithms not supported by GPUI's built-in layouts.

Masonry Layout (Pinterest-Style)

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

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

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

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.

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.

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.

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.

Merged from GPUI Kit skill. Apache-2.0. Imported 2026-09-28; local links and version-sensitive guidance adapted. The current documentation and locked source take precedence over example signatures.

Source: SKILL.md on GitHub

No alerts3d3 checks · Risk SAFE
  • Gen Agent Trust Hub3d

    The skill provides comprehensive documentation and reference material for building native desktop applications using Rust and GPUI. No security risks, prompt injections, or malicious behaviors were identified. External dependencies originate from reputable organizations in the Rust ecosystem.

  • Socket3d

    No alerts

  • Snyk3d

    Risk: LOW · No issues

Signed by skilld at 52f4cfd. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 4 days ago

README badge

README badge for lassejlv/skills/build-gpui-apps