Skip to content
This repository was archived by the owner on Apr 29, 2021. It is now read-only.

Commit bc20316

Browse files
committed
[Feature] Add Drag and Drop feature between UnityObject and UIWidgets
1 parent 3c6e377 commit bc20316

13 files changed

+695
-34
lines changed
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
using System.Collections.Generic;
2+
using Unity.UIWidgets.foundation;
3+
using Unity.UIWidgets.scheduler;
4+
using UnityEditor;
5+
6+
namespace Unity.UIWidgets.gestures {
7+
public partial class MouseTracker {
8+
bool _enableDragFromEditorRelease = false;
9+
10+
void _handleDragFromEditorEvent(PointerEvent evt, int deviceId) {
11+
if (evt is PointerDragFromEditorReleaseEvent) {
12+
this._enableDragFromEditorRelease = false;
13+
this._scheduleDragFromEditorReleaseCheck();
14+
this._lastMouseEvent.Remove(deviceId);
15+
}
16+
else if (evt is PointerDragFromEditorEnterEvent ||
17+
evt is PointerDragFromEditorHoverEvent ||
18+
evt is PointerDragFromEditorExitEvent) {
19+
if (!this._lastMouseEvent.ContainsKey(deviceId) ||
20+
this._lastMouseEvent[deviceId].position != evt.position) {
21+
// Only schedule a frame if we have our first event, or if the
22+
// location of the mouse has changed, and only if there are tracked annotations.
23+
this._scheduleDragFromEditorMousePositionCheck();
24+
}
25+
26+
this._lastMouseEvent[deviceId] = evt;
27+
}
28+
}
29+
30+
void _scheduleDragFromEditorReleaseCheck() {
31+
DragAndDrop.AcceptDrag();
32+
33+
var lastMouseEvent = new List<(PointerEvent, int)>();
34+
foreach (int deviceId in this._lastMouseEvent.Keys) {
35+
lastMouseEvent.Add((this._lastMouseEvent[deviceId], deviceId));
36+
}
37+
38+
SchedulerBinding.instance.addPostFrameCallback(_ => {
39+
foreach (var lastEvent in lastMouseEvent) {
40+
MouseTrackerAnnotation hit = this.annotationFinder(lastEvent.Item1.position);
41+
42+
if (hit == null) {
43+
foreach (_TrackedAnnotation trackedAnnotation in this._trackedAnnotations.Values) {
44+
if (trackedAnnotation.activeDevices.Contains(lastEvent.Item2)) {
45+
trackedAnnotation.activeDevices.Remove(lastEvent.Item2);
46+
}
47+
}
48+
49+
return;
50+
}
51+
52+
_TrackedAnnotation hitAnnotation = this._findAnnotation(hit);
53+
54+
// release
55+
if (hitAnnotation.activeDevices.Contains(lastEvent.Item2)) {
56+
if (hitAnnotation.annotation?.onDragFromEditorRelease != null) {
57+
hitAnnotation.annotation.onDragFromEditorRelease(
58+
PointerDragFromEditorReleaseEvent
59+
.fromDragFromEditorEvent(
60+
lastEvent.Item1, DragAndDrop.objectReferences));
61+
}
62+
63+
hitAnnotation.activeDevices.Remove(lastEvent.Item2);
64+
}
65+
}
66+
});
67+
SchedulerBinding.instance.scheduleFrame();
68+
}
69+
70+
/// <summary>
71+
/// Due to the [DragAndDrop] property, DragAndDrop.visualMode must be set to Copy
72+
/// after which editor window can trigger DragPerform event.
73+
/// And because visualMode will be set to None when every frame finished in IMGUI,
74+
/// here we start a scheduler to update VisualMode in every post frame.
75+
/// When [_enableDragFromEditorRelease] set to false, it will stop, vice versa.
76+
/// </summary>
77+
void _enableDragFromEditorReleaseVisualModeLoop() {
78+
if (this._enableDragFromEditorRelease) {
79+
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
80+
SchedulerBinding.instance.addPostFrameCallback(_ => {
81+
this._enableDragFromEditorReleaseVisualModeLoop();
82+
});
83+
SchedulerBinding.instance.scheduleFrame();
84+
}
85+
}
86+
87+
void _scheduleDragFromEditorMousePositionCheck() {
88+
SchedulerBinding.instance.addPostFrameCallback(_ => { this.collectDragFromEditorMousePositions(); });
89+
SchedulerBinding.instance.scheduleFrame();
90+
}
91+
92+
public void collectDragFromEditorMousePositions() {
93+
void exitAnnotation(_TrackedAnnotation trackedAnnotation, int deviceId) {
94+
if (trackedAnnotation.activeDevices.Contains(deviceId)) {
95+
this._enableDragFromEditorRelease = false;
96+
if (trackedAnnotation.annotation?.onDragFromEditorExit != null) {
97+
trackedAnnotation.annotation.onDragFromEditorExit(
98+
PointerDragFromEditorExitEvent.fromDragFromEditorEvent(
99+
this._lastMouseEvent[deviceId]));
100+
}
101+
102+
trackedAnnotation.activeDevices.Remove(deviceId);
103+
}
104+
}
105+
106+
void exitAllDevices(_TrackedAnnotation trackedAnnotation) {
107+
if (trackedAnnotation.activeDevices.isNotEmpty()) {
108+
HashSet<int> deviceIds = new HashSet<int>(trackedAnnotation.activeDevices);
109+
foreach (int deviceId in deviceIds) {
110+
exitAnnotation(trackedAnnotation, deviceId);
111+
}
112+
}
113+
}
114+
115+
if (!this.mouseIsConnected) {
116+
foreach (var annotation in this._trackedAnnotations.Values) {
117+
exitAllDevices(annotation);
118+
}
119+
120+
return;
121+
}
122+
123+
foreach (int deviceId in this._lastMouseEvent.Keys) {
124+
PointerEvent lastEvent = this._lastMouseEvent[deviceId];
125+
MouseTrackerAnnotation hit = this.annotationFinder(lastEvent.position);
126+
127+
if (hit == null) {
128+
foreach (_TrackedAnnotation trackedAnnotation in this._trackedAnnotations.Values) {
129+
exitAnnotation(trackedAnnotation, deviceId);
130+
}
131+
132+
return;
133+
}
134+
135+
_TrackedAnnotation hitAnnotation = this._findAnnotation(hit);
136+
137+
// While acrossing two areas, set the flag to true to prevent setting the Pointer Copy VisualMode to None
138+
bool enterFlag = false;
139+
140+
// enter
141+
if (!hitAnnotation.activeDevices.Contains(deviceId)) {
142+
hitAnnotation.activeDevices.Add(deviceId);
143+
enterFlag = true;
144+
// Both onRelease or onEnter event will enable Copy VisualMode
145+
if (hitAnnotation.annotation?.onDragFromEditorRelease != null ||
146+
hitAnnotation.annotation?.onDragFromEditorEnter != null) {
147+
if (!this._enableDragFromEditorRelease) {
148+
this._enableDragFromEditorRelease = true;
149+
this._enableDragFromEditorReleaseVisualModeLoop();
150+
}
151+
152+
if (hitAnnotation.annotation?.onDragFromEditorEnter != null) {
153+
hitAnnotation.annotation.onDragFromEditorEnter(
154+
PointerDragFromEditorEnterEvent
155+
.fromDragFromEditorEvent(lastEvent));
156+
}
157+
}
158+
}
159+
160+
// hover
161+
if (hitAnnotation.annotation?.onDragFromEditorHover != null) {
162+
hitAnnotation.annotation.onDragFromEditorHover(
163+
PointerDragFromEditorHoverEvent.fromDragFromEditorEvent(lastEvent));
164+
}
165+
166+
// leave
167+
foreach (_TrackedAnnotation trackedAnnotation in this._trackedAnnotations.Values) {
168+
if (hitAnnotation == trackedAnnotation) {
169+
continue;
170+
}
171+
172+
if (trackedAnnotation.activeDevices.Contains(deviceId)) {
173+
if (!enterFlag) {
174+
this._enableDragFromEditorRelease = false;
175+
}
176+
177+
if (trackedAnnotation.annotation?.onDragFromEditorExit != null) {
178+
trackedAnnotation.annotation.onDragFromEditorExit(
179+
PointerDragFromEditorExitEvent
180+
.fromDragFromEditorEvent(lastEvent));
181+
}
182+
183+
trackedAnnotation.activeDevices.Remove(deviceId);
184+
}
185+
}
186+
}
187+
}
188+
}
189+
}

Runtime/editor/editor_mouse_tracking.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/editor/editor_window.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ protected virtual TimeSpan getTime() {
163163

164164
protected float deltaTime;
165165
protected float unscaledDeltaTime;
166-
166+
167167
void updatePhysicalSize() {
168168
var size = this.queryWindowSize();
169169
this._physicalSize = new Size(
@@ -183,7 +183,7 @@ protected virtual void updateSafeArea() {
183183
public void onViewMetricsChanged() {
184184
this._viewMetricsChanged = true;
185185
}
186-
186+
187187
protected abstract bool hasFocus();
188188

189189
public void OnEnable() {
@@ -400,6 +400,26 @@ void _doOnGUI(Event evt) {
400400
evt.button
401401
);
402402
}
403+
else if (evt.type == EventType.DragUpdated) {
404+
pointerData = new PointerData(
405+
timeStamp: Timer.timespanSinceStartup,
406+
change: PointerChange.dragFromEditorMove,
407+
kind: PointerDeviceKind.mouse,
408+
device: evt.button,
409+
physicalX: evt.mousePosition.x * this._devicePixelRatio,
410+
physicalY: evt.mousePosition.y * this._devicePixelRatio
411+
);
412+
}
413+
else if (evt.type == EventType.DragPerform) {
414+
pointerData = new PointerData(
415+
timeStamp: Timer.timespanSinceStartup,
416+
change: PointerChange.dragFromEditorRelease,
417+
kind: PointerDeviceKind.mouse,
418+
device: evt.button,
419+
physicalX: evt.mousePosition.x * this._devicePixelRatio,
420+
physicalY: evt.mousePosition.y * this._devicePixelRatio
421+
);
422+
}
403423

404424
if (pointerData != null) {
405425
this.onPointerEvent(new PointerDataPacket(new List<PointerData> {

Runtime/editor/widgets.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Unity.UIWidgets.foundation;
5+
using Unity.UIWidgets.gestures;
6+
using Unity.UIWidgets.rendering;
7+
using Unity.UIWidgets.widgets;
8+
using UnityEngine;
9+
using Object = UnityEngine.Object;
10+
11+
namespace Unity.UIWidgets.editor {
12+
public delegate void DragFromEditorEnterCallback();
13+
14+
public delegate void DragFromEditorHoverCallback();
15+
16+
public delegate void DragFromEditorExitCallback();
17+
18+
public delegate void DragFromEditorReleaseCallback(DragFromEditorDetails details);
19+
20+
public class DragFromEditorDetails {
21+
public DragFromEditorDetails(Object[] objectReferences) {
22+
this.objectReferences = objectReferences;
23+
}
24+
25+
public readonly Object[] objectReferences;
26+
}
27+
28+
public class UnityObjectDetector : StatefulWidget {
29+
public UnityObjectDetector(
30+
Key key = null,
31+
Widget child = null,
32+
DragFromEditorEnterCallback onEnter = null,
33+
DragFromEditorHoverCallback onHover = null,
34+
DragFromEditorExitCallback onExit = null,
35+
DragFromEditorReleaseCallback onRelease = null,
36+
HitTestBehavior? behavior = null
37+
) : base(key: key) {
38+
this.child = child;
39+
this.onDragFromEditorEnter = onEnter;
40+
this.onDragFromEditorHover = onHover;
41+
this.onDragFromEditorExit = onExit;
42+
this.onDragFromEditorRelease = onRelease;
43+
this.behavior = behavior;
44+
}
45+
46+
public readonly Widget child;
47+
48+
public readonly DragFromEditorEnterCallback onDragFromEditorEnter;
49+
public readonly DragFromEditorHoverCallback onDragFromEditorHover;
50+
public readonly DragFromEditorExitCallback onDragFromEditorExit;
51+
public readonly DragFromEditorReleaseCallback onDragFromEditorRelease;
52+
53+
public readonly HitTestBehavior? behavior;
54+
55+
public override State createState() {
56+
return new UnityObjectDetectorState();
57+
}
58+
}
59+
60+
public class UnityObjectDetectorState : State<UnityObjectDetector> {
61+
HitTestBehavior _defaultBehavior {
62+
get { return this.widget.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild; }
63+
}
64+
65+
public override Widget build(BuildContext context) {
66+
Widget result = new Listener(
67+
child: this.widget.child,
68+
onPointerDragFromEditorEnter: this.widget.onDragFromEditorEnter == null
69+
? ((PointerDragFromEditorEnterEventListener) null)
70+
: (evt) => { this.widget.onDragFromEditorEnter.Invoke(); },
71+
onPointerDragFromEditorHover: this.widget.onDragFromEditorHover == null
72+
? ((PointerDragFromEditorHoverEventListener) null)
73+
: (evt) => { this.widget.onDragFromEditorHover.Invoke(); },
74+
onPointerDragFromEditorExit: this.widget.onDragFromEditorExit == null
75+
? ((PointerDragFromEditorExitEventListener) null)
76+
: (evt) => { this.widget.onDragFromEditorExit.Invoke(); },
77+
onPointerDragFromEditorRelease: this.widget.onDragFromEditorRelease == null
78+
? ((PointerDragFromEditorReleaseEventListener) null)
79+
: (evt) => {
80+
this.widget.onDragFromEditorRelease.Invoke(new DragFromEditorDetails(evt.objectReferences));
81+
},
82+
behavior: this.widget.behavior ?? this._defaultBehavior
83+
);
84+
return result;
85+
}
86+
}
87+
88+
}

Runtime/editor/widgets/unity_object_detector.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/gestures/binding.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ void _handlePointerEvent(PointerEvent evt) {
9494
if (hitTestResult != null ||
9595
evt is PointerHoverEvent ||
9696
evt is PointerAddedEvent ||
97-
evt is PointerRemovedEvent
97+
evt is PointerRemovedEvent ||
98+
evt is PointerDragFromEditorHoverEvent ||
99+
evt is PointerDragFromEditorReleaseEvent
98100
) {
99101
this.dispatchEvent(evt, hitTestResult);
100102
}
@@ -115,7 +117,11 @@ public virtual void hitTest(HitTestResult result, Offset position) {
115117

116118
public void dispatchEvent(PointerEvent evt, HitTestResult hitTestResult) {
117119
if (hitTestResult == null) {
118-
D.assert(evt is PointerHoverEvent || evt is PointerAddedEvent || evt is PointerRemovedEvent);
120+
D.assert(evt is PointerHoverEvent ||
121+
evt is PointerAddedEvent ||
122+
evt is PointerRemovedEvent ||
123+
evt is PointerDragFromEditorHoverEvent ||
124+
evt is PointerDragFromEditorReleaseEvent);
119125
try {
120126
this.pointerRouter.route(evt);
121127
}

0 commit comments

Comments
 (0)