1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-06-16 10:29:54 +00:00

Add swipe,tap and press widget to hammerjs (#3214)

* Create swipe.js

* add swipe widget

* add popup handling

* velocity mini-tweak

* add press widget

* add tap widget

* remove popup from tap widget - not possible as it seems

* add pan widget + utility css

* correcting field name

* naming and formatting

* add usage

* add pinch widget

* add pinch widget

* various small fixes

* adding absolute coordinates to pan widget

* prevent default dragging for pan widget

* improve pan widget stability
This commit is contained in:
BurningTreeC 2018-04-18 12:33:59 +02:00 committed by Jeremy Ruston
parent df809bcb87
commit 8bf7dd7172
9 changed files with 1076 additions and 2 deletions

View File

@ -15,6 +15,41 @@
"type": "text/plain",
"title": "$:/plugins/tiddlywiki/hammerjs/license"
}
}
},{
"file": "widgets/swipe.js",
"fields": {
"type": "application/javascript",
"title": "$:/plugins/tiddlywiki/hammerjs/widgets/swipe.js",
"module-type": "widget"
}
},{
"file": "widgets/press.js",
"fields": {
"type": "application/javascript",
"title": "$:/plugins/tiddlywiki/hammerjs/widgets/press.js",
"module-type": "widget"
}
},{
"file": "widgets/tap.js",
"fields": {
"type": "application/javascript",
"title": "$:/plugins/tiddlywiki/hammerjs/widgets/tap.js",
"module-type": "widget"
}
},{
"file": "widgets/pan.js",
"fields": {
"type": "application/javascript",
"title": "$:/plugins/tiddlywiki/hammerjs/widgets/pan.js",
"module-type": "widget"
}
},{
"file": "widgets/pinch.js",
"fields": {
"type": "application/javascript",
"title": "$:/plugins/tiddlywiki/hammerjs/widgets/pinch.js",
"module-type": "widget"
}
}
]
}

View File

@ -0,0 +1,291 @@
/*\
title: $:/plugins/tiddlywiki/hammerjs/widgets/pan.js
type: application/javascript
module-type: widget
actions triggered on pan gestures + event coordinates
\*/
(function (global) {
"use strict";
/*jslint node: true, browser: true */
/*global $tw: false */
var Widget = require("$:/core/modules/widgets/widget.js").widget;
if (typeof window !== 'undefined') {
var Hammer = require("$:/plugins/tiddlywiki/hammerjs/hammer.js");
}
var PanWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
PanWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
PanWidget.prototype.render = function(parent,nextSibling) {
var self = this;
var parentDomNode = parent;
// Compute attributes and execute state
this.computeAttributes();
this.execute();
if (self === this && parent !== undefined && nextSibling !== undefined && this.children !== undefined) {
self.renderChildren(parent,nextSibling);
} else if (self === this && parent !== undefined && nextSibling !== undefined && nextSibling !== null) {
self.refresh();
parentDomNode = parent;
} else {
if(self.parentWidget !== undefined) {
self.parentWidget.refreshSelf();
parentDomNode = parent;
} else {
return false;
}
}
if(this.panTargets === undefined || this.panTargets === "") {
return false;
}
var panElementClass;
var panMultipleClasses = null;
if(this.panTargets.indexOf(' ') !== -1) {
panMultipleClasses = true;
panElementClass = self.panTargets.split(' ');
} else {
panElementClass = self.panTargets;
}
if(panElementClass === undefined || panElementClass === "" || parentDomNode === undefined) {
return false;
}
var domNodeList = [];
if (panMultipleClasses === true) {
for (var i=0; i < elementClass.length; i++) {
var panElements = parentDomNode.getElementsByClassName(panElementClass[i]);
for (var k=0; k < panElements.length; k++) {
domNodeList[i + k] = panElements[k];
}
}
} else {
domNodeList = parentDomNode.getElementsByClassName(panElementClass);
}
var elementIndex;
var panStartValues = [];
for(i=0; i < domNodeList.length; i++) {
elementIndex = i;
var currentElement = domNodeList[i];
var hammer = new Hammer.Manager(domNodeList[i]);
hammer.add(new Hammer.Pan({
event: 'pan',
pointers: self.panPointers,
threshold: self.panThreshold,
direction: Hammer.DIRECTION_ALL
}));
hammer.get('pan');
var scrollLeft = null,
scrollTop = null;
var startX = null;
var startY = null;
var elementTop = null;
var elementLeft = null;
var elementBottom = null;
var elementRight = null;
var elementWidth = null;
var elementHeight = null;
var startActions = null;
var singleElement = null;
var pointerType = null;
var domNodeRect = null;
var parentDomNodeRect = null;
var elementAbsoluteTop = null;
var elementAbsoluteLeft = null;
var fieldStartNames = [ 'starting-x', 'starting-y', 'element-top', 'element-left', 'element-bottom', 'element-right', 'element-width', 'element-height', 'pointer-type', 'parent-x', 'parent-y' ];
hammer.on('touchmove panstart panmove', function(e) {
// Prevent default behaviour
e.preventDefault && e.preventDefault();
e.stopPropagation && e.stopPropagation();
// Set a "dragging" state tiddler - gets deleted when panning ends
$tw.wiki.setText("$:/state/dragging","text",undefined,"yes",null);
// Get the coordinates of the parent Dom Node
if (parentDomNodeRect === null && parentDomNode !== undefined && parentDomNode.parentElement !== undefined) {
parentDomNodeRect = parentDomNode.parentElement.getBoundingClientRect();
}
// Get the current coordinates of the element
if (domNodeRect === null) {
domNodeRect = currentElement.getBoundingClientRect();
}
if (self.panStartActions && startActions !== "done") {
self.invokeActionString(self.panStartActions,self,e);
startActions = "done";
}
// Absolute coordinates of the pointer
scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
scrollTop = window.pageYOffset || document.documentElement.scrollTop;
elementAbsoluteLeft = (e.center.x + scrollLeft).toFixed(self.userToFixed);
elementAbsoluteTop = (e.center.y + scrollTop).toFixed(self.userToFixed);
// Set values at pan-start only
if (panStartValues.length === 0) {
panStartValues[0] = e.center.x.toFixed(self.userToFixed);
panStartValues[1] = e.center.y.toFixed(self.userToFixed);
panStartValues[2] = domNodeRect.top.toFixed(self.userToFixed);
panStartValues[3] = domNodeRect.left.toFixed(self.userToFixed);
panStartValues[4] = domNodeRect.bottom.toFixed(self.userToFixed);
panStartValues[5] = domNodeRect.right.toFixed(self.userToFixed);
panStartValues[6] = domNodeRect.width.toFixed(self.userToFixed);
panStartValues[7] = domNodeRect.height.toFixed(self.userToFixed);
panStartValues[8] = e.pointerType;
panStartValues[9] = parentDomNodeRect.left.toFixed(self.userToFixed) || "undefined";
panStartValues[10] = parentDomNodeRect.top.toFixed(self.userToFixed) || "undefined";
for(var t = 0; t<panStartValues.length; t++){
if(domNodeList.length === 1) {
singleElement = true;
self.setField(self.panStateTiddler,fieldStartNames[t],panStartValues[t]);
} else {
var fieldName = fieldStartNames[t] + "-" + elementIndex;
self.setField(self.panStateTiddler,fieldName,panStartValues[t]);
}
}
if(singleElement === true) {
self.setField(self.panStateTiddler,'delta-x',e.deltaX.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'delta-y',e.deltaY.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'relative-x',e.center.x.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'relative-y',e.center.y.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'absolute-x',elementAbsoluteLeft);
self.setField(self.panStateTiddler,'absolute-y',elementAbsoluteTop);
} else {
self.setField(self.panStateTiddler,'delta-x-' + elementIndex,e.deltaX.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'delta-y-' + elementIndex,e.deltaY.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'relative-x-' + elementIndex,e.center.x.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'relative-y-' + elementIndex,e.center.y.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'absolute-x-' + elementIndex,elementAbsoluteLeft);
self.setField(self.panStateTiddler,'absolute-y-' + elementIndex,elementAbsoluteTop);
}
}
function panWidgetTimeoutFunction(timestamp) {
// Invoke actions that should be repeated every cycle if specified
if(self.panRepeatActions) {
self.invokeActionString(self.panRepeatActions,self,e);
}
if(singleElement === true) {
self.setField(self.panStateTiddler,'delta-x',e.deltaX.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'delta-y',e.deltaY.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'relative-x',e.center.x.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'relative-y',e.center.y.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'absolute-x',elementAbsoluteLeft);
self.setField(self.panStateTiddler,'absolute-y',elementAbsoluteTop);
} else {
self.setField(self.panStateTiddler,'delta-x-' + elementIndex,e.deltaX.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'delta-y-' + elementIndex,e.deltaY.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'relative-x-' + elementIndex,e.center.x.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'relative-y-' + elementIndex,e.center.y.toFixed(self.userToFixed));
self.setField(self.panStateTiddler,'absolute-x-' + elementIndex,elementAbsoluteLeft);
self.setField(self.panStateTiddler,'absolute-y-' + elementIndex,elementAbsoluteTop);
}
};
window.requestAnimationFrame(panWidgetTimeoutFunction);
})
.on('panend pancancel touchend mouseup', function(e) {
startX = null;
startY = null;
scrollLeft = null;
scrollTop = null;
elementTop = null;
elementLeft = null;
elementBottom = null;
elementRight = null;
elementWidth = null;
elementHeight = null;
startActions = null;
singleElement = null;
pointerType = null;
domNodeRect = null;
parentDomNodeRect = null;
panStartValues = [];
if(self.panEndActions) {
self.invokeActionString(self.panEndActions,self,e);
}
// Delete the "dragging" state tiddler
$tw.wiki.deleteTiddler("$:/state/dragging");
if (self.parentWidget !== undefined) {
self.parentWidget.refreshSelf();
}
return true;
});
}
};
/*
Set the computed values in the state-tiddler fields
*/
PanWidget.prototype.setField = function(tiddler,field,value) {
$tw.wiki.setText(tiddler,field,undefined,value,{ suppressTimestamp: true });
};
/*
Compute the internal state of the widget
*/
PanWidget.prototype.execute = function() {
this.panTargets = this.getAttribute("targets", "");
this.panStateTiddler = this.getAttribute("state","$:/state/pan");
this.panPointers = parseInt(this.getAttribute("pointers","1"));
this.panThreshold = parseInt(this.getAttribute("threshold","0"));
this.userToFixed = parseInt(this.getAttribute("decimals","0"));
this.panStartActions = this.getAttribute("startactions","");
this.panRepeatActions = this.getAttribute("repeatactions","");
this.panEndActions = this.getAttribute("endactions","");
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
PanWidget.prototype.refresh = function(changedTiddlers) {
var self = this;
var changedAttributes = this.computeAttributes();
if(Object.keys(changedAttributes).length) {
self.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
};
exports.pan = PanWidget;
})();

View File

@ -0,0 +1,192 @@
/*\
title: $:/plugins/tiddlywiki/hammerjs/widgets/pinch.js
type: application/javascript
module-type: widget
actions triggered on pinch gestures + event values
\*/
(function (global) {
"use strict";
/*jslint node: true, browser: true */
/*global $tw: false */
var Widget = require("$:/core/modules/widgets/widget.js").widget;
if (typeof window !== 'undefined') {
var Hammer = require("$:/plugins/tiddlywiki/hammerjs/hammer.js");
}
var PinchWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
PinchWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
PinchWidget.prototype.render = function(parent,nextSibling) {
var self = this;
var parentDomNode = parent;
// Compute attributes and execute state
this.computeAttributes();
this.execute();
if(self === this && parent !== undefined && nextSibling !== undefined) {
self.renderChildren(parent,nextSibling);
} else if (self === this) {
self.refresh();
parentDomNode = parent;
} else {
return false;
}
if(this.pinchTargets === undefined || this.pinchTargets === "") {
return false;
}
var pinchElementClass;
var pinchMultipleClasses = null;
if(this.pinchTargets.indexOf(' ') !== -1) {
pinchMultipleClasses = true;
pinchElementClass = self.pinchTargets.split(' ');
} else {
pinchElementClass = self.pinchTargets;
}
if(pinchElementClass === undefined || pinchElementClass === "" || parentDomNode === undefined) {
return false;
}
var domNodeList = [];
if (pinchMultipleClasses === true) {
for (var i=0; i < pinchElementClass.length; i++) {
var pinchElements = parentDomNode.getElementsByClassName(pinchElementClass[i]);
for (var k=0; k < pinchElements.length; k++) {
domNodeList[i + k] = pinchElements[k];
}
}
} else {
domNodeList = parentDomNode.getElementsByClassName(pinchElementClass);
}
var elementIndex;
for(i=0; i < domNodeList.length; i++) {
elementIndex = i;
var hammer = new Hammer.Manager(domNodeList[i]);
hammer.add(new Hammer.Pinch({
event: 'pinch',
pointers: 2,
threshold: self.pinchThreshold
}));
hammer.get('pinch').set({ enable: true });
var singleElement = null;
var firstSet = null;
var startActions = null;
hammer.on('pinch pinchstart pinchmove', function(e) {
if(startActions !== "done") {
self.invokeActionString(self.pinchStartActions,self,e);
startActions = "done";
}
if(singleElement === null && domNodeList.length === 1) {
singleElement = true;
}
if(firstSet !== "done") {
var rotationValue = e.rotation.toFixed(self.userToFixed);
var scaleValue = e.scale.toFixed(5);
if(singleElement === true) {
self.setField(self.pinchStateTiddler,'rotation',rotationValue);
self.setField(self.pinchStateTiddler,'scale',scaleValue);
} else {
var fieldNameRot = 'rotation' + "-" + elementIndex;
var fieldNameScale = 'scale' + "-" + elementIndex;
self.setField(self.pinchStateTiddler,fieldNameRot,rotationValue);
self.setField(self.pinchStateTiddler,fieldNameScale,scaleValue);
}
firstSet = "done";
}
function pinchWidgetTimeoutFunction(timestamp) {
var rotationValue = e.rotation.toFixed(self.userToFixed);
var scaleValue = e.scale.toFixed(5);
if(singleElement === true) {
self.setField(self.pinchStateTiddler,'rotation',rotationValue);
self.setField(self.pinchStateTiddler,'scale',scaleValue);
} else {
var fieldNameRot = 'rotation' + "-" + elementIndex;
var fieldNameScale = 'scale' + "-" + elementIndex;
self.setField(self.pinchStateTiddler,fieldNameRot,rotationValue);
self.setField(self.pinchStateTiddler,fieldNameScale,scaleValue);
}
};
window.requestAnimationFrame(pinchWidgetTimeoutFunction);
})
.on('pinchend pinchcancel touchend', function(e) {
e.stopPropagation && e.stopPropagation();
if(self.pinchEndActions) {
self.invokeActionString(self.pinchEndActions,self,e);
}
firstSet = null;
startActions = null;
if(self.parentWidget !== undefined) {
self.parentWidget.refreshSelf();
}
return true; // Action was invoked
});
}
};
/*
Set the computed values in the state-tiddler fields
*/
PinchWidget.prototype.setField = function(tiddler,field,value) {
$tw.wiki.setText(tiddler,field,undefined,value,{ suppressTimestamp: true });
};
/*
Compute the internal state of the widget
*/
PinchWidget.prototype.execute = function() {
this.pinchTargets = this.getAttribute("targets","");
this.userToFixed = parseInt(this.getAttribute("decimals","0"));
this.pinchThreshold = parseInt(this.getAttribute("threshold","0"));
this.pinchStateTiddler = this.getAttribute("statetiddler","$:/state/pinch");
this.pinchStartActions = this.getAttribute("startactions","");
this.pinchEndActions = this.getAttribute("endactions","");
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
PinchWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(Object.keys(changedAttributes).length) {
this.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
};
exports.pinch = PinchWidget;
})();

View File

@ -0,0 +1,173 @@
/*\
title: $:/plugins/tiddlywiki/hammerjs/widgets/press.js
type: application/javascript
module-type: widget
actions triggered on press gestures
\*/
(function (global) {
"use strict";
/*jslint node: true, browser: true */
/*global $tw: false */
var Widget = require("$:/core/modules/widgets/widget.js").widget;
if (typeof window !== 'undefined') {
var Hammer = require("$:/plugins/tiddlywiki/hammerjs/hammer.js");
}
var PressWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
PressWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
PressWidget.prototype.render = function(parent,nextSibling) {
var self = this;
var parentDomNode = parent;
// Compute attributes and execute state
this.computeAttributes();
this.execute();
if(self === this && parent !== undefined && nextSibling !== undefined ) {
self.renderChildren(parent,nextSibling);
} else if(self === this) {
self.refresh();
parentDomNode = parent;
} else {
return false;
}
if(this.pressTargets === undefined || this.pressTargets === "") {
return false;
}
var pressElementClass;
var pressMultipleClasses = null;
if(this.pressTargets.indexOf(' ') !== -1) {
pressMultipleClasses = true;
pressElementClass = self.pressTargets.split(' ');
} else {
pressElementClass = self.pressTargets;
}
if(pressElementClass === undefined || pressElementClass === "" || parentDomNode === undefined) {
return false;
}
var domNodeList = [];
if (pressMultipleClasses === true) {
for (var i=0; i < pressElementClass.length;i++) {
var pressElements = parentDomNode.getElementsByClassName(pressElementClass[i]);
for (var k=0; k < pressElements.length; k++) {
domNodeList[i + k] = pressElements[k];
self.domNodes.push(pressElements[k]);
}
}
} else {
domNodeList = parentDomNode.getElementsByClassName(pressElementClass);
self.domNodes.push(domNodeList);
}
var isPoppedUp = this.pressPopup && this.isPoppedUp(),
pressElementIndex;
for(i=0; i < domNodeList.length; i++) {
pressElementIndex = i;
var currentElement = domNodeList[i];
var hammer = new Hammer.Manager(domNodeList[i]);
// Event Listener to cancel browser popup menu on long press
currentElement.addEventListener('contextmenu', function(e) {
e.preventDefault && e.preventDefault();
e.stopPropagation && e.stopPropagation();
e.cancelBubble = true;
e.returnValue = false;
return false;
});
hammer.add(new Hammer.Press({
event: 'press',
pointers: self.pressPointers,
threshold: self.pressThreshold,
time: self.pressTime
}));
hammer.get('press');
hammer.on('press', function(e) {
if (self.pressPopup) {
self.triggerPopup(e);
}
if(self.pressStartActions) {
self.invokeActionString(self.pressStartActions,self,e);
}
return true;
})
.on('pressup', function(e) {
if(self.pressEndActions) {
self.invokeActionString(self.pressEndActions,self,e);
}
if (self.pressPopup === undefined && self.parentWidget !== undefined && self.domNodes[0] !== undefined && self.domNodes[0].parentNode !== undefined) {
self.parentWidget.refreshSelf();
}
return true;
});
}
};
PressWidget.prototype.isPoppedUp = function() {
var tiddler = this.wiki.getTiddler(this.pressPopup);
var result = tiddler && tiddler.fields.text ? $tw.popup.readPopupState(tiddler.fields.text) : false;
return result;
};
PressWidget.prototype.triggerPopup = function(event) {
$tw.popup.triggerPopup({
domNode: this.domNodes[0],
title: this.pressPopup,
wiki: this.wiki
});
};
/*
Compute the internal state of the widget
*/
PressWidget.prototype.execute = function() {
this.pressTargets = this.getAttribute("targets");
this.pressPointers = parseInt(this.getAttribute("pointers","1"));
this.pressTime = parseInt(this.getAttribute("time","0"));
this.pressThreshold = parseInt(this.getAttribute("threshold","1000"));
this.pressStartActions = this.getAttribute("startactions","");
this.pressEndActions = this.getAttribute("endactions","");
this.pressPopup = this.getAttribute("popup");
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
PressWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(Object.keys(changedAttributes).length) {
this.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
};
exports.press = PressWidget;
})();

View File

@ -0,0 +1,168 @@
/*\
title: $:/plugins/tiddlywiki/hammerjs/widgets/swipe.js
type: application/javascript
module-type: widget
actions triggered on swipe gestures
\*/
(function (global) {
"use strict";
/*jslint node: true, browser: true */
/*global $tw: false */
var Widget = require("$:/core/modules/widgets/widget.js").widget;
if (typeof window !== 'undefined') {
var Hammer = require("$:/plugins/tiddlywiki/hammerjs/hammer.js");
}
var SwipeWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
SwipeWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
SwipeWidget.prototype.render = function(parent,nextSibling) {
var self = this;
var parentDomNode = parent;
// Compute attributes and execute state
this.computeAttributes();
this.execute();
if(self === this && parent !== undefined && nextSibling !== undefined) {
self.renderChildren(parent,nextSibling);
} else if (self === this) {
self.refresh();
parentDomNode = parent;
} else {
return false;
}
// Return if no target elements specified
if(this.swipeTargets === undefined || this.swipeTargets === "") {
return false;
}
// Get the Elements with the specified class
var swipeElementClass;
var swipeMultipleClasses = null;
if(this.swipeTargets.indexOf(' ') !== -1) {
swipeMultipleClasses = true;
swipeElementClass = self.swipeTargets.split(' ');
} else {
swipeElementClass = self.swipeTargets;
}
if(swipeElementClass === undefined || swipeElementClass === "" || parentDomNode === undefined) {
return false;
}
// If more than one element with the target class is found, store them in an array and cycle through that
var domNodeList = [];
if (swipeMultipleClasses === true) {
for (var i=0; i < swipeElementClass.length; i++) {
var swipeElements = parentDomNode.getElementsByClassName(swipeElementClass[i]);
for (var k=0; k < swipeElements.length; k++) {
domNodeList[i + k] = swipeElements[k];
self.domNodes.push(swipeElements[k]);
}
}
} else {
domNodeList = parentDomNode.getElementsByClassName(swipeElementClass);
self.domNodes.push(domNodeList);
}
// Create the swipe direction used by Hammer
var swipeDirection = 'swipe' + this.swipeDirection;
// Handle a popup state tiddler
var isPoppedUp = this.swipePopup && this.isPoppedUp();
// Create a new Hammer instance for each found dom node
var swipeElementIndex;
for(i=0; i < domNodeList.length; i++) {
swipeElementIndex = i;
var hammer = new Hammer.Manager(domNodeList[i]);
hammer.add(new Hammer.Swipe({
event: 'swipe',
pointers: self.swipePointers,
threshold: self.swipeThreshold,
velocity: self.swipeVelocity,
direction: Hammer.DIRECTION_ALL
}));
// Tell Hammer it should listen for the swipe event
hammer.get('swipe');
hammer.on(swipeDirection, function(e) {
e.preventDefault && e.preventDefault();
e.stopPropagation && e.stopPropagation();
if (self.swipePopup) {
self.triggerPopup(e);
}
if(self.swipeActions) {
self.invokeActionString(self.swipeActions,self,e);
}
if(self.swipePopup === undefined && self.parentWidget !== undefined && self.domNodes[0] !== undefined && self.domNodes[0].parentNode !== undefined) {
self.parentWidget.refreshSelf();
}
return true; // Action was invoked
});
}
};
SwipeWidget.prototype.isPoppedUp = function() {
var tiddler = this.wiki.getTiddler(this.swipePopup);
var result = tiddler && tiddler.fields.text ? $tw.popup.readPopupState(tiddler.fields.text) : false;
return result;
};
SwipeWidget.prototype.triggerPopup = function(event) {
$tw.popup.triggerPopup({
domNode: this.domNodes[0],
title: this.swipePopup,
wiki: this.wiki
});
};
/*
Compute the internal state of the widget
*/
SwipeWidget.prototype.execute = function() {
this.swipeTargets = this.getAttribute("targets");
this.swipeActions = this.getAttribute("actions","");
this.swipeVelocity = parseFloat(this.getAttribute("velocity", "0.1"));
this.swipeDirection = this.getAttribute("direction","");
this.swipePointers = parseInt(this.getAttribute("pointers","1"));
this.swipeThreshold = parseInt(this.getAttribute("threshold","0"));
this.swipePopup = this.getAttribute("popup");
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
SwipeWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(Object.keys(changedAttributes).length) {
this.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
};
exports.swipe = SwipeWidget;
})();

View File

@ -0,0 +1,142 @@
/*\
title: $:/plugins/tiddlywiki/hammerjs/widgets/tap.js
type: application/javascript
module-type: widget
actions triggered on taps&clicks
\*/
(function (global) {
"use strict";
/*jslint node: true, browser: true */
/*global $tw: false */
var Widget = require("$:/core/modules/widgets/widget.js").widget;
if (typeof window !== 'undefined') {
var Hammer = require("$:/plugins/tiddlywiki/hammerjs/hammer.js");
}
var TapWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
TapWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
TapWidget.prototype.render = function(parent,nextSibling) {
var self = this;
var parentDomNode = parent;
// Compute attributes and execute state
this.computeAttributes();
this.execute();
if (self === this && parent !== undefined && nextSibling !== undefined) {
self.renderChildren(parent,nextSibling);
} else if (self === this) {
self.refresh();
parentDomNode = parent;
} else {
return false;
}
if(this.tapTargets === undefined || this.tapTargets === "") {
return false;
}
var tapElementClass;
var tapMultipleClasses = null;
if(this.tapTargets.indexOf(' ') !== -1) {
tapMultipleClasses = true;
tapElementClass = self.tapTargets.split(' ');
} else {
tapElementClass = self.tapTargets;
}
if(tapElementClass === undefined || tapElementClass === "" || parentDomNode === undefined) {
return false;
}
var domNodeList = [];
if (tapMultipleClasses === true) {
for (var i=0; i < tapElementClass.length; i++) {
var tapElements = parentDomNode.getElementsByClassName(tapElementClass[i]);
for (var k=0; k < tapElements.length; k++) {
domNodeList[i + k] = tapElements[k];
self.domNodes.push(tapElements[k]);
}
}
} else {
domNodeList = parentDomNode.getElementsByClassName(tapElementClass);
self.domNodes.push(domNodeList);
}
var tapElementIndex;
for(i=0; i < domNodeList.length; i++) {
tapElementIndex = i;
var hammer = new Hammer.Manager(domNodeList[i]);
hammer.add(new Hammer.Tap({
event: 'usertap',
pointers: self.tapPointers,
taps: self.tapCount,
interval: self.tapInterval,
time: self.tapTime,
threshold: self.tapThreshold,
posThreshold: self.tapPosThreshold
}));
hammer.get('usertap');
hammer.on('usertap', function(e) {
if(self.tapActions) {
self.invokeActionString(self.tapActions,self,e);
}
if (self.parentWidget !== undefined && self.domNodes[0] !== undefined && self.domNodes[0].parentNode !== undefined) {
self.parentWidget.refreshSelf();
}
return true; // Action was invoked
});
}
};
/*
Compute the internal state of the widget
*/
TapWidget.prototype.execute = function() {
this.tapTargets = this.getAttribute("targets");
this.tapCount = parseInt(this.getAttribute("taps","1"));
this.tapPointers = parseInt(this.getAttribute("pointers","1"));
this.tapThreshold = parseInt(this.getAttribute("threshold","100"));
this.tapPosThreshold = parseInt(this.getAttribute("posthreshold","200"));
this.tapTime = parseInt(this.getAttribute("time","250"));
this.tapInterval = parseInt(this.getAttribute("interval","300"));
this.tapActions = this.getAttribute("actions","");
this.makeChildWidgets();
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
TapWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(Object.keys(changedAttributes).length) {
this.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
};
exports.tap = TapWidget;
})();

View File

@ -3,5 +3,5 @@
"description": "HammerJS library by Jorik Tangelder (Eight Media)",
"author": "JeremyRuston",
"core-version": ">=5.0.0",
"list": "readme license"
"list": "readme usage license"
}

View File

@ -0,0 +1,10 @@
title: $:/plugins/tiddlywiki/hammerjs/styles
tags: $:/tags/Stylesheet
<$list filter="[[$:/state/dragging]!is[missing]]">
iframe {
pointer-events: none;
}
</$list>

View File

@ -0,0 +1,63 @@
title: $:/plugins/tiddlywiki/hammerjs/usage
The ''swipe'', ''press'', ''pan'', ''tap'' and ''pinch'' widgets included in this plugin enable users of creating html elements that trigger user-actions when the corresponding gesture gets executed on the element - or, in case of the pan and pinch widgets, in addition to triggering user-actions, populate a state tiddler with coordinates and values of the element itself and the gesture
The pan widget stores the coordinates of the element and the relative and absolute coordinates of the pointer in $:/state/pan or a user-defined tiddler
The pinch widget stores the scale value and the angle of the detected rotation of the pinch gesture in $:/state/pinch or a user-defined tiddler
With those values users can position elements following the mouse, create custom sliders or create image galleries with pinch-zoomable and rotatable images, in combination with [ext[css calculations|https://www.w3schools.com/cssref/func_calc.asp]] and/or tiddlywiki plugins that allow number-crunching
!!Swipe Widget
!!Press Widget
!!Pan Widget
|!Attribute |!Description |!Optional? |h
|targets |the css class of the target element(s) |no |
|startactions |the actions that should be performed when panning starts |yes |
|endactions |the actions that should be performed when panning ends |yes |
|repeatactions |actions that are repeatedly performed while panning |yes |
|state |optional tiddler where values get stored - default: $:/state/pan |yes |
|pointers |the number of pointers needed for the swipe gesture - default: 1 |yes |
|threshold |a minimal distance in px required before recognizing the pan gesture - default: 0 |yes |
|decimals |the number of decimal points for the stored values in the state tiddler - default: 0 |yes |
As an example, an element with the class `tc-test-class` is made pannable by
```
<$pan targets="tc-test-class">
<div class="tc-test-class"/>
</$pan>
```
When users click this element, hold the mouse and move it, the tiddler $:/state/pan gets populated with the pan-values
Those values can be used to position that same element using css in a `Stylesheet Tiddler` - note that the values from the state tiddler can be transcluded within the css `calc()` function:
```css
.tc-test-class {
width: 70px;
height: 70px;
background-color: #f4f4f4;
position: absolute;
left: calc({{$:/state/pan!!absolute-x}}px - 35px);
top: calc({{$:/state/pan!!absolute-y}}px - 35px)
}
```
!!Tap Widget
!!Pinch Widget