| OLD | NEW |
| 1 Gestures | 1 Gestures |
| 2 ======== | 2 ======== |
| 3 | 3 |
| 4 ```javascript | 4 ```javascript |
| 5 | 5 |
| 6 callback GestureCallback void (Event event); | 6 callback GestureCallback void (Event event); |
| 7 | 7 |
| 8 dictionary GestureState { |
| 9 Boolean valid = false; // if true, the event was part of the current gesture |
| 10 Boolean finished = true; // if true, we're ready for the next gesture to start |
| 11 } |
| 12 |
| 8 abstract class Gesture { | 13 abstract class Gesture { |
| 9 constructor (); | 14 constructor (); |
| 10 | 15 |
| 11 // Gestures cycle through states: | 16 Boolean processEvent(EventTarget target, Event event); |
| 12 // - idle: nothing is going on (active, accepted, and discarding | 17 // - if this.ready=true, clear the sendEvent() buffer and forget |
| 13 // are false). | 18 // the last accept() callback, if any. |
| 14 // - buffering: the GestureChooser is passing in some events, but | 19 // - let returnValue = this.processEventInternal(...) |
| 15 // hasn't yet committed to using this Gesture, and this Gesture | 20 // - if this.discarding: |
| 16 // hasn't yet decided that this set of events isn't interesting | 21 // - assert: returnValue.valid == false |
| 17 // (active is true, accepted is false, discarding is false). | 22 // - if !returnValue.valid, then clear the sendEvent() buffer and |
| 18 // - forwarding: this Gesture is still interesting and the | 23 // forget the last accept() callback, if any |
| 19 // GestureChooser has decided to use this Gesture so events are | 24 // - set this.ready = returnValue.finished |
| 20 // being sent along (active is true, accepted is true, discarding | 25 // - set this.canceled = !returnValue.valid |
| 21 // is false). | 26 // - set this.discarding = !returnValue.valid && !returnValue.finished |
| 22 // - discarding: this Gesture got cancelled or didn't match the | 27 // - set this.active = returnValue.valid && !returnValue.finished |
| 23 // pattern (active is true, accepted is false, discarding is | 28 // - return returnValue.valid |
| 24 // true). | |
| 25 | 29 |
| 26 // TODO(ianh): Need to handle gestures that want to send events | 30 readonly attribute Boolean active; // defaults to false |
| 27 // beyond the end of the gesture, e.g. inertia in scrolling. In that | 31 // true if the last time processEvent was invoked, valid was true |
| 28 // mode, gestures are sending events but are simultaneously no | 32 // and finished was false, and we haven't been cancel()ed |
| 29 // longer "active"... | |
| 30 | 33 |
| 31 Boolean processEvent(EventTarget target, Event event); | 34 readonly attribute Boolean canceled; // defaults to false |
| 32 // as the events are received, they get examined to see if they fit | 35 // true if either the last time processEvent was invoked, valid was |
| 33 // the pattern for the gesture; if they do, then returns true, else, | 36 // false, or, we have been cancel()ed |
| 34 // returns false | 37 |
| 35 // - returning true after false has been returned is a contract | 38 readonly attribute Boolean discarding; // defaults to false |
| 36 // violation unless active became false in between | 39 // true if the last time processEvent was invoked, valid was false |
| 37 // - manipulating the event is a contract violation | 40 // and finished was false, and we haven't been cancel()ed |
| 38 // | 41 |
| 39 // TODO(ianh): replace processEvent()'s return value with an enum: | 42 readonly attribute Boolean ready; // defaults to true |
| 40 // - acceptable (true and active is true) | 43 // true if the last time processEvent was invoked, the gesture was |
| 41 // - discarding (false but active is still true) | 44 // over, or, we have been cancel()ed |
| 42 // - finished (false and active is now false) | |
| 43 // in such a world, the contract would be that you can't return | |
| 44 // 'acceptable' after returning 'discarding' without first returning | |
| 45 // 'finished' | |
| 46 | 45 |
| 47 void accept(GestureCallback callback); | 46 void accept(GestureCallback callback); |
| 48 // set accepted to true, send the buffered gesture events to | 47 // assert: this.canceled == false |
| 49 // callback, and use that callback for all future Gesture events | 48 // remember the giver accept callback, and send the buffered gesture |
| 50 // until the gesture is complete | 49 // events to that callback |
| 51 // - call this immediately after getting a positive result from | 50 // - call this immediately after getting a positive result from |
| 52 // processEvent() | 51 // processEvent() |
| 53 | 52 |
| 53 virtual void cancel(); |
| 54 // set active=false, canceled=true, discarding=false, ready=false |
| 55 // clear the sendEvent() buffer and forget the last accept() |
| 56 // callback, if any |
| 57 // descendants may override this if they have more state to drop |
| 58 |
| 54 // internal API: | 59 // internal API: |
| 55 // void sendEvent(Event event) | 60 |
| 56 // - assert: active is true, discarding is false | 61 virtual GestureState processEventInternal(EventTarget target, Event event); |
| 62 // descendants override this |
| 63 // default implementation returns { } (defaults) |
| 64 // - if this.discarding == false, then: |
| 65 // - optionally, call sendEvent() to fire gesture-specific |
| 66 // events |
| 67 // - as the events are received, they get examined to see if they |
| 68 // fit the pattern for the gesture; if they do, then return an |
| 69 // object with valid=true; if more events for this gesture could |
| 70 // still come in, return finished=false. |
| 71 // - if you returned valid=false finished=false, then the next call |
| 72 // to this must not return valid=true |
| 73 // - doing anything with the event or target other than reading |
| 74 // state is a contract violation |
| 75 // - you are allowed to call sendEvent() at any time during a |
| 76 // processEventInternal() call, or between calls to |
| 77 // processEventInternal() assuming that the last such call |
| 78 // returned either valid=true or finished=true. |
| 79 |
| 80 void sendEvent(Event event); |
| 81 // used internally to queue up or send events |
| 82 // - assert: this.discarding == false |
| 57 // - if accepted is true, then send the event straight to the | 83 // - if accepted is true, then send the event straight to the |
| 58 // callback | 84 // callback |
| 59 // - otherwise, add it to the buffer | 85 // - otherwise, add it to the buffer |
| 60 // | |
| 61 // void discard() | |
| 62 // - throw away the buffer, set discarding to true and accepted to | |
| 63 // false | |
| 64 | |
| 65 readonly attribute Boolean active; // not idle (buffering, forwarding, or disc
arding) | |
| 66 readonly attribute Boolean accepted; // true if active and accept() has been c
alled (forwarding or discarding) | |
| 67 readonly attribute Boolean discarding; // true if active and processEvent() ha
s returned false (discarding) | |
| 68 // 'active' is currently part of the contract between Gesture and GestureChoos
er (the other two are not) | |
| 69 | |
| 70 // active can be true even if processEvent() returned false; this is | |
| 71 // the discarding state. It means that the Gesture isn't sending any | |
| 72 // more events, but that the pointers haven't yet reached a state in | |
| 73 // which a new touch could begin. For example, a Tap gesture where | |
| 74 // the finger has gone out of the bounding box can't be retriggered | |
| 75 // until the finger is lifted. | |
| 76 | 86 |
| 77 } | 87 } |
| 78 | 88 |
| 79 class GestureChooser : EventTarget { | 89 class GestureChooser : EventTarget { |
| 80 constructor (EventTarget? target = null, Array<Gesture> candidates = []); | 90 constructor (EventTarget? target = null, Array<Gesture> candidates = []); |
| 81 // throws if any of the candidates are active | 91 // throws if any of the candidates are active |
| 82 | 92 |
| 83 readonly attribute EventTarget? target; | 93 readonly attribute EventTarget? target; |
| 84 void setTarget(EventTarget? target); | 94 void setTarget(EventTarget? target); |
| 85 | 95 |
| 86 Array<Gesture> getGestures(); | 96 Array<Gesture> getGestures(); |
| 87 void addGesture(Gesture candidate); | 97 void addGesture(Gesture candidate); |
| 88 // throw if candidates.active is true | 98 // throw if candidates.active is true |
| 89 void removeGesture(Gesture candidate); | 99 void removeGesture(Gesture candidate); |
| 90 // if active is true and candidate was the last Gesture in our list | 100 // if active is true and candidate was the last Gesture in our list |
| 91 // to be active, set active and accepted to false | 101 // to be active, set active and accepted to false |
| 92 | 102 |
| 93 // while target is not null and the list of candidates is not empty, | 103 // while target is not null and the list of candidates is not empty, |
| 94 // ensures that it is registered as an event listener for | 104 // ensures that it is registered as an event listener for |
| 95 // pointer-down, pointer-move, and pointer-up events on the target; | 105 // pointer-down, pointer-move, and pointer-up events on the target; |
| 96 // when the target changes, or when the list of candidates is | 106 // when the target changes, or when the list of candidates is |
| 97 // emptied, unregisters itself | 107 // emptied, unregisters itself |
| 98 | 108 |
| 99 readonly attribute Boolean active; // at least one of the gestures is active (
initially false) | 109 readonly attribute Boolean active; // at least one of the gestures is active (
initially false) |
| 100 readonly attribute Boolean accepted; // we accepted a gesture since the last t
ime active was false (initially false) | 110 readonly attribute Boolean accepted; // we accepted a gesture since the last t
ime active was false (initially false) |
| 111 |
| 101 // any time one of the pointer events is received: | 112 // any time one of the pointer events is received: |
| 102 // - if it's pointer-down and it's already captured, ignore the | 113 // - if it's pointer-down and it's already captured, ignore the |
| 103 // event; otherwise: | 114 // event; otherwise: |
| 104 // - let /candidates/ be a list of gestures, initially empty | 115 // - let /candidates/ be a list of Gestures, initially empty |
| 105 // - if none of the registered gestures are active, then add all of | 116 // - if all of the registered Gestures have ready==true, then add |
| 106 // them to /candidates/ otherwise, add all the active ones to | 117 // all of them to /candidates/; otherwise, add all the Gestures |
| 107 // /candidates/ | 118 // with ready==false to /candidates/ |
| 108 // - call processEvent() with the event on all the Gestures in | 119 // - call processEvent() with the event on all the Gestures in |
| 109 // /candidates/ | 120 // /candidates/ |
| 110 // - if it's pointer-down and at least one Gesture returned true, | 121 // - if it's pointer-down then: |
| 111 // then capture the event | 122 // - if at least one Gesture returned true, then capture the |
| 123 // event |
| 124 // - else send cancel() to all the gestures in /candidates/. |
| 112 // - if accepted is false, and exactly one of the processEvent() | 125 // - if accepted is false, and exactly one of the processEvent() |
| 113 // methods returned true, then set accepted to true and call that | 126 // methods returned true, then set accepted to true and call that |
| 114 // Gesture's accept() method, passing it a method that fires the | 127 // Gesture's accept() method, passing it a method that fires the |
| 115 // provided event on the current target (if not null) | 128 // provided event on the current target (if not null) |
| 116 // - if all the processEvent() methods returned false, and all the | 129 // - if all the registered Gestures are now ready==true (regardless |
| 117 // Gestures are no longer active, then set active and accepted to | 130 // of the return values), then set active and accepted to false; |
| 118 // false; otherwise, set active to true | 131 // otherwise, set active to true |
| 132 |
| 119 } | 133 } |
| 120 | 134 |
| 121 class TapGesture : Gesture { | 135 class TapGesture : Gesture { |
| 122 Boolean processEvent(EventTarget target, Event event); | 136 |
| 123 // if discarding is true: | 137 // internal state: |
| 124 // - if the event is a primary pointer-up, set active, accepted, and | 138 // Integer numButtons = 0; |
| 125 // discarding to false, and return false | 139 // Boolean primaryDown = false; |
| 126 // - otherwise, just return false | 140 |
| 127 // if EventTarget isn't an Element: | 141 virtual Boolean internalProcessEvent(EventTarget target, Event event); |
| 128 // - set active to true, discard(), and return false | 142 // - if the event is a pointer-down: |
| 129 // if the event is pointer-down: | 143 // - increment this.numButtons |
| 130 // - if it's primary: | 144 // - otherwise if it is a pointer-up: |
| 131 // - assert: active is false | 145 // - assert: this.numButtons > 0 |
| 132 // - sendEvent() a tap-down event | 146 // - decrement this.numButtons |
| 133 // - set active to true and return true | 147 // - if this.discarding == true: |
| 134 // - otherwise, if we're active: | 148 // return { valid: false, finished: this.numButtons == 0 } |
| 135 // - return true | 149 // - if EventTarget isn't an Element: |
| 136 // - otherwise: | 150 // - assert: event is a pointer-down |
| 137 // - return false | 151 // - assert: this.numButtons > 0 |
| 138 // if the event is pointer-move: | 152 // - return { valid: false, finished: false } |
| 139 // - if it's primary: | 153 // - if the event is pointer-down: |
| 140 // - if it hit tests within target's bounding box: | 154 // - assert: this.numButtons > 0 |
| 141 // - sendEvent() a tap-move event | 155 // - if it's primary: |
| 142 // - return true | 156 // - assert: this.ready==true // this is the first press |
| 157 // - this.primaryDown = true |
| 158 // - sendEvent() a tap-down event |
| 159 // - return { valid: true, finished: false } |
| 160 // - otherwise: |
| 161 // - if this.ready == false: |
| 162 // - // this is a right-click or similar |
| 163 // - return { valid: false, finished: false } |
| 164 // - otherwise, if this.canceled==false: |
| 165 // - assert: this.active==true |
| 166 // - // this is some bogus secondary press that we should ignore |
| 167 // // but it doesn't invalidate the existing primary press |
| 168 // - return { valid true, finished: false } |
| 169 // - otherwise: |
| 170 // - // this is some secondary press but we don't have a first press |
| 171 // // we have to wait til it's done before we can start a |
| 172 // // tap gesture again |
| 173 // - return { valid: false, finished: false } |
| 174 // - otherwise: |
| 175 // - assert: this.active |
| 176 // // if we're ready, forcibly the first event we'll see is a pointer-down
, |
| 177 // // so this.ready will never be true here |
| 178 // // if we're cancelled, then we won't get to here |
| 179 // - if the event is pointer-move: |
| 180 // - assert: this.numButtons > 0 |
| 181 // // because otherwise we would have lost capture and thus not be gett
ing the events |
| 182 // - if it's primary: |
| 183 // - if it hit tests within target's bounding box: |
| 184 // - sendEvent() a tap-move event |
| 185 // - return { valid: true, finished: false } |
| 186 // - otherwise: |
| 187 // - sendEvent() a tap-cancel event |
| 188 // - return { valid: false, finished: false } |
| 143 // - otherwise: | 189 // - otherwise: |
| 144 // - sendEvent() a tap-cancel event | 190 // - // this is the move of some bogus secondary press |
| 145 // - discard() and return false | 191 // // ignore it, but continue listening |
| 146 // - otherwise, if we're active: | 192 // - return { valid: true, finished: false } |
| 147 // - return true | 193 // - if the event is pointer-up: |
| 148 // - otherwise: | 194 // - if it's primary: |
| 149 // - return false | 195 // - sendEvent() a tap event |
| 150 // if the event is pointer-up: | 196 // - this.primaryDown = false |
| 151 // - if it's primary: | 197 // - return { valid: true, finished: this.numButtons == 0 } |
| 152 // - sendEvent() a tap event | 198 // - otherwise: |
| 153 // - set accepted and active to false, discard(), and return false | 199 // - // this is the 'up' of some bogus secondary press |
| 154 // - otherwise, if we're active: | 200 // // ignore it, but continue listening for our primary up |
| 155 // - return true | 201 // - return { valid: this.primaryDown, finished: this.numButtons == 0
} |
| 156 // - otherwise: | |
| 157 // - return false | |
| 158 } | 202 } |
| 159 | 203 |
| 160 class ScrollGesture : Gesture { | 204 class ScrollGesture : Gesture { |
| 161 Boolean processEvent(EventTarget target, Event event); | 205 Boolean processEvent(EventTarget target, Event event); |
| 162 // this fires the following events: | 206 // this fires the following events: |
| 163 // TODO(ianh): fill this in | 207 // TODO(ianh): fill this in |
| 164 } | 208 } |
| 165 | 209 |
| 166 ``` | 210 ``` |
| OLD | NEW |