Start reorganizing examples (#10)
* Update crate name * wip * reduce to just the gpui crate * Fix misisng example images * Delete ownership_post.rs * Update Cargo.toml * Update README.md * bump to 0.3.3 * organize * organize * Add more learn examples * Delete _ownership_and_data_flow.rs * wip learn examples * Fix colors * tidy * Move _template up * add animation example * Update examples * remove legacy examples from toml * Delete examples.toml
This commit is contained in:
@@ -0,0 +1,567 @@
|
||||
//! Interactive Elements Example
|
||||
//!
|
||||
//! This example demonstrates interactive patterns in GPUI:
|
||||
//!
|
||||
//! 1. Click events - single click, double click, click count
|
||||
//! 2. Hover states - hover styling and on_hover callbacks
|
||||
//! 3. Mouse events - mouse down, mouse up, mouse move
|
||||
//! 4. Drag and drop - draggable elements and drop targets
|
||||
|
||||
#[path = "../prelude.rs"]
|
||||
mod example_prelude;
|
||||
|
||||
use example_prelude::init_example;
|
||||
use gpui::{
|
||||
App, Application, Bounds, ClickEvent, Colors, Context, Entity, Half, Hsla, IntoElement,
|
||||
MouseButton, MouseMoveEvent, Pixels, Point, Render, Window, WindowBounds, WindowOptions, div,
|
||||
prelude::*, px, size,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Click Events Demo
|
||||
// ============================================================================
|
||||
//
|
||||
// Demonstrates different click interactions:
|
||||
// - Single click
|
||||
// - Double click (click_count == 2)
|
||||
// - Click count tracking
|
||||
|
||||
struct ClickDemo {
|
||||
click_count: usize,
|
||||
last_click_type: String,
|
||||
}
|
||||
|
||||
impl ClickDemo {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
click_count: 0,
|
||||
last_click_type: "None".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ClickDemo {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let colors = Colors::for_appearance(window);
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.p_4()
|
||||
.rounded_lg()
|
||||
.bg(colors.surface)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.font_weight(gpui::FontWeight::SEMIBOLD)
|
||||
.text_color(colors.text)
|
||||
.child("Click Events"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.text_muted)
|
||||
.child("Single click, double click, or triple click the button"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("click-target")
|
||||
.px_4()
|
||||
.py_2()
|
||||
.rounded_md()
|
||||
.bg(colors.accent)
|
||||
.text_color(colors.selected_text)
|
||||
.text_sm()
|
||||
.cursor_pointer()
|
||||
.hover(|style| style.bg(colors.accent_hover))
|
||||
.active(|style| style.bg(colors.accent_active))
|
||||
.child("Click Me!")
|
||||
// on_click receives a ClickEvent with click_count() method
|
||||
.on_click(cx.listener(|this, event: &ClickEvent, _window, cx| {
|
||||
this.click_count += 1;
|
||||
this.last_click_type = match event.click_count() {
|
||||
1 => "Single Click".to_string(),
|
||||
2 => "Double Click".to_string(),
|
||||
3 => "Triple Click".to_string(),
|
||||
n => format!("{n}x Click"),
|
||||
};
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.mt_2()
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.text_muted)
|
||||
.child(format!("Total clicks: {}", self.click_count)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.text_muted)
|
||||
.child(format!("Last: {}", self.last_click_type)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Hover Demo
|
||||
// ============================================================================
|
||||
//
|
||||
// Demonstrates hover interactions:
|
||||
// - hover() style modifier for CSS-like hover states
|
||||
// - on_hover() callback for programmatic hover detection
|
||||
|
||||
struct HoverDemo {
|
||||
is_hovered: bool,
|
||||
hover_count: usize,
|
||||
}
|
||||
|
||||
impl HoverDemo {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
is_hovered: false,
|
||||
hover_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for HoverDemo {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let colors = Colors::for_appearance(window);
|
||||
let is_hovered = self.is_hovered;
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.p_4()
|
||||
.rounded_lg()
|
||||
.bg(colors.surface)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.font_weight(gpui::FontWeight::SEMIBOLD)
|
||||
.text_color(colors.text)
|
||||
.child("Hover Events"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.text_muted)
|
||||
.child("Move your mouse in and out of the target"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("hover-target")
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded_md()
|
||||
.border_2()
|
||||
.border_color(if is_hovered {
|
||||
colors.accent
|
||||
} else {
|
||||
colors.border
|
||||
})
|
||||
.bg(if is_hovered {
|
||||
colors.accent_hover
|
||||
} else {
|
||||
colors.surface_hover
|
||||
})
|
||||
.text_color(if is_hovered {
|
||||
colors.selected_text
|
||||
} else {
|
||||
colors.text
|
||||
})
|
||||
.text_sm()
|
||||
.cursor_pointer()
|
||||
.child(if is_hovered {
|
||||
"Mouse Inside!"
|
||||
} else {
|
||||
"Hover Over Me"
|
||||
})
|
||||
// on_hover callback receives a bool: true when mouse enters, false when it leaves
|
||||
.on_hover(cx.listener(|this, &hovered, _window, cx| {
|
||||
this.is_hovered = hovered;
|
||||
if hovered {
|
||||
this.hover_count += 1;
|
||||
}
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.text_muted)
|
||||
.mt_2()
|
||||
.child(format!("Times hovered: {}", self.hover_count)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mouse Events Demo
|
||||
// ============================================================================
|
||||
//
|
||||
// Demonstrates low-level mouse events:
|
||||
// - on_mouse_down - fires when mouse button is pressed
|
||||
// - on_mouse_up - fires when mouse button is released
|
||||
// - on_mouse_move - fires when mouse moves over element
|
||||
|
||||
struct MouseEventsDemo {
|
||||
mouse_position: Option<Point<Pixels>>,
|
||||
is_pressed: bool,
|
||||
event_log: Vec<String>,
|
||||
}
|
||||
|
||||
impl MouseEventsDemo {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
mouse_position: None,
|
||||
is_pressed: false,
|
||||
event_log: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn log_event(&mut self, event: &str) {
|
||||
self.event_log.push(event.to_string());
|
||||
if self.event_log.len() > 5 {
|
||||
self.event_log.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for MouseEventsDemo {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let colors = Colors::for_appearance(window);
|
||||
let is_pressed = self.is_pressed;
|
||||
let position_text = self
|
||||
.mouse_position
|
||||
.map(|p| format!("({:.0}, {:.0})", f32::from(p.x), f32::from(p.y)))
|
||||
.unwrap_or_else(|| "—".to_string());
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.p_4()
|
||||
.rounded_lg()
|
||||
.bg(colors.surface)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.font_weight(gpui::FontWeight::SEMIBOLD)
|
||||
.text_color(colors.text)
|
||||
.child("Mouse Events"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.text_muted)
|
||||
.child("Move and click within the target area"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("mouse-events-target")
|
||||
.h_20()
|
||||
.rounded_md()
|
||||
.border_2()
|
||||
.border_color(if is_pressed {
|
||||
colors.accent
|
||||
} else {
|
||||
colors.border
|
||||
})
|
||||
.bg(if is_pressed {
|
||||
colors.accent_hover
|
||||
} else {
|
||||
colors.surface_hover
|
||||
})
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_sm()
|
||||
.text_color(colors.text)
|
||||
.child(format!("Position: {}", position_text))
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(|this, _event, _window, cx| {
|
||||
this.is_pressed = true;
|
||||
this.log_event("Mouse Down");
|
||||
cx.notify();
|
||||
}),
|
||||
)
|
||||
.on_mouse_up(
|
||||
MouseButton::Left,
|
||||
cx.listener(|this, _event, _window, cx| {
|
||||
this.is_pressed = false;
|
||||
this.log_event("Mouse Up");
|
||||
cx.notify();
|
||||
}),
|
||||
)
|
||||
.on_mouse_move(cx.listener(|this, event: &MouseMoveEvent, _window, cx| {
|
||||
this.mouse_position = Some(event.position);
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_0p5()
|
||||
.mt_2()
|
||||
.text_xs()
|
||||
.text_color(colors.text_muted)
|
||||
.children(
|
||||
self.event_log
|
||||
.iter()
|
||||
.map(|e| div().child(format!("• {}", e))),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Drag and Drop Demo
|
||||
// ============================================================================
|
||||
//
|
||||
// Demonstrates drag and drop:
|
||||
// - on_drag - makes an element draggable, provides drag data
|
||||
// - on_drop - makes an element a drop target, receives drag data
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct DragData {
|
||||
index: usize,
|
||||
color: Hsla,
|
||||
position: Point<Pixels>,
|
||||
}
|
||||
|
||||
impl DragData {
|
||||
fn new(index: usize, color: Hsla) -> Self {
|
||||
Self {
|
||||
index,
|
||||
color,
|
||||
position: Point::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_position(mut self, position: Point<Pixels>) -> Self {
|
||||
self.position = position;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Render trait for DragData allows it to be rendered as drag feedback
|
||||
impl Render for DragData {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let size = gpui::size(px(80.), px(40.));
|
||||
|
||||
// Position the drag preview at the cursor
|
||||
div()
|
||||
.pl(self.position.x - size.width.half())
|
||||
.pt(self.position.y - size.height.half())
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.w(size.width)
|
||||
.h(size.height)
|
||||
.bg(self.color.opacity(0.8))
|
||||
.text_color(gpui::white())
|
||||
.text_xs()
|
||||
.rounded_md()
|
||||
.shadow_lg()
|
||||
.child(format!("Item {}", self.index + 1)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct DragDropDemo {
|
||||
dropped_item: Option<DragData>,
|
||||
}
|
||||
|
||||
impl DragDropDemo {
|
||||
fn new() -> Self {
|
||||
Self { dropped_item: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for DragDropDemo {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let colors = Colors::for_appearance(window);
|
||||
let item_colors = [colors.error, colors.success, colors.warning];
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.p_4()
|
||||
.rounded_lg()
|
||||
.bg(colors.surface)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.font_weight(gpui::FontWeight::SEMIBOLD)
|
||||
.text_color(colors.text)
|
||||
.child("Drag and Drop"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.text_muted)
|
||||
.child("Drag items to the drop zone below"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap_2()
|
||||
.children(item_colors.into_iter().enumerate().map(|(index, color)| {
|
||||
let drag_data = DragData::new(index, color.into());
|
||||
|
||||
div()
|
||||
.id(("drag-item", index))
|
||||
.px_3()
|
||||
.py_2()
|
||||
.rounded_md()
|
||||
.border_2()
|
||||
.border_color(color)
|
||||
.text_color(color)
|
||||
.text_xs()
|
||||
.cursor_grab()
|
||||
.hover(move |style| {
|
||||
let c: Hsla = color.into();
|
||||
style.bg(c.opacity(0.1))
|
||||
})
|
||||
.child(format!("Item {}", index + 1))
|
||||
// on_drag takes: drag data, and a closure that creates the drag preview
|
||||
.on_drag(drag_data, |data: &DragData, position, _, cx| {
|
||||
cx.new(|_| data.with_position(position))
|
||||
})
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("drop-target")
|
||||
.mt_2()
|
||||
.h_16()
|
||||
.rounded_md()
|
||||
.border_2()
|
||||
.border_dashed()
|
||||
.border_color(
|
||||
self.dropped_item
|
||||
.map(|d| d.color)
|
||||
.unwrap_or_else(|| colors.border.into()),
|
||||
)
|
||||
.when_some(self.dropped_item, |el, data| el.bg(data.color.opacity(0.2)))
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_xs()
|
||||
.text_color(colors.text_muted)
|
||||
// on_drop receives the drag data when an item is dropped
|
||||
.on_drop(cx.listener(|this, data: &DragData, _window, cx| {
|
||||
this.dropped_item = Some(*data);
|
||||
cx.notify();
|
||||
}))
|
||||
.child(
|
||||
self.dropped_item
|
||||
.map(|d| format!("Dropped: Item {}", d.index + 1))
|
||||
.unwrap_or_else(|| "Drop Zone".to_string()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main Application
|
||||
// ============================================================================
|
||||
|
||||
struct InteractiveElementsExample {
|
||||
click_demo: Entity<ClickDemo>,
|
||||
hover_demo: Entity<HoverDemo>,
|
||||
mouse_events_demo: Entity<MouseEventsDemo>,
|
||||
drag_drop_demo: Entity<DragDropDemo>,
|
||||
}
|
||||
|
||||
impl InteractiveElementsExample {
|
||||
fn new(cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
click_demo: cx.new(|_| ClickDemo::new()),
|
||||
hover_demo: cx.new(|_| HoverDemo::new()),
|
||||
mouse_events_demo: cx.new(|_| MouseEventsDemo::new()),
|
||||
drag_drop_demo: cx.new(|_| DragDropDemo::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for InteractiveElementsExample {
|
||||
fn render(&mut self, window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let colors = Colors::for_appearance(window);
|
||||
|
||||
div()
|
||||
.id("main")
|
||||
.size_full()
|
||||
.p_6()
|
||||
.bg(colors.background)
|
||||
.overflow_scroll()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_6()
|
||||
.max_w(px(800.))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.text_xl()
|
||||
.font_weight(gpui::FontWeight::BOLD)
|
||||
.text_color(colors.text)
|
||||
.child("Interactive Elements"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(colors.text_muted)
|
||||
.child("Click, hover, mouse events, and drag-and-drop in GPUI"),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.grid()
|
||||
.grid_cols(2)
|
||||
.gap_4()
|
||||
.child(self.click_demo.clone())
|
||||
.child(self.hover_demo.clone())
|
||||
.child(self.mouse_events_demo.clone())
|
||||
.child(self.drag_drop_demo.clone()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
let bounds = Bounds::centered(None, size(px(700.), px(650.)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
..Default::default()
|
||||
},
|
||||
|_, cx| cx.new(|cx| InteractiveElementsExample::new(cx)),
|
||||
)
|
||||
.expect("Failed to open window");
|
||||
|
||||
init_example(cx, "Interactive Elements");
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user