diff --git a/Cargo.toml b/Cargo.toml index 7dda86c8f1418..a9aaa9fa94a3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4591,6 +4591,18 @@ description = "Demonstrates picking debug overlay" category = "Picking" wasm = true +[[example]] +name = "dragdrop_picking" +path = "examples/picking/dragdrop_picking.rs" +doc-scrape-examples = true +required-features = ["mesh_picking"] + +[package.metadata.example.dragdrop_picking] +name = "Drag and Drop" +description = "Demonstrates drag and drop using picking events" +category = "Picking" +wasm = true + [[example]] name = "animation_masks" path = "examples/animation/animation_masks.rs" diff --git a/examples/README.md b/examples/README.md index da86eac2fbdc1..7215a97b11935 100644 --- a/examples/README.md +++ b/examples/README.md @@ -416,6 +416,7 @@ Example | Description Example | Description --- | --- +[Drag and Drop](../examples/picking/dragdrop_picking.rs) | Demonstrates drag and drop using picking events [Mesh Picking](../examples/picking/mesh_picking.rs) | Demonstrates picking meshes [Picking Debug Tools](../examples/picking/debug_picking.rs) | Demonstrates picking debug overlay [Showcases simple picking events and usage](../examples/picking/simple_picking.rs) | Demonstrates how to use picking events to spawn simple objects diff --git a/examples/picking/dragdrop_picking.rs b/examples/picking/dragdrop_picking.rs new file mode 100644 index 0000000000000..62718ffd4c1ce --- /dev/null +++ b/examples/picking/dragdrop_picking.rs @@ -0,0 +1,172 @@ +//! Demonstrates drag and drop functionality using picking events. + +use bevy::prelude::*; + +#[derive(Component)] +struct DropArea; + +#[derive(Component)] +struct DraggableButton; + +#[derive(Component)] +struct GhostPreview; + +#[derive(Component)] +struct DroppedElement; + +const AREA_SIZE: f32 = 500.0; +const BUTTON_WIDTH: f32 = 150.0; +const BUTTON_HEIGHT: f32 = 50.0; +const ELEMENT_SIZE: f32 = 25.0; + +fn main() { + App::new() + .add_plugins((DefaultPlugins, MeshPickingPlugin)) + .add_systems(Startup, setup) + .run(); +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + commands.spawn(Camera2d); + + commands + .spawn(( + Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + align_items: AlignItems::Center, + justify_content: JustifyContent::Start, + ..default() + }, + Pickable::IGNORE, + )) + .with_children(|parent| { + parent + .spawn(( + DraggableButton, + Node { + width: Val::Px(BUTTON_WIDTH), + height: Val::Px(BUTTON_HEIGHT), + margin: UiRect::all(Val::Px(10.0)), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }, + BackgroundColor(Color::srgb(1.0, 0.0, 0.0)), + )) + .with_child(( + Text::new("Drag from me"), + TextColor(Color::WHITE), + Pickable::IGNORE, + )) + .observe( + |mut event: On>, + mut button_color: Single<&mut BackgroundColor, With>| { + button_color.0 = Color::srgb(1.0, 0.5, 0.0); + event.propagate(false); + }, + ) + .observe( + |mut event: On>, + mut button_color: Single<&mut BackgroundColor, With>| { + button_color.0 = Color::srgb(1.0, 0.0, 0.0); + event.propagate(false); + }, + ); + }); + + commands + .spawn(( + DropArea, + Mesh2d(meshes.add(Rectangle::new(AREA_SIZE, AREA_SIZE))), + MeshMaterial2d(materials.add(Color::srgb(0.1, 0.4, 0.1))), + Transform::IDENTITY, + children![( + Text2d::new("Drop here"), + TextFont::from_font_size(50.), + TextColor(Color::BLACK), + Pickable::IGNORE, + Transform::from_translation(Vec3::Z), + )], + )) + .observe(on_drag_enter) + .observe(on_drag_over) + .observe(on_drag_drop) + .observe(on_drag_leave); +} + +fn on_drag_enter( + mut event: On>, + button: Single>, + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + if event.dragged == *button { + let Some(position) = event.hit.position else { + return; + }; + commands.spawn(( + GhostPreview, + Mesh2d(meshes.add(Circle::new(ELEMENT_SIZE))), + MeshMaterial2d(materials.add(Color::srgba(1.0, 1.0, 0.6, 0.5))), + Transform::from_translation(position + 2. * Vec3::Z), + Pickable::IGNORE, + )); + event.propagate(false); + } +} + +fn on_drag_over( + mut event: On>, + button: Single>, + mut ghost_transform: Single<&mut Transform, With>, +) { + if event.dragged == *button { + let Some(position) = event.hit.position else { + return; + }; + ghost_transform.translation = position; + event.propagate(false); + } +} + +fn on_drag_drop( + mut event: On>, + button: Single>, + mut commands: Commands, + ghost: Single>, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + if event.dropped == *button { + commands.entity(*ghost).despawn(); + let Some(position) = event.hit.position else { + return; + }; + commands.spawn(( + DroppedElement, + Mesh2d(meshes.add(Circle::new(ELEMENT_SIZE))), + MeshMaterial2d(materials.add(Color::srgb(1.0, 1.0, 0.6))), + Transform::from_translation(position + 2. * Vec3::Z), + Pickable::IGNORE, + )); + event.propagate(false); + } +} + +fn on_drag_leave( + mut event: On>, + button: Single>, + mut commands: Commands, + ghost: Single>, +) { + if event.dragged == *button { + commands.entity(*ghost).despawn(); + event.propagate(false); + } +}