浏览代码

gesture

/main
kg 6 年前
当前提交
4c8583f7
共有 42 个文件被更改,包括 1702 次插入19 次删除
  1. 68
      Assets/UIWidgets/editor/editor_window.cs
  2. 249
      Assets/UIWidgets/gestures/arena.cs
  3. 12
      Assets/UIWidgets/rendering/object.mixin.gen.cs
  4. 6
      Assets/UIWidgets/rendering/object.mixin.njk
  5. 2
      Assets/UIWidgets/ui/geometry.cs
  6. 24
      Assets/UIWidgets/ui/window.cs
  7. 5
      Packages/manifest.json
  8. 2
      ProjectSettings/ProjectVersion.txt
  9. 8
      Assets/UIWidgets/async.meta
  10. 42
      Assets/UIWidgets/foundation/basic_types.cs
  11. 3
      Assets/UIWidgets/foundation/basic_types.cs.meta
  12. 22
      Assets/UIWidgets/foundation/debug.cs
  13. 3
      Assets/UIWidgets/foundation/debug.cs.meta
  14. 8
      Assets/UIWidgets/gestures.meta
  15. 11
      Assets/UIWidgets/gestures/arena.cs.meta
  16. 101
      Assets/UIWidgets/gestures/binding.cs
  17. 3
      Assets/UIWidgets/gestures/binding.cs.meta
  18. 9
      Assets/UIWidgets/gestures/constants.cs
  19. 3
      Assets/UIWidgets/gestures/constants.cs.meta
  20. 139
      Assets/UIWidgets/gestures/converter.cs
  21. 3
      Assets/UIWidgets/gestures/converter.cs.meta
  22. 130
      Assets/UIWidgets/gestures/events.cs
  23. 3
      Assets/UIWidgets/gestures/events.cs.meta
  24. 53
      Assets/UIWidgets/gestures/hit_test.cs
  25. 3
      Assets/UIWidgets/gestures/hit_test.cs.meta
  26. 66
      Assets/UIWidgets/gestures/pointer_router.cs
  27. 3
      Assets/UIWidgets/gestures/pointer_router.cs.meta
  28. 206
      Assets/UIWidgets/gestures/recognizer.cs
  29. 3
      Assets/UIWidgets/gestures/recognizer.cs.meta
  30. 138
      Assets/UIWidgets/gestures/tap.cs
  31. 3
      Assets/UIWidgets/gestures/tap.cs.meta
  32. 118
      Assets/UIWidgets/gestures/team.cs
  33. 3
      Assets/UIWidgets/gestures/team.cs.meta
  34. 51
      Assets/UIWidgets/ui/pointer.cs
  35. 3
      Assets/UIWidgets/ui/pointer.cs.meta
  36. 26
      Assets/UIWidgets/async/microtask_queue.cs
  37. 3
      Assets/UIWidgets/async/microtask_queue.cs.meta
  38. 105
      Assets/UIWidgets/async/priority_queue.cs
  39. 3
      Assets/UIWidgets/async/priority_queue.cs.meta
  40. 67
      Assets/UIWidgets/async/timer.cs
  41. 11
      Assets/UIWidgets/async/timer.cs.meta

68
Assets/UIWidgets/editor/editor_window.cs


using System;
using System.Collections.Generic;
using UIWidgets.async;
using UIWidgets.ui;
using UnityEditor;
using UnityEngine;

public EditorWindow editorWindow;
public Rect _lastPosition;
public readonly DateTime _epoch = DateTime.Now;
public readonly MicrotaskQueue _microtaskQueue = new MicrotaskQueue();
public readonly TimerProvider _timerProvider = new TimerProvider();
if (Event.current.type == EventType.Repaint) {
var evt = Event.current;
if (evt.type == EventType.Repaint) {
this.flushMicrotasks();
return;
}
if (this.onPointerEvent != null) {
PointerData pointerData = null;
if (evt.type == EventType.MouseDown) {
pointerData = new PointerData(
timeStamp: DateTime.Now,
change: PointerChange.down,
kind: PointerDeviceKind.mouse,
device: evt.button,
physicalX: evt.mousePosition.x,
physicalY: evt.mousePosition.y
);
} else if (evt.type == EventType.MouseUp) {
pointerData = new PointerData(
timeStamp: DateTime.Now,
change: PointerChange.up,
kind: PointerDeviceKind.mouse,
device: evt.button,
physicalX: evt.mousePosition.x,
physicalY: evt.mousePosition.y
);
} else if (evt.type == EventType.MouseDrag) {
pointerData = new PointerData(
timeStamp: DateTime.Now,
change: PointerChange.move,
kind: PointerDeviceKind.mouse,
device: evt.button,
physicalX: evt.mousePosition.x,
physicalY: evt.mousePosition.y
);
}
if (pointerData != null) {
this.onPointerEvent(new PointerDataPacket(new List<PointerData> {
pointerData
}));
}
this.flushMicrotasks();
this._timerProvider.update();
bool dirty = false;
if (this._devicePixelRatio != EditorGUIUtility.pixelsPerPoint) {
dirty = true;

var paintContext = new PaintContext {canvas = new CanvasImpl()};
layer.paint(paintContext);
}
public override void scheduleMicrotask(Action callback) {
this._microtaskQueue.scheduleMicrotask(callback);
}
public override void flushMicrotasks() {
this._microtaskQueue.flushMicrotasks();
}
public override Timer run(TimeSpan duration, Action callback) {
return this._timerProvider.run(duration, callback);
}
}
}

249
Assets/UIWidgets/gestures/arena.cs


using System.Collections.Generic;
using System.Linq;
using System.Text;
using UIWidgets.foundation;
using UIWidgets.ui;
using UnityEngine;
public enum GestureDisposition {
accepted,
rejected,
}
public interface GestureArenaMember {
void acceptGesture(int pointer);
void rejectGesture(int pointer);
}
public class GestureArenaEntry {
public GestureArenaEntry(
GestureArenaManager arena = null,
int pointer = 0,
GestureArenaMember member = null) {
this._arena = arena;
this._pointer = pointer;
this._member = member;
}
readonly GestureArenaManager _arena;
readonly int _pointer;
readonly GestureArenaMember _member;
public virtual void resolve(GestureDisposition disposition) {
this._arena._resolve(this._pointer, this._member, disposition);
}
}
class _GestureArena {
public List<GestureArenaMember> members = new List<GestureArenaMember>();
public bool isOpen = true;
public bool isHeld = false;
public bool hasPendingSweep = false;
public GestureArenaMember eagerWinner;
public void add(GestureArenaMember member) {
D.assert(this.isOpen);
this.members.Add(member);
}
public override string ToString() {
StringBuilder buffer = new StringBuilder();
if (this.members.isEmpty()) {
buffer.Append("<empty>");
} else {
buffer.Append(string.Join(", ", this.members.Select(
member => member == this.eagerWinner
? string.Format("{0} (eager winner)", member)
: member.ToString()).ToArray()));
}
if (this.isOpen) {
buffer.Append(" [open]");
}
if (this.isHeld) {
buffer.Append(" [held]");
}
if (this.hasPendingSweep) {
buffer.Append(" [hasPendingSweep]");
}
return buffer.ToString();
}
}
public class GestureArenaManager {
readonly Dictionary<int, _GestureArena> _arenas = new Dictionary<int, _GestureArena>();
readonly Window _window;
public GestureArenaManager(Window window) {
this._window = window;
}
public GestureArenaEntry add(int pointer, GestureArenaMember member) {
_GestureArena state = this._arenas.putIfAbsent(pointer, () => {
D.assert(this._debugLogDiagnostic(pointer, "★ Opening new gesture arena."));
return state = new _GestureArena();
});
state.add(member);
D.assert(this._debugLogDiagnostic(pointer, string.Format("Adding: {0}", member)));
return new GestureArenaEntry(this, pointer, member);
}
public void close(int pointer) {
_GestureArena state;
if (!this._arenas.TryGetValue(pointer, out state)) {
return;
}
state.isOpen = false;
D.assert(this._debugLogDiagnostic(pointer, "Closing", state));
this._tryToResolveArena(pointer, state);
}
public void sweep(int pointer) {
_GestureArena state;
if (!this._arenas.TryGetValue(pointer, out state)) {
return;
}
D.assert(!state.isOpen);
if (state.isHeld) {
state.hasPendingSweep = true;
D.assert(this._debugLogDiagnostic(pointer, "Delaying sweep", state));
return;
}
D.assert(this._debugLogDiagnostic(pointer, "Sweeping", state));
this._arenas.Remove(pointer);
if (state.members.isNotEmpty()) {
D.assert(this._debugLogDiagnostic(
pointer, string.Format("Winner: {0}", state.members.First())));
state.members.First().acceptGesture(pointer);
for (int i = 1; i < state.members.Count; i++) {
state.members[i].rejectGesture(pointer);
}
}
}
public void hold(int pointer) {
_GestureArena state;
if (!this._arenas.TryGetValue(pointer, out state)) {
return;
}
state.isHeld = true;
D.assert(this._debugLogDiagnostic(pointer, "Holding", state));
}
public void release(int pointer) {
_GestureArena state;
if (!this._arenas.TryGetValue(pointer, out state)) {
return;
}
state.isHeld = false;
D.assert(this._debugLogDiagnostic(pointer, "Releasing", state));
if (state.hasPendingSweep) {
this.sweep(pointer);
}
}
internal void _resolve(int pointer, GestureArenaMember member, GestureDisposition disposition) {
_GestureArena state;
if (!this._arenas.TryGetValue(pointer, out state)) {
return;
}
D.assert(this._debugLogDiagnostic(pointer,
string.Format("{0}: {1}",
disposition == GestureDisposition.accepted ? "Accepting" : "Rejecting",
member)));
D.assert(state.members.Contains(member));
if (disposition == GestureDisposition.rejected) {
state.members.Remove(member);
member.rejectGesture(pointer);
if (!state.isOpen) {
this._tryToResolveArena(pointer, state);
}
} else {
if (state.isOpen) {
state.eagerWinner = state.eagerWinner ?? member;
} else {
D.assert(this._debugLogDiagnostic(pointer,
string.Format("Self-declared winner: {0}", member)));
this._resolveInFavorOf(pointer, state, member);
}
}
}
void _tryToResolveArena(int pointer, _GestureArena state) {
D.assert(this._arenas[pointer] == state);
D.assert(!state.isOpen);
if (state.members.Count == 1) {
this._window.scheduleMicrotask(() => this._resolveByDefault(pointer, state));
} else if (state.members.isEmpty()) {
this._arenas.Remove(pointer);
D.assert(this._debugLogDiagnostic(pointer, "Arena empty."));
} else if (state.eagerWinner != null) {
D.assert(this._debugLogDiagnostic(pointer,
string.Format("Eager winner: {0}", state.eagerWinner)));
this._resolveInFavorOf(pointer, state, state.eagerWinner);
}
}
void _resolveByDefault(int pointer, _GestureArena state) {
if (!this._arenas.ContainsKey(pointer)) {
return;
}
D.assert(this._arenas[pointer] == state);
D.assert(!state.isOpen);
List<GestureArenaMember> members = state.members;
D.assert(members.Count == 1);
this._arenas.Remove(pointer);
D.assert(this._debugLogDiagnostic(pointer,
string.Format("Default winner: {0}", state.members.First())));
state.members.First().acceptGesture(pointer);
}
void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
D.assert(state == this._arenas[pointer]);
D.assert(state != null);
D.assert(state.eagerWinner == null || state.eagerWinner == member);
D.assert(!state.isOpen);
this._arenas.Remove(pointer);
foreach (GestureArenaMember rejectedMember in state.members) {
if (rejectedMember != member) {
rejectedMember.rejectGesture(pointer);
}
}
member.acceptGesture(pointer);
}
bool _debugLogDiagnostic(int pointer, string message, _GestureArena state = null) {
D.assert(() => {
if (D.debugPrintGestureArenaDiagnostics) {
int? count = state != null ? state.members.Count : (int?) null;
string s = count != 1 ? "s" : "";
Debug.LogFormat("Gesture arena {0} ❙ {1}{2}",
pointer.ToString().PadRight(4),
message,
count != null ? string.Format(" with {0} member{1}.", count, s) : "");
}
return true;
});
return true;
}
}
}

