1 
  2 /**
  3  * @class 舞台是可视对象树的根,可视对象只有添加到舞台或其子对象后才会被渲染出来。
  4  * @augments Container
  5  * @param {Object} properties 创建对象的属性参数。可包含此类所有可写属性。
  6  * @module hilo/view/Stage
  7  * @requires hilo/core/Hilo
  8  * @requires hilo/core/Class
  9  * @requires hilo/view/Container
 10  * @requires hilo/renderer/CanvasRenderer
 11  * @property {HTMLElement} canvas 舞台所对应的画布。它可以是一个canvas或一个普通的div。只读属性。
 12  * @property {Renderer} renderer 舞台渲染器。只读属性。
 13  * @property {Boolean} paused 指示舞台是否暂停刷新渲染。 
 14  * @property {Object} viewport 舞台内容在页面中的渲染区域。包含的属性有:left、top、width、height。只读属性。
 15  */
 16 var Stage = Class.create(/** @lends Stage.prototype */{
 17     Extends: Container,
 18     constructor: function Stage(properties){
 19         properties = properties || {};
 20         this.id = this.id || properties.id || Hilo.getUid('Stage');
 21         Stage.superclass.constructor.call(this, properties);
 22 
 23         //init canvas
 24         var canvas = this.canvas;
 25         if(typeof canvas === 'string') canvas = Hilo.getElement(canvas);
 26         if(!canvas){
 27             canvas = Hilo.createElement('canvas', {
 28                 style: {
 29                     position: 'absolute'
 30                 }
 31             });
 32         }
 33         this.canvas = canvas;
 34 
 35         if(canvas.parentNode) this.updateViewport();
 36 
 37         if(!properties.width) this.width = this.viewport.width || 320;
 38         if(!properties.height) this.height = this.viewport.height || 480;
 39         
 40         var isCanvas = canvas.getContext;
 41         if(isCanvas){
 42             canvas.width = this.width;
 43             canvas.height = this.height;
 44         }
 45 
 46         //init renderer
 47         var props = {canvas:canvas, stage:this};
 48         this.renderer = isCanvas ? new CanvasRenderer(props) : new DOMRenderer(props);
 49     },
 50 
 51     canvas: null,
 52     renderer: null,
 53     paused: false,
 54     viewport: null,
 55 
 56     /**
 57      * 调用tick会触发舞台的更新和渲染。
 58      */
 59     tick: function(delta){
 60         if(!this.paused){
 61             this._render(this.renderer, delta);
 62         }
 63     },
 64 
 65     /**
 66      * 开启/关闭DOM事件功能。
 67      */
 68     enableDOMEvent: function(type, enable){
 69         var me = this,
 70             canvas = me.canvas,
 71             types = typeof type === 'string' ? [type] : type,
 72             handler = me._domListener || (me._domListener = function(e){me._onDOMEvent(e)});
 73 
 74         for(var i = 0; i < types.length; i++){
 75             var type = types[i];
 76             if(enable === false){
 77                 canvas.removeEventListener(type, handler);   
 78             }else{
 79                 canvas.addEventListener(type, handler, false);
 80             }
 81         }
 82     },
 83 
 84     /**
 85      * DOM事件处理函数。此方法会把事件调度到事件的坐标点所对应的可视对象。
 86      * @private
 87      */
 88     _onDOMEvent: function(e){
 89         var type = e.type, event = e, isTouch = type.indexOf('touch') == 0;
 90 
 91         //calculate stageX/stageY
 92         var posObj = e;
 93         if(isTouch){
 94             var touches = e.touches, changedTouches = e.changedTouches;
 95             posObj = (touches && touches.length) ? touches[0] : 
 96                      (changedTouches && changedTouches.length) ? changedTouches[0] : null;
 97         }
 98 
 99         var x = posObj.pageX || posObj.clientX, y = posObj.pageY || posObj.clientY, 
100             viewport = this.viewport || this.updateViewport();
101 
102         event.stageX = x = (x - viewport.left) / this.scaleX;
103         event.stageY = y = (y - viewport.top) / this.scaleY;
104         
105         var obj = this.getViewAtPoint(x, y, true, false, true),
106             canvas = this.canvas, target = this._eventTarget;
107             
108         //fire mouseout/touchout event for last event target
109         var leave = type === 'mouseout' && !canvas.contains(e.relatedTarget); 
110         if(target && (target != obj || leave)){
111             var out = (type === 'touchmove') ? 'touchout' : 
112                       (type === 'mousemove' || leave || !obj) ? 'mouseout' : null;
113             if(out) target.fire(out);
114             event.lastEventTarget = target;
115             this._eventTarget = null;
116         }
117         
118         //fire event for current view
119         if(obj && obj.pointerEnabled && type !== 'mouseout'){
120             event.eventTarget = this._eventTarget = obj;
121             obj.fire(event);
122         }
123         
124         //set cursor for current view
125         if(!isTouch){
126             var cursor = (obj && obj.pointerEnabled && obj.useHandCursor) ? 'pointer' : '';
127             canvas.style.cursor = cursor;
128         }
129 
130         //fire event for stage
131         if(leave || type !== "mouseout") this.fire(event);
132 
133         //fix android: `touchmove` fires only once
134         if(Hilo.browser.android && type === 'touchmove'){
135             e.preventDefault();
136         }
137     },
138 
139     /**
140      * 更新舞台在页面中的渲染区域。当舞台canvas的样式border、margin、padding等属性更改后,需要调用此方法更新舞台渲染区域。
141      */
142     updateViewport: function(){
143         return this.viewport = Hilo.getElementRect(this.canvas);
144     }
145 
146 });