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
  • Select an option

  • Save 958/986346 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown
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