Skip to content

Commit fe53a9d

Browse files
committed
feat: support multiple drop targets
1 parent 2175585 commit fe53a9d

File tree

3 files changed

+183
-136
lines changed

3 files changed

+183
-136
lines changed

packages/desktop_drop/lib/desktop_drop_web.dart

Lines changed: 78 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import 'dart:async';
2-
import 'dart:html' as html show window, Url;
2+
import 'dart:html' as html show window, Url, DataTransfer;
33

44
import 'package:flutter/cupertino.dart';
55
import 'package:flutter/services.dart';
@@ -25,66 +25,92 @@ class DesktopDropWeb {
2525
pluginInstance._registerEvents();
2626
}
2727

28+
html.DataTransfer? _dataTransfer;
29+
2830
void _registerEvents() {
29-
html.window.onDrop.listen((event) {
30-
event.preventDefault();
31+
html.window.onDragEnter.listen(
32+
(event) {
33+
event.preventDefault();
34+
_dataTransfer = event.dataTransfer;
35+
channel.invokeMethod('entered', [
36+
event.client.x.toDouble(),
37+
event.client.y.toDouble(),
38+
]);
39+
},
40+
);
3141

32-
final results = <WebDropItem>[];
42+
html.window.onDragOver.listen(
43+
(event) {
44+
event.preventDefault();
45+
_dataTransfer = event.dataTransfer;
46+
channel.invokeMethod('updated', [
47+
event.client.x.toDouble(),
48+
event.client.y.toDouble(),
49+
]);
50+
},
51+
);
3352

34-
try {
35-
final items = event.dataTransfer.files;
36-
if (items != null) {
37-
for (final item in items) {
38-
results.add(
39-
WebDropItem(
40-
uri: html.Url.createObjectUrl(item),
41-
name: item.name,
42-
size: item.size,
43-
type: item.type,
44-
relativePath: item.relativePath,
45-
lastModified: item.lastModified != null
46-
? DateTime.fromMillisecondsSinceEpoch(item.lastModified!)
47-
: item.lastModifiedDate,
48-
),
49-
);
53+
html.window.onDrop.listen(
54+
(event) {
55+
event.preventDefault();
56+
_dataTransfer = null;
57+
final results = <WebDropItem>[];
58+
59+
try {
60+
final items = event.dataTransfer.files;
61+
if (items != null) {
62+
for (final item in items) {
63+
results.add(
64+
WebDropItem(
65+
uri: html.Url.createObjectUrl(item),
66+
name: item.name,
67+
size: item.size,
68+
type: item.type,
69+
relativePath: item.relativePath,
70+
lastModified: item.lastModified != null
71+
? DateTime.fromMillisecondsSinceEpoch(item.lastModified!)
72+
: item.lastModifiedDate,
73+
),
74+
);
75+
}
5076
}
77+
} catch (e, s) {
78+
debugPrint('desktop_drop_web: $e $s');
79+
} finally {
80+
channel.invokeMethod(
81+
"performOperation_web",
82+
results.map((e) => e.toJson()).toList(),
83+
);
5184
}
52-
} catch (e, s) {
53-
debugPrint('desktop_drop_web: $e $s');
54-
} finally {
55-
channel.invokeMethod(
56-
"performOperation_web",
57-
results.map((e) => e.toJson()).toList(),
58-
);
59-
}
60-
});
61-
62-
html.window.onDragEnter.listen((event) {
63-
event.preventDefault();
64-
channel.invokeMethod('entered', [
65-
event.client.x.toDouble(),
66-
event.client.y.toDouble(),
67-
]);
68-
});
69-
70-
html.window.onDragOver.listen((event) {
71-
event.preventDefault();
72-
channel.invokeMethod('updated', [
73-
event.client.x.toDouble(),
74-
event.client.y.toDouble(),
75-
]);
76-
});
85+
},
86+
);
7787

78-
html.window.onDragLeave.listen((event) {
79-
event.preventDefault();
80-
channel.invokeMethod('exited', [
81-
event.client.x.toDouble(),
82-
event.client.y.toDouble(),
83-
]);
84-
});
88+
html.window.onDragLeave.listen(
89+
(event) {
90+
event.preventDefault();
91+
_dataTransfer = null;
92+
channel.invokeMethod('exited', [
93+
event.client.x.toDouble(),
94+
event.client.y.toDouble(),
95+
]);
96+
},
97+
);
8598
}
8699

87100
Future<dynamic> handleMethodCall(MethodCall call) async {
101+
switch (call.method) {
102+
case 'updateDroppableStatus':
103+
final enable = call.arguments as bool;
104+
final current = _dataTransfer?.dropEffect;
105+
final newValue = enable ? 'copy' : 'move';
106+
if (current != newValue) {
107+
_dataTransfer?.dropEffect = newValue;
108+
}
109+
return;
110+
default:
111+
break;
112+
}
113+
88114
throw PlatformException(
89115
code: 'Unimplemented',
90116
details: 'desktop_drop for web doesn\'t implement \'${call.method}\'',

packages/desktop_drop/lib/src/channel.dart

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import 'drop_item.dart';
88
import 'events.dart';
99
import 'utils/platform.dart' if (dart.library.html) 'utils/platform_web.dart';
1010

11-
typedef RawDropListener = void Function(DropEvent);
11+
abstract class RawDropListener {
12+
void onEvent(DropEvent event);
13+
bool isInBounds(DropEvent event);
14+
}
1215

1316
class DesktopDrop {
1417
static const MethodChannel _channel = MethodChannel('desktop_drop');
@@ -19,22 +22,24 @@ class DesktopDrop {
1922

2023
final _listeners = <RawDropListener>{};
2124

22-
var _inited = false;
25+
var _initialized = false;
2326

2427
Offset? _offset;
2528

2629
void init() {
27-
if (_inited) {
30+
if (_initialized) {
2831
return;
2932
}
30-
_inited = true;
31-
_channel.setMethodCallHandler((call) async {
32-
try {
33-
return await _handleMethodChannel(call);
34-
} catch (e, s) {
35-
debugPrint('_handleMethodChannel: $e $s');
36-
}
37-
});
33+
_initialized = true;
34+
_channel.setMethodCallHandler(
35+
(call) async {
36+
try {
37+
return await _handleMethodChannel(call);
38+
} catch (e, s) {
39+
debugPrint('_handleMethodChannel: $e $s');
40+
}
41+
},
42+
);
3843
}
3944

4045
Future<void> _handleMethodChannel(MethodCall call) async {
@@ -45,15 +50,14 @@ class DesktopDrop {
4550
_notifyEvent(DropEnterEvent(location: _offset!));
4651
break;
4752
case "updated":
48-
if (_offset == null && Platform.isLinux) {
49-
final position = (call.arguments as List).cast<double>();
50-
_offset = Offset(position[0], position[1]);
51-
_notifyEvent(DropEnterEvent(location: _offset!));
52-
return;
53-
}
5453
final position = (call.arguments as List).cast<double>();
54+
final previousOffset = _offset;
5555
_offset = Offset(position[0], position[1]);
56-
_notifyEvent(DropUpdateEvent(location: _offset!));
56+
if (previousOffset == null) {
57+
_notifyEvent(DropEnterEvent(location: _offset!));
58+
} else {
59+
_notifyEvent(DropUpdateEvent(location: _offset!));
60+
}
5761
break;
5862
case "exited":
5963
_notifyEvent(DropExitEvent(location: _offset ?? Offset.zero));
@@ -109,9 +113,19 @@ class DesktopDrop {
109113
}
110114

111115
void _notifyEvent(DropEvent event) {
112-
for (final listener in _listeners) {
113-
listener(event);
116+
final reversedListeners = _listeners.toList(growable: false).reversed;
117+
var foundTargetListener = false;
118+
for (final listener in reversedListeners) {
119+
final isInBounds = listener.isInBounds(event);
120+
if (isInBounds && !foundTargetListener) {
121+
foundTargetListener = true;
122+
listener.onEvent(event);
123+
} else {
124+
listener.onEvent(DropExitEvent(location: event.location));
125+
}
114126
}
127+
128+
_channel.invokeMethod('updateDroppableStatus', foundTargetListener);
115129
}
116130

117131
void addRawDropEventListener(RawDropListener listener) {

0 commit comments

Comments
 (0)