1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 00:32:42 +01:00

Add support for device swipe events

Summary:
Ref T2700. Allow JS to listen for swipes on devices.

There are a bunch of tricky cases here and I probably didn't get them all totally right, but this interaction broadly looks like this:

  - We implement gesture recognition for the mouse in device modes (narrow browser), and for touch events from an actual device.
  - The sigil `touchable` indicates that a node wants to react to touch events.
  - When the user touches a `touchable` node, we start listening for moves. They might be tapping/clicking (in which case we don't care), but they might also be gesturing.
  - Once the user moves their finger/pointer far enough away from the tap origin, we recognize it as a gesture. I hardcoded this at 20px; I wasn't able to find any "official" Apple value, but 20px seems like a common default.
  - At this point, we look at where their finger has moved.
    - If they moved it mostly up/down, we interpret the gesture as "scroll" and just stop listening. The device does its own thing.
    - However, if they moved it mostly left/right, we interpret it as a "swipe". We start killing the moves so the device doesn't scroll.
  - Once we've recognized that a gesture is underway, we send a "gesture.swipe.start" event and then "gesture.swipe.move" events for every move.
  - When the user ends the gesture, we send "gesture.swipe.end".
  - If the user cancels the gesture (currently, only by tapping with a second finger), we send "gesture.swipe.cancel".
  - Gesture events have raw position data and some convenience fields.

Test Plan:
Wrote UI example and used it from the Desktop, iPhone simulator, and a real iphone.

  - The code always seems to get "scroll" vs "swipe" correct (i.e., consistent with my intentions).
  - The threshold feels pretty good to me.
  - Tapping with a second finger cancels the action.

Reviewers: chad, btrahan

Reviewed By: chad

CC: aran

Maniphest Tasks: T2700

Differential Revision: https://secure.phabricator.com/D5308
This commit is contained in:
epriestley 2013-03-09 13:53:15 -08:00
parent 4c914a5c49
commit 6b5204dca9
7 changed files with 382 additions and 101 deletions

View file

@ -51,6 +51,7 @@ $package_spec = array(
'javelin-behavior-konami',
'javelin-behavior-aphlict-dropdown',
'javelin-behavior-history-install',
'javelin-behavior-phabricator-gesture',
'javelin-behavior-phabricator-active-nav',
'javelin-behavior-phabricator-nav',

View file

@ -805,7 +805,7 @@ celerity_register_resource_map(array(
),
'conpherence-widget-pane-css' =>
array(
'uri' => '/res/6e5755bb/rsrc/css/application/conpherence/widget-pane.css',
'uri' => '/res/e67ad581/rsrc/css/application/conpherence/widget-pane.css',
'type' => 'css',
'requires' =>
array(
@ -1167,28 +1167,30 @@ celerity_register_resource_map(array(
),
'javelin-behavior-conpherence-menu' =>
array(
'uri' => '/res/0ad6ab54/rsrc/js/application/conpherence/behavior-menu.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-workflow',
3 => 'javelin-util',
4 => 'javelin-stratcom',
5 => 'javelin-uri',
),
'disk' => '/rsrc/js/application/conpherence/behavior-menu.js',
),
'javelin-behavior-conpherence-pontificate' =>
array(
'uri' => '/res/06214a06/rsrc/js/application/conpherence/behavior-pontificate.js',
'uri' => '/res/cb1a5cf0/rsrc/js/application/conpherence/behavior-menu.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-request',
3 => 'javelin-stratcom',
4 => 'javelin-uri',
5 => 'javelin-util',
6 => 'javelin-workflow',
),
'disk' => '/rsrc/js/application/conpherence/behavior-menu.js',
),
'javelin-behavior-conpherence-pontificate' =>
array(
'uri' => '/res/15263692/rsrc/js/application/conpherence/behavior-pontificate.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'javelin-workflow',
),
'disk' => '/rsrc/js/application/conpherence/behavior-pontificate.js',
),
@ -1746,6 +1748,34 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/application/core/behavior-file-tree.js',
),
'javelin-behavior-phabricator-gesture' =>
array(
'uri' => '/res/1222d486/rsrc/js/application/core/behavior-gesture.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-behavior-device',
2 => 'javelin-stratcom',
3 => 'javelin-vector',
4 => 'javelin-dom',
5 => 'javelin-magical-init',
),
'disk' => '/rsrc/js/application/core/behavior-gesture.js',
),
'javelin-behavior-phabricator-gesture-example' =>
array(
'uri' => '/res/da636e19/rsrc/js/application/uiexample/gesture-example.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-stratcom',
1 => 'javelin-behavior',
2 => 'javelin-vector',
3 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/uiexample/gesture-example.js',
),
'javelin-behavior-phabricator-keyboard-pager' =>
array(
'uri' => '/res/56d64eff/rsrc/js/application/core/behavior-keyboard-pager.js',
@ -2664,7 +2694,7 @@ celerity_register_resource_map(array(
),
'phabricator-core-css' =>
array(
'uri' => '/res/b34e5c75/rsrc/css/core/core.css',
'uri' => '/res/1e7afaa9/rsrc/css/core/core.css',
'type' => 'css',
'requires' =>
array(
@ -3055,7 +3085,7 @@ celerity_register_resource_map(array(
),
'phabricator-standard-page-view' =>
array(
'uri' => '/res/252faaf4/rsrc/css/application/base/standard-page-view.css',
'uri' => '/res/70fa2da4/rsrc/css/application/base/standard-page-view.css',
'type' => 'css',
'requires' =>
array(
@ -3500,7 +3530,7 @@ celerity_register_resource_map(array(
), array(
'packages' =>
array(
'8e2735e2' =>
'fdd3bb5f' =>
array(
'name' => 'core.pkg.css',
'symbols' =>
@ -3543,10 +3573,10 @@ celerity_register_resource_map(array(
35 => 'phabricator-object-item-list-view-css',
36 => 'global-drag-and-drop-css',
),
'uri' => '/res/pkg/8e2735e2/core.pkg.css',
'uri' => '/res/pkg/fdd3bb5f/core.pkg.css',
'type' => 'css',
),
'0ed3458f' =>
'9abfe389' =>
array(
'name' => 'core.pkg.js',
'symbols' =>
@ -3578,15 +3608,16 @@ celerity_register_resource_map(array(
24 => 'javelin-behavior-konami',
25 => 'javelin-behavior-aphlict-dropdown',
26 => 'javelin-behavior-history-install',
27 => 'javelin-behavior-phabricator-active-nav',
28 => 'javelin-behavior-phabricator-nav',
29 => 'javelin-behavior-phabricator-remarkup-assist',
30 => 'phabricator-textareautils',
31 => 'phabricator-file-upload',
32 => 'javelin-behavior-global-drag-and-drop',
33 => 'javelin-behavior-phabricator-reveal-content',
27 => 'javelin-behavior-phabricator-gesture',
28 => 'javelin-behavior-phabricator-active-nav',
29 => 'javelin-behavior-phabricator-nav',
30 => 'javelin-behavior-phabricator-remarkup-assist',
31 => 'phabricator-textareautils',
32 => 'phabricator-file-upload',
33 => 'javelin-behavior-global-drag-and-drop',
34 => 'javelin-behavior-phabricator-reveal-content',
),
'uri' => '/res/pkg/0ed3458f/core.pkg.js',
'uri' => '/res/pkg/9abfe389/core.pkg.js',
'type' => 'js',
),
'dca4a03d' =>
@ -3733,17 +3764,17 @@ celerity_register_resource_map(array(
'reverse' =>
array(
'aphront-attached-file-view-css' => 'eb35a026',
'aphront-crumbs-view-css' => '8e2735e2',
'aphront-dialog-view-css' => '8e2735e2',
'aphront-error-view-css' => '8e2735e2',
'aphront-form-view-css' => '8e2735e2',
'aphront-list-filter-view-css' => '8e2735e2',
'aphront-pager-view-css' => '8e2735e2',
'aphront-panel-view-css' => '8e2735e2',
'aphront-table-view-css' => '8e2735e2',
'aphront-tokenizer-control-css' => '8e2735e2',
'aphront-tooltip-css' => '8e2735e2',
'aphront-typeahead-control-css' => '8e2735e2',
'aphront-crumbs-view-css' => 'fdd3bb5f',
'aphront-dialog-view-css' => 'fdd3bb5f',
'aphront-error-view-css' => 'fdd3bb5f',
'aphront-form-view-css' => 'fdd3bb5f',
'aphront-list-filter-view-css' => 'fdd3bb5f',
'aphront-pager-view-css' => 'fdd3bb5f',
'aphront-panel-view-css' => 'fdd3bb5f',
'aphront-table-view-css' => 'fdd3bb5f',
'aphront-tokenizer-control-css' => 'fdd3bb5f',
'aphront-tooltip-css' => 'fdd3bb5f',
'aphront-typeahead-control-css' => 'fdd3bb5f',
'differential-changeset-view-css' => '8aaacd1b',
'differential-core-view-css' => '8aaacd1b',
'differential-inline-comment-editor' => '322728f3',
@ -3757,19 +3788,19 @@ celerity_register_resource_map(array(
'differential-table-of-contents-css' => '8aaacd1b',
'diffusion-commit-view-css' => 'c8ce2d88',
'diffusion-icons-css' => 'c8ce2d88',
'global-drag-and-drop-css' => '8e2735e2',
'global-drag-and-drop-css' => 'fdd3bb5f',
'inline-comment-summary-css' => '8aaacd1b',
'javelin-aphlict' => '0ed3458f',
'javelin-aphlict' => '9abfe389',
'javelin-behavior' => 'cd1d650a',
'javelin-behavior-aphlict-dropdown' => '0ed3458f',
'javelin-behavior-aphlict-listen' => '0ed3458f',
'javelin-behavior-aphront-basic-tokenizer' => '0ed3458f',
'javelin-behavior-aphlict-dropdown' => '9abfe389',
'javelin-behavior-aphlict-listen' => '9abfe389',
'javelin-behavior-aphront-basic-tokenizer' => '9abfe389',
'javelin-behavior-aphront-drag-and-drop' => '322728f3',
'javelin-behavior-aphront-drag-and-drop-textarea' => '322728f3',
'javelin-behavior-aphront-form-disable-on-submit' => '0ed3458f',
'javelin-behavior-aphront-form-disable-on-submit' => '9abfe389',
'javelin-behavior-audit-preview' => 'f96657b8',
'javelin-behavior-dark-console' => 'dca4a03d',
'javelin-behavior-device' => '0ed3458f',
'javelin-behavior-device' => '9abfe389',
'javelin-behavior-differential-accept-with-errors' => '322728f3',
'javelin-behavior-differential-add-reviewers-and-ccs' => '322728f3',
'javelin-behavior-differential-comment-jump' => '322728f3',
@ -3785,31 +3816,32 @@ celerity_register_resource_map(array(
'javelin-behavior-diffusion-commit-graph' => 'f96657b8',
'javelin-behavior-diffusion-pull-lastmodified' => 'f96657b8',
'javelin-behavior-error-log' => 'dca4a03d',
'javelin-behavior-global-drag-and-drop' => '0ed3458f',
'javelin-behavior-history-install' => '0ed3458f',
'javelin-behavior-konami' => '0ed3458f',
'javelin-behavior-lightbox-attachments' => '0ed3458f',
'javelin-behavior-global-drag-and-drop' => '9abfe389',
'javelin-behavior-history-install' => '9abfe389',
'javelin-behavior-konami' => '9abfe389',
'javelin-behavior-lightbox-attachments' => '9abfe389',
'javelin-behavior-load-blame' => '322728f3',
'javelin-behavior-maniphest-batch-selector' => '7707de41',
'javelin-behavior-maniphest-subpriority-editor' => '7707de41',
'javelin-behavior-maniphest-transaction-controls' => '7707de41',
'javelin-behavior-maniphest-transaction-expand' => '7707de41',
'javelin-behavior-maniphest-transaction-preview' => '7707de41',
'javelin-behavior-phabricator-active-nav' => '0ed3458f',
'javelin-behavior-phabricator-autofocus' => '0ed3458f',
'javelin-behavior-phabricator-keyboard-shortcuts' => '0ed3458f',
'javelin-behavior-phabricator-nav' => '0ed3458f',
'javelin-behavior-phabricator-active-nav' => '9abfe389',
'javelin-behavior-phabricator-autofocus' => '9abfe389',
'javelin-behavior-phabricator-gesture' => '9abfe389',
'javelin-behavior-phabricator-keyboard-shortcuts' => '9abfe389',
'javelin-behavior-phabricator-nav' => '9abfe389',
'javelin-behavior-phabricator-object-selector' => '322728f3',
'javelin-behavior-phabricator-oncopy' => '0ed3458f',
'javelin-behavior-phabricator-remarkup-assist' => '0ed3458f',
'javelin-behavior-phabricator-reveal-content' => '0ed3458f',
'javelin-behavior-phabricator-search-typeahead' => '0ed3458f',
'javelin-behavior-phabricator-tooltips' => '0ed3458f',
'javelin-behavior-phabricator-watch-anchor' => '0ed3458f',
'javelin-behavior-refresh-csrf' => '0ed3458f',
'javelin-behavior-phabricator-oncopy' => '9abfe389',
'javelin-behavior-phabricator-remarkup-assist' => '9abfe389',
'javelin-behavior-phabricator-reveal-content' => '9abfe389',
'javelin-behavior-phabricator-search-typeahead' => '9abfe389',
'javelin-behavior-phabricator-tooltips' => '9abfe389',
'javelin-behavior-phabricator-watch-anchor' => '9abfe389',
'javelin-behavior-refresh-csrf' => '9abfe389',
'javelin-behavior-repository-crossreference' => '322728f3',
'javelin-behavior-toggle-class' => '0ed3458f',
'javelin-behavior-workflow' => '0ed3458f',
'javelin-behavior-toggle-class' => '9abfe389',
'javelin-behavior-workflow' => '9abfe389',
'javelin-dom' => 'cd1d650a',
'javelin-event' => 'cd1d650a',
'javelin-install' => 'cd1d650a',
@ -3828,48 +3860,48 @@ celerity_register_resource_map(array(
'javelin-util' => 'cd1d650a',
'javelin-vector' => 'cd1d650a',
'javelin-workflow' => 'cd1d650a',
'lightbox-attachment-css' => '8e2735e2',
'lightbox-attachment-css' => 'fdd3bb5f',
'maniphest-task-summary-css' => 'eb35a026',
'maniphest-transaction-detail-css' => 'eb35a026',
'phabricator-busy' => '0ed3458f',
'phabricator-busy' => '9abfe389',
'phabricator-content-source-view-css' => '8aaacd1b',
'phabricator-core-buttons-css' => '8e2735e2',
'phabricator-core-css' => '8e2735e2',
'phabricator-crumbs-view-css' => '8e2735e2',
'phabricator-directory-css' => '8e2735e2',
'phabricator-core-buttons-css' => 'fdd3bb5f',
'phabricator-core-css' => 'fdd3bb5f',
'phabricator-crumbs-view-css' => 'fdd3bb5f',
'phabricator-directory-css' => 'fdd3bb5f',
'phabricator-drag-and-drop-file-upload' => '322728f3',
'phabricator-dropdown-menu' => '0ed3458f',
'phabricator-file-upload' => '0ed3458f',
'phabricator-filetree-view-css' => '8e2735e2',
'phabricator-flag-css' => '8e2735e2',
'phabricator-form-view-css' => '8e2735e2',
'phabricator-header-view-css' => '8e2735e2',
'phabricator-jump-nav' => '8e2735e2',
'phabricator-keyboard-shortcut' => '0ed3458f',
'phabricator-keyboard-shortcut-manager' => '0ed3458f',
'phabricator-main-menu-view' => '8e2735e2',
'phabricator-menu-item' => '0ed3458f',
'phabricator-nav-view-css' => '8e2735e2',
'phabricator-notification' => '0ed3458f',
'phabricator-notification-css' => '8e2735e2',
'phabricator-notification-menu-css' => '8e2735e2',
'phabricator-object-item-list-view-css' => '8e2735e2',
'phabricator-dropdown-menu' => '9abfe389',
'phabricator-file-upload' => '9abfe389',
'phabricator-filetree-view-css' => 'fdd3bb5f',
'phabricator-flag-css' => 'fdd3bb5f',
'phabricator-form-view-css' => 'fdd3bb5f',
'phabricator-header-view-css' => 'fdd3bb5f',
'phabricator-jump-nav' => 'fdd3bb5f',
'phabricator-keyboard-shortcut' => '9abfe389',
'phabricator-keyboard-shortcut-manager' => '9abfe389',
'phabricator-main-menu-view' => 'fdd3bb5f',
'phabricator-menu-item' => '9abfe389',
'phabricator-nav-view-css' => 'fdd3bb5f',
'phabricator-notification' => '9abfe389',
'phabricator-notification-css' => 'fdd3bb5f',
'phabricator-notification-menu-css' => 'fdd3bb5f',
'phabricator-object-item-list-view-css' => 'fdd3bb5f',
'phabricator-object-selector-css' => '8aaacd1b',
'phabricator-paste-file-upload' => '0ed3458f',
'phabricator-prefab' => '0ed3458f',
'phabricator-paste-file-upload' => '9abfe389',
'phabricator-prefab' => '9abfe389',
'phabricator-project-tag-css' => 'eb35a026',
'phabricator-remarkup-css' => '8e2735e2',
'phabricator-remarkup-css' => 'fdd3bb5f',
'phabricator-shaped-request' => '322728f3',
'phabricator-side-menu-view-css' => '8e2735e2',
'phabricator-standard-page-view' => '8e2735e2',
'phabricator-textareautils' => '0ed3458f',
'phabricator-tooltip' => '0ed3458f',
'phabricator-transaction-view-css' => '8e2735e2',
'phabricator-zindex-css' => '8e2735e2',
'sprite-apps-large-css' => '8e2735e2',
'sprite-gradient-css' => '8e2735e2',
'sprite-icon-css' => '8e2735e2',
'sprite-menu-css' => '8e2735e2',
'syntax-highlighting-css' => '8e2735e2',
'phabricator-side-menu-view-css' => 'fdd3bb5f',
'phabricator-standard-page-view' => 'fdd3bb5f',
'phabricator-textareautils' => '9abfe389',
'phabricator-tooltip' => '9abfe389',
'phabricator-transaction-view-css' => 'fdd3bb5f',
'phabricator-zindex-css' => 'fdd3bb5f',
'sprite-apps-large-css' => 'fdd3bb5f',
'sprite-gradient-css' => 'fdd3bb5f',
'sprite-icon-css' => 'fdd3bb5f',
'sprite-menu-css' => 'fdd3bb5f',
'syntax-highlighting-css' => 'fdd3bb5f',
),
));

View file

@ -960,6 +960,7 @@ phutil_register_library_map(array(
'PhabricatorFormExample' => 'applications/uiexample/examples/PhabricatorFormExample.php',
'PhabricatorGarbageCollectorConfigOptions' => 'applications/config/option/PhabricatorGarbageCollectorConfigOptions.php',
'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/PhabricatorGarbageCollectorDaemon.php',
'PhabricatorGestureExample' => 'applications/uiexample/examples/PhabricatorGestureExample.php',
'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php',
'PhabricatorGitHubConfigOptions' => 'applications/config/option/PhabricatorGitHubConfigOptions.php',
'PhabricatorGlobalLock' => 'infrastructure/util/PhabricatorGlobalLock.php',
@ -2481,6 +2482,7 @@ phutil_register_library_map(array(
'PhabricatorFormExample' => 'PhabricatorUIExample',
'PhabricatorGarbageCollectorConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon',
'PhabricatorGestureExample' => 'PhabricatorUIExample',
'PhabricatorGitHubConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorGlobalLock' => 'PhutilLock',
'PhabricatorGlobalUploadTargetView' => 'AphrontView',

View file

@ -0,0 +1,35 @@
<?php
final class PhabricatorGestureExample extends PhabricatorUIExample {
public function getName() {
return 'Gestures';
}
public function getDescription() {
return hsprintf(
'Use <tt>touchable</tt> to listen for gesture events. Note that you '.
'must be in device mode for this to work (you can narrow your browser '.
'window if you are on a desktop).');
}
public function renderExample() {
$id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'phabricator-gesture-example',
array(
'rootID' => $id,
));
return javelin_tag(
'div',
array(
'sigil' => 'touchable',
'id' => $id,
'style' => 'width: 320px; height: 240px; margin: auto;',
),
'');
}
}

View file

@ -154,6 +154,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
Javelin::initBehavior('toggle-class', array());
Javelin::initBehavior('konami', array());
Javelin::initBehavior('history-install');
Javelin::initBehavior('phabricator-gesture');
$current_token = null;
if ($user) {

View file

@ -0,0 +1,125 @@
/**
* @provides javelin-behavior-phabricator-gesture
* @requires javelin-behavior
* javelin-behavior-device
* javelin-stratcom
* javelin-vector
* javelin-dom
* javelin-magical-init
* @javelin
*/
/**
* Basic gesture recognition. Unstable. Only supports swipes.
*/
JX.behavior('phabricator-gesture', function(config) {
var target = null;
var swiping = false;
var p0;
var p1;
JX.Stratcom.listen(
['touchstart', 'mousedown'],
'touchable',
function(e) {
if (JX.Device.getDevice() == 'desktop') {
return;
}
if (JX.Stratcom.pass()) {
return;
}
if (target && e.getType() == 'touchstart') {
// This corresponds to a second finger touching while the first finger
// is held: stop the swipe.
var event_data = get_swipe_data();
var event_target = target;
stop_swipe();
JX.DOM.invoke(event_target, 'gesture.swipe.cancel', event_data);
return;
}
target = e.getNode('touchable');
p0 = JX.Vector.getPos(e);
p1 = JX.Vector.getPos(e);
});
JX.enableDispatch(document.body, 'mousemove');
JX.Stratcom.listen(
['touchmove', 'mousemove'],
null,
function(e) {
if (!target) {
return;
}
p1 = JX.Vector.getPos(e);
if (!swiping) {
// Here, we haven't started the swipe yet. We're waiting for you to
// move your finger far enough to make it clear that you intend to
// swipe or drag, not just tap.
var dx = (p1.x - p0.x);
var dy = (p1.y - p0.y);
var swipe_radius = 20;
if ((dx * dx) + (dy * dy) >= (swipe_radius * swipe_radius)) {
// You've moved your finger far enough away from the origin that
// we're sure you mean to swipe, scroll, or drag, not just tap.
// Decide if you're trying to scroll or swipe. If your finger's
// motion has been primarily vertical, assume this is a scroll. If
// your finger's motion has been primarily horizontal, assume this
// is a swipe.
if (dy * dy >= dx * dx) {
stop_swipe();
return;
}
swiping = true;
JX.DOM.invoke(target, 'gesture.swipe.start', get_swipe_data());
}
}
if (swiping) {
if (!e.getNode('touchable')) {
p1 = JX.$V(p0);
}
JX.DOM.invoke(target, 'gesture.swipe.move', get_swipe_data());
e.prevent();
}
});
JX.Stratcom.listen(
['touchend', 'touchcancel', 'mouseup'],
null,
function(e) {
if (!target) {
return;
}
// NOTE: Clear the event state first so we don't keep swiping if a
// handler throws.
var event_target = target;
var event_data = get_swipe_data();
stop_swipe();
JX.DOM.invoke(event_target, 'gesture.swipe.end', event_data);
});
function get_swipe_data() {
var dir = (p1.x > p0.x) ? 'left' : 'right';
var length = Math.abs(p1.x - p0.x);
return {
p0: p0,
p1: p1,
dir: dir,
length: length
};
}
function stop_swipe() {
target = null;
swiping = false;
}
});

View file

@ -0,0 +1,85 @@
/**
* @requires javelin-stratcom
* javelin-behavior
* javelin-vector
* javelin-dom
* @provides javelin-behavior-phabricator-gesture-example
*/
JX.behavior('phabricator-gesture-example', function(config) {
var strokes = [];
var current = [];
var root = JX.$(config.rootID);
var canvas = JX.$N('canvas');
var d = JX.Vector.getDim(root);
canvas.width = d.x;
canvas.height = d.y;
root.appendChild(canvas);
var cxt = canvas.getContext('2d');
JX.Stratcom.listen(
'gesture.swipe.end',
null,
function(e) {
var stroke = get_stroke(e);
strokes.push(stroke);
current = [];
redraw();
});
JX.Stratcom.listen(
'gesture.swipe.move',
null,
function(e) {
var stroke = get_stroke(e);
current = [stroke];
redraw();
});
JX.Stratcom.listen(
'gesture.swipe.cancel',
null,
function(e) {
current = [];
redraw();
});
function get_stroke(e) {
var data = e.getData();
var p = JX.$V(root);
return [
data.p0.x - p.x,
data.p0.y - p.y,
data.p1.x - p.x,
data.p1.y - p.y
];
}
function redraw() {
cxt.fillStyle = '#dfdfdf';
cxt.fillRect(0, 0, d.x, d.y);
for (var ii = 0; ii < strokes.length; ii++) {
var s = strokes[ii];
cxt.strokeStyle = 'rgba(0, 0, 0, 0.50)';
cxt.beginPath();
cxt.moveTo(s[0], s[1]);
cxt.lineTo(s[2], s[3]);
cxt.stroke();
}
for (var ii = 0; ii < current.length; ii++) {
var s = current[ii];
cxt.strokeStyle = 'rgba(255, 0, 0, 1)';
cxt.beginPath();
cxt.moveTo(s[0], s[1]);
cxt.lineTo(s[2], s[3]);
cxt.stroke();
}
}
redraw();
});