Created
July 2, 2010 12:24
-
-
Save nazoking/461284 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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="[email protected]" 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))); | |
}, | |
mousemove:function(event){ | |
if( gesturing ){ | |
doAction(event,tracer.add(directioner.find(event))); | |
} | |
}, | |
contextmenu:function(event){ | |
if ( tracer.actioned ){ | |
stopEvent( event ); | |
tracer.actioned=false; | |
} | |
} | |
}; | |
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.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(ext){ | |
if( typeof(ext)=='string' ){ | |
var e = KeySnail.Ext.exts[ext]; | |
if( !e ) | |
util.alert(ProgrammableGesture,M({ja:"KeySnailエクステンションが設定されていない",en:"no defined extention"})+"\n"+ext); | |
return new Action( e.description, function(){ e.action(); } ); | |
}else{ | |
return ext; | |
} | |
} | |
// 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 | |
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; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment