【FocusRotation】フォーカスの巡回を作成する(入れ子対応版)
FocusRotation はタブキーによるフォーカスの巡回を作成します。
以前もサンプルを投稿しましたが、今回は FocusRotation を入れ子(木構造)にできるようにしました。
このサンプルでは [B1, [A1, A2, A3], B2, B3], [C1, C2, C3] の3つの巡回を作成しています。
バグ修正版は以下参照。
http://trace.wetcradle.com/?p=235
/**
* Copyright wetcradle ( http://wonderfl.net/user/wetcradle )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/tdSk
*/
/**
FocusRotation はタブキーによるフォーカスの巡回を作成します。
以前もサンプルを投稿しましたが、今回は FocusRotation を入れ子(木構造)にできるようにしました。
このサンプルでは [B1, [A1, A2, A3], B2, B3], [C1, C2, C3] の3つの巡回を作成しています。
*/
package {
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFieldType;
public class FocusRotationSample extends Sprite {
private var textFields:Vector.<TextField>;
public function FocusRotationSample():void {
super();
configTextFields();
textFields[0].text = "A1";
textFields[4].text = "A2";
textFields[8].text = "A3";
var focusRotationA:FocusRotation = new FocusRotation([textFields[0], textFields[4], textFields[8]]);
textFields[1].text = "B1";
textFields[2].text = "B2";
textFields[5].text = "B3";
var focusRotationB:FocusRotation = new FocusRotation();
focusRotationB.members = [
textFields[1],
focusRotationA,
textFields[2],
textFields[5]
];
textFields[3].text = "C1";
textFields[6].text = "C2";
textFields[7].text = "C3";
var focusRotationC:FocusRotation = new FocusRotation();
focusRotationC.addMember(textFields[3]);
focusRotationC.addMember(textFields[6]);
focusRotationC.addMember(textFields[7]);
focusRotationA.focusFirst();
}
private function configTextFields():void {
textFields = new Vector.<TextField>();
for (var i:int=0; i<9; i++) {
var textField:TextField = new TextField();
textField.type = TextFieldType.INPUT;
textField.border = true;
textField.width = 100;
textField.height = 22;
textField.x = (i % 3) * 110 + 10;
textField.y = Math.floor(i / 3) * 50 + 10;
addChild(textField);
textFields.push(textField);
}
}
}
}
import fl.core.UIComponent;
import flash.display.InteractiveObject;
import flash.events.Event;
import flash.events.FocusEvent;
/**
タブキーによるフォーカスの巡回を作成します。
InteractiveObject と FocusRotation をメンバとして登録できます。
複数の FocusRotation で FocusRotation の木構造を定義することができます。
木構造は FocusRotation をメンバとして登録することによって作成できます。
1つの木構造でフォーカスが巡回されます。
以下の InteractiveObject であるメンバはフォーカスの巡回がスキップされます。
・stage の表示リストに含まれていない
・UIComponent で enabled が false
*/
class FocusRotation {
protected var _members:Array;
protected var _parent:FocusRotation;
protected var _active:Boolean = false;
//members のうち表示オブジェクトに含まれるもの
//activeMembers は整合性は保たれない。validateActiveMembers を呼ぶ必要がある。
protected var activeMembers:Array = [];
protected var memberIndex2ActiveMemberIndex:Array = [];
protected var invalidActiveMembers:Boolean = false;
////////////////////////////////////////////////////////////
//////////////コンストラクタ
////////////////////////////////////////////////////////////
public function FocusRotation(members:Array=null):void {
super();
this.members = members || [];
inactivate();
}
////////////////////////////////////////////////////////////
//////////////プロパティ
////////////////////////////////////////////////////////////
//______________ parent ______________//
/**
*/
public function get parent():FocusRotation {
return _parent;
}
//______________ members ______________//
/**
ゲッターではコピーを返します。
セッターではコピーを使用します。
メンバが重複している場合はエラーになります。
*/
public function get members():Array {
return _members.concat();
}
public function set members(value:Array):void {
var item:Object;
// エラーチェック
for each (item in value) {
if (!(item is InteractiveObject || item is FocusRotation)) {
throw new ArgumentError(this+": set members: member must be InteractiveObject or FocusRotation: "+item);
}
}
if (value.length != ArrayTools.removeDuplication(value, true).length) {
throw new Error(this+": set members: duplicate members");
}
for each (item in _members) {
if (item is FocusRotation) {
FocusRotation(item).setParent(null);
}
}
_members = value.concat();
invalidateActiveMembers();
for each (item in _members) {
if (item is FocusRotation) {
FocusRotation(item).setParent(this);
}
}
}
//______________ active ______________//
public function get active():Boolean {
return _active;
}
//______________ length ______________//
public function get length():int {
return _members.length;
}
////////////////////////////////////////////////////////////
//////////////パブリックメソッド
////////////////////////////////////////////////////////////
//______________ destroy ______________//
public function destroy():void {
members = [];
inactivate();
_members = null;
}
//______________ activate ______________//
/**
*/
public function activate():void {
if (active) {
return;
}
_active = true;
FocusRotationManager.focusRotationManager.activateRotation(this);
}
//______________ inactivate ______________//
public function inactivate():void {
if (!active) {
return;
}
_active = false;
FocusRotationManager.focusRotationManager.inactivateRotation(this);
}
//______________ focusFirst ______________//
/**
activate メソッドを呼び、最初のメンバにフォーカスを設定します。
*/
public function focusFirst():void {
focusNext(-1);
}
//______________ focusLast ______________//
/**
activate メソッドを呼び、最後のメンバにフォーカスを設定します。
*/
public function focusLast():void {
focusBack(-1);
}
//______________ focusNext ______________//
/**
activate メソッドを呼び、baseIndex の次のメンバにフォーカスを設定します。
baseIndex に -1 を指定した場合は、最初のメンバにフォーカスを設定します。
*/
public function focusNext(baseIndex:int):void {
//trace(this+": focusNext: baseIndex="+baseIndex+", parent="+parent);
activate();
validateActiveMembers();
var activeBaseIndex:int = baseIndex < 0 ? -1 : Math.floor(Number(memberIndex2ActiveMemberIndex[baseIndex]));
if (activeMembers.length) {
for (var i:int=(activeBaseIndex+1)%activeMembers.length; i!=activeBaseIndex; i=(i+1)%activeMembers.length) {
if (parent && i == 0 && baseIndex >= 0) {
parent.focusNext(parent.indexOf(this));
break;
}
else if (tryToFocus(activeMembers[i], true)) {
break;
}
}
}
}
//______________ focusBack ______________//
/**
activate メソッドを呼び、baseIndex の次のメンバにフォーカスを設定します。
baseIndex に -1 を指定した場合は、最後のメンバにフォーカスを設定します。
*/
public function focusBack(baseIndex:int):void {
activate();
validateActiveMembers();
var activeBaseIndex:int = baseIndex < 0 ? 0 : Math.ceil(Number(memberIndex2ActiveMemberIndex[baseIndex]));
if (activeMembers.length) {
for (var i:int=(activeBaseIndex+activeMembers.length-1)%activeMembers.length; i!=activeBaseIndex; i=(i+activeMembers.length-1)%activeMembers.length) {
if (parent && i == activeMembers.length - 1 && baseIndex >= 0) {
parent.focusBack(parent.indexOf(this));
break;
}
else if (tryToFocus(activeMembers[i], false)) {
break;
}
}
}
}
//______________ indexOf ______________//
/**
*/
public function indexOf(member:Object):int {
return _members.indexOf(member);
}
//______________ addMember ______________//
public function addMember(interactiveObjectMember:InteractiveObject):void {
addMemberAt(interactiveObjectMember, _members.length);
}
//______________ addMemberAt ______________//
/**
メンバが重複している場合はエラーになります。
*/
public function addMemberAt(interactiveObjectMember:InteractiveObject, index:uint):void {
if (indexOf(interactiveObjectMember) != -1) {
throw new ArgumentError(this+": addMemberAt: duplicate members: "+interactiveObjectMember);
}
_members.splice(index, 0, interactiveObjectMember);
invalidateActiveMembers();
}
//______________ replaceMemberAt ______________//
/**
メンバが重複している場合はエラーになります。
*/
public function replaceMemberAt(interactiveObjectMember:InteractiveObject, index:uint):void {
if (indexOf(interactiveObjectMember) != -1) {
throw new ArgumentError(this+": replaceMemberAt: duplicate members: "+interactiveObjectMember);
}
var removedMembers:Array = _members.splice(index, 1, interactiveObjectMember);
if (removedMembers.length) {
if (removedMembers[0] is FocusRotation) {
FocusRotation(removedMembers[0]).setParent(null);
}
}
invalidateActiveMembers();
}
//______________ removeMember ______________//
public function removeMember(interactiveObjectMember:InteractiveObject):void {
var index:int = indexOf(interactiveObjectMember);
if (index == -1) {
throw new Error(this+": removeMember: not found: "+interactiveObjectMember);
}
_members.splice(index, 1);
invalidateActiveMembers();
}
//______________ addFocusRotation ______________//
public function addFocusRotation(focusRotationMember:FocusRotation):void {
addFocusRotationAt(focusRotationMember, _members.length);
}
//______________ addFocusRotationAt ______________//
/**
メンバが重複している場合はエラーになります。
*/
public function addFocusRotationAt(focusRotationMember:FocusRotation, index:uint):void {
if (indexOf(focusRotationMember) != -1) {
throw new ArgumentError(this+": addFocusRotationAt: duplicate members: "+focusRotationMember);
}
_members.splice(index, 0, focusRotationMember);
invalidateActiveMembers();
focusRotationMember.setParent(this);
}
//______________ replaceFocusRotationAt ______________//
/**
メンバが重複している場合はエラーになります。
*/
public function replaceFocusRotationAt(focusRotationMember:FocusRotation, index:uint):void {
if (indexOf(focusRotationMember) != -1) {
throw new ArgumentError(this+": replaceFocusRotationAt: duplicate members: "+focusRotationMember);
}
var removedMembers:Array = _members.splice(index, 1, focusRotationMember);
if (removedMembers.length) {
if (removedMembers[0] is FocusRotation) {
FocusRotation(removedMembers[0]).setParent(null);
}
}
invalidateActiveMembers();
focusRotationMember.setParent(this);
}
//______________ removeFocusRotation ______________//
public function removeFocusRotation(focusRotationMember:FocusRotation):void {
var index:int = indexOf(focusRotationMember);
if (index == -1) {
throw new Error(this+": removeFocusRotation: not found: "+focusRotationMember);
}
_members.splice(index, 1);
invalidateActiveMembers();
focusRotationMember.setParent(null);
}
////////////////////////////////////////////////////////////
//////////////メソッド
////////////////////////////////////////////////////////////
//______________ setParent ______________//
/*
親は子を追加・削除するときに、子の setParent メソッドを用いて、親であることを子に知らせなければならない。
*/
internal function setParent(parent:FocusRotation):void {
//trace(this+": setParent: "+parent);
if (this.parent === parent) {
return;
}
if (this.parent && this.parent.indexOf(this) != -1) {
this.parent.removeFocusRotation(this);
}
_parent = parent;
}
//______________ refreshActiveMembers ______________//
protected function refreshActiveMembers():void {
trace(this+": refreshActiveMembers");
var member:Object;
var interactiveObjectMember:InteractiveObject;
var focusRotationMember:FocusRotation;
for each (member in _members) {
if (member is InteractiveObject) {
interactiveObjectMember = InteractiveObject(member);
interactiveObjectMember.removeEventListener(FocusEvent.FOCUS_IN, memberFocusInHandler);
interactiveObjectMember.removeEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeEvent);
interactiveObjectMember.removeEventListener(Event.ADDED_TO_STAGE, memberAddedToStageHandler);
interactiveObjectMember.removeEventListener(Event.REMOVED_FROM_STAGE, memberRemovedFromStageHandler);
}
}
activeMembers = [];
memberIndex2ActiveMemberIndex = [];
for (var i:int=0; i<_members.length; i++) {
member = _members[i];
if (member is InteractiveObject) {
interactiveObjectMember = InteractiveObject(member);
if (interactiveObjectMember.stage) {
// interactiveObjectMember.tabEnabled = true;
activeMembers.push(interactiveObjectMember);
memberIndex2ActiveMemberIndex[i] = activeMembers.length - 1;
}
else {
memberIndex2ActiveMemberIndex[i] = activeMembers.length - 0.5;
}
interactiveObjectMember.addEventListener(FocusEvent.FOCUS_IN, memberFocusInHandler);
interactiveObjectMember.addEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeEvent);
interactiveObjectMember.addEventListener(Event.ADDED_TO_STAGE, memberAddedToStageHandler);
interactiveObjectMember.addEventListener(Event.REMOVED_FROM_STAGE, memberRemovedFromStageHandler);
}
else if (member is FocusRotation) {
activeMembers.push(member);
memberIndex2ActiveMemberIndex[i] = activeMembers.length - 1;
}
}
}
//______________ invalidateActiveMembers ______________//
protected function invalidateActiveMembers():void {
invalidActiveMembers = true;
FunctionTools.callAfter(this, 1, validateActiveMembers)
}
//______________ validateActiveMembers ______________//
protected function validateActiveMembers():void {
if (invalidActiveMembers) {
refreshActiveMembers();
invalidActiveMembers = false;
}
}
//______________ tryToFocus ______________//
protected function tryToFocus(member:Object, forward:Boolean):Boolean {
if (member is UIComponent) {
if (UIComponent(member).enabled) {
UIComponent(member).setFocus();
return true;
}
}
else if (member is InteractiveObject) {
InteractiveObject(member).stage.focus = InteractiveObject(member);
return true;
}
else if (member is FocusRotation) {
if (forward) {
FocusRotation(member).focusFirst();
}
else {
FocusRotation(member).focusLast();
}
return true;
}
return false;
}
////////////////////////////////////////////////////////////
//////////////イベントハンドラ
////////////////////////////////////////////////////////////
//______________ memberFocusInHandler ______________//
protected function memberFocusInHandler(e:FocusEvent):void {
activate();
}
//______________ keyFocusChangeEvent ______________//
protected function keyFocusChangeEvent(e:FocusEvent):void {
validateActiveMembers();
var memberIndex:int = activeMembers.indexOf(e.currentTarget);
if (memberIndex == -1) {
trace(this+": keyFocusChangeEvent: invalid member: "+e.currentTarget);
return;
}
if (e.shiftKey) {
focusBack(memberIndex);
}
else {
focusNext(memberIndex);
}
e.preventDefault();
}
//______________ memberAddedToStageHandler ______________//
protected function memberAddedToStageHandler(e:Event):void {
invalidateActiveMembers();
}
//______________ memberRemovedFromStageHandler ______________//
protected function memberRemovedFromStageHandler(e:Event):void {
invalidateActiveMembers();
}
}
import flash.utils.Dictionary;
class FocusRotationManager {
protected static var _focusRotationManager:FocusRotationManager;
protected var _activeRotation:FocusRotation;
private static var allowInstantiation:Boolean = false;
////////////////////////////////////////////////////////////
//////////////コンストラクタ
////////////////////////////////////////////////////////////
public function FocusRotationManager():void {
super();
if (!allowInstantiation) {
throw new Error("Instantiation is prohibited");
}
}
////////////////////////////////////////////////////////////
//////////////プロパティ
////////////////////////////////////////////////////////////
//______________ focusRotationManager ______________//
public static function get focusRotationManager():FocusRotationManager {
if (!_focusRotationManager) {
allowInstantiation = true;
_focusRotationManager = new FocusRotationManager();
allowInstantiation = false;
}
return _focusRotationManager;
}
//______________ activeRotation ______________//
public function get activeRotation():FocusRotation {
return _activeRotation;
}
////////////////////////////////////////////////////////////
//////////////メソッド
////////////////////////////////////////////////////////////
//______________ activateRotation ______________//
internal function activateRotation(rotation:FocusRotation):void {
if (_activeRotation && _activeRotation !== rotation) {
_activeRotation.inactivate();
}
_activeRotation = rotation;
}
//______________ inactivateRotation ______________//
internal function inactivateRotation(rotation:FocusRotation):void {
if (_activeRotation === rotation) {
_activeRotation = null;
}
}
}
import flash.utils.Dictionary;
class ArrayTools {
//______________ equal ______________//
/**
指定した2つの配列の長さと各要素が等しいか調べます。
*/
public static function equal(array1:Array, array2:Array, strict:Boolean=false):Boolean {
if (array1.length != array2.length) {
return false;
}
var i:int;
if (strict) {
for (i=0; i<array1.length; i++) {
if (array1[i] !== array2[i]) {
return false;
}
}
}
else {
for (i=0; i<array1.length; i++) {
if (array1[i] != array2[i]) {
return false;
}
}
}
return true;
}
//______________ fill ______________//
/**
配列の指定範囲を全て指定したアイテムに置き換えます。
指定範囲が配列の長さを超える場合は新たにアイテムを追加します。
lengthがuint.MAX_VALUEのときは配列の最後までが範囲となります。
*/
public static function fill(array:Array, item:Object, startIndex:uint=0, length:uint=uint.MAX_VALUE):void {
var lastIndex:uint = (length == uint.MAX_VALUE) ? array.length : startIndex + length;
for (var i:int=startIndex; i<lastIndex; i++) {
array[i] = item;
}
}
//______________ chain ______________//
/**
2つ以上の配列を連結して新しい配列を返します。
*/
public static function chain(array1:Array, array2:Array, ...args):Array {
var item:Object;
var array:Array = array1.concat();
args.unshift(array2);
for each (item in args) {
var tempArray:Array = item as Array;
if (tempArray) {
for each (item in tempArray) {
array.push(item);
}
}
}
return array;
}
//______________ sort ______________//
/**
マージソートを行います。元の配列は変更されません。
funcは配列のアイテムを引数にとり、ソートの基準となる数値を返します。
例えば、各要素が{name:String, num:Number}となっている配列について、
numで降順にソートしたい場合は以下のような関数をcompareFunctionに指定すれば良いです。
function compareFunction(a:Object, b:Object):Number {
return Number(b.num) - Number(a.num);
}
*/
public static function sort(array:Array, compareFunction:Function):Array {
return mergeSort(array);
function mergeSort(targetArray:Array):Array {
if (targetArray.length <= 1) {
return targetArray;
}
var headLength:uint = Math.ceil(targetArray.length / 2);
var a:Array = mergeSort(targetArray.slice(0, headLength));
var b:Array = mergeSort(targetArray.slice(headLength));
var c:Array = [];
var k:uint = 0;
var i:uint = 0;
var j:uint = 0;
while(i < a.length && j < b.length) {
if (compareFunction(a[i], b[j]) <= 0) {
c[k++] = a[i++];
}
else {
c[k++] = b[j++];
}
}
while (i < a.length) {
c[k++] = a[i++];
}
while (j < b.length) {
c[k++] = b[j++];
}
return c;
}
}
//______________ getItemOf ______________//
/**
*/
public static function getItemOf(array:Array, key:String, value:*, strict:Boolean=false):Object {
var item:Object;
if (strict) {
for each (item in array) {
if (item[key] === value) {
return item;
}
}
}
else {
for each (item in array) {
if (item[key] == value) {
return item;
}
}
}
return null;
}
//______________ removeDuplication ______________//
/**
重複したアイテムを持たない配列を返します。元の配列は変更されません。
strictがtrueの場合は、厳密な等価(===)が使用されます。
*/
public static function removeDuplication(array:Array, strict:Boolean=false):Array {
var item:Object;
var resultArray:Array = [];
if (strict) {
var dictionary:Dictionary = new Dictionary();
for each (item in array) {
if (!dictionary[item]) {
resultArray.push(item);
dictionary[item] = true;
}
}
}
else {
var object:Object = {};
for each (item in array) {
if (!object[item]) {
resultArray.push(item);
object[item] = true;
}
}
}
return resultArray;
}
//______________ remove ______________//
/**
arrayからitemを削除します。
このメソッドは、コピーを作成しないで、配列を変更します。
先頭から検索を行い最初に発見されたアイテムを削除します。削除されたインデックスは詰められます。
strictがtrueの場合は、厳密な等価(===)が使用されます。
削除されたインデックスを返します。何も削除されなかった場合は-1を返します。
*/
public static function remove(array:Array, searchElement:*, strict:Boolean=false):int {
var index:int;
if (strict) {
if ((index = array.indexOf(searchElement)) == -1) {
return -1;
}
}
else {
for (var i:int=0; i<array.length; i++) {
if (array[i] == searchElement) {
index = i;
break;
}
}
}
array.splice(index, 1);
return index;
}
//______________ indexOf ______________//
/**
Array.indexOfと同様の機能ですが、strictな比較を行うか指定できます。
strictがtrueの場合は、厳密な等価(===)が使用されます。
*/
public static function indexOf(array:Array, searchElement:*, strict:Boolean=false):int {
var index:int;
if (strict) {
return array.indexOf(searchElement);
}
else {
for (var i:int=0; i<array.length; i++) {
if (array[i] == searchElement) {
return i;
}
}
}
return -1;
}
//______________ lastIndexOf ______________//
/**
Array.lastIndexOfと同様の機能ですが、strictな比較を行うか指定できます。
strictがtrueの場合は、厳密な等価(===)が使用されます。
*/
public static function lastIndexOf(array:Array, searchElement:*, strict:Boolean=false):int {
var index:int;
if (strict) {
return array.indexOf(searchElement);
}
else {
for (var i:int=array.length; i>=0; i--) {
if (array[i] == searchElement) {
return i;
}
}
}
return -1;
}
}
import flash.display.Sprite;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;
class FunctionTools {
//______________ callAfterFrame ______________//
/**
1フレーム後にメソッドをコールします。
thisObjectにはfuncメソッドを持っているオブジェクトを指定します。
...argsにはfuncへ渡す引数を指定します。
*/
public static function callAfterFrame(thisObject:Object, func:Function, ...args):void {
var dispatcher:Sprite = new Sprite();
if (args.length > 2) {
throw new ArgumentError("over the limit of arguments count");
}
dispatcher.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
function enterFrameHandler(e:Event):void {
dispatcher.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
func.apply(thisObject, args);
}
}
//______________ callAfter ______________//
/**
指定した時間後にメソッドをコールします。
thisObjectにはfuncメソッドを持っているオブジェクトを指定します。
...argsにはfuncへ渡す引数を指定します。
*/
public static function callAfter(thisObject:Object, msec:Number, func:Function, ...args):void {
var timer:Timer = new Timer(msec, 1);
if (args.length > 2) {
throw new ArgumentError("over the limit of arguments count");
}
timer.addEventListener(TimerEvent.TIMER_COMPLETE, timerCompleteHandler);
timer.start();
function timerCompleteHandler(e:TimerEvent):void {
timer.removeEventListener(TimerEvent.TIMER_COMPLETE, timerCompleteHandler);
func.apply(thisObject, args);
}
}
}