add gpui skills
This commit is contained in:
632
.agents/skills/gpui-element/references/examples.md
Normal file
632
.agents/skills/gpui-element/references/examples.md
Normal file
@@ -0,0 +1,632 @@
|
||||
# Element Implementation Examples
|
||||
|
||||
Complete examples of implementing custom elements for various scenarios.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Simple Text Element](#simple-text-element)
|
||||
2. [Interactive Element with Selection](#interactive-element-with-selection)
|
||||
3. [Complex Element with Child Management](#complex-element-with-child-management)
|
||||
|
||||
## Simple Text Element
|
||||
|
||||
A basic text element with syntax highlighting support.
|
||||
|
||||
```rust
|
||||
pub struct SimpleText {
|
||||
id: ElementId,
|
||||
text: SharedString,
|
||||
highlights: Vec<(Range<usize>, HighlightStyle)>,
|
||||
}
|
||||
|
||||
impl IntoElement for SimpleText {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for SimpleText {
|
||||
type RequestLayoutState = StyledText;
|
||||
type PrepaintState = Hitbox;
|
||||
|
||||
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, Self::RequestLayoutState) {
|
||||
// Create styled text with highlights
|
||||
let mut runs = Vec::new();
|
||||
let mut ix = 0;
|
||||
|
||||
for (range, highlight) in &self.highlights {
|
||||
// Add unstyled text before highlight
|
||||
if ix < range.start {
|
||||
runs.push(window.text_style().to_run(range.start - ix));
|
||||
}
|
||||
|
||||
// Add highlighted text
|
||||
runs.push(
|
||||
window.text_style()
|
||||
.highlight(*highlight)
|
||||
.to_run(range.len())
|
||||
);
|
||||
ix = range.end;
|
||||
}
|
||||
|
||||
// Add remaining unstyled text
|
||||
if ix < self.text.len() {
|
||||
runs.push(window.text_style().to_run(self.text.len() - ix));
|
||||
}
|
||||
|
||||
let styled_text = StyledText::new(self.text.clone()).with_runs(runs);
|
||||
let (layout_id, _) = styled_text.request_layout(
|
||||
global_id,
|
||||
inspector_id,
|
||||
window,
|
||||
cx
|
||||
);
|
||||
|
||||
(layout_id, styled_text)
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
styled_text: &mut Self::RequestLayoutState,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) -> Self::PrepaintState {
|
||||
// Prepaint the styled text
|
||||
styled_text.prepaint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
bounds,
|
||||
&mut (),
|
||||
window,
|
||||
cx
|
||||
);
|
||||
|
||||
// Create hitbox for interaction
|
||||
let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
|
||||
hitbox
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
styled_text: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Self::PrepaintState,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) {
|
||||
// Paint the styled text
|
||||
styled_text.paint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
bounds,
|
||||
&mut (),
|
||||
&mut (),
|
||||
window,
|
||||
cx
|
||||
);
|
||||
|
||||
// Set cursor style for text
|
||||
window.set_cursor_style(CursorStyle::IBeam, hitbox);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Interactive Element with Selection
|
||||
|
||||
A text element that supports text selection via mouse interaction.
|
||||
|
||||
```rust
|
||||
#[derive(Clone)]
|
||||
pub struct Selection {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
pub struct SelectableText {
|
||||
id: ElementId,
|
||||
text: SharedString,
|
||||
selectable: bool,
|
||||
selection: Option<Selection>,
|
||||
}
|
||||
|
||||
impl IntoElement for SelectableText {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for SelectableText {
|
||||
type RequestLayoutState = TextLayout;
|
||||
type PrepaintState = Option<Hitbox>;
|
||||
|
||||
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, Self::RequestLayoutState) {
|
||||
let styled_text = StyledText::new(self.text.clone());
|
||||
let (layout_id, _) = styled_text.request_layout(
|
||||
global_id,
|
||||
inspector_id,
|
||||
window,
|
||||
cx
|
||||
);
|
||||
|
||||
// Extract text layout for selection painting
|
||||
let text_layout = styled_text.layout().clone();
|
||||
|
||||
(layout_id, text_layout)
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
_global_id: Option<&GlobalElementId>,
|
||||
_inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_text_layout: &mut Self::RequestLayoutState,
|
||||
window: &mut Window,
|
||||
_cx: &mut App
|
||||
) -> Self::PrepaintState {
|
||||
// Only create hitbox if selectable
|
||||
if self.selectable {
|
||||
Some(window.insert_hitbox(bounds, HitboxBehavior::Normal))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
text_layout: &mut Self::RequestLayoutState,
|
||||
hitbox: &mut Self::PrepaintState,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) {
|
||||
// Paint text
|
||||
let styled_text = StyledText::new(self.text.clone());
|
||||
styled_text.paint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
bounds,
|
||||
&mut (),
|
||||
&mut (),
|
||||
window,
|
||||
cx
|
||||
);
|
||||
|
||||
// Paint selection if any
|
||||
if let Some(selection) = &self.selection {
|
||||
Self::paint_selection(selection, text_layout, &bounds, window, cx);
|
||||
}
|
||||
|
||||
// Handle mouse events for selection
|
||||
if let Some(hitbox) = hitbox {
|
||||
window.set_cursor_style(CursorStyle::IBeam, hitbox);
|
||||
|
||||
// Mouse down to start selection
|
||||
window.on_mouse_event({
|
||||
let bounds = bounds.clone();
|
||||
move |event: &MouseDownEvent, phase, window, cx| {
|
||||
if bounds.contains(&event.position) && phase.bubble() {
|
||||
// Start selection at mouse position
|
||||
let char_index = Self::position_to_index(
|
||||
event.position,
|
||||
&bounds,
|
||||
text_layout
|
||||
);
|
||||
self.selection = Some(Selection {
|
||||
start: char_index,
|
||||
end: char_index,
|
||||
});
|
||||
cx.notify();
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Mouse drag to extend selection
|
||||
window.on_mouse_event({
|
||||
let bounds = bounds.clone();
|
||||
move |event: &MouseMoveEvent, phase, window, cx| {
|
||||
if let Some(selection) = &mut self.selection {
|
||||
if phase.bubble() {
|
||||
let char_index = Self::position_to_index(
|
||||
event.position,
|
||||
&bounds,
|
||||
text_layout
|
||||
);
|
||||
selection.end = char_index;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectableText {
|
||||
fn paint_selection(
|
||||
selection: &Selection,
|
||||
text_layout: &TextLayout,
|
||||
bounds: &Bounds<Pixels>,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) {
|
||||
// Calculate selection bounds from text layout
|
||||
let selection_rects = text_layout.rects_for_range(
|
||||
selection.start..selection.end
|
||||
);
|
||||
|
||||
// Paint selection background
|
||||
for rect in selection_rects {
|
||||
window.paint_quad(paint_quad(
|
||||
Bounds::new(
|
||||
point(bounds.left() + rect.origin.x, bounds.top() + rect.origin.y),
|
||||
rect.size
|
||||
),
|
||||
Corners::default(),
|
||||
cx.theme().selection_background,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn position_to_index(
|
||||
position: Point<Pixels>,
|
||||
bounds: &Bounds<Pixels>,
|
||||
text_layout: &TextLayout
|
||||
) -> usize {
|
||||
// Convert screen position to character index
|
||||
let relative_pos = point(
|
||||
position.x - bounds.left(),
|
||||
position.y - bounds.top()
|
||||
);
|
||||
text_layout.index_for_position(relative_pos)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Complex Element with Child Management
|
||||
|
||||
A container element that manages multiple children with scrolling support.
|
||||
|
||||
```rust
|
||||
pub struct ComplexElement {
|
||||
id: ElementId,
|
||||
children: Vec<Box<dyn Element<RequestLayoutState = (), PrepaintState = ()>>>,
|
||||
scrollable: bool,
|
||||
scroll_offset: Point<Pixels>,
|
||||
}
|
||||
|
||||
struct ComplexLayoutState {
|
||||
child_layouts: Vec<LayoutId>,
|
||||
total_height: Pixels,
|
||||
}
|
||||
|
||||
struct ComplexPaintState {
|
||||
child_bounds: Vec<Bounds<Pixels>>,
|
||||
hitbox: Hitbox,
|
||||
}
|
||||
|
||||
impl IntoElement for ComplexElement {
|
||||
type Element = Self;
|
||||
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for ComplexElement {
|
||||
type RequestLayoutState = ComplexLayoutState;
|
||||
type PrepaintState = ComplexPaintState;
|
||||
|
||||
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, Self::RequestLayoutState) {
|
||||
let mut child_layouts = Vec::new();
|
||||
let mut total_height = px(0.);
|
||||
|
||||
// Request layout for all children
|
||||
for child in &mut self.children {
|
||||
let (child_layout_id, _) = child.request_layout(
|
||||
global_id,
|
||||
inspector_id,
|
||||
window,
|
||||
cx
|
||||
);
|
||||
child_layouts.push(child_layout_id);
|
||||
|
||||
// Get child size from layout
|
||||
let child_size = window.layout_bounds(child_layout_id).size();
|
||||
total_height += child_size.height;
|
||||
}
|
||||
|
||||
// Create container layout
|
||||
let layout_id = window.request_layout(
|
||||
Style {
|
||||
flex_direction: FlexDirection::Column,
|
||||
gap: px(8.),
|
||||
size: Size {
|
||||
width: relative(1.0),
|
||||
height: if self.scrollable {
|
||||
// Fixed height for scrollable
|
||||
px(400.)
|
||||
} else {
|
||||
// Auto height for non-scrollable
|
||||
total_height
|
||||
},
|
||||
},
|
||||
..default()
|
||||
},
|
||||
child_layouts.clone(),
|
||||
cx
|
||||
);
|
||||
|
||||
(layout_id, ComplexLayoutState {
|
||||
child_layouts,
|
||||
total_height,
|
||||
})
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
layout_state: &mut Self::RequestLayoutState,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) -> Self::PrepaintState {
|
||||
let mut child_bounds = Vec::new();
|
||||
let mut y_offset = self.scroll_offset.y;
|
||||
|
||||
// Calculate child bounds and prepaint children
|
||||
for (child, layout_id) in self.children.iter_mut()
|
||||
.zip(&layout_state.child_layouts)
|
||||
{
|
||||
let child_size = window.layout_bounds(*layout_id).size();
|
||||
let child_bound = Bounds::new(
|
||||
point(bounds.left(), bounds.top() + y_offset),
|
||||
child_size
|
||||
);
|
||||
|
||||
// Only prepaint visible children
|
||||
if self.is_visible(&child_bound, &bounds) {
|
||||
child.prepaint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
child_bound,
|
||||
&mut (),
|
||||
window,
|
||||
cx
|
||||
);
|
||||
}
|
||||
|
||||
child_bounds.push(child_bound);
|
||||
y_offset += child_size.height + px(8.); // gap
|
||||
}
|
||||
|
||||
let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
|
||||
|
||||
ComplexPaintState {
|
||||
child_bounds,
|
||||
hitbox,
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
layout_state: &mut Self::RequestLayoutState,
|
||||
paint_state: &mut Self::PrepaintState,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) {
|
||||
// Paint background
|
||||
window.paint_quad(paint_quad(
|
||||
bounds,
|
||||
Corners::all(px(4.)),
|
||||
cx.theme().background,
|
||||
));
|
||||
|
||||
// Paint visible children only
|
||||
for (i, child) in self.children.iter_mut().enumerate() {
|
||||
let child_bounds = paint_state.child_bounds[i];
|
||||
|
||||
if self.is_visible(&child_bounds, &bounds) {
|
||||
child.paint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
child_bounds,
|
||||
&mut (),
|
||||
&mut (),
|
||||
window,
|
||||
cx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Paint scrollbar if scrollable
|
||||
if self.scrollable {
|
||||
self.paint_scrollbar(bounds, layout_state, window, cx);
|
||||
}
|
||||
|
||||
// Handle scroll events
|
||||
if self.scrollable {
|
||||
window.on_mouse_event({
|
||||
let hitbox = paint_state.hitbox.clone();
|
||||
let total_height = layout_state.total_height;
|
||||
let visible_height = bounds.size.height;
|
||||
|
||||
move |event: &ScrollWheelEvent, phase, window, cx| {
|
||||
if hitbox.is_hovered(window) && phase.bubble() {
|
||||
// Update scroll offset
|
||||
self.scroll_offset.y -= event.delta.y;
|
||||
|
||||
// Clamp scroll offset
|
||||
let max_scroll = (total_height - visible_height).max(px(0.));
|
||||
self.scroll_offset.y = self.scroll_offset.y
|
||||
.max(px(0.))
|
||||
.min(max_scroll);
|
||||
|
||||
cx.notify();
|
||||
cx.stop_propagation();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ComplexElement {
|
||||
fn is_visible(&self, child_bounds: &Bounds<Pixels>, container_bounds: &Bounds<Pixels>) -> bool {
|
||||
// Check if child is within visible area
|
||||
child_bounds.bottom() >= container_bounds.top() &&
|
||||
child_bounds.top() <= container_bounds.bottom()
|
||||
}
|
||||
|
||||
fn paint_scrollbar(
|
||||
&self,
|
||||
bounds: Bounds<Pixels>,
|
||||
layout_state: &ComplexLayoutState,
|
||||
window: &mut Window,
|
||||
cx: &mut App
|
||||
) {
|
||||
let scrollbar_width = px(8.);
|
||||
let visible_height = bounds.size.height;
|
||||
let total_height = layout_state.total_height;
|
||||
|
||||
if total_height <= visible_height {
|
||||
return; // No need for scrollbar
|
||||
}
|
||||
|
||||
// Calculate scrollbar position and size
|
||||
let scroll_ratio = self.scroll_offset.y / (total_height - visible_height);
|
||||
let thumb_height = (visible_height / total_height) * visible_height;
|
||||
let thumb_y = scroll_ratio * (visible_height - thumb_height);
|
||||
|
||||
// Paint scrollbar track
|
||||
let track_bounds = Bounds::new(
|
||||
point(bounds.right() - scrollbar_width, bounds.top()),
|
||||
size(scrollbar_width, visible_height)
|
||||
);
|
||||
window.paint_quad(paint_quad(
|
||||
track_bounds,
|
||||
Corners::default(),
|
||||
cx.theme().scrollbar_track,
|
||||
));
|
||||
|
||||
// Paint scrollbar thumb
|
||||
let thumb_bounds = Bounds::new(
|
||||
point(bounds.right() - scrollbar_width, bounds.top() + thumb_y),
|
||||
size(scrollbar_width, thumb_height)
|
||||
);
|
||||
window.paint_quad(paint_quad(
|
||||
thumb_bounds,
|
||||
Corners::all(px(4.)),
|
||||
cx.theme().scrollbar_thumb,
|
||||
));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Using SimpleText
|
||||
|
||||
```rust
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.child(SimpleText {
|
||||
id: ElementId::Name("code-text".into()),
|
||||
text: "fn main() { println!(\"Hello\"); }".into(),
|
||||
highlights: vec![
|
||||
(0..2, HighlightStyle::keyword()),
|
||||
(3..7, HighlightStyle::function()),
|
||||
],
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Using SelectableText
|
||||
|
||||
```rust
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.child(SelectableText {
|
||||
id: ElementId::Name("selectable-text".into()),
|
||||
text: "Select this text with your mouse".into(),
|
||||
selectable: true,
|
||||
selection: self.current_selection.clone(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Using ComplexElement
|
||||
|
||||
```rust
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let children: Vec<Box<dyn Element<_, _>>> = self.items
|
||||
.iter()
|
||||
.map(|item| Box::new(div().child(item.name.clone())) as Box<_>)
|
||||
.collect();
|
||||
|
||||
div()
|
||||
.child(ComplexElement {
|
||||
id: ElementId::Name("scrollable-list".into()),
|
||||
children,
|
||||
scrollable: true,
|
||||
scroll_offset: self.scroll_offset,
|
||||
})
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user