1 /**
  2  * @fileOverview
  3  * ui.enchant.js v2 (2012/11/05)
  4  * ui parts support
  5  * @require enchant.js v0.5.2 or later
  6  * @require image files for gamepad, icons (default: pad.png, apad.png, icon0.png, font0.png)
  7  *
  8  * @features
  9  * - D-Pad (left, right, up, down)
 10  * - Analog Pad
 11  * - Button (3 built-in themes and can be customized)
 12  * - MutableText
 13  * - ScoreLabel
 14  * - TimeLabel
 15  * - LifeLabel
 16  * - Bar
 17  * - VirtualMap
 18  *
 19  * @usage
 20  * [D-Pad]
 21  *      var pad = new Pad();
 22  *      pad.x = 0;
 23  *      pad.y = 220;
 24  *      core.rootScene.addChild(pad);
 25  *  (input of X direction can be detected from "Xbuttonup" "Xbuttondown" events
 26  *   or enchant.Core.instance.input.X)
 27  *
 28  * [A-Pad]
 29  *      var pad = new APad();
 30  *      pad.x = 0;
 31  *      pad.y = 220;
 32  *      core.rootScene.addChild(pad);
 33  *  (input can be detected from pad.vx/vy and pad.touched)
 34  *
 35  * [Button]
 36  *      var button = new Button("Press me");
 37  *      button.addEventListener("touchstart", function(){ ... })
 38  *      core.rootScene.addEventListener(button);
 39  *
 40  *      var button_light = new Button("Press me", "light");
 41  *      core.rootScene.addEventListener(button);
 42  *
 43  *      var button_blue = new Button("Press me", "blue");
 44  *      core.rootScene.addEventListener(button);
 45  */
 46 
 47 /**
 48  * @type {Object}
 49  */
 50 enchant.ui = { assets: ['pad.png', 'apad.png', 'icon0.png', 'font0.png'] };
 51 
 52 /**
 53  * 方向キーパッドのクラス: Pad
 54  * @scope enchant.ui.Pad
 55  */
 56 enchant.ui.Pad = enchant.Class.create(enchant.Sprite, {
 57     /**
 58      * 方向キーパッドオブジェクトを作成する。
 59      * @constructs
 60      * @extends enchant.Sprite
 61      */
 62     initialize: function() {
 63         var core = enchant.Core.instance;
 64         var image = core.assets['pad.png'];
 65         enchant.Sprite.call(this, image.width / 2, image.height);
 66         this.image = image;
 67         this.input = { left: false, right: false, up: false, down: false };
 68         this.addEventListener('touchstart', function(e) {
 69             this._updateInput(this._detectInput(e.localX, e.localY));
 70         });
 71         this.addEventListener('touchmove', function(e) {
 72             this._updateInput(this._detectInput(e.localX, e.localY));
 73         });
 74         this.addEventListener('touchend', function(e) {
 75             this._updateInput({ left: false, right: false, up: false, down: false });
 76         });
 77     },
 78     _detectInput: function(x, y) {
 79         x -= this.width * 0.5;
 80         y -= this.height * 0.5;
 81         var input = { left: false, right: false, up: false, down: false };
 82         if (x * x + y * y <= 2500 && x * x + y * y > 200) {
 83             if (x < 0 && y < x * x * 0.1 && y > x * x * -0.1) {
 84                 input.left = true;
 85             }
 86             if (x > 0 && y < x * x * 0.1 && y > x * x * -0.1) {
 87                 input.right = true;
 88             }
 89             if (y < 0 && x < y * y * 0.1 && x > y * y * -0.1) {
 90                 input.up = true;
 91             }
 92             if (y > 0 && x < y * y * 0.1 && x > y * y * -0.1) {
 93                 input.down = true;
 94             }
 95         }
 96         return input;
 97     },
 98     _updateInput: function(input) {
 99         var core = enchant.Core.instance;
100         ['left', 'right', 'up', 'down'].forEach(function(type) {
101             if (this.input[type] && !input[type]) {
102                 core.changeButtonState(type, false);
103             }
104             if (!this.input[type] && input[type]) {
105                 core.changeButtonState(type, true);
106             }
107         }, this);
108         this.input = input;
109     }
110 });
111 
112 /**
113  * アナログパッドのクラス: APad
114  * @scope enchant.ui.APad
115  */
116 enchant.ui.APad = enchant.Class.create(enchant.Group, {
117     /**
118      * アナログパッドオブジェクトを作成する。
119      * @constructs
120      * @extends enchant.Group
121      * @param mode
122      *   'direct': 入力ベクトルは正規化されない (大きさは 0~1 の間)
123      *   'normal': 入力ベクトルを常に正規化する (大きさは常に1となる)
124      */
125     initialize: function(mode) {
126         var core = enchant.Core.instance;
127         var image = core.assets['apad.png'];
128         var w = this.width = image.width;
129         var h = this.height = image.height;
130         enchant.Group.call(this);
131 
132         this.outside = new enchant.Sprite(w, h);
133         var outsideImage = new enchant.Surface(w, h);
134         outsideImage.draw(image, 0, 0, w, h / 4, 0, 0, w, h / 4);
135         outsideImage.draw(image, 0, h / 4 * 3, w, h / 4, 0, h / 4 * 3, w, h / 4);
136         outsideImage.draw(image, 0, h / 4, w / 4, h / 2, 0, h / 4, w / 4, h / 2);
137         outsideImage.draw(image, w / 4 * 3, h / 4, w / 4, h / 2, w / 4 * 3, h / 4, w / 4, h / 2);
138         this.outside.image = outsideImage;
139         this.inside = new enchant.Sprite(w / 2, h / 2);
140         var insideImage = new enchant.Surface(w / 2, h / 2);
141         insideImage.draw(image, w / 4, h / 4, w / 2, h / 2, 0, 0, w / 2, h / 2);
142         this.inside.image = insideImage;
143         this.r = w / 2;
144 
145         /**
146          * isTouched
147          * @type {Boolean}
148          * タッチされているかどうか
149          */
150         this.isTouched = false;
151 
152         /**
153          * vx, vy
154          * @type {Number}
155          * 入力ベクトルの(x, y)方向の大きさ
156          */
157         this.vx = 0;
158         this.vy = 0;
159 
160         /**
161          * rad, dist
162          * @type {Number}
163          * 入力ベクトルの極座標表示
164          * radは角度、distはベクトルの大きさを示す
165          */
166         this.rad = 0;
167         this.dist = 0;
168 
169         if (mode === 'direct') {
170             this.mode = 'direct';
171         } else {
172             this.mode = 'normal';
173         }
174         this._updateImage();
175         this.addChild(this.inside);
176         this.addChild(this.outside);
177         this.addEventListener('touchstart', function(e) {
178             this._detectInput(e.localX, e.localY);
179             this._calcPolar(e.localX, e.localY);
180             this._updateImage(e.localX, e.localY);
181             this._dispatchPadEvent('apadstart');
182             this.isTouched = true;
183         });
184         this.addEventListener('touchmove', function(e) {
185             this._detectInput(e.localX, e.localY);
186             this._calcPolar(e.localX, e.localY);
187             this._updateImage(e.localX, e.localY);
188             this._dispatchPadEvent('apadmove');
189         });
190         this.addEventListener('touchend', function(e) {
191             this._detectInput(this.width / 2, this.height / 2);
192             this._calcPolar(this.width / 2, this.height / 2);
193             this._updateImage(this.width / 2, this.height / 2);
194             this._dispatchPadEvent('apadend');
195             this.isTouched = false;
196         });
197     },
198     _dispatchPadEvent: function(type) {
199         var e = new enchant.Event(type);
200         e.vx = this.vx;
201         e.vy = this.vy;
202         e.rad = this.rad;
203         e.dist = this.dist;
204         this.dispatchEvent(e);
205     },
206     _updateImage: function(x, y) {
207         x -= this.width / 2;
208         y -= this.height / 2;
209         this.inside.x = this.vx * (this.r - 10) + 25;
210         this.inside.y = this.vy * (this.r - 10) + 25;
211     },
212     _detectInput: function(x, y) {
213         x -= this.width / 2;
214         y -= this.height / 2;
215         var distance = Math.sqrt(x * x + y * y);
216         var tan = y / x;
217         var rad = Math.atan(tan);
218         var dir = x / Math.abs(x);
219         if (distance === 0) {
220             this.vx = 0;
221             this.vy = 0;
222         } else if (x === 0) {
223             this.vx = 0;
224             if (this.mode === 'direct') {
225                 this.vy = (y / this.r);
226             } else {
227                 dir = y / Math.abs(y);
228                 this.vy = Math.pow((y / this.r), 2) * dir;
229             }
230         } else if (distance < this.r) {
231             if (this.mode === 'direct') {
232                 this.vx = (x / this.r);
233                 this.vy = (y / this.r);
234             } else {
235                 this.vx = Math.pow((distance / this.r), 2) * Math.cos(rad) * dir;
236                 this.vy = Math.pow((distance / this.r), 2) * Math.sin(rad) * dir;
237             }
238         } else {
239             this.vx = Math.cos(rad) * dir;
240             this.vy = Math.sin(rad) * dir;
241         }
242     },
243     _calcPolar: function(x, y) {
244         x -= this.width / 2;
245         y -= this.height / 2;
246         var add = 0;
247         var rad = 0;
248         var dist = Math.sqrt(x * x + y * y);
249         if (dist > this.r) {
250             dist = this.r;
251         }
252         dist /= this.r;
253         if (this.mode === 'normal') {
254             dist *= dist;
255         }
256         if (x >= 0 && y < 0) {
257             add = Math.PI / 2 * 3;
258             rad = x / y;
259         } else if (x < 0 && y <= 0) {
260             add = Math.PI;
261             rad = y / x;
262         } else if (x <= 0 && y > 0) {
263             add = Math.PI / 2;
264             rad = x / y;
265         } else if (x > 0 && y >= 0) {
266             add = 0;
267             rad = y / x;
268         }
269         if (x === 0 || y === 0) {
270             rad = 0;
271         }
272         this.rad = Math.abs(Math.atan(rad)) + add;
273         this.dist = dist;
274     }
275 });
276 
277 /**
278  * ボタンオブジェクトのクラス: Button
279  * available in only DOMGroup
280  *
281  * @scope enchant.ui.Button.prototype
282  * @deprecated
283  * @classes
284  */
285 enchant.ui.Button = enchant.Class.create(enchant.Entity, {
286     /**
287      * ボタンオブジェクトを作成する。
288      * @constructs
289      * @extends enchant.Entity
290      */
291     initialize: function(text, theme, height, width) {
292         enchant.Entity.call(this);
293 
294         if (enchant.CanvasLayer) {
295             this._element = 'div';
296         }
297 
298         this.width = width || null;
299         this.height = height || null;
300         this.text = text;
301         this.pressed = false;
302 
303         // デフォルトのスタイル (テーマで上書き可能)
304         var style = this._style;
305         style["display"] = "inline-block";
306         style["font-size"] = "12px";
307         style["height"] = "2em";
308         style["line-height"] = "2em";
309         style["min-width"] = "2em";
310         style["padding"] = "2px 10px";
311         style["text-align"] = "center";
312         style["font-weight"] = "bold";
313         style["border-radius"] = "0.5em";
314 
315         // テーマの指定がなければ "dark" を使う
316         theme = theme || "dark";
317 
318         if (typeof theme === "string") {
319             // theme 引数が string なら、その名前のデフォルトテーマを使う
320             this.theme = enchant.ui.Button.DEFAULT_THEME[theme];
321         } else {
322             // theme 引数が object なら、その引数をテーマとして扱う
323             this.theme = theme;
324         }
325 
326         // テーマを適用する
327         this._applyTheme(this.theme.normal);
328 
329         // タッチしたときの挙動
330         this.addEventListener("touchstart", function() {
331             this._applyTheme(this.theme.active);
332             this.pressed = true;
333             this.y++;
334         });
335 
336         // タッチが離されたときの挙動
337         this.addEventListener("touchend", function() {
338             this._applyTheme(this.theme.normal);
339             this.pressed = false;
340             this.y--;
341         });
342     },
343     _applyTheme: function(theme) {
344         var style = this._style;
345         var css = enchant.ui.Button.theme2css(theme);
346         for (var i in css) {
347             if (css.hasOwnProperty(i)) {
348                 style[i] = css[i];
349             }
350         }
351     },
352     /**
353      * 表示するテキスト
354      * @type {String}
355      */
356     text: {
357         get: function() {
358             return this._text;
359         },
360         set: function(text) {
361                 this._text = text;
362             if (!enchant.CanvasLayer) {
363                 this._element.innerHTML = text;
364             }
365         }
366     },
367     /**
368      * フォントサイズ
369      */
370     size: {
371         get: function() {
372             return this._style.fontSize;
373         },
374         set: function(size) {
375             this._style.fontSize = size;
376         }
377     },
378     /**
379      * フォントの指定
380      * @type {String}
381      */
382     font: {
383         get: function() {
384             return this._style.font;
385         },
386         set: function(font) {
387             this._style.font = font;
388         }
389     },
390     /**
391      * Text color settings.
392      * CSS 'color' can be set to same format as properties.
393      * @type {String}
394      */
395     color: {
396         get: function() {
397             return this._style.color;
398         },
399         set: function(color) {
400             this._style.color = color;
401         }
402     },
403     cvsRender: function() {
404         // not available now
405     },
406     domRender: function() {
407         var element = this._domManager.element;
408         element.innerHTML = this._text;
409         element.style.font = this._font;
410         element.style.color = this._color;
411         element.style.textAlign = this._textAlign;
412     }
413 });
414 
415 enchant.ui.Button.theme2css = function(theme) {
416     var prefix = '-' + enchant.ENV.VENDOR_PREFIX.toLowerCase() + '-';
417     var obj = {};
418     var bg = theme.background;
419     var bd = theme.border;
420     var ts = theme.textShadow;
421     var bs = theme.boxShadow;
422     if (prefix === '-ms-') {
423         obj['background'] = bg.start;
424     } else {
425         obj['background-image'] = prefix + bg.type + '('+ [ 'top', bg.start, bg.end ] + ')';
426     }
427     obj['color'] = theme.color;
428     obj['border'] = bd.color + ' ' + bd.width + ' ' + bd.type;
429     obj['text-shadow'] = ts.offsetX + 'px ' + ts.offsetY + 'px ' + ts.blur + ' ' + ts.color;
430     obj['box-shadow'] = bs.offsetX + 'px ' + bs.offsetY + 'px ' + bs.blur + ' ' + bs.color;
431     return obj;
432 };
433 
434 enchant.ui.Button.DEFAULT_THEME = {
435     dark: {
436         normal: {
437             color: '#fff',
438             background: { type: 'linear-gradient', start: '#666', end: '#333' },
439             border: { color: '#333', width: 1, type: 'solid' },
440             textShadow: { offsetX: 0, offsetY: 1, blur: 0, color: '#666' },
441             boxShadow: { offsetX: 0, offsetY: 1, blur: 0, color: 'rgba(255, 255, 255, 0.3)' }
442         },
443         active: {
444             color: '#ccc',
445             background: { type: 'linear-gradient', start: '#333', end: '#000' },
446             border: { color: '#333', width: 1, type: 'solid' },
447             textShadow: { offsetX: 0, offsetY: 1, blur: 0, color: '#000' },
448             boxShadow: { offsetX: 0, offsetY: 1, blur: 0, color: 'rgba(255, 255, 255, 0.3)' }
449         }
450     },
451     light: {
452         normal: {
453             color: '#333',
454             background: { type: 'linear-gradient', start: '#fff', end:'#ccc' },
455             border: { color: '#999', width: 1, type: 'solid' },
456             textShadow: { offsetX: 0, offsetY: 1, blur: 0, color: '#fff' },
457             boxShadow: { offsetX: 0, offsetY: 1, blur: 0, color: 'rgba(0, 0, 0, 1)' },
458         },
459         active: {
460             color: '#333',
461             background: { type: 'linear-gradient', start: '#ccc', end: '#999' },
462             border: { color: '#666', width: 1, type: 'solid' },
463             textShadow: { offsetX: 0, offsetY: 1, blur: 0, color: '#ccc' },
464             boxShadow: { offsetX: 0, offsetY: 1, blur: 0, color: 'rgba(255, 255, 255, 0.3)' }
465         }
466     },
467     blue: {
468         normal: {
469             color: '#fff',
470             background: { type: 'linear-gradient', start: '#04f', end: '#04c' },
471             border: { color: '#026', width: 1, type: 'solid' },
472             textShadow: { offsetX: 0, offsetY: 1, blur: 0, color: '#666' },
473             boxShadow: { offsetX: 0, offsetY: 1, blur: 0, color: 'rgba(0, 0, 0, 0.5)' }
474         },
475         active: {
476             color: '#ccc',
477             background: { type: 'linear-gradient', start: '#029', end: '#026' },
478             border: { color: '#026', width: 1, type: 'solid' },
479             textShadow: { offsetX: 0, offsetY: 1, blur: 0, color: '#000' },
480             boxShadow: 'none'
481         }
482     }
483 };
484 
485 /**
486  * @scope enchant.ui.MutableText.prototype
487  * @type {*}
488  */
489 enchant.ui.MutableText = enchant.Class.create(enchant.Sprite, {
490     /**
491      *
492      * @usage
493      *     var text = new MutableText(0, 0);
494      *     game.text = 'Hello, world!';
495      *     game.rootScene.addChild(text);
496      *
497      * @constructs
498      * @param posX
499      * @param posY
500      * @param width
501      */
502     initialize: function(x, y, width) {
503         enchant.Sprite.call(this, 0, 0);
504         this.fontSize = 16;
505         this.widthItemNum = 16;
506         this.x = x;
507         this.y = y;
508         this._imageAge = Number.MAX_VALUE;
509         this.text = '';
510         if (arguments[2]) {
511             this.row = Math.floor(arguments[2] / this.fontSize);
512         }
513     },
514     /**
515      * ラベルの内容を書き換える関数
516      * @param txt
517      */
518     setText: function(txt) {
519         var i, x, y, wNum, charCode, charPos;
520         this._text = txt;
521         var newWidth;
522         if (!this.returnLength) {
523             this.width = Math.min(this.fontSize * this._text.length, enchant.Game.instance.width);
524         } else {
525             this.width = Math.min(this.returnLength * this.fontSize, enchant.Game.instance.width);
526         }
527         this.height = this.fontSize * (Math.ceil(this._text.length / this.row) || 1);
528         // if image is to small or was to big for a long time create new image
529         if(!this.image || this.width > this.image.width || this.height > this.image.height || this._imageAge > 300) {
530             this.image = new enchant.Surface(this.width, this.height);
531             this._imageAge = 0;
532         } else if(this.width < this.image.width || this.height < this.image.height) {
533             this._imageAge++;
534         } else {
535             this._imageAge = 0;
536         }
537         this.image.context.clearRect(0, 0, this.width, this.height);
538         for (i = 0; i < txt.length; i++) {
539             charCode = txt.charCodeAt(i);
540             if (charCode >= 32 && charCode <= 127) {
541                 charPos = charCode - 32;
542             } else {
543                 charPos = 0;
544             }
545             x = charPos % this.widthItemNum;
546             y = (charPos / this.widthItemNum) | 0;
547             this.image.draw(enchant.Game.instance.assets['font0.png'],
548                 x * this.fontSize, y * this.fontSize, this.fontSize, this.fontSize,
549                 (i % this.row) * this.fontSize, ((i / this.row) | 0) * this.fontSize, this.fontSize, this.fontSize);
550         }
551     },
552     /**
553      * ラベルの内容
554      * @type {String}
555      */
556     text: {
557         get: function() {
558             return this._text;
559         },
560         set: function(txt) {
561             this.setText(txt);
562         }
563     },
564     /**
565      * @type {Number}
566      */
567     row: {
568         get: function() {
569             return this.returnLength || this.width / this.fontSize;
570         },
571         set: function(row) {
572             this.returnLength = row;
573             this.text = this.text;
574         }
575     }
576 });
577 
578 /**
579  * @scope enchant.ui.ScoreLabel.prototype
580  * @type {*}
581  */
582 enchant.ui.ScoreLabel = enchant.Class.create(enchant.ui.MutableText, {
583     /**
584      * スコアを表示するラベル。
585      * 画像フォントクラス (MutableText) を使って表示する。
586      * @constructs
587      * @param x
588      * @param y
589      */
590     initialize: function(x, y) {
591         enchant.ui.MutableText.call(this, 0, 0);
592         switch (arguments.length) {
593             case 2:
594                 this.y = y;
595                 this.x = x;
596                 break;
597             case 1:
598                 this.x = x;
599                 break;
600             default:
601                 break;
602         }
603         this._score = 0;
604         this._current = 0;
605         this.easing = 2.5;
606         this.text = this.label = 'SCORE:';
607         this.addEventListener('enterframe', function() {
608             if (this.easing === 0) {
609                 this.text = this.label + (this._current = this._score);
610             } else {
611                 var dist = this._score - this._current;
612                 if (0 < dist) {
613                     this._current += Math.ceil(dist / this.easing);
614                 } else if (dist < 0) {
615                     this._current += Math.floor(dist / this.easing);
616                 }
617                 this.text = this.label + this._current;
618             }
619         });
620     },
621     /**
622      * スコア
623      * @type {Number}
624      */
625     score: {
626         get: function() {
627             return this._score;
628         },
629         set: function(newscore) {
630             this._score = newscore;
631         }
632     }
633 });
634 
635 /**
636  * @type {*}
637  * @scope enchant.ui.TimeLabel.prototype
638  */
639 enchant.ui.TimeLabel = enchant.Class.create(enchant.ui.MutableText, {
640     /**
641      * 残り時間などのタイムを表示するラベル
642      * @constructs
643      * @param x
644      * @param y
645      * @param counttype
646      */
647     initialize: function(x, y, counttype) {
648         enchant.ui.MutableText.call(this, 0, 0);
649         switch (arguments.length) {
650             case 3:
651             case 2:
652                 this.y = y;
653                 this.x = x;
654                 break;
655             case 1:
656                 this.x = x;
657                 break;
658             default:
659                 break;
660         }
661         this._time = 0;
662         this._count = 1;// この数を毎フレーム每に足して上げ下げを制御する
663         if (counttype === 'countdown') {
664             this._count = -1;
665         }
666         this.text = this.label = 'TIME:';
667         this.addEventListener('enterframe', function() {
668             this._time += this._count;
669             this.text = this.label + (this._time / enchant.Game.instance.fps).toFixed(2);
670         });
671     },
672     /**
673      * 残り時間
674      * @type {Number}
675      */
676     time: {
677         get: function() {
678             return Math.floor(this._time / enchant.Game.instance.fps);
679         },
680         set: function(newtime) {
681             this._time = newtime * enchant.Game.instance.fps;
682         }
683     }
684 });
685 
686 /**
687  * @type {*}
688  * @scope enchant.ui.LifeLabel.prototype
689  */
690 enchant.ui.LifeLabel = enchant.Class.create(enchant.Group, {
691     /**
692      * ライフを表示する専用のラベル
693      * icon0.png 内のハートの画像を用いる
694      * @constructs
695      * @param x
696      * @param y
697      * @param maxlife
698      */
699     initialize: function(x, y, maxlife) {
700         enchant.Group.call(this);
701         this.x = x || 0;
702         this.y = y || 0;
703         this._maxlife = maxlife || 9;
704         this._life = 0;
705         this.label = new enchant.ui.MutableText(0, 0, 80);
706         this.label.text = 'LIFE:';
707         this.addChild(this.label);
708         this.heart = [];
709         for (var i = 0; i < this._maxlife; i++) {
710             this.heart[i] = new enchant.Sprite(16, 16);
711             this.heart[i].image = enchant.Game.instance.assets['icon0.png'];
712             this.heart[i].x = this.label.width + i * 16;
713             this.heart[i].y = -3;
714             this.heart[i].frame = 10;
715             this.addChild(this.heart[i]);
716         }
717     },
718     /**
719      * 残りライフの数
720      * @type {Number}
721      */
722     life: {
723         get: function() {
724             return this._life;
725         },
726         set: function(newlife) {
727             this._life = newlife;
728             if (this._maxlife < newlife) {
729                 this._life = this._maxlife;
730             }
731             for (var i = 0; i < this._maxlife; i++) {
732                 this.heart[i].visible = (i <= newlife - 1);
733             }
734         }
735     }
736 });
737 
738 /**
739  * @scope enchant.ui.Bar
740  * @type {*}
741  */
742 enchant.ui.Bar = enchant.Class.create(enchant.Sprite, {
743     /**
744      * イージング付きのバークラス
745      * @constructs
746      * @param x
747      * @param y
748      */
749     initialize: function(x, y) {
750         enchant.Sprite.call(this, 1, 16);
751         this.image = new enchant.Surface(1, 16);// Null用
752         this.image.context.fillColor = 'RGB(0, 0, 256)';
753         this.image.context.fillRect(0, 0, 1, 16);
754         this._direction = 'right';
755         this._origin = 0;
756         this._maxvalue = enchant.Game.instance.width;
757         this._lastvalue = 0;
758         this.value = 0;
759         this.easing = 5;
760         switch (arguments.length) {
761             case 2:
762                 this.y = y;
763                 this.x = x;
764                 this._origin = x;
765                 break;
766             case 1:
767                 this.x = x;
768                 this._origin = x;
769                 break;
770             default:
771                 break;
772         }
773         this.addEventListener('enterframe', function() {
774             if (this.value < 0) {
775                 this.value = 0;
776             }
777             this._lastvalue += (this.value - this._lastvalue) / this.easing;
778             if (Math.abs(this._lastvalue - this.value) < 1.3) {
779                 this._lastvalue = this.value;
780             }
781             this.width = (this._lastvalue) | 0;
782             if (this.width > this._maxvalue) {
783                 this.width = this._maxvalue;
784             }
785             if (this._direction === 'left') {
786                 this._x = this._origin - this.width;
787             } else {
788                 this._x = this._origin;
789             }
790             this._updateCoordinate();
791         });
792     },
793     /**
794      * バーの向き ('right' or 'left')
795      * @default 'right'
796      * @type {String}
797      */
798     direction: {
799         get: function() {
800             return this._direction;
801         },
802         set: function(newdirection) {
803             if (newdirection !== 'right' && newdirection !== 'left') {
804                 // ignore
805             } else {
806                 this._direction = newdirection;
807             }
808         }
809     },
810     /**
811      * x 座標
812      * @type {Number}
813      */
814     x: {
815         get: function() {
816             return this._origin;
817         },
818         set: function(x) {
819             this._x = x;
820             this._origin = x;
821             this._dirty = true;
822         }
823     },
824     /**
825      * @type {Number}
826      */
827     maxvalue: {
828         get: function() {
829             return this._maxvalue;
830         },
831         set: function(val) {
832             this._maxvalue = val;
833         }
834     }
835 });
836 
837 /**
838  * @scope enchant.ui.VirtualMap.prototype
839  */
840 enchant.ui.VirtualMap = enchant.Class.create(enchant.Group, {
841     /**
842      * マップライクな Group
843      * addChildで Sprite 等を追加すると、自動的に mx, my プロパティが追加され、
844      * VirtualMap内での座標で Sprite を操作できる
845      *
846      * 使い方
847      * //20 x 20 メッシュの縦横320ピクセルの盤を作り、その上に16 x 16の駒を8つ並べる
848      * var board = new VirtualMap(20, 20);
849      * board.width = 320;
850      * board.height = 320;
851      * for(var i=0; i<8; i++){
852      *     var piece = new Sprite(16, 16);
853      *     piece.image = game.assets['icon0.gif'];
854      *     board.addChild(piece);
855      *     piece.mx = i + 3;
856      *     piece.my = 16;
857      * }
858      * game.rootScene.addChild(board);
859      *
860      * @param meshWidth
861      * @param meshHeight
862      * @constructs
863      */
864     initialize: function(meshWidth, meshHeight) {
865         enchant.Group.call(this);
866         this.meshWidth = meshWidth || 16;
867         this.meshHeight = meshHeight || 16;
868     },
869     /**
870      * VirtualMap にオブジェクトを追加する (自動的にバインドされる)
871      * @param obj
872      */
873     addChild: function(obj) {
874         enchant.Group.prototype.addChild.call(this, obj);
875         this.bind(obj);
876     },
877     /**
878      * VirtualMap にオブジェクトを追加する
879      * reference で指定したオブジェクトより前に追加される (自動的にバインドされる)。
880      * @param obj
881      * @param reference
882      */
883     insertBefore: function(obj, reference) {
884         enchant.Group.prototype.insertBefore.call(this, obj, reference);
885         this.bind(obj);
886     },
887     /**
888      * オブジェクトを VirtualMap にバインドする。
889      * バインドされたオブジェクトはメッシュ座標 mx, my プロパティを持ち、これを操作することで
890      * VirtualMap の中を移動させることができる。
891      * @param obj
892      */
893     bind: function(obj) {
894         Object.defineProperties(obj, {
895             "mx": {
896                 get: function() {
897                     return Math.floor(this.x / this.parentNode.meshWidth);
898                 },
899                 set: function(arg) {
900                     this.x = Math.floor(arg * this.parentNode.meshWidth);
901                 }
902             },
903             "my": {
904                 get: function() {
905                     return Math.floor(this.y / this.parentNode.meshHeight);
906                 },
907                 set: function(arg) {
908                     this.y = Math.floor(arg * this.parentNode.meshWidth);
909                 }
910             }
911         });
912         obj.mx = 0;
913         obj.my = 0;
914     }
915 });
916 
917 function rand(num) {
918     return Math.floor(Math.random() * num);
919 }
920