|
|
|
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Linq; |
|
|
|
using Unity.UIWidgets.foundation; |
|
|
|
|
|
|
|
|
|
|
public delegate void PointerExitEventListener(PointerExitEvent evt); |
|
|
|
|
|
|
|
public delegate void PointerDragFromEditorEnterEventListener(PointerDragFromEditorEnterEvent evt); |
|
|
|
|
|
|
|
public delegate void PointerDragFromEditorHoverEventListener(PointerDragFromEditorHoverEvent evt); |
|
|
|
|
|
|
|
public delegate void PointerDragFromEditorExitEventListener(PointerDragFromEditorExitEvent evt); |
|
|
|
public delegate void _UpdatedDeviceHandler(_MouseState mouseState, |
|
|
|
HashSet<MouseTrackerAnnotation> previousAnnotations); |
|
|
|
public delegate void PointerDragFromEditorReleaseEventListener(PointerDragFromEditorReleaseEvent evt); |
|
|
|
public delegate void _UpdatedDeviceHandler(_MouseState mouseState, HashSet<MouseTrackerAnnotation> previousAnnotations); |
|
|
|
public class _MouseState { |
|
|
|
PointerEvent _latestEvent; |
|
|
|
|
|
|
|
public class _MouseState { |
|
|
|
public _MouseState(PointerEvent initialEvent = null |
|
|
|
) { |
|
|
|
D.assert(initialEvent != null); |
|
|
|
|
|
|
public HashSet<MouseTrackerAnnotation> annotations { |
|
|
|
get { return _annotations;} |
|
|
|
} |
|
|
|
HashSet<MouseTrackerAnnotation> _annotations = new HashSet<MouseTrackerAnnotation>(); |
|
|
|
|
|
|
|
public HashSet<MouseTrackerAnnotation> replaceAnnotations(HashSet<MouseTrackerAnnotation> value) { |
|
|
|
HashSet<MouseTrackerAnnotation> previous = _annotations; |
|
|
|
_annotations = value; |
|
|
|
return previous; |
|
|
|
} |
|
|
|
public HashSet<MouseTrackerAnnotation> annotations { get; private set; } = |
|
|
|
new HashSet<MouseTrackerAnnotation>(); |
|
|
|
|
|
|
|
// The most recently processed mouse event observed from this device.
|
|
|
|
public PointerEvent latestEvent { |
|
|
|
|
|
|
_latestEvent = value; |
|
|
|
} |
|
|
|
} |
|
|
|
PointerEvent _latestEvent; |
|
|
|
get { return latestEvent.device;} |
|
|
|
get { return latestEvent.device; } |
|
|
|
} |
|
|
|
|
|
|
|
public HashSet<MouseTrackerAnnotation> replaceAnnotations(HashSet<MouseTrackerAnnotation> value) { |
|
|
|
var previous = annotations; |
|
|
|
annotations = value; |
|
|
|
return previous; |
|
|
|
return Event == null ? "null" : describeIdentity(Event); |
|
|
|
return Event == null ? "null" : describeIdentity(Object: Event); |
|
|
|
string describeLatestEvent = $"latestEvent: {describeEvent(latestEvent)}"; |
|
|
|
string describeAnnotations = $"annotations: [list of {annotations.Count}]"; |
|
|
|
return $"{describeIdentity(this)}"+$"({describeLatestEvent}, {describeAnnotations})"; |
|
|
|
|
|
|
|
var describeLatestEvent = $"latestEvent: {describeEvent(Event: latestEvent)}"; |
|
|
|
var describeAnnotations = $"annotations: [list of {annotations.Count}]"; |
|
|
|
return $"{describeIdentity(this)}" + $"({describeLatestEvent}, {describeAnnotations})"; |
|
|
|
|
|
|
|
public class MouseTrackerAnnotation { |
|
|
|
|
|
|
|
public class MouseTrackerAnnotation : Diagnosticable { |
|
|
|
public readonly PointerEnterEventListener onEnter; |
|
|
|
|
|
|
|
public readonly PointerExitEventListener onExit; |
|
|
|
|
|
|
|
public readonly PointerHoverEventListener onHover; |
|
|
|
|
|
|
|
PointerExitEventListener onExit = null, |
|
|
|
PointerDragFromEditorEnterEventListener onDragFromEditorEnter = null, |
|
|
|
PointerDragFromEditorHoverEventListener onDragFromEditorHover = null, |
|
|
|
PointerDragFromEditorExitEventListener onDragFromEditorExit = null, |
|
|
|
PointerDragFromEditorReleaseEventListener onDragFromEditorRelease = null |
|
|
|
PointerExitEventListener onExit = null |
|
|
|
|
|
|
|
this.onDragFromEditorEnter = onDragFromEditorEnter; |
|
|
|
this.onDragFromEditorHover = onDragFromEditorHover; |
|
|
|
this.onDragFromEditorExit = onDragFromEditorExit; |
|
|
|
this.onDragFromEditorRelease = onDragFromEditorRelease; |
|
|
|
public readonly PointerEnterEventListener onEnter; |
|
|
|
|
|
|
|
public readonly PointerHoverEventListener onHover; |
|
|
|
|
|
|
|
public readonly PointerExitEventListener onExit; |
|
|
|
|
|
|
|
public readonly PointerDragFromEditorEnterEventListener onDragFromEditorEnter; |
|
|
|
public readonly PointerDragFromEditorHoverEventListener onDragFromEditorHover; |
|
|
|
public readonly PointerDragFromEditorExitEventListener onDragFromEditorExit; |
|
|
|
public readonly PointerDragFromEditorReleaseEventListener onDragFromEditorRelease; |
|
|
|
|
|
|
|
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
|
|
|
base.debugFillProperties(properties: properties); |
|
|
|
properties.add(new FlagsSummary<Delegate>( |
|
|
|
"callbacks", |
|
|
|
new Dictionary<string, Delegate> { |
|
|
|
{"enter", onEnter}, |
|
|
|
{"hover", onHover}, |
|
|
|
{"exit", onExit} |
|
|
|
}, |
|
|
|
"<none>" |
|
|
|
)); |
|
|
|
} |
|
|
|
public readonly MouseTrackerAnnotation annotation; |
|
|
|
|
|
|
|
public HashSet<int> activeDevices = new HashSet<int>(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
public delegate IEnumerable<MouseTrackerAnnotation> MouseDetectorAnnotationFinder(Offset offset); |
|
|
|
|
|
|
|
public class MouseTracker : ChangeNotifier { |
|
|
|
public readonly Dictionary<int, _MouseState> _mouseStates = new Dictionary<int, _MouseState>(); |
|
|
|
readonly PointerRouter _router; |
|
|
|
public readonly MouseTrackerAnnotation annotation; |
|
|
|
public readonly Dictionary<MouseTrackerAnnotation, _TrackedAnnotation> _trackedAnnotations = |
|
|
|
new Dictionary<MouseTrackerAnnotation, _TrackedAnnotation>(); |
|
|
|
public HashSet<int> activeDevices = new HashSet<int>(); |
|
|
|
} |
|
|
|
public readonly MouseDetectorAnnotationFinder annotationFinder; |
|
|
|
public delegate IEnumerable<MouseTrackerAnnotation> MouseDetectorAnnotationFinder(Offset offset); |
|
|
|
bool _duringDeviceUpdate; |
|
|
|
public bool _hasScheduledPostFrameCheck; |
|
|
|
public class MouseTracker : ChangeNotifier{ |
|
|
|
|
|
|
|
router.addGlobalRoute(_handleEvent); |
|
|
|
router.addGlobalRoute(route: _handleEvent); |
|
|
|
|
|
|
|
public bool mouseIsConnected { |
|
|
|
get { return _mouseStates.isNotEmpty(); } |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public static bool _duringBuildPhase { |
|
|
|
get { return SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks; } |
|
|
|
} |
|
|
|
|
|
|
|
_router.removeGlobalRoute(_handleEvent); |
|
|
|
_router.removeGlobalRoute(route: _handleEvent); |
|
|
|
|
|
|
|
public readonly MouseDetectorAnnotationFinder annotationFinder; |
|
|
|
readonly Dictionary<int, PointerEvent> _lastMouseEvent = new Dictionary<int, PointerEvent>(); |
|
|
|
readonly PointerRouter _router; |
|
|
|
public readonly Dictionary<int, _MouseState> _mouseStates = new Dictionary<int, _MouseState>(); |
|
|
|
|
|
|
|
public bool mouseIsConnected { |
|
|
|
get { return _lastMouseEvent.isNotEmpty(); } |
|
|
|
} |
|
|
|
|
|
|
|
if (state == null) |
|
|
|
if (state == null) { |
|
|
|
} |
|
|
|
|
|
|
|
PointerEvent lastEvent = state.latestEvent; |
|
|
|
var lastEvent = state.latestEvent; |
|
|
|
|
|
|
|
D.assert((value is PointerAddedEvent) == (lastEvent is PointerRemovedEvent)); |
|
|
|
if (value is PointerSignalEvent) |
|
|
|
|
|
|
|
D.assert(value is PointerAddedEvent == lastEvent is PointerRemovedEvent); |
|
|
|
if (value is PointerSignalEvent) { |
|
|
|
} |
|
|
|
|
|
|
|
public readonly Dictionary<MouseTrackerAnnotation, _TrackedAnnotation> _trackedAnnotations = |
|
|
|
new Dictionary<MouseTrackerAnnotation, _TrackedAnnotation>(); |
|
|
|
|
|
|
|
if (Event.kind != PointerDeviceKind.mouse) |
|
|
|
if (Event.kind != PointerDeviceKind.mouse) { |
|
|
|
if (Event is PointerSignalEvent) |
|
|
|
} |
|
|
|
|
|
|
|
if (Event is PointerSignalEvent) { |
|
|
|
int device = Event.device; |
|
|
|
_MouseState existingState = _mouseStates.getOrDefault(device); |
|
|
|
if (!_shouldMarkStateDirty(existingState, Event)) |
|
|
|
} |
|
|
|
|
|
|
|
var device = Event.device; |
|
|
|
var existingState = _mouseStates.getOrDefault(key: device); |
|
|
|
if (!_shouldMarkStateDirty(state: existingState, value: Event)) { |
|
|
|
PointerEvent previousEvent = existingState?.latestEvent; |
|
|
|
} |
|
|
|
|
|
|
|
var previousEvent = existingState?.latestEvent; |
|
|
|
handleUpdatedDevice: (_MouseState mouseState, HashSet<MouseTrackerAnnotation> previousAnnotations) =>{ |
|
|
|
D.assert(mouseState.device == Event.device); |
|
|
|
_dispatchDeviceCallbacks( |
|
|
|
(mouseState, previousAnnotations) => { |
|
|
|
D.assert(mouseState.device == Event.device); |
|
|
|
_dispatchDeviceCallbacks( |
|
|
|
lastAnnotations: previousAnnotations, |
|
|
|
nextAnnotations: mouseState.annotations, |
|
|
|
previousEvent: previousEvent, |
|
|
|
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
public HashSet<MouseTrackerAnnotation> _findAnnotations(_MouseState state) { |
|
|
|
var globalPosition = state.latestEvent.position; |
|
|
|
var device = state.device; |
|
|
|
var result = new HashSet<MouseTrackerAnnotation>(); |
|
|
|
foreach (var values in annotationFinder(offset: globalPosition)) { |
|
|
|
result.Add(item: values); |
|
|
|
} |
|
|
|
|
|
|
|
return _mouseStates.ContainsKey(key: device) |
|
|
|
? result |
|
|
|
: new HashSet<MouseTrackerAnnotation>(); |
|
|
|
} |
|
|
|
|
|
|
|
public void _updateAllDevices() { |
|
|
|
_updateDevices( |
|
|
|
handleUpdatedDevice: (mouseState, previousAnnotations) => { |
|
|
|
_dispatchDeviceCallbacks( |
|
|
|
lastAnnotations: previousAnnotations, |
|
|
|
nextAnnotations: mouseState.annotations, |
|
|
|
previousEvent: mouseState.latestEvent |
|
|
|
); |
|
|
|
} |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
void _updateDevices( |
|
|
|
PointerEvent targetEvent = null, |
|
|
|
_UpdatedDeviceHandler handleUpdatedDevice = null) { |
|
|
|
D.assert(handleUpdatedDevice != null); |
|
|
|
D.assert(result: !_duringBuildPhase); |
|
|
|
D.assert(result: !_duringDeviceUpdate); |
|
|
|
var mouseWasConnected = mouseIsConnected; |
|
|
|
|
|
|
|
_MouseState targetState = null; |
|
|
|
if (targetEvent != null) { |
|
|
|
targetState = _mouseStates.getOrDefault(key: targetEvent.device); |
|
|
|
if (targetState == null) { |
|
|
|
targetState = new _MouseState(initialEvent: targetEvent); |
|
|
|
_mouseStates[key: targetState.device] = targetState; |
|
|
|
} |
|
|
|
else { |
|
|
|
D.assert(!(targetEvent is PointerAddedEvent)); |
|
|
|
targetState.latestEvent = targetEvent; |
|
|
|
if (targetEvent is PointerRemovedEvent) { |
|
|
|
_mouseStates.Remove(key: targetEvent.device); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
D.assert(targetState == null == (targetEvent == null)); |
|
|
|
|
|
|
|
D.assert(() => { |
|
|
|
_duringDeviceUpdate = true; |
|
|
|
return true; |
|
|
|
}); |
|
|
|
|
|
|
|
var dirtyStates = targetEvent == null |
|
|
|
? (IEnumerable<_MouseState>) _mouseStates.Values |
|
|
|
: new List<_MouseState> {targetState}; |
|
|
|
foreach (var dirtyState in dirtyStates) { |
|
|
|
var nextAnnotations = _findAnnotations(state: dirtyState); |
|
|
|
var lastAnnotations = dirtyState.replaceAnnotations(value: nextAnnotations); |
|
|
|
handleUpdatedDevice(mouseState: dirtyState, previousAnnotations: lastAnnotations); |
|
|
|
} |
|
|
|
|
|
|
|
D.assert(() => { |
|
|
|
_duringDeviceUpdate = false; |
|
|
|
return true; |
|
|
|
}); |
|
|
|
|
|
|
|
if (mouseWasConnected != mouseIsConnected) { |
|
|
|
notifyListeners(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
) { |
|
|
|
) { |
|
|
|
PointerEvent latestEvent = unhandledEvent ?? previousEvent; |
|
|
|
var latestEvent = unhandledEvent ?? previousEvent; |
|
|
|
var exiting = new List<MouseTrackerAnnotation>(); |
|
|
|
if (!nextAnnotations.Contains(lastAnnotation)) { |
|
|
|
exitingAnnotations.Append(lastAnnotation); |
|
|
|
if (!nextAnnotations.Contains(item: lastAnnotation)) { |
|
|
|
exiting.Add(item: lastAnnotation); |
|
|
|
foreach ( MouseTrackerAnnotation annotation in exitingAnnotations) { |
|
|
|
|
|
|
|
exitingAnnotations = exiting; |
|
|
|
foreach (var annotation in exitingAnnotations) { |
|
|
|
annotation.onExit(PointerExitEvent.fromMouseEvent(latestEvent)); |
|
|
|
annotation.onExit(PointerExitEvent.fromMouseEvent(hover: latestEvent)); |
|
|
|
|
|
|
|
List<MouseTrackerAnnotation> entering = new List<MouseTrackerAnnotation>(); |
|
|
|
var entering = new List<MouseTrackerAnnotation>(); |
|
|
|
if (!lastAnnotations.Contains(nextAnnotation)) { |
|
|
|
entering.Add(nextAnnotation); |
|
|
|
if (!lastAnnotations.Contains(item: nextAnnotation)) { |
|
|
|
entering.Add(item: nextAnnotation); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
foreach ( MouseTrackerAnnotation annotation in enteringAnnotations) { |
|
|
|
foreach (var annotation in enteringAnnotations) { |
|
|
|
annotation.onEnter(PointerEnterEvent.fromMouseEvent(latestEvent)); |
|
|
|
annotation.onEnter(PointerEnterEvent.fromMouseEvent(hover: latestEvent)); |
|
|
|
if (unhandledEvent is PointerHoverEvent) { |
|
|
|
Offset lastHoverPosition = previousEvent is PointerHoverEvent ? previousEvent.position : null; |
|
|
|
bool pointerHasMoved = lastHoverPosition == null || lastHoverPosition != unhandledEvent.position; |
|
|
|
nextAnnotations.ToList().Reverse(); |
|
|
|
IEnumerable<MouseTrackerAnnotation> hoveringAnnotations = pointerHasMoved ? nextAnnotations : enteringAnnotations; |
|
|
|
foreach ( MouseTrackerAnnotation annotation in hoveringAnnotations) { |
|
|
|
if (annotation.onHover != null) { |
|
|
|
annotation.onHover((PointerHoverEvent)unhandledEvent); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
public HashSet<MouseTrackerAnnotation> _findAnnotations(_MouseState state) { |
|
|
|
Offset globalPosition = state.latestEvent.position; |
|
|
|
int device = state.device; |
|
|
|
HashSet<MouseTrackerAnnotation> result = new HashSet<MouseTrackerAnnotation>(); |
|
|
|
foreach (var values in annotationFinder(globalPosition)) { |
|
|
|
result.Add(values); |
|
|
|
if (unhandledEvent is PointerHoverEvent) { |
|
|
|
var lastHoverPosition = previousEvent is PointerHoverEvent ? previousEvent.position : null; |
|
|
|
var pointerHasMoved = lastHoverPosition == null || lastHoverPosition != unhandledEvent.position; |
|
|
|
nextAnnotations.ToList().Reverse(); |
|
|
|
var hoveringAnnotations = pointerHasMoved ? nextAnnotations : enteringAnnotations; |
|
|
|
foreach (var annotation in hoveringAnnotations) { |
|
|
|
if (annotation.onHover != null) { |
|
|
|
annotation.onHover((PointerHoverEvent) unhandledEvent); |
|
|
|
} |
|
|
|
} |
|
|
|
return (_mouseStates.ContainsKey(device)) |
|
|
|
? result |
|
|
|
: new HashSet<MouseTrackerAnnotation>{} as HashSet<MouseTrackerAnnotation>; |
|
|
|
public static bool _duringBuildPhase { |
|
|
|
get { |
|
|
|
return SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks; |
|
|
|
} |
|
|
|
} |
|
|
|
public void _updateAllDevices() { |
|
|
|
_updateDevices( |
|
|
|
handleUpdatedDevice: (_MouseState mouseState, HashSet<MouseTrackerAnnotation> previousAnnotations)=> { |
|
|
|
_dispatchDeviceCallbacks( |
|
|
|
lastAnnotations: previousAnnotations, |
|
|
|
nextAnnotations: mouseState.annotations, |
|
|
|
previousEvent: mouseState.latestEvent, |
|
|
|
unhandledEvent: null |
|
|
|
); |
|
|
|
} |
|
|
|
); |
|
|
|
} |
|
|
|
bool _duringDeviceUpdate = false; |
|
|
|
|
|
|
|
void _updateDevices( |
|
|
|
PointerEvent targetEvent = null, |
|
|
|
_UpdatedDeviceHandler handleUpdatedDevice = null) { |
|
|
|
D.assert(handleUpdatedDevice != null); |
|
|
|
D.assert(!_duringBuildPhase); |
|
|
|
D.assert(!_duringDeviceUpdate); |
|
|
|
bool mouseWasConnected = mouseIsConnected; |
|
|
|
|
|
|
|
_MouseState targetState = null; |
|
|
|
if (targetEvent != null) { |
|
|
|
targetState = _mouseStates.getOrDefault(targetEvent.device); |
|
|
|
if (targetState == null) { |
|
|
|
targetState = new _MouseState(initialEvent: targetEvent); |
|
|
|
_mouseStates[targetState.device] = targetState; |
|
|
|
} else { |
|
|
|
D.assert(!(targetEvent is PointerAddedEvent)); |
|
|
|
targetState.latestEvent = targetEvent; |
|
|
|
if (targetEvent is PointerRemovedEvent) |
|
|
|
_mouseStates.Remove(targetEvent.device); |
|
|
|
} |
|
|
|
public void schedulePostFrameCheck() { |
|
|
|
D.assert(result: _duringBuildPhase); |
|
|
|
D.assert(result: !_duringDeviceUpdate); |
|
|
|
if (!mouseIsConnected) { |
|
|
|
return; |
|
|
|
D.assert((targetState == null) == (targetEvent == null)); |
|
|
|
D.assert(()=> { |
|
|
|
_duringDeviceUpdate = true; |
|
|
|
return true; |
|
|
|
}); |
|
|
|
|
|
|
|
IEnumerable<_MouseState> dirtyStates = targetEvent == null ? (IEnumerable<_MouseState>) _mouseStates.Values : new List<_MouseState>{targetState}; |
|
|
|
foreach ( _MouseState dirtyState in dirtyStates) { |
|
|
|
HashSet<MouseTrackerAnnotation> nextAnnotations = _findAnnotations(dirtyState); |
|
|
|
HashSet<MouseTrackerAnnotation> lastAnnotations = dirtyState.replaceAnnotations(nextAnnotations); |
|
|
|
handleUpdatedDevice(dirtyState, lastAnnotations); |
|
|
|
} |
|
|
|
D.assert(() =>{ |
|
|
|
_duringDeviceUpdate = false; |
|
|
|
return true; |
|
|
|
}); |
|
|
|
|
|
|
|
if (mouseWasConnected != mouseIsConnected) |
|
|
|
notifyListeners(); |
|
|
|
} |
|
|
|
public bool _hasScheduledPostFrameCheck = false; |
|
|
|
public void schedulePostFrameCheck() { |
|
|
|
D.assert(_duringBuildPhase); |
|
|
|
D.assert(!_duringDeviceUpdate); |
|
|
|
if (!mouseIsConnected) |
|
|
|
return; |
|
|
|
_hasScheduledPostFrameCheck = true; |
|
|
|
SchedulerBinding.instance.addPostFrameCallback((stamp => { |
|
|
|
D.assert(_hasScheduledPostFrameCheck); |
|
|
|
_hasScheduledPostFrameCheck = false; |
|
|
|
_updateAllDevices(); |
|
|
|
})); |
|
|
|
_hasScheduledPostFrameCheck = true; |
|
|
|
SchedulerBinding.instance.addPostFrameCallback(stamp => { |
|
|
|
D.assert(result: _hasScheduledPostFrameCheck); |
|
|
|
_hasScheduledPostFrameCheck = false; |
|
|
|
_updateAllDevices(); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |