| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 typedef void AnimationCallback(num currentTime); | |
| 6 | |
| 7 class CallbackData { | |
| 8 final AnimationCallback callback; | |
| 9 final num minTime; | |
| 10 int id; | |
| 11 | |
| 12 static int _nextId; | |
| 13 | |
| 14 bool ready(num time) => minTime === null || minTime <= time; | |
| 15 | |
| 16 CallbackData(this.callback, this.minTime) { | |
| 17 // TODO(jacobr): static init needs cleanup, see http://b/4161827 | |
| 18 if (_nextId === null) { | |
| 19 _nextId = 1; | |
| 20 } | |
| 21 id = _nextId++; | |
| 22 } | |
| 23 } | |
| 24 | |
| 25 /** | |
| 26 * Animation scheduler implementing the functionality provided by | |
| 27 * [:window.requestAnimationFrame:] for platforms that do not support it | |
| 28 * or support it badly. When multiple UI components are animating at once, | |
| 29 * this approach yields superior performance to calling setTimeout directly as | |
| 30 * all pieces of the UI will animate at the same time resulting in fewer | |
| 31 * layouts. | |
| 32 */ | |
| 33 // TODO(jacobr): use window.requestAnimationFrame when it is available and | |
| 34 // 60fps for the current browser. | |
| 35 class AnimationScheduler { | |
| 36 static final FRAMES_PER_SECOND = 60; | |
| 37 static final MS_PER_FRAME = 1000 ~/ FRAMES_PER_SECOND; | |
| 38 static final USE_INTERVALS = false; | |
| 39 | |
| 40 /** List of callbacks to be executed next animation frame. */ | |
| 41 List<CallbackData> _callbacks; | |
| 42 int _intervalId; | |
| 43 bool _isMobileSafari = false; | |
| 44 CSSStyleDeclaration _safariHackStyle; | |
| 45 int _frameCount = 0; | |
| 46 bool _webkitAnimationFrameMaybeAvailable = true; | |
| 47 | |
| 48 AnimationScheduler() | |
| 49 : _callbacks = new List<CallbackData>() { | |
| 50 if (_isMobileSafari) { | |
| 51 // TODO(jacobr): find a better workaround for the issue that 3d transforms | |
| 52 // sometimes don't render on iOS without forcing a layout. | |
| 53 final element = new Element.tag('div'); | |
| 54 document.body.nodes.add(element); | |
| 55 _safariHackStyle = element.style; | |
| 56 _safariHackStyle.position = 'absolute'; | |
| 57 } | |
| 58 } | |
| 59 | |
| 60 /** | |
| 61 * Cancel the pending callback matching the specified [id]. | |
| 62 * This is not heavily optimized as typically users don't cancel animation | |
| 63 * frames. | |
| 64 */ | |
| 65 void cancelRequestAnimationFrame(int id) { | |
| 66 _callbacks = _callbacks.filter((CallbackData e) => e.id != id); | |
| 67 } | |
| 68 | |
| 69 /** | |
| 70 * Schedule [callback] to execute at the next animation frame that occurs | |
| 71 * at or after [minTime]. If [minTime] is not specified, the first available | |
| 72 * animation frame is used. Returns an id that can be used to cancel the | |
| 73 * pending callback. | |
| 74 */ | |
| 75 int requestAnimationFrame(AnimationCallback callback, | |
| 76 [Element element = null, | |
| 77 num minTime = null]) { | |
| 78 final callbackData = new CallbackData(callback, minTime); | |
| 79 _requestAnimationFrameHelper(callbackData); | |
| 80 return callbackData.id; | |
| 81 } | |
| 82 | |
| 83 void _requestAnimationFrameHelper(CallbackData callbackData) { | |
| 84 _callbacks.add(callbackData); | |
| 85 if (_intervalId === null) { | |
| 86 _setupInterval(); | |
| 87 } | |
| 88 } | |
| 89 | |
| 90 void _setupInterval() { | |
| 91 // Assert that we never schedule multiple frames at once. | |
| 92 assert(_intervalId === null); | |
| 93 if (USE_INTERVALS) { | |
| 94 _intervalId = window.setInterval(_step, MS_PER_FRAME); | |
| 95 } else { | |
| 96 if (_webkitAnimationFrameMaybeAvailable) { | |
| 97 try { | |
| 98 // TODO(jacobr): passing in document should not be required. | |
| 99 _intervalId = window.webkitRequestAnimationFrame( | |
| 100 (int ignored) { _step(); }, document); | |
| 101 // TODO(jacobr) fix this odd type error. | |
| 102 } catch (var e) { | |
| 103 _webkitAnimationFrameMaybeAvailable = false; | |
| 104 } | |
| 105 } | |
| 106 if (!_webkitAnimationFrameMaybeAvailable) { | |
| 107 _intervalId = window.setTimeout(() { _step(); }, MS_PER_FRAME); | |
| 108 } | |
| 109 } | |
| 110 } | |
| 111 | |
| 112 void _step() { | |
| 113 if (_callbacks.isEmpty()) { | |
| 114 // Cancel the interval on the first frame where there aren't actually | |
| 115 // any available callbacks. | |
| 116 assert(_intervalId != null); | |
| 117 if (USE_INTERVALS) { | |
| 118 window.clearInterval(_intervalId); | |
| 119 } | |
| 120 _intervalId = null; | |
| 121 } else if (USE_INTERVALS == false) { | |
| 122 _intervalId = null; | |
| 123 _setupInterval(); | |
| 124 } | |
| 125 int numRemaining = 0; | |
| 126 int minTime = new Date.now().value + MS_PER_FRAME; | |
| 127 | |
| 128 int len = _callbacks.length; | |
| 129 for (final callback in _callbacks) { | |
| 130 if (!callback.ready(minTime)) { | |
| 131 numRemaining++; | |
| 132 } | |
| 133 } | |
| 134 | |
| 135 if (numRemaining == len) { | |
| 136 // TODO(jacobr): we could be more clever about this case if delayed | |
| 137 // requests really become the main use case... | |
| 138 return; | |
| 139 } | |
| 140 // Some callbacks need to be executed. | |
| 141 final currentCallbacks = _callbacks; | |
| 142 _callbacks = new List<CallbackData>(); | |
| 143 | |
| 144 for (final callbackData in currentCallbacks) { | |
| 145 if (callbackData.ready(minTime)) { | |
| 146 try { | |
| 147 (callbackData.callback)(minTime); | |
| 148 } catch (var e) { | |
| 149 final msg = e.toString(); | |
| 150 print('Suppressed exception ${msg} triggered by callback'); | |
| 151 } | |
| 152 } else { | |
| 153 _callbacks.add(callbackData); | |
| 154 } | |
| 155 } | |
| 156 | |
| 157 _frameCount++; | |
| 158 if (_isMobileSafari) { | |
| 159 // Hack to work around an iOS bug where sometimes animations do not | |
| 160 // render if only webkit transforms were modified. | |
| 161 // TODO(jacobr): find a cleaner workaround. | |
| 162 int offset = _frameCount % 2; | |
| 163 _safariHackStyle.left = '${offset}px'; | |
| 164 } | |
| 165 } | |
| 166 } | |
| OLD | NEW |