Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(27)

Unified Diff: sky/specs/gestures.md

Issue 872523002: Specs: Gestures Mark III -- handle how to have a gesture claim that it (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sky/specs/gestures.md
diff --git a/sky/specs/gestures.md b/sky/specs/gestures.md
index 037fa247bd2fddff51a4d7d94a5f00d2d97e5636..cefa311e0ed54a5953a28a95061153fcf15c10d4 100644
--- a/sky/specs/gestures.md
+++ b/sky/specs/gestures.md
@@ -1,60 +1,105 @@
Gestures
========
+TODO(ianh): make it possible for a Gesture to time out and cancel even
+without having seen a pointer event, to handle double-tap events
+
+TODO(ianh): even with that, we should keep track of finished-but-valid
+candidates so that when double-tap cancels itself, the tap, which was
+still valid even though it's done, can be accepted
+
```javascript
callback GestureCallback void (Event event);
dictionary GestureState {
Boolean valid = false; // if true, the event was part of the current gesture
+ Boolean forceCommit = false; // if true, the gesture thinks that other gestures should give up
Boolean finished = true; // if true, we're ready for the next gesture to start
}
+dictionary SendEventOptions {
+ Integer? coallesceGroup = null; // when queuing events, only the last event with each group is kept
+ Boolean precommit = false; // if true, event should just be sent right away, not queued
+}
+
abstract class Gesture {
constructor ();
- Boolean processEvent(EventTarget target, Event event);
- // - if this.ready=true, clear the sendEvent() buffer and forget
- // the last accept() callback, if any.
+ attribute GestureCallback callback;
+ // set by GestureChooser to point to itself
+
+ GestureState processEvent(EventTarget target, Event event);
+ // - if this.ready=true:
+ // - clear the sendEvent() buffer
+ // - set this.accepted = false
// - let returnValue = this.processEventInternal(...)
// - if this.discarding:
// - assert: returnValue.valid == false
- // - if !returnValue.valid, then clear the sendEvent() buffer and
- // forget the last accept() callback, if any
- // - set this.ready = returnValue.finished
+ // - if returnValue.valid == false
+ // - assert: returnValue.forceCommit == false
+ // - if !returnValue.valid, then:
+ // - clear the sendEvent() buffer
+ // - set this.accepted = false
// - set this.canceled = !returnValue.valid
+ // - set this.ready = returnValue.finished
// - set this.discarding = !returnValue.valid && !returnValue.finished
// - set this.active = returnValue.valid && !returnValue.finished
- // - return returnValue.valid
-
- readonly attribute Boolean active; // defaults to false
- // true if the last time processEvent was invoked, valid was true
- // and finished was false, and we haven't been cancel()ed
+ // - return returnValue
readonly attribute Boolean canceled; // defaults to false
// true if either the last time processEvent was invoked, valid was
// false, or, we have been cancel()ed
+ readonly attribute Boolean ready; // defaults to true
+ // true if the last time processEvent was invoked, the gesture was
+ // over
+
readonly attribute Boolean discarding; // defaults to false
// true if the last time processEvent was invoked, valid was false
+ // and finished was false, or, we have been cancel()ed
+ // (aka canceled && !ready)
+
+ readonly attribute Boolean active; // defaults to false
+ // true if the last time processEvent was invoked, valid was true
// and finished was false, and we haven't been cancel()ed
+ // (aka !canceled && !ready)
- readonly attribute Boolean ready; // defaults to true
- // true if the last time processEvent was invoked, the gesture was
- // over, or, we have been cancel()ed
+ readonly attribute Boolean accepted; // defaults to false
+ // true accept() was called and we haven't been cancel()ed since
- void accept(GestureCallback callback);
+ void accept();
// assert: this.canceled == false
- // remember the giver accept callback, and send the buffered gesture
- // events to that callback
+ // set accepted = true
+ // send the buffered gesture events to the callback
// - call this immediately after getting a positive result from
// processEvent()
virtual void cancel();
- // set active=false, canceled=true, discarding=false, ready=false
- // clear the sendEvent() buffer and forget the last accept()
- // callback, if any
- // descendants may override this if they have more state to drop
+ // called to indicate that this gesture isn't going to be chosen,
+ // or if it was chosen, that it is finished
+ // - assert: this.canceled == false
+ // - set this.canceled = true
+ // - set this.discarding = !this.ready
+ // - set this.active = false
+ // - clear the sendEvent() buffer
+ // - set this.accepted = false
+ // - descendants may override this if they have more state to drop,
+ // or if they want to send an event to report that it's canceled,
+ // especially if this.accepted is true
+
+ virtual void reset();
+ // called immediately after the first pointer-down of a possible
+ // gesture is sent to processEvents() to indicate that the pointer
+ // wasn't captured so we are to forget anything ever happened (later
+ // pointer-downs are always captured)
+ // - set this.canceled = true
+ // - set this.ready = true
+ // - set this.discarding = false
+ // - set this.active = false
+ // - clear the sendEvent() buffer
+ // - set this.accepted = false
+ // - descendants may override this if they have more state to drop
// internal API:
@@ -73,17 +118,32 @@ abstract class Gesture {
// - doing anything with the event or target other than reading
// state is a contract violation
// - you are allowed to call sendEvent() at any time during a
- // processEventInternal() call, or between calls to
- // processEventInternal() assuming that the last such call
- // returned either valid=true or finished=true.
+ // processEventInternal() call, or after a call to
+ // processEventInternal(), assuming that the last such call
+ // returned either valid=true or finished=true, until the next
+ // call to processEventInternal() or cancel().
+ // - set forceCommit=true on the return value if you are confident
+ // that this is the gesture the user meant, even if it's possible
+ // that another gesture is still claiming it's valid (e.g. a long
+ // press might forceCommit to override a scroll, if the user
+ // hasn't moved for a while)
+ // - if you send events, you can set precommit=true to send the
+ // event even before the gesture has been accepted
+ // - if you send precommit events, make sure to send corresponding
+ // "cancel" events if reset() or cancel() are called
- void sendEvent(Event event);
+ void sendEvent(Event event, SendEventOptions options);
// used internally to queue up or send events
// - assert: this.discarding == false
- // - if accepted is true, then send the event straight to the
- // callback
- // - otherwise, add it to the buffer
-
+ // - assert: options.precommit is false or options.coallesceGroup
+ // is null
+ // - set event.gesture = this
+ // - if this.accepted is true or if options.precommit is true, then
+ // send the event straight to the callback
+ // - otherwise:
+ // - if the buffer has an entry with the same coallesceGroup
+ // identifier, drop it
+ // - add the event to the buffer
}
class GestureChooser : EventTarget {
@@ -109,25 +169,59 @@ class GestureChooser : EventTarget {
readonly attribute Boolean active; // at least one of the gestures is active (initially false)
readonly attribute Boolean accepted; // we accepted a gesture since the last time active was false (initially false)
+ // internal state:
+ // /candidates/ is a list of Gesture objects, initially empty
+ //
// any time one of the pointer events is received:
// - if it's pointer-down and it's already captured, ignore the
- // event; otherwise:
- // - let /candidates/ be a list of Gestures, initially empty
- // - if all of the registered Gestures have ready==true, then add
- // all of them to /candidates/; otherwise, add all the Gestures
- // with ready==false to /candidates/
+ // event and skip the remaining steps
+ // - let captured be a boolean
+ // - if /candidates/ is empty, then:
+ // - set captured to false
+ // - if accepted is true, call cancel() on whatever the last
+ // accepted candidate was, if any
+ // - add all the registered Gestures to /candidates/
+ // - otherwise:
+ // - set captured to true
+ // - if it's pointer-down, capture the event
// - call processEvent() with the event on all the Gestures in
- // /candidates/
- // - if it's pointer-down then:
- // - if at least one Gesture returned true, then capture the
- // event
- // - else send cancel() to all the gestures in /candidates/.
- // - if accepted is false, and exactly one of the processEvent()
- // methods returned true, then set accepted to true and call that
- // Gesture's accept() method, passing it a method that fires the
- // provided event on the current target (if not null)
- // - if all the registered Gestures are now ready==true (regardless
- // of the return values), then set active and accepted to false;
+ // /candidates/, collecting their return values (GestureState
+ // objects)
+ // - set forcingAccept to false
+ // - set willAccept to null
+ // - for each Gesture in /candidates/, in registration order:
+ // - if it returned valid==false, then
+ // - if it is our last accepted candidate, then:
+ // - set this.accepted = false
+ // - if it returned valid==true:
+ // - if it's pointer-down, then:
+ // - set captured to true
+ // - capture the event
+ // - if this.accepted == true:
+ // - assert: this Gesture is the last accepted candidate
+ // - if it returned forceCommit==true then:
+ // - assert that its accepted attribute is false
+ // - if forcingAccept is false, then:
+ // - set willAccept to this Gesture
+ // - set forcingAccept to true
+ // - otherwise:
+ // - if forcingAccept is false:
+ // - if willAccept is null:
+ // - set willAccept to this Gesture
+ // - otherwise:
+ // - set willAccept to 'undecided'
+ // - if it returned finished==true
+ // - remove the Gesture from /candidates/
+ // - if willAccept is set to a Gesture:
+ // - set this.accepted = true
+ // - call the Gesture's accept() method; this is now the last
+ // accepted candidate
+ // - call cancel() on all the other Gesture objects that returned
+ // valid==true
+ // - if captured is false:
+ // - call reset() on all the gestures in /candidates/, and then
+ // let /candidates/ be empty
+ // - if /candidates/ is now empty, then set active to false;
// otherwise, set active to true
}
@@ -155,7 +249,7 @@ class TapGesture : Gesture {
// - if it's primary:
// - assert: this.ready==true // this is the first press
// - this.primaryDown = true
- // - sendEvent() a tap-down event
+ // - sendEvent() a tap-down event, with precommit=true
// - return { valid: true, finished: false }
// - otherwise:
// - if this.ready == false:
@@ -181,10 +275,10 @@ class TapGesture : Gesture {
// // because otherwise we would have lost capture and thus not be getting the events
// - if it's primary:
// - if it hit tests within target's bounding box:
- // - sendEvent() a tap-move event
+ // - sendEvent() a tap-move event, with precommit=true
// - return { valid: true, finished: false }
// - otherwise:
- // - sendEvent() a tap-cancel event
+ // - sendEvent() a tap-cancel event, with precommit=true
// - return { valid: false, finished: false }
// - otherwise:
// - // this is the move of some bogus secondary press
@@ -194,17 +288,101 @@ class TapGesture : Gesture {
// - if it's primary:
// - sendEvent() a tap event
// - this.primaryDown = false
- // - return { valid: true, finished: this.numButtons == 0 }
+ // - return { valid: true, forceCommit: this.numButtons == 0, finished: this.numButtons == 0 }
// - otherwise:
// - // this is the 'up' of some bogus secondary press
// // ignore it, but continue listening for our primary up
// - return { valid: this.primaryDown, finished: this.numButtons == 0 }
}
-class ScrollGesture : Gesture {
- Boolean processEvent(EventTarget target, Event event);
- // this fires the following events:
- // TODO(ianh): fill this in
+class LongPressGesture : Gesture {
+ GestureState processEvent(EventTarget target, Event event);
+ // long-tap-start: sent when the primary pointer goes down
+ // long-tap-cancel: sent when cancel(), reset(), or finger goes out of bounding box
+ // long-tap: sent when the primary pointer is released
}
+class DoubleTapGesture : Gesture {
+ GestureState processEvent(EventTarget target, Event event);
+ // double-tap-start: sent when the primary pointer goes down the first time
+ // double-tap-cancel: sent when cancel(), reset(), or finger goes out of bounding box, or it times out
+ // double-tap: sent when the primary pointer is released the second time
+}
+
+
+abstract class ScrollGesture : Gesture {
+ GestureState processEvent(EventTarget target, Event event);
+ // this fires the following events (inertia is a boolean, delta is a float):
+ // scroll-start, with field inertia=false, delta=0; precommit=true
+ // scroll, with fields inertia (is this a simulated scroll from inertia or a real scroll?), delta (number of pixels to scroll); precommit=true
+ // scroll-end, with field inertia (same), delta=0; precommit=true
+ // scroll-start is fired right away
+ // scroll is sent whenever the primary pointer moves while down
+ // scroll is also sent after the pointer goes back up, based on inertia
+ // scroll-end is sent after the pointer goes back up once the scroll reaches delta=0
+ // scroll-end is also sent when the gesture is canceled or reset
+ // processEvent() returns:
+ // - valid=true pretty much always so long as there's a primary touch (e.g. not for a right-click)
+ // - forceCommit=true when you travel a certain distance
+ // - finished=true when the primary pointer goes up
+}
+
+class HorizontalScrollGesture : ScrollGesture { }
+ // a ScrollGesture giving x-axis scrolling
+
+class VerticalScrollGesture : ScrollGesture { }
+ // a ScrollGesture giving y-axis scrolling
+
+
+class PanGesture : Gesture {
+ // similar to ScrollGesture, but with two axes
+ // pan-start, pan, pan-end
+ // events have inertia (boolean), dx (float), dy (float)
+}
+
+
+abstract class ZoomGesture : Gesture {
+ GestureState processEvent(EventTarget target, Event event);
+ // zoom-start: sent when we could start zooming (e.g. for pinch-zoom, when two fingers hit the glass) (precommit)
+ // zoom-end: sent when cancel()ed after zoom-start, or when the fingers are lifted (precommit)
+ // zoom, with a 'scale' attribute, whose value is a multiple of the scale factor at zoom-start
+ // e.g. if the user zooms to 2x, you'd get a bunch of 'zoom' events like scale=1.0, scale=1.17, ... scale=1.91, scale=2.0
+}
+
+class PinchZoomGesture : ZoomGesture {
+ // a ZoomGesture for two-finger-pinch gesture
+ // zoom is precommit
+}
+
+class DoubleTapZoomGesture : ZoomGesture {
+ // a ZoomGesture for the double-tap-slide gesture
+ // when the slide starts, forceCommit
+}
+
+
+class PanAndZoomGesture : Gesture {
+ GestureState processEvent(EventTarget target, Event event);
+ // manipulate-start (precommit)
+ // manipulate: (precommit)
+ // panX, panY: pixels
+ // scaleX, scaleY: a multiplier of the scale at manipulate-start
+ // rotation: turns
+ // manipulate-end (precommit)
+}
+
+
+abstract class FlingGesture : Gesture {
+ GestureState processEvent(EventTarget target, Event event);
+ // fling-start: when the gesture begins (precommit)
+ // fling-move: while the user is directly dragging the element (has delta attribute with the distance from fling-start) (precommit)
+ // fling: the user has released the pointer and the decision is it was in fact flung
+ // fling-cancel: cancel(), or the user has released the pointer and the decision is it was not flung (precommit)
+ // fling-end: cancel(), reset(), or after fling or fling-cancel (precommit)
+}
+
+class FlingLeftGesture : FlingGesture { }
+class FlingRightGesture : FlingGesture { }
+class FlingUpGesture : FlingGesture { }
+class FlingDownGesture : FlingGesture { }
+
```
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698