1use 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}