use revue::event::drag::{DragContext, DragData};
use revue::layout::Rect;
use revue::prelude::*;
use revue::widget::{DropZone, SortableList};
struct DragDropDemo {
tasks: Vec<String>,
completed: Vec<String>,
trash: Vec<String>,
active_tab: usize,
status: String,
}
impl DragDropDemo {
fn new() -> Self {
Self {
tasks: vec![
"Review pull request".to_string(),
"Write documentation".to_string(),
"Fix bug #123".to_string(),
"Add unit tests".to_string(),
"Refactor module".to_string(),
],
completed: Vec::new(),
trash: Vec::new(),
active_tab: 0,
status: "Use ↑↓ to select, Enter to drag, Tab to switch zones".to_string(),
}
}
fn handle_key(&mut self, key: &Key) -> bool {
match key {
Key::Tab => {
self.active_tab = (self.active_tab + 1) % 3;
self.status = format!("Switched to tab {}", self.active_tab + 1);
true
}
Key::Char('1') => {
self.active_tab = 0;
true
}
Key::Char('2') => {
self.active_tab = 1;
true
}
Key::Char('3') => {
self.active_tab = 2;
true
}
Key::Char('r') => {
*self = Self::new();
self.status = "Demo reset!".to_string();
true
}
_ => false,
}
}
}
impl View for DragDropDemo {
fn render(&self, ctx: &mut RenderContext) {
let view = vstack()
.gap(1)
.child(self.render_header())
.child(self.render_tabs())
.child(self.render_main_content())
.child(self.render_status())
.child(self.render_controls());
view.render(ctx);
}
fn meta(&self) -> WidgetMeta {
WidgetMeta::new("DragDropDemo")
}
}
impl DragDropDemo {
fn render_header(&self) -> impl View {
vstack()
.child(
Text::new("🎯 Drag & Drop Demo")
.bold()
.fg(Color::CYAN)
.align(Alignment::Center),
)
.child(Text::muted("Sortable lists and drop zones").align(Alignment::Center))
}
fn render_tabs(&self) -> impl View {
hstack()
.gap(2)
.child(self.tab_button("Tasks", 0))
.child(self.tab_button("Completed", 1))
.child(self.tab_button("Trash", 2))
}
fn tab_button(&self, label: &str, index: usize) -> Text {
if self.active_tab == index {
Text::new(format!("[{}]", label)).fg(Color::CYAN).bold()
} else {
Text::new(format!(" {} ", label)).fg(Color::WHITE)
}
}
fn render_main_content(&self) -> impl View {
hstack()
.gap(2)
.child(self.render_task_list())
.child(self.render_drop_zones())
}
fn render_task_list(&self) -> Border {
let items: Vec<Text> = self
.tasks
.iter()
.enumerate()
.map(|(i, task)| {
let prefix = if i == 0 { "▶ " } else { " " };
Text::new(format!("{}{}", prefix, task))
})
.collect();
let content = if items.is_empty() {
vstack().child(Text::muted("No tasks"))
} else {
let mut stack = vstack();
for item in items {
stack = stack.child(item);
}
stack
};
Border::panel()
.title(format!("📋 Tasks ({})", self.tasks.len()))
.child(content)
}
fn render_drop_zones(&self) -> impl View {
vstack()
.gap(1)
.child(
Border::rounded()
.title(format!("✅ Completed ({})", self.completed.len()))
.fg(Color::GREEN)
.child(self.render_completed_items()),
)
.child(
Border::rounded()
.title(format!("🗑️ Trash ({})", self.trash.len()))
.fg(Color::RED)
.child(self.render_trash_items()),
)
}
fn render_completed_items(&self) -> impl View {
if self.completed.is_empty() {
vstack().child(Text::muted("Drop completed tasks here"))
} else {
let mut stack = vstack();
for item in &self.completed {
stack = stack.child(Text::new(format!("✓ {}", item)).fg(Color::GREEN));
}
stack
}
}
fn render_trash_items(&self) -> impl View {
if self.trash.is_empty() {
vstack().child(Text::muted("Drop items to delete"))
} else {
let mut stack = vstack();
for item in &self.trash {
stack = stack.child(Text::new(format!("✗ {}", item)).fg(Color::RED));
}
stack
}
}
fn render_status(&self) -> impl View {
Border::single().child(
Text::new(&self.status)
.fg(Color::YELLOW)
.align(Alignment::Center),
)
}
fn render_controls(&self) -> impl View {
Border::single().title("Controls").child(
vstack()
.child(
hstack()
.gap(4)
.child(Text::muted("[1-3]"))
.child(Text::new("Switch panels")),
)
.child(
hstack()
.gap(4)
.child(Text::muted("[Tab]"))
.child(Text::new("Next panel")),
)
.child(
hstack()
.gap(4)
.child(Text::muted("[r]"))
.child(Text::new("Reset demo")),
)
.child(
hstack()
.gap(4)
.child(Text::muted("[q]"))
.child(Text::new("Quit")),
),
)
}
}
struct SortableDemo {
items: Vec<String>,
reorder_count: usize,
}
impl SortableDemo {
#[allow(dead_code)]
fn new() -> Self {
Self {
items: vec![
"First item".to_string(),
"Second item".to_string(),
"Third item".to_string(),
"Fourth item".to_string(),
"Fifth item".to_string(),
],
reorder_count: 0,
}
}
}
impl View for SortableDemo {
fn render(&self, ctx: &mut RenderContext) {
let _sortable = SortableList::new(self.items.clone())
.selected_color(Color::CYAN)
.handles(true);
let view = vstack()
.gap(1)
.child(Text::new("SortableList API Demo").bold())
.child(Text::muted("Drag items to reorder"))
.child(Border::single().child({
let mut stack = vstack();
for (i, item) in self.items.iter().enumerate() {
stack = stack.child(
hstack()
.gap(1)
.child(Text::new("⠿").fg(Color::CYAN))
.child(Text::new(format!("{}. {}", i + 1, item))),
);
}
stack
}))
.child(Text::muted(format!(
"Reordered {} times",
self.reorder_count
)));
view.render(ctx);
}
fn meta(&self) -> WidgetMeta {
WidgetMeta::new("SortableDemo")
}
}
struct DropZoneDemo {
dropped_items: Vec<String>,
}
impl DropZoneDemo {
#[allow(dead_code)]
fn new() -> Self {
Self {
dropped_items: Vec::new(),
}
}
}
impl View for DropZoneDemo {
fn render(&self, ctx: &mut RenderContext) {
let _dropzone = DropZone::new("Drop items here").accepts(&["text", "file", "list_item"]);
let view = vstack()
.gap(1)
.child(Text::new("DropZone API Demo").bold())
.child(
Border::rounded().fg(Color::CYAN).child(
vstack()
.child(Text::new("📥 Drop Zone").align(Alignment::Center))
.child(Text::muted("Accepts: text, file, list_item")),
),
)
.child(Text::new(format!(
"Received {} items",
self.dropped_items.len()
)));
view.render(ctx);
}
fn meta(&self) -> WidgetMeta {
WidgetMeta::new("DropZoneDemo")
}
}
fn demonstrate_drag_context_api() {
let mut ctx = DragContext::new();
ctx.start_drag(DragData::text("Hello World"), 10, 5);
assert!(ctx.is_dragging());
assert_eq!(ctx.position(), (10, 5));
ctx.update_position(15, 8);
assert_eq!(ctx.offset(), (5, 3));
use revue::event::drag::DropTarget;
let target1 = DropTarget::new(1, Rect::new(0, 0, 20, 10));
let target2 = DropTarget::new(2, Rect::new(25, 0, 20, 10));
ctx.register_target(target1);
ctx.register_target(target2);
if ctx.is_over_target() {
println!("Over a drop target: {:?}", ctx.hovered_target());
}
let result = ctx.end_drag();
println!("Drag result: {:?}", result);
let _text_data = DragData::text("Plain text");
let _file_data = DragData::file("/path/to/file.txt");
let _list_data = DragData::list_item(0, "First Item");
let _tree_data = DragData::tree_node("node-1", "Root Node");
#[derive(Debug)]
struct CustomItem {
id: u64,
name: String,
}
let custom = CustomItem {
id: 42,
name: "My Item".to_string(),
};
println!("Custom item: id={}, name={}", custom.id, custom.name);
let _custom_data = DragData::new("custom", custom).with_label("My Item");
}
fn main() -> Result<()> {
println!("🎯 Drag & Drop Example");
println!("======================\n");
println!("This example demonstrates:");
println!(" • SortableList - reorderable list widget");
println!(" • DropZone - drag target areas");
println!(" • DragContext - manual drag state management");
println!(" • DragData - type-safe drag payloads\n");
demonstrate_drag_context_api();
let mut app = App::builder().build();
let demo = DragDropDemo::new();
app.run(demo, |event, app_view, _app| match event {
Event::Key(key_event) => app_view.handle_key(&key_event.key),
_ => false,
})
}