Skip to content

Commit 564e807

Browse files
committed
feat: support multiple drop targets
1 parent 0334bba commit 564e807

File tree

3 files changed

+184
-144
lines changed

3 files changed

+184
-144
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: 35 additions & 28 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,51 +22,48 @@ 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 {
4146
switch (call.method) {
4247
case "entered":
43-
assert(_offset == null);
4448
final position = (call.arguments as List).cast<double>();
4549
_offset = Offset(position[0], position[1]);
4650
_notifyEvent(DropEnterEvent(location: _offset!));
4751
break;
4852
case "updated":
49-
if (_offset == null && Platform.isLinux) {
50-
final position = (call.arguments as List).cast<double>();
51-
_offset = Offset(position[0], position[1]);
52-
_notifyEvent(DropEnterEvent(location: _offset!));
53-
return;
54-
}
55-
assert(_offset != null);
5653
final position = (call.arguments as List).cast<double>();
54+
final previousOffset = _offset;
5755
_offset = Offset(position[0], position[1]);
58-
_notifyEvent(DropUpdateEvent(location: _offset!));
56+
if (previousOffset == null) {
57+
_notifyEvent(DropEnterEvent(location: _offset!));
58+
} else {
59+
_notifyEvent(DropUpdateEvent(location: _offset!));
60+
}
5961
break;
6062
case "exited":
61-
assert(_offset != null);
6263
_notifyEvent(DropExitEvent(location: _offset ?? Offset.zero));
6364
_offset = null;
6465
break;
6566
case "performOperation":
66-
assert(_offset != null);
6767
final paths = (call.arguments as List).cast<String>();
6868
_notifyEvent(
6969
DropDoneEvent(
@@ -75,10 +75,8 @@ class DesktopDrop {
7575
break;
7676
case "performOperation_linux":
7777
// gtk notify 'exit' before 'performOperation'.
78-
assert(_offset == null);
7978
final text = (call.arguments as List<dynamic>)[0] as String;
80-
final offset = ((call.arguments as List<dynamic>)[1] as List<dynamic>)
81-
.cast<double>();
79+
final offset = ((call.arguments as List<dynamic>)[1] as List<dynamic>).cast<double>();
8280
final paths = const LineSplitter().convert(text).map((e) {
8381
try {
8482
return Uri.tryParse(e)?.toFilePath() ?? '';
@@ -93,7 +91,6 @@ class DesktopDrop {
9391
));
9492
break;
9593
case "performOperation_web":
96-
assert(_offset != null);
9794
final results = (call.arguments as List)
9895
.cast<Map>()
9996
.map((e) => WebDropItem.fromJson(e.cast<String, dynamic>()))
@@ -116,9 +113,19 @@ class DesktopDrop {
116113
}
117114

118115
void _notifyEvent(DropEvent event) {
119-
for (final listener in _listeners) {
120-
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+
}
121126
}
127+
128+
_channel.invokeMethod('updateDroppableStatus', foundTargetListener);
122129
}
123130

124131
void addRawDropEventListener(RawDropListener listener) {

0 commit comments

Comments
 (0)