dragdrop_picking/
dragdrop_picking.rs

1//! Demonstrates drag and drop functionality using picking events.
2
3use bevy::prelude::*;
4
5#[derive(Component)]
6struct DropArea;
7
8#[derive(Component)]
9struct DraggableButton;
10
11#[derive(Component)]
12struct GhostPreview;
13
14#[derive(Component)]
15struct DroppedElement;
16
17const AREA_SIZE: f32 = 500.0;
18const BUTTON_WIDTH: f32 = 150.0;
19const BUTTON_HEIGHT: f32 = 50.0;
20const ELEMENT_SIZE: f32 = 25.0;
21
22fn main() {
23    App::new()
24        .add_plugins((DefaultPlugins, MeshPickingPlugin))
25        .add_systems(Startup, setup)
26        .run();
27}
28
29fn setup(
30    mut commands: Commands,
31    mut meshes: ResMut<Assets<Mesh>>,
32    mut materials: ResMut<Assets<ColorMaterial>>,
33) {
34    commands.spawn(Camera2d);
35
36    commands
37        .spawn((
38            Node {
39                width: Val::Percent(100.0),
40                height: Val::Percent(100.0),
41                align_items: AlignItems::Center,
42                justify_content: JustifyContent::Start,
43                ..default()
44            },
45            Pickable::IGNORE,
46        ))
47        .with_children(|parent| {
48            parent
49                .spawn((
50                    DraggableButton,
51                    Node {
52                        width: Val::Px(BUTTON_WIDTH),
53                        height: Val::Px(BUTTON_HEIGHT),
54                        margin: UiRect::all(Val::Px(10.0)),
55                        justify_content: JustifyContent::Center,
56                        align_items: AlignItems::Center,
57                        ..default()
58                    },
59                    BackgroundColor(Color::srgb(1.0, 0.0, 0.0)),
60                ))
61                .with_child((
62                    Text::new("Drag from me"),
63                    TextColor(Color::WHITE),
64                    Pickable::IGNORE,
65                ))
66                .observe(
67                    |mut event: On<Pointer<DragStart>>,
68                     mut button_color: Single<&mut BackgroundColor, With<DraggableButton>>| {
69                        button_color.0 = Color::srgb(1.0, 0.5, 0.0);
70                        event.propagate(false);
71                    },
72                )
73                .observe(
74                    |mut event: On<Pointer<DragEnd>>,
75                     mut button_color: Single<&mut BackgroundColor, With<DraggableButton>>| {
76                        button_color.0 = Color::srgb(1.0, 0.0, 0.0);
77                        event.propagate(false);
78                    },
79                );
80        });
81
82    commands
83        .spawn((
84            DropArea,
85            Mesh2d(meshes.add(Rectangle::new(AREA_SIZE, AREA_SIZE))),
86            MeshMaterial2d(materials.add(Color::srgb(0.1, 0.4, 0.1))),
87            Transform::IDENTITY,
88            children![(
89                Text2d::new("Drop here"),
90                TextFont::from_font_size(50.),
91                TextColor(Color::BLACK),
92                Pickable::IGNORE,
93                Transform::from_translation(Vec3::Z),
94            )],
95        ))
96        .observe(on_drag_enter)
97        .observe(on_drag_over)
98        .observe(on_drag_drop)
99        .observe(on_drag_leave);
100}
101
102fn on_drag_enter(
103    mut event: On<Pointer<DragEnter>>,
104    button: Single<Entity, With<DraggableButton>>,
105    mut commands: Commands,
106    mut meshes: ResMut<Assets<Mesh>>,
107    mut materials: ResMut<Assets<ColorMaterial>>,
108) {
109    if event.dragged == *button {
110        let Some(position) = event.hit.position else {
111            return;
112        };
113        commands.spawn((
114            GhostPreview,
115            Mesh2d(meshes.add(Circle::new(ELEMENT_SIZE))),
116            MeshMaterial2d(materials.add(Color::srgba(1.0, 1.0, 0.6, 0.5))),
117            Transform::from_translation(position + 2. * Vec3::Z),
118            Pickable::IGNORE,
119        ));
120        event.propagate(false);
121    }
122}
123
124fn on_drag_over(
125    mut event: On<Pointer<DragOver>>,
126    button: Single<Entity, With<DraggableButton>>,
127    mut ghost_transform: Single<&mut Transform, With<GhostPreview>>,
128) {
129    if event.dragged == *button {
130        let Some(position) = event.hit.position else {
131            return;
132        };
133        ghost_transform.translation = position;
134        event.propagate(false);
135    }
136}
137
138fn on_drag_drop(
139    mut event: On<Pointer<DragDrop>>,
140    button: Single<Entity, With<DraggableButton>>,
141    mut commands: Commands,
142    ghost: Single<Entity, With<GhostPreview>>,
143    mut meshes: ResMut<Assets<Mesh>>,
144    mut materials: ResMut<Assets<ColorMaterial>>,
145) {
146    if event.dropped == *button {
147        commands.entity(*ghost).despawn();
148        let Some(position) = event.hit.position else {
149            return;
150        };
151        commands.spawn((
152            DroppedElement,
153            Mesh2d(meshes.add(Circle::new(ELEMENT_SIZE))),
154            MeshMaterial2d(materials.add(Color::srgb(1.0, 1.0, 0.6))),
155            Transform::from_translation(position + 2. * Vec3::Z),
156            Pickable::IGNORE,
157        ));
158        event.propagate(false);
159    }
160}
161
162fn on_drag_leave(
163    mut event: On<Pointer<DragLeave>>,
164    button: Single<Entity, With<DraggableButton>>,
165    mut commands: Commands,
166    ghost: Single<Entity, With<GhostPreview>>,
167) {
168    if event.dragged == *button {
169        commands.entity(*ghost).despawn();
170        event.propagate(false);
171    }
172}