12
Assets/UIWidgets/rendering/object.mixin.gen.cs


}
}
public void insert(ChildType child, ChildType after = null) {
public virtual void insert(ChildType child, ChildType after = null) {
this.adoptChild(child);
this._insertIntoChildList(child, after);
}

this._childCount--;
}
public void remove(ChildType child) {
public virtual void remove(ChildType child) {
public void removeAll() {
public virtual void removeAll() {
ChildType child = this._firstChild;
while (child != null) {
var childParentData = (ParentDataType) child.parentData;

}
}
public void insert(ChildType child, ChildType after = null) {
public virtual void insert(ChildType child, ChildType after = null) {
this.adoptChild(child);
this._insertIntoChildList(child, after);
}

this._childCount--;
}
public void remove(ChildType child) {
public virtual void remove(ChildType child) {
public void removeAll() {
public virtual void removeAll() {
ChildType child = this._firstChild;
while (child != null) {
var childParentData = (ParentDataType) child.parentData;

6
Assets/UIWidgets/rendering/object.mixin.njk


}
}
public void insert(ChildType child, ChildType after = null) {
public virtual void insert(ChildType child, ChildType after = null) {
this.adoptChild(child);
this._insertIntoChildList(child, after);
}

this._childCount--;
}
public void remove(ChildType child) {
public virtual void remove(ChildType child) {
public void removeAll() {
public virtual void removeAll() {
ChildType child = this._firstChild;
while (child != null) {
var childParentData = (ParentDataType) child.parentData;

2
Assets/UIWidgets/ui/geometry.cs


}
public static bool operator ==(OffsetBase a, OffsetBase b) {
return a.Equals(b);
return object.Equals(a, b);
}
public static bool operator !=(OffsetBase a, OffsetBase b) {

24
Assets/UIWidgets/ui/window.cs


using System;
using UIWidgets.rendering;
using UIWidgets.widgets;
using UIWidgets.async;
public delegate void PointerDataPacketCallback(PointerDataPacket packet);
public abstract class Window {
public double devicePixelRatio {

public VoidCallback _onDrawFrame;
public PointerDataPacketCallback onPointerEvent {
get { return this._onPointerEvent; }
set { this._onPointerEvent = value; }
}
public PointerDataPacketCallback _onPointerEvent;
public abstract void scheduleMicrotask(Action callback);
public abstract void flushMicrotasks();
public abstract Timer run(TimeSpan duration, Action callback);
public Timer run(Action callback) {
return this.run(TimeSpan.Zero, callback);
}
}

5
Packages/manifest.json


{
"dependencies": {
"com.unity.ads": "2.0.8",
"com.unity.analytics": "2.0.16",
"com.unity.package-manager-ui": "1.9.11",
"com.unity.analytics": "3.0.9",
"com.unity.collab-proxy": "1.2.6",
"com.unity.package-manager-ui": "2.0.0-preview.6",
"com.unity.purchasing": "2.0.3",
"com.unity.textmeshpro": "1.2.4",
"com.unity.modules.ai": "1.0.0",

2
ProjectSettings/ProjectVersion.txt


m_EditorVersion: 2018.2.4f1
m_EditorVersion: 2018.3.0a9

8
Assets/UIWidgets/async.meta


fileFormatVersion: 2
guid: d164f4ad25c6f4b51b5f3e6a945a7296
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

42
Assets/UIWidgets/foundation/basic_types.cs


using System;
using System.Collections;
using System.Collections.Generic;
namespace UIWidgets.foundation {
public static class CollectionUtils {
public static V putIfAbsent<K, V>(this IDictionary<K, V> it, K key, Func<V> ifAbsent) {
V value;
if (it.TryGetValue(key, out value)) {
return value;
}
value = ifAbsent();
it[key] = value;
return value;
}
public static bool isEmpty<T>(this ICollection<T> it) {
return it.Count == 0;
}
public static bool isNotEmpty<T>(this ICollection<T> it) {
return it.Count != 0;
}
public static bool isEmpty(this ICollection it) {
return it.Count == 0;
}
public static bool isNotEmpty(this ICollection it) {
return it.Count != 0;
}
public static bool isEmpty<TKey, TValue>(this IDictionary<TKey, TValue> it) {
return it.Count == 0;
}
public static bool isNotEmpty<TKey, TValue>(this IDictionary<TKey, TValue> it) {
return it.Count != 0;
}
}
}

3
Assets/UIWidgets/foundation/basic_types.cs.meta


fileFormatVersion: 2
guid: 5677c530963d4d59baef909ab962ab44
timeCreated: 1535605561

22
Assets/UIWidgets/foundation/debug.cs


using System;
using System.Diagnostics;
namespace UIWidgets.foundation {
public static class D {
[Conditional("UIWidgets_DEBUG")]
public static void assert(Func<bool> result) {
D.assert(result());
}
[Conditional("UIWidgets_DEBUG")]
public static void assert(bool result) {
if (!result) {
throw new Exception("assertion failed. check stacktrace.");
}
}
public static bool debugPrintGestureArenaDiagnostics = true;
public static bool debugPrintHitTestResults = true;
}
}

3
Assets/UIWidgets/foundation/debug.cs.meta


fileFormatVersion: 2
guid: 89e39060aaba41798359de8d7e6a013e
timeCreated: 1536123374

8
Assets/UIWidgets/gestures.meta


fileFormatVersion: 2
guid: 8617cc0ea80d74e80a0579e70d4e2d2c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

11
Assets/UIWidgets/gestures/arena.cs.meta


fileFormatVersion: 2
guid: 8f1294e5215474e2d9ff98330207760e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

101
Assets/UIWidgets/gestures/binding.cs


using System;
using System.Collections.Generic;
using UIWidgets.foundation;
using UIWidgets.ui;
using UnityEngine;
namespace UIWidgets.gestures {
public class GestureBinding : HitTestable, HitTestDispatcher, HitTestTarget {
public GestureBinding(Window window) {
this.window = window;
this.window._onPointerEvent += this._handlePointerDataPacket;
this.gestureArena = new GestureArenaManager(window);
}
public readonly Window window;
readonly Queue<PointerEvent> _pendingPointerEvents = new Queue<PointerEvent>();
void _handlePointerDataPacket(PointerDataPacket packet) {
foreach (var pointerEvent in PointerEventConverter.expand(packet.data)) {
this._pendingPointerEvents.Enqueue(pointerEvent);
}
this._flushPointerEventQueue();
}
public void cancelPointer(int pointer) {
if (this._pendingPointerEvents.isEmpty()) {
this.window.scheduleMicrotask(this._flushPointerEventQueue);
}
this._pendingPointerEvents.Enqueue(
new PointerCancelEvent(timeStamp: DateTime.Now, pointer: pointer));
}
void _flushPointerEventQueue() {
while (this._pendingPointerEvents.Count != 0) {
this._handlePointerEvent(this._pendingPointerEvents.Dequeue());
}
}
public readonly PointerRouter pointerRouter = new PointerRouter();
public readonly GestureArenaManager gestureArena;
public readonly Dictionary<int, HitTestResult> _hitTests = new Dictionary<int, HitTestResult>();
void _handlePointerEvent(PointerEvent evt) {
HitTestResult result;
if (evt is PointerDownEvent) {
D.assert(!this._hitTests.ContainsKey(evt.pointer));
result = new HitTestResult();
this.hitTest(result, evt.position);
this._hitTests[evt.pointer] = result;
D.assert(() => {
if (D.debugPrintHitTestResults) {
Debug.LogFormat("{0}: {1}", evt, result);
}
return true;
});
} else if (evt is PointerUpEvent || evt is PointerCancelEvent) {
result = this._hitTests[evt.pointer];
this._hitTests.Remove(evt.pointer);
} else if (evt.down) {
result = this._hitTests[evt.pointer];
} else {
return;
}
if (result != null) {
this.dispatchEvent(evt, result);
}
}
public void hitTest(HitTestResult result, Offset position) {
result.add(new HitTestEntry(this));
}
public void dispatchEvent(PointerEvent evt, HitTestResult result) {
foreach (HitTestEntry entry in result.path) {
try {
entry.target.handleEvent(evt, entry);
}
catch (Exception ex) {
Debug.LogError("Error while dispatching a pointer event: " + ex);
}
}
}
public void handleEvent(PointerEvent evt, HitTestEntry entry) {
this.pointerRouter.route(evt);
if (evt is PointerDownEvent) {
this.gestureArena.close(evt.pointer);
} else if (evt is PointerUpEvent) {
this.gestureArena.sweep(evt.pointer);
}
}
}
}

3
Assets/UIWidgets/gestures/binding.cs.meta


fileFormatVersion: 2
guid: 4cdb394be98c414fb19a438686b949c1
timeCreated: 1535609218

9
Assets/UIWidgets/gestures/constants.cs


using System;
namespace UIWidgets.gestures {
public static class Constants {
public const double kTouchSlop = 18.0;
public static readonly TimeSpan kPressTimeout = new TimeSpan(0, 0, 0, 100);
}
}

3
Assets/UIWidgets/gestures/constants.cs.meta


fileFormatVersion: 2
guid: fe1823eee58c44999e824c526724caa3
timeCreated: 1535608065

139
Assets/UIWidgets/gestures/converter.cs


using System.Collections.Generic;
using UIWidgets.foundation;
using UIWidgets.ui;
namespace UIWidgets.gestures {
class _PointerState {
public _PointerState(Offset lastPosition) {
this.lastPosition = lastPosition ?? Offset.zero;
}
public int pointer {
get { return this._pointer; }
}
int _pointer;
static int _pointerCount = 0;
public void startNewPointer() {
_PointerState._pointerCount += 1;
this._pointer = _PointerState._pointerCount;
}
public bool down {
get { return this._down; }
}
bool _down = false;
public void setDown() {
D.assert(!this._down);
this._down = true;
}
public void setUp() {
D.assert(this._down);
this._down = false;
}
public Offset lastPosition;
public override string ToString() {
return string.Format("_PointerState(pointer: {0}, down: {1}, lastPosition: {2})",
this.pointer, this.down, this.lastPosition);
}
}
public static class PointerEventConverter {
static readonly Dictionary<int, _PointerState> _pointers = new Dictionary<int, _PointerState>();
static _PointerState _ensureStateForPointer(PointerData datum, Offset position) {
return _pointers.putIfAbsent(
datum.device,
() => new _PointerState(position));
}
public static IEnumerable<PointerEvent> expand(IEnumerable<PointerData> data) {
foreach (PointerData datum in data) {
var position = new Offset(datum.physicalX, datum.physicalY);
var timeStamp = datum.timeStamp;
var kind = datum.kind;
switch (datum.change) {
case PointerChange.down: {
_PointerState state = _ensureStateForPointer(datum, position);
D.assert(!state.down);
state.startNewPointer();
state.setDown();
yield return new PointerDownEvent(
timeStamp: timeStamp,
pointer: state.pointer,
kind: kind,
device: datum.device,
position: position
);
}
break;
case PointerChange.move: {
D.assert(_pointers.ContainsKey(datum.device));
_PointerState state = _pointers[datum.device];
D.assert(state.down);
Offset offset = position - state.lastPosition;
state.lastPosition = position;
yield return new PointerMoveEvent(
timeStamp: timeStamp,
pointer: state.pointer,
kind: kind,
device: datum.device,
position: position,
delta: offset
);
}
break;
case PointerChange.up:
case PointerChange.cancel: {
D.assert(_pointers.ContainsKey(datum.device));
_PointerState state = _pointers[datum.device];
D.assert(state.down);
if (position != state.lastPosition) {
Offset offset = position - state.lastPosition;
state.lastPosition = position;
yield return new PointerMoveEvent(
timeStamp: timeStamp,
pointer: state.pointer,
kind: kind,
device: datum.device,
position: position,
delta: offset,
synthesized: true
);
}
D.assert(position == state.lastPosition);
state.setUp();
if (datum.change == PointerChange.up) {
yield return new PointerUpEvent(
timeStamp: timeStamp,
pointer: state.pointer,
kind: kind,
device: datum.device,
position: position
);
} else {
yield return new PointerCancelEvent(
timeStamp: timeStamp,
pointer: state.pointer,
kind: kind,
device: datum.device,
position: position
);
}
}
break;
}
}
}
}
}

3
Assets/UIWidgets/gestures/converter.cs.meta


fileFormatVersion: 2
guid: 929719430a6b48a4b9639a1bb8b37e88
timeCreated: 1536051892

130
Assets/UIWidgets/gestures/events.cs


using System;
using UIWidgets.ui;
namespace UIWidgets.gestures {
public abstract class PointerEvent {
public PointerEvent(
DateTime timeStamp,
int pointer = 0,
PointerDeviceKind kind = PointerDeviceKind.mouse,
int device = 0,
Offset position = null,
Offset delta = null,
bool down = false,
bool synthesized = false
) {
this.timeStamp = timeStamp;
this.pointer = pointer;
this.kind = kind;
this.device = device;
this.position = position ?? Offset.zero;
this.delta = delta ?? Offset.zero;
this.down = down;
this.synthesized = synthesized;
}
public readonly DateTime timeStamp;
public readonly int pointer;
public PointerDeviceKind kind;
public int device;
public readonly Offset position;
public readonly Offset delta;
public readonly bool down;
public readonly bool synthesized;
}
public class PointerDownEvent : PointerEvent {
public PointerDownEvent(
DateTime timeStamp,
int pointer = 0,
PointerDeviceKind kind = PointerDeviceKind.mouse,
int device = 0,
Offset position = null,
Offset delta = null,
bool down = false,
bool synthesized = false)
: base(
timeStamp,
pointer,
kind,
device,
position,
delta,
down,
synthesized) {
}
}
public class PointerUpEvent : PointerEvent {
public PointerUpEvent(
DateTime timeStamp,
int pointer = 0,
PointerDeviceKind kind = PointerDeviceKind.mouse,
int device = 0,
Offset position = null,
Offset delta = null,
bool down = false,
bool synthesized = false)
: base(
timeStamp,
pointer,
kind,
device,
position,
delta,
down,
synthesized) {
}
}
public class PointerCancelEvent : PointerEvent {
public PointerCancelEvent(
DateTime timeStamp,
int pointer = 0,
PointerDeviceKind kind = PointerDeviceKind.mouse,
int device = 0,
Offset position = null,
Offset delta = null,
bool down = false,
bool synthesized = false)
: base(
timeStamp,
pointer,
kind,
device,
position,
delta,
down,
synthesized) {
}
}
public class PointerMoveEvent : PointerEvent {
public PointerMoveEvent(
DateTime timeStamp,
int pointer = 0,
PointerDeviceKind kind = PointerDeviceKind.mouse,
int device = 0,
Offset position = null,
Offset delta = null,
bool down = false,
bool synthesized = false)
: base(
timeStamp,
pointer,
kind,
device,
position,
delta,
down,
synthesized) {
}
}
}

3
Assets/UIWidgets/gestures/events.cs.meta


fileFormatVersion: 2
guid: 85b73f18462c41199d2bd67c8f012ba6
timeCreated: 1535595953

53
Assets/UIWidgets/gestures/hit_test.cs


using System.Collections.Generic;
using System.Linq;
using UIWidgets.foundation;
using UIWidgets.ui;
namespace UIWidgets.gestures {
public interface HitTestable {
void hitTest(HitTestResult result, Offset position);
}
public interface HitTestDispatcher {
void dispatchEvent(PointerEvent @event, HitTestResult result);
}
public interface HitTestTarget {
void handleEvent(PointerEvent @event, HitTestEntry entry);
}
public class HitTestEntry {
public HitTestEntry(HitTestTarget target) {
this.target = target;
}
public readonly HitTestTarget target;
public override string ToString() {
return this.target.ToString();
}
}
public class HitTestResult {
public HitTestResult(List<HitTestEntry> path = null) {
this._path = path ?? new List<HitTestEntry>();
}
public IList<HitTestEntry> path {
get { return this._path.AsReadOnly(); }
}
readonly List<HitTestEntry> _path;
public void add(HitTestEntry entry) {
this._path.Add(entry);
}
public override string ToString() {
return string.Format("HitTestResult({0})",
this._path.isEmpty()
? "<empty path>"
: string.Join(", ", this._path.Select(x => x.ToString()).ToArray()));
}
}
}

3
Assets/UIWidgets/gestures/hit_test.cs.meta


fileFormatVersion: 2
guid: 8e54d41203874208ad368d1a42e611f8
timeCreated: 1535595576

66
Assets/UIWidgets/gestures/pointer_router.cs


using System;
using System.Collections.Generic;
using UIWidgets.foundation;
using UnityEngine;
namespace UIWidgets.gestures {
public delegate void PointerRoute(PointerEvent evt);
public class PointerRouter {
readonly Dictionary<int, HashSet<PointerRoute>> _routeMap = new Dictionary<int, HashSet<PointerRoute>>();
readonly HashSet<PointerRoute> _globalRoutes = new HashSet<PointerRoute>();
public void addRoute(int pointer, PointerRoute route) {
var routes = this._routeMap.putIfAbsent(pointer, () => new HashSet<PointerRoute>());
D.assert(!routes.Contains(route));
routes.Add(route);
}
public void removeRoute(int pointer, PointerRoute route) {
D.assert(this._routeMap.ContainsKey(pointer));
var routes = this._routeMap[pointer];
routes.Remove(route);
if (routes.isEmpty()) {
this._routeMap.Remove(pointer);
}
}
public void addGlobalRoute(PointerRoute route) {
D.assert(!this._globalRoutes.Contains(route));
this._globalRoutes.Add(route);
}
public void removeGlobalRoute(PointerRoute route) {
D.assert(this._globalRoutes.Contains(route));
this._globalRoutes.Remove(route);
}
void _dispatch(PointerEvent evt, PointerRoute route) {
try {
route(evt);
}
catch (Exception ex) {
Debug.LogError("Error while routing a pointer event: " + ex);
}
}
public void route(PointerEvent evt) {
HashSet<PointerRoute> routes;
this._routeMap.TryGetValue(evt.pointer, out routes);
if (routes != null) {
foreach (PointerRoute route in new List<PointerRoute>(routes)) {
if (routes.Contains(route)) {
this._dispatch(evt, route);
}
}
}
foreach (PointerRoute route in new List<PointerRoute>(this._globalRoutes)) {
if (this._globalRoutes.Contains(route)) {
this._dispatch(evt, route);
}
}
}
}
}

3
Assets/UIWidgets/gestures/pointer_router.cs.meta


fileFormatVersion: 2
guid: 37ff4ce5ebb74851a0bf2e62f12d9c50
timeCreated: 1535604768

206
Assets/UIWidgets/gestures/recognizer.cs


using System;
using System.Collections.Generic;
using UIWidgets.async;
using UIWidgets.foundation;
using UIWidgets.ui;
using UnityEngine;
namespace UIWidgets.gestures {
public delegate T RecognizerCallback<T>();
public abstract class GestureRecognizer : GestureArenaMember {
protected GestureRecognizer(GestureBinding binding = null) {
this._binding = binding;
}
protected readonly GestureBinding _binding;
public abstract void addPointer(PointerDownEvent evt);
public virtual void dispose() {
}
protected T invokeCallback<T>(string name, RecognizerCallback<T> callback) {
T result = default(T);
try {
result = callback();
}
catch (Exception ex) {
Debug.LogError("Error while handling a gesture [" + name + "]: " + ex);
}
return result;
}
public abstract void acceptGesture(int pointer);
public abstract void rejectGesture(int pointer);
}
public abstract class OneSequenceGestureRecognizer : GestureRecognizer {
protected OneSequenceGestureRecognizer(GestureBinding binding = null) : base(binding) {
}
readonly Dictionary<int, GestureArenaEntry> _entries = new Dictionary<int, GestureArenaEntry>();
readonly HashSet<int> _trackedPointers = new HashSet<int>();
protected abstract void handleEvent(PointerEvent evt);
public override void acceptGesture(int pointer) {
}
public override void rejectGesture(int pointer) {
}
protected abstract void didStopTrackingLastPointer(int pointer);
protected virtual void resolve(GestureDisposition disposition) {
var localEntries = new List<GestureArenaEntry>(this._entries.Values);
this._entries.Clear();
foreach (GestureArenaEntry entry in localEntries) {
entry.resolve(disposition);
}
}
public override void dispose() {
this.resolve(GestureDisposition.rejected);
foreach (int pointer in this._trackedPointers) {
this._binding.pointerRouter.removeRoute(pointer, this.handleEvent);
}
this._trackedPointers.Clear();
D.assert(this._entries.isEmpty());
base.dispose();
}
public GestureArenaTeam team {
get { return this._team; }
set {
D.assert(value != null);
D.assert(this._entries.isEmpty());
D.assert(this._trackedPointers.isEmpty());
D.assert(this._team == null);
this._team = value;
}
}
GestureArenaTeam _team;
GestureArenaEntry _addPointerToArena(int pointer) {
if (this._team != null) {
return this._team.add(pointer, this);
}
return this._binding.gestureArena.add(pointer, this);
}
protected void startTrackingPointer(int pointer) {
this._binding.pointerRouter.addRoute(pointer, this.handleEvent);
this._trackedPointers.Add(pointer);
D.assert(!this._entries.ContainsKey(pointer));
this._entries[pointer] = this._addPointerToArena(pointer);
}
protected void stopTrackingPointer(int pointer) {
if (this._trackedPointers.Contains(pointer)) {
this._binding.pointerRouter.removeRoute(pointer, this.handleEvent);
this._trackedPointers.Remove(pointer);
if (this._trackedPointers.isEmpty()) {
this.didStopTrackingLastPointer(pointer);
}
}
}
protected void stopTrackingIfPointerNoLongerDown(PointerEvent evt) {
if (evt is PointerUpEvent || evt is PointerCancelEvent) {
this.stopTrackingPointer(evt.pointer);
}
}
}
public enum GestureRecognizerState {
ready,
possible,
defunct,
}
public abstract class PrimaryPointerGestureRecognizer : OneSequenceGestureRecognizer {
protected PrimaryPointerGestureRecognizer(
TimeSpan? deadline = null,
GestureBinding binding = null
) : base(binding: binding) {
this.deadline = deadline;
}
public readonly TimeSpan? deadline;
public GestureRecognizerState state = GestureRecognizerState.ready;
public int primaryPointer;
public Offset initialPosition;
Timer _timer;
public override void addPointer(PointerDownEvent evt) {
this.startTrackingPointer(evt.pointer);
if (this.state == GestureRecognizerState.ready) {
this.state = GestureRecognizerState.possible;
this.primaryPointer = evt.pointer;
this.initialPosition = evt.position;
if (this.deadline != null) {
this._timer = this._binding.window.run(this.deadline.Value, this.didExceedDeadline);
}
}
}
protected override void handleEvent(PointerEvent evt) {
D.assert(this.state != GestureRecognizerState.ready);
if (this.state == GestureRecognizerState.possible && evt.pointer == this.primaryPointer) {
if (evt is PointerMoveEvent && this._getDistance(evt) > Constants.kTouchSlop) {
this.resolve(GestureDisposition.rejected);
this.stopTrackingPointer(this.primaryPointer);
} else {
this.handlePrimaryPointer(evt);
}
}
this.stopTrackingIfPointerNoLongerDown(evt);
}
protected abstract void handlePrimaryPointer(PointerEvent evt);
protected virtual void didExceedDeadline() {
D.assert(this.deadline == null);
}
public override void rejectGesture(int pointer) {
if (pointer == this.primaryPointer && this.state == GestureRecognizerState.possible) {
this._stopTimer();
this.state = GestureRecognizerState.defunct;
}
}
protected override void didStopTrackingLastPointer(int pointer) {
this._stopTimer();
this.state = GestureRecognizerState.ready;
}
public override void dispose() {
this._stopTimer();
base.dispose();
}
void _stopTimer() {
if (this._timer != null) {
this._timer.cancel();
this._timer = null;
}
}
double _getDistance(PointerEvent evt) {
Offset offset = evt.position - this.initialPosition;
return offset.distance;
}
}
}

3
Assets/UIWidgets/gestures/recognizer.cs.meta


fileFormatVersion: 2
guid: d4c72baa549e450abe95d694fbad1a7b
timeCreated: 1535606392

138
Assets/UIWidgets/gestures/tap.cs


using UIWidgets.ui;
namespace UIWidgets.gestures {
public class TapDownDetails {
public TapDownDetails(Offset globalPosition = null) {
this.globalPosition = globalPosition ?? Offset.zero;
}
public readonly Offset globalPosition;
}
public delegate void GestureTapDownCallback(TapDownDetails details);
public class TapUpDetails {
public TapUpDetails(Offset globalPosition = null) {
this.globalPosition = globalPosition ?? Offset.zero;
}
public readonly Offset globalPosition;
}
public delegate void GestureTapUpCallback(TapUpDetails details);
public delegate void GestureTapCallback();
public delegate void GestureTapCancelCallback();
public class TapGestureRecognizer : PrimaryPointerGestureRecognizer {
public TapGestureRecognizer(GestureBinding binding)
: base(deadline: Constants.kPressTimeout, binding: binding) {
}
public GestureTapDownCallback onTapDown;
public GestureTapUpCallback onTapUp;
public GestureTapCallback onTap;
public GestureTapCancelCallback onTapCancel;
bool _sentTapDown = false;
bool _wonArenaForPrimaryPointer = false;
Offset _finalPosition;
protected override void handlePrimaryPointer(PointerEvent evt) {
if (evt is PointerUpEvent) {
this._finalPosition = evt.position;
this._checkUp();
} else if (evt is PointerCancelEvent) {
this._reset();
}
}
protected override void resolve(GestureDisposition disposition) {
if (this._wonArenaForPrimaryPointer && disposition == GestureDisposition.rejected) {
if (this.onTapCancel != null) {
this.invokeCallback<object>("spontaneous onTapCancel", () => {
this.onTapCancel();
return null;
});
}
this._reset();
}
base.resolve(disposition);
}
protected override void didExceedDeadline() {
this._checkDown();
}
public override void acceptGesture(int pointer) {
base.acceptGesture(pointer);
if (pointer == this.primaryPointer) {
this._checkDown();
this._wonArenaForPrimaryPointer = true;
this._checkUp();
}
}
public override void rejectGesture(int pointer) {
base.rejectGesture(pointer);
if (pointer == this.primaryPointer) {
if (this.onTapCancel != null) {
this.invokeCallback<object>("forced onTapCancel", () => {
this.onTapCancel();
return null;
});
}
this._reset();
}
}
void _checkDown() {
if (!this._sentTapDown) {
if (this.onTapDown != null)
this.invokeCallback<object>("onTapDown", () => {
this.onTapDown(new TapDownDetails(globalPosition: this.initialPosition));
return null;
});
this._sentTapDown = true;
}
}
void _checkUp() {
if (this._wonArenaForPrimaryPointer && this._finalPosition != null) {
this.resolve(GestureDisposition.accepted);
if (!this._wonArenaForPrimaryPointer || this._finalPosition == null) {
return;
}
if (this.onTapUp != null)
this.invokeCallback<object>("onTapUp", () => {
this.onTapUp(new TapUpDetails(globalPosition: this._finalPosition));
return null;
});
if (this.onTap != null) {
this.invokeCallback<object>("onTap", () => {
this.onTap();
return null;
});
}
this._reset();
}
}
void _reset() {
this._sentTapDown = false;
this._wonArenaForPrimaryPointer = false;
this._finalPosition = null;
}
}
}

3
Assets/UIWidgets/gestures/tap.cs.meta


fileFormatVersion: 2
guid: f6283d6471a24256ad84c37472dfe262
timeCreated: 1535608354

118
Assets/UIWidgets/gestures/team.cs


using System.Collections.Generic;
using UIWidgets.foundation;
namespace UIWidgets.gestures {
class _CombiningGestureArenaEntry : GestureArenaEntry {
public _CombiningGestureArenaEntry(_CombiningGestureArenaMember _combiner, GestureArenaMember _member) {
this._combiner = _combiner;
this._member = _member;
}
readonly _CombiningGestureArenaMember _combiner;
readonly GestureArenaMember _member;
public override void resolve(GestureDisposition disposition) {
this._combiner._resolve(this._member, disposition);
}
}
class _CombiningGestureArenaMember : GestureArenaMember {
public _CombiningGestureArenaMember(GestureArenaTeam _owner, int _pointer) {
this._owner = _owner;
this._pointer = _pointer;
}
readonly GestureArenaTeam _owner;
readonly List<GestureArenaMember> _members = new List<GestureArenaMember>();
readonly int _pointer;
bool _resolved = false;
GestureArenaMember _winner;
GestureArenaEntry _entry;
public void acceptGesture(int pointer) {
D.assert(this._pointer == pointer);
D.assert(this._winner != null || this._members.isNotEmpty());
this._close();
this._winner = this._winner ?? this._owner.captain ?? this._members[0];
foreach (GestureArenaMember member in this._members) {
if (member != this._winner) {
member.rejectGesture(pointer);
}
}
this._winner.acceptGesture(pointer);
}
public void rejectGesture(int pointer) {
D.assert(this._pointer == pointer);
this._close();
foreach (GestureArenaMember member in this._members) {
member.rejectGesture(pointer);
}
}
void _close() {
D.assert(!this._resolved);
this._resolved = true;
var combiner = this._owner._combiners[this._pointer];
D.assert(combiner == this);
this._owner._combiners.Remove(this._pointer);
}
internal GestureArenaEntry _add(int pointer, GestureArenaMember member) {
D.assert(!this._resolved);
D.assert(this._pointer == pointer);
this._members.Add(member);
this._entry = this._entry ?? this._owner._gestureArena.add(pointer, this);
return new _CombiningGestureArenaEntry(this, member);
}
internal void _resolve(GestureArenaMember member, GestureDisposition disposition) {
if (this._resolved) {
return;
}
if (disposition == GestureDisposition.rejected) {
this._members.Remove(member);
member.rejectGesture(this._pointer);
if (this._members.isEmpty()) {
this._entry.resolve(disposition);
}
} else {
this._winner = this._winner ?? this._owner.captain ?? member;
this._entry.resolve(disposition);
}
}
}
public class GestureArenaTeam {
public GestureArenaTeam(GestureArenaManager gestureArena) {
this._gestureArena = gestureArena;
}
internal readonly GestureArenaManager _gestureArena;
internal readonly Dictionary<int, _CombiningGestureArenaMember> _combiners =
new Dictionary<int, _CombiningGestureArenaMember>();
public GestureArenaMember captain;
public GestureArenaEntry add(int pointer, GestureArenaMember member) {
_CombiningGestureArenaMember combiner;
if (!this._combiners.TryGetValue(pointer, out combiner)) {
combiner = new _CombiningGestureArenaMember(this, pointer);
this._combiners[pointer] = combiner;
}
return combiner._add(pointer, member);
}
}
}

3
Assets/UIWidgets/gestures/team.cs.meta


fileFormatVersion: 2
guid: 7f4c3bdaf7be471ea5c3eeb4d7656d27
timeCreated: 1535593919

51
Assets/UIWidgets/ui/pointer.cs


using System;
using System.Collections.Generic;
namespace UIWidgets.ui {
public enum PointerChange {
cancel,
add,
remove,
hover,
down,
move,
up,
}
public enum PointerDeviceKind {
touch,
mouse,
}
public class PointerData {
public PointerData(
DateTime timeStamp,
PointerChange change,
PointerDeviceKind kind,
int device,
double physicalX,
double physicalY) {
this.timeStamp = timeStamp;
this.change = change;
this.kind = kind;
this.device = device;
this.physicalX = physicalX;
this.physicalY = physicalY;
}
public readonly DateTime timeStamp;
public PointerChange change;
public PointerDeviceKind kind;
public int device;
public double physicalX;
public double physicalY;
}
public class PointerDataPacket {
public PointerDataPacket(List<PointerData> data) {
this.data = data;
}
public readonly List<PointerData> data;
}
}

3
Assets/UIWidgets/ui/pointer.cs.meta


fileFormatVersion: 2
guid: b5409a8bf1ca47a187b9a230dbcdcd98
timeCreated: 1536048980

26
Assets/UIWidgets/async/microtask_queue.cs


using System;
using System.Collections.Generic;
using UIWidgets.foundation;
using UnityEngine;
namespace UIWidgets.async {
public class MicrotaskQueue {
private Queue<Action> _queue = new Queue<Action>();
public void scheduleMicrotask(Action action) {
this._queue.Enqueue(action);
}
public void flushMicrotasks() {
while (this._queue.isNotEmpty()) {
var action = this._queue.Dequeue();
try {
action();
}
catch (Exception ex) {
Debug.LogError("Error to execute microtask: " + ex);
}
}
}
}
}

3
Assets/UIWidgets/async/microtask_queue.cs.meta


fileFormatVersion: 2
guid: 6d95e65550134a478f8f77093fc6d6f1
timeCreated: 1535723558

105
Assets/UIWidgets/async/priority_queue.cs


using System;
using System.Collections.Generic;
namespace UIWidgets.async {
public class PriorityQueue<T> where T : IComparable<T> {
private readonly List<T> _data;
public PriorityQueue() {
this._data = new List<T>();
}
public void enqueue(T item) {
this._data.Add(item);
int ci = this._data.Count - 1; // child index; start at end
while (ci > 0) {
int pi = (ci - 1) / 2; // parent index
if (this._data[ci].CompareTo(this._data[pi]) >= 0) {
break; // child item is larger than (or equal) parent so we're done
}
T tmp = this._data[ci];
this._data[ci] = this._data[pi];
this._data[pi] = tmp;
ci = pi;
}
}
public T dequeue() {
// assumes pq is not empty; up to calling code
int li = this._data.Count - 1; // last index (before removal)
T frontItem = this._data[0]; // fetch the front
this._data[0] = this._data[li];
this._data.RemoveAt(li);
--li; // last index (after removal)
int pi = 0; // parent index. start at front of pq
while (true) {
int ci = pi * 2 + 1; // left child index of parent
if (ci > li) {
break; // no children so done
}
int rc = ci + 1; // right child
if (rc <= li && this._data[rc].CompareTo(this._data[ci]) < 0) {
// if there is a rc (ci + 1), and it is smaller than left child, use the rc instead
ci = rc;
}
if (this._data[pi].CompareTo(this._data[ci]) <= 0) {
break; // parent is smaller than (or equal to) smallest child so done
}
T tmp = this._data[pi];
this._data[pi] = this._data[ci];
this._data[ci] = tmp; // swap parent and child
pi = ci;
}
return frontItem;
}
public T peek() {
T frontItem = this._data[0];
return frontItem;
}
public int count {
get { return this._data.Count; }
}
public override string ToString() {
string s = "";
for (int i = 0; i < this._data.Count; ++i) {
s += this._data[i] + " ";
}
s += "count = " + this._data.Count;
return s;
}
public bool isConsistent() {
// is the heap property true for all data?
if (this._data.Count == 0) {
return true;
}
int li = this._data.Count - 1; // last index
for (int pi = 0; pi < this._data.Count; ++pi) {
// each parent index
int lci = 2 * pi + 1; // left child index
int rci = 2 * pi + 2; // right child index
if (lci <= li && this._data[pi].CompareTo(this._data[lci]) > 0) {
return false; // if lc exists and it's greater than parent then bad.
}
if (rci <= li && this._data[pi].CompareTo(this._data[rci]) > 0) {
return false; // check the right child too.
}
}
return true; // passed all checks
}
}
}

3
Assets/UIWidgets/async/priority_queue.cs.meta


fileFormatVersion: 2
guid: 2d77ab33460640ddb5e3abbb2a11da34
timeCreated: 1536040675

67
Assets/UIWidgets/async/timer.cs


using System;
using UnityEngine;
namespace UIWidgets.async {
public abstract class Timer {
public abstract void cancel();
}
public class TimerProvider {
private readonly PriorityQueue<TimerImpl> _queue;
public TimerProvider() {
this._queue = new PriorityQueue<TimerImpl>();
}
public Timer run(TimeSpan duration, Action callback) {
var timer = new TimerImpl(DateTime.Now + duration, callback);
this._queue.enqueue(timer);
return timer;
}
public void update() {
var now = DateTime.Now;
while (this._queue.count > 0 && this._queue.peek().deadline <= now) {
var timer = this._queue.dequeue();
timer.invoke();
}
}
private class TimerImpl : Timer, IComparable<TimerImpl> {
public readonly DateTime deadline;
private readonly Action _callback;
private bool _done;
public TimerImpl(DateTime deadline, Action callback) {
this.deadline = deadline;
this._callback = callback;
this._done = false;
}
public override void cancel() {
this._done = true;
}
public void invoke() {
if (this._done) {
return;
}
this._done = true;
try {
this._callback();
}
catch (Exception ex) {
Debug.LogError("Error to execute timer callback: " + ex);
}
}
public int CompareTo(TimerImpl other) {
return this.deadline.CompareTo(other.deadline);
}
}
}
}

11
Assets/UIWidgets/async/timer.cs.meta


fileFormatVersion: 2
guid: 22e7b4cca5d354a4e9aa008cafcfdfe8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存