Skip to content

Instantly share code, notes, and snippets.

@958
Forked from nazoking/ProgrammableGesture.ks.js
Created May 23, 2011 07:06
Show Gist options
  • Save 958/986346 to your computer and use it in GitHub Desktop.
Save 958/986346 to your computer and use it in GitHub Desktop.
var PLUGIN_INFO =
<KeySnailPlugin>
<name>ProgrammableGesture</name>
<version>0.2</version>
<include>main</include>
<license>The MIT License</license>
<minVersion>1.6.3</minVersion>
<description>ProgrammableGesture</description>
<description lang="ja">プログラマブルなマウスジェスチャ</description>
<author mail="nazoking@gmail.com" homepage="http://nazo.yi.org/">nazoking</author>
<options>
<option>
<name>ProgrammableGesture.actions</name>
<type>function(ProgrammableGesture)</type>
<description>動作を設定します。ジェスチャが開始される時に呼ばれます。動作設定を返すようにしてください。</description>
</option>
<option>
<name>ProgrammableGesture.tolerance</name>
<type>integer</type>
<description>動作を検知する際の閾値を設定します。設定した数値xピクセル分マウスが移動すると、その動作と認識します。</description>
</option>
</options>
<detail><![CDATA[
==== 概要 ====
マウスジェスチャをDSLっぽいので設定します。結構柔軟に定義できます。
javascriptを書けるので何でも出来ます。
次のようにkeysnail.js に定義します。
>||
plugins.options["ProgrammableGesture.actions"]=function(p){with(p){return{
Left:chain("back",{
Down:chain("close-tab-window"),
Up:chain("undo-closed-tab"),
}),
Right:chain("forward",{
WheelUp:soon("scroll-page-up"),
WheelDown:soon("scroll-page-down"),
WheelHoldUp:soon("scroll-to-the-bottom-of-the-page"),
WheelHoldDown:soon("scroll-to-the-top-of-the-page"),
}),
WheelUp:soon(action("前のタブ",function(){ gBrowser.mTabContainer.advanceSelectedTab(-1, true); })),
WheelDown:soon(action("次のタブ",function(){ gBrowser.mTabContainer.advanceSelectedTab(1, true); })),
};
}}
||<
ProgrammableGesture.actions に関数を登録します。
キーが動作、値にchainを登録したオブジェクトを返すようにしてください。
chain の第2引数にさらにオブジェクトを登録することで、「連続する動作」を定義します。
エクステンションに登録されていない動作は action 関数で動作を作成することが出来ます。
Builtin command as Ext( http://wiki.github.com/mooz/keysnail/plugin )を併用すると便利でしょう。
== リファレンス ==
DSL的に使えるように plugins.ProgrammableGesture には次の関数が定義されています。with構文などで利用するとよいでしょう。
=== chain(ext,moreaction) , chain(moreaction) ===
その時点でジェスチャ終了ならext実行。moreaction が定義されている場合は継続。
chain の第一引数extに文字列を定義すると、KeySnailのエクステンションと解釈し、マウスボタンが放されたときそれを実行します。次の動作のための前動作を定義したいだけの場合は、第一引数を飛ばすことも出来ます。
>||
{
Left:chain("back")
Up:chain("close-tab-window",{Down:chain("undo-closed-tab")})
Right:chain({Left:chain("forward")})
}
||<
このように定義すると、
押下(ジェスチャ開始)後、
左 → 戻る
右 → 左 → 進む
上 → タブ、ウィンドウを閉じる
上 → 下 → 閉じたタブを戻す
の設定となります。
エクステンションに登録されていない動作を行いたい場合はaction で定義してください。
=== soon(ext) ===
発生したら即アクションが発生する。
>||
{ Left:chain({Right:soon("back"),Up:soon("forward")}) }
||<
このように定義すると、押下、左 の後、マウスボタンを開放せずに 上に移動するとbackが、 下に移動するとfoward が実行されます。押下、左 の後、上移動し(back)、さらにそのまま下移動するとfowardが実行されます。
=== action(ラベル,関数) ===
エクステンションが定義されていない動作をしたい場合、extの代わりに利用する。
>||
{ WheelUp:soon(action("前のタブ",function(){ gBrowser.mTabContainer.advanceSelectedTab(-1, true); })) }
||<
このように定義すると、左ボタンを押下しながらホイール上回転でタブを一つ前に移動します。
ラベルは、ジェスチャ中に次のアクションを示したり、アクション実行時にステータスバーに表示されます。
=== stop() ===
ジェスチャ中断
>||
{ WheelUp:soon(action("前のタブ",function(){ stop(); })) }
||<
このように定義すると、左ボタンを押下しながらホイール上回転したとき、左ボタンを放さなくてもマウスジェスチャが中断されます。
== 認識できる動作 ==
次の動作を認識できます。
マウスの移動:
Left Right Up Down
ホイール回転:
WheelUp WheelDown
ホーイル押しながら回転:
WheelHoldUp WheelHoldDown
ジェスチャ開始後にクリック:
LeftClick RightClick MiddleClick
]]></detail>
</KeySnailPlugin>;
var ProgrammableGesture = (function(){
const MOTION_LABEL={
Left:L("←"),
Right:L("→"),
Up:L("↑"),
Down:L("↓"),
WheelUp:L("W↑"),
WheelDown:L("W↓"),
WheelHoldUp:L("W押↑"),
WheelHoldDown:L("W押↓"),
LeftClick:L("左押"),
RightClick:L("右押"),
MiddleClick:L("中押")
};
// 認識する最小のマウスの動き
// イベント動作
var gesturing = false;
var handlers ={
mousedown: function (event){
if( gesturing ) return doAction(event,tracer.add(directioner.find(event)));
if( event.button == 2 ) gestureStart(event);// 右クリックでジェスチャ開始
},
mouseup: function (event){
if( gesturing ) doAction(event,tracer.add(directioner.find(event)));
display.echoStatusBar('', 0);
},
mousemove:function(event){
if( gesturing ){
doAction(event,tracer.add(directioner.find(event)));
}
},
contextmenu:function(event){
if ( tracer.actioned ){
stopEvent( event );
tracer.actioned=false;
var elem = document.getElementById("contentAreaContextMenu");
elem.setAttribute("hidden", "true");
setTimeout(function() elem.removeAttribute("hidden"), 0);
}
}
};
handlers.DOMMouseScroll = handlers.mousemove;
// ------------ 共通動作 ---------------
// ジェスチャ開始
function gestureStart(event){
gesturing = true;
message(M({ja:"ジェスチャ開始",en:"Gesture start"}));
directioner.tolerance = plugins.options["ProgrammableGesture.tolerance"] || 20;
tracer.init(plugins.options["ProgrammableGesture.actions"](ProgrammableGesture));
directioner.init(event);
}
function doAction(event,act){
if( act && act.action ){
message("Action! "+ act );
tracer.actioned = act;
act.execute( event );
return true;
}else{
message(M({ja:"ジェスチャ",en:"Gesture"}) +"... "+ tracer );
}
}
function stopEvent( event ){
event.preventDefault();
event.stopPropagation();
}
function message(msg){
display.echoStatusBar( msg );
}
// 動き検出
var directioner={
lastX:0,
lastY:0,
tolerance:20,
wheelhold:false,
init:function (event){
this.lastX = event.screenX;
this.lastY = event.screenY;
this.wheelhold=false;
this.start_button = event.button;
},
// 動きを検出して文字列にする
find:function(event){
stopEvent( event );
switch( event.type ){
case "mousemove":
var distanceX = event.screenX - this.lastX;
var distanceY = event.screenY - this.lastY;
if (Math.abs(distanceX) < this.tolerance && Math.abs(distanceY) < this.tolerance) return "";
this.lastX = event.screenX;
this.lastY = event.screenY;
return (Math.abs(distanceX) > Math.abs(distanceY)) ?
( distanceX < 0 ? "Left" : "Right" ) :
( distanceY < 0 ? "Up" : "Down" );
case "DOMMouseScroll":
return (this.wheelhold ? "WheelHold" : "Wheel" )+((event.detail < 0 )? "Up" : "Down");
case "mousedown":
if(event.button==1){
this.wheelhold = true;
break;
}else
return ["LeftClick","MiddleClick","RightClick"][event.button];
case "mouseup":
if( gesturing ){
if( event.button== this.start_button ){
return "Over";
}
}
if(event.button==1){
this.wheelhold=false;
}
return ["","MiddleClick",""][event.button];
}
return "";
}
}
// ジェスチャー判定
var tracer={
chain:"",
last:"",
nextAction:null,
actioned:false,
init:function(action){
this.chain="";
this.last="";
this.nextAction=chain(action) || noaction;
this.actioned = false;
},
add:function(direction){
if( direction == "Over" ){
gesturing = false;
return this.check();
}
if( MOTION_LABEL[direction] && direction != this.last ){
var act = this.nextAction.next(direction)|| noaction;
if( act.soon ){
return act;
}else{
this.chain += MOTION_LABEL[direction];
this.last = direction;
this.actioned = false;
this.nextAction = act;
}
}
},
check:function(){
return (!this.actioned) && this.nextAction;
},
toString:function(){
return this.chain +" "+(this.actioned ? ("("+this.actioned+")"): "" )+ this.nextAction.nextActions();
}
}
// ActionChainクラス
function ActionChain(action,moreaction,soon){
this.action = action;
this.nexts = moreaction;
this.soon = soon;
}
ActionChain.prototype.next=function(a){
return ( a && this.nexts && this.nexts[a] );
}
ActionChain.prototype.execute=function(event){
if(this.action==null)return;
return this.action.action(event);
}
ActionChain.prototype.nextActions=function(){
var m = ( this.action )? ("["+this.action.label+"]") : "";
for( var i in this.nexts ){
m += " " + MOTION_LABEL[i] + (this.nexts[i].soon ? L(":即") : "" ) +":"+this.nexts[i];
}
return m;
}
ActionChain.prototype._dump=function(prefix,m){
var k = prefix;
if(this.action) m.push( [ prefix, this.action.label] );
for( var i in this.nexts ){
Application.console.log(this.nexts[i].prototype);
this.nexts[i]._dump( prefix + " "+ MOTION_LABEL[i], m );
}
}
ActionChain.prototype.toString=function(){
if( this.action && this.action.label ){
return this.action.label + ( this.nexts ? "..." : "" );
}else{
return ""
}
}
// アクションクラス
function Action(label,func){
this.label = label;
this.action = func;
}
// 即何もせずに終了
var noaction = new ActionChain(
new Action(M({ja:"定義なしでジェスチャ終了",en:"no defined"}),
function(){
stop();
}),
null,true );
// keysnaylのエクステンションをActionオブジェクトに変更
function ext2action(action){
if( typeof(action)=='string' ){
var e = ext.exts[action];
if( !e ) {
util.alert(ProgrammableGesture,M({ja:"KeySnailエクステンションが設定されていない",en:"no defined extention"})+"\n"+action);
return new Action( action, function(){ } );
} else {
return new Action( e.description, function(){ e.action(); } );
}
}else{
return action;
}
}
// DSL: 次のアクションを待つ。これでジェスチャ終了なら action を実行
function chain(action,moreaction){
if( typeof(action)=="string" ){
action = ext2action(action);
}else if(typeof(action)=="object"){
if( action instanceof Action ){
}else{
moreaction = action;
action = null;
}
}
return new ActionChain(action,moreaction);
}
// DSL: 次のアクションを待たずに action を実行
function soon(a){
return new ActionChain( ext2action(a),null,true);
}
// DSL:アクションオブジェクト生成
function action(label,func){ return new Action(label,func) }
// DSL:ジェスチャ中断
function stop(){
gesturing=false;
}
// イベント登録
function setEvents( add ){
var p = gBrowser.mPanelContainer || window;
add = [add ? "addEventListener" : "removeEventListener"];
for( var e in handlers){
p[add](e, handlers[e], true);
}
}
return {
chain:chain,
soon:soon,
action:action,
stop:function(){ plugins.ProgrammableGesture._stop(); },
_stop:stop,
directioner:directioner,
ActionChain:ActionChain,
init:function(){
setEvents( true );
},
uninit:function(){
setEvents( false );
},
dump:function(){
var a=[];
chain(plugins.options["ProgrammableGesture.actions"])._dump("",a);
var m="";
a.forEach(function(x){ m+= x.join(" ")+"\n"; });
util.alert("ProgrammableGesture",m);
}
};
})();
if(my.ProgrammableGesture){
my.ProgrammableGesture.uninit();
}
my.ProgrammableGesture = ProgrammableGesture;
ProgrammableGesture.init();
plugins.ProgrammableGesture = ProgrammableGesture;
@958
Copy link
Author

958 commented May 23, 2011

最新の keysnail で動くようにした

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment