1 /** 2 * Hilo 3 * Copyright 2015 alibaba.com 4 * Licensed under the MIT License 5 */ 6 7 /** 8 * @language=en 9 * Demo: 10 * <pre> 11 * var stage = new Hilo.Stage({ 12 * renderType:'canvas', 13 * container: containerElement, 14 * width: 320, 15 * height: 480 16 * }); 17 * </pre> 18 * @class Stage is the root of all visual object tree, any visual object will be render only after being added to Stage or any children elements of Stage. Normally, every hilo application start with an stage instance. 19 * @augments Container 20 * @param {Object} properties Properties parameters for the object. Includes all writable properties of this class. Some important like: 21 * <ul> 22 * <li><b>container</b>:String|HTMLElement - Assign the parent container element of the Stage in the page. It should be a dom container or an id. If this parameter is not given and canvas isn't in the dom tree, you should add the stage vanvas into the dom tree yourself, otherwise Stage will not render. optional.</li> 23 * <li><b>renderType</b>:String - Renering way: canvas|dom|webgl,default value is canvas, optional.</li> 24 * <li><b>canvas</b>:String|HTMLCanvasElement|HTMLElement - 指定舞台所对应的画布元素。它是一个canvas或普通的div,也可以传入元素的id。若为canvas,则使用canvas来渲染所有对象,否则使用dom+css来渲染。可选。</li> 25 * <li><b>width</b>:Number</li> - The width of the Stage, default value is the width of canvas, optional. 26 * <li><b>height</b>:Number</li> - The height of the Stage, default value is the height of canvas, optional. 27 * <li><b>paused</b>:Boolean</li> - Whether stop rendering the Stage, default value is false, optional. 28 * </ul> 29 * @module hilo/view/Stage 30 * @requires hilo/core/Hilo 31 * @requires hilo/core/Class 32 * @requires hilo/view/Container 33 * @requires hilo/renderer/CanvasRenderer 34 * @requires hilo/renderer/DOMRenderer 35 * @requires hilo/renderer/WebGLRenderer 36 * @property {HTMLCanvasElement|HTMLElement} canvas The canvas the Stage is related to. It can be a canvas or a div element, readonly! 37 * @property {Renderer} renderer Stage renderer, readonly! 38 * @property {Boolean} paused Paused Stage rendering. 39 * @property {Object} viewport Rendering area of the Stage. Including properties like: left, top, width, height. readonly! 40 */ 41 var Stage = Class.create(/** @lends Stage.prototype */{ 42 Extends: Container, 43 constructor: function(properties){ 44 properties = properties || {}; 45 this.id = this.id || properties.id || Hilo.getUid('Stage'); 46 Stage.superclass.constructor.call(this, properties); 47 48 this._initRenderer(properties); 49 50 //init size 51 var width = this.width, height = this.height, 52 viewport = this.updateViewport(); 53 if(!properties.width) width = (viewport && viewport.width) || 320; 54 if(!properties.height) height = (viewport && viewport.height) || 480; 55 this.resize(width, height, true); 56 }, 57 58 canvas: null, 59 renderer: null, 60 paused: false, 61 viewport: null, 62 63 /** 64 * @language=en 65 * @private 66 */ 67 _initRenderer: function(properties){ 68 var canvas = properties.canvas; 69 var container = properties.container; 70 var renderType = properties.renderType||'canvas'; 71 72 if(typeof canvas === 'string') canvas = Hilo.getElement(canvas); 73 if(typeof container === 'string') container = Hilo.getElement(container); 74 75 if(!canvas){ 76 var canvasTagName = renderType === 'dom'?'div':'canvas'; 77 canvas = Hilo.createElement(canvasTagName, { 78 style: { 79 position: 'absolute' 80 } 81 }); 82 } 83 else if(!canvas.getContext){ 84 renderType = 'dom'; 85 } 86 87 this.canvas = canvas; 88 if(container) container.appendChild(canvas); 89 90 var props = {canvas:canvas, stage:this}; 91 switch(renderType){ 92 case 'dom': 93 this.renderer = new DOMRenderer(props); 94 break; 95 case 'webgl': 96 if(WebGLRenderer.isSupported){ 97 this.renderer = new WebGLRenderer(props); 98 } 99 else{ 100 this.renderer = new CanvasRenderer(props); 101 } 102 break; 103 case 'canvas': 104 default: 105 this.renderer = new CanvasRenderer(props); 106 break; 107 } 108 }, 109 110 /** 111 * @language=en 112 * Add Stage canvas to DOM container. Note: this function overwrite View.addTo function. 113 * @param {HTMLElement} domElement An dom element. 114 * @returns {Stage} The Stage Object, chained call supported. 115 */ 116 addTo: function(domElement){ 117 var canvas = this.canvas; 118 if(canvas.parentNode !== domElement){ 119 domElement.appendChild(canvas); 120 } 121 return this; 122 }, 123 124 /** 125 * @language=en 126 * Invoke tick function and Stage will update and render. Developer may not need to use this funciton. 127 * @param {Number} delta The time had pass between this tick invoke and last tick invoke. 128 */ 129 tick: function(delta){ 130 if(!this.paused){ 131 this._render(this.renderer, delta); 132 } 133 }, 134 135 /** 136 * @language=en 137 * Turn on/off Stage response to DOM event. To make visual objects on the Stage interactive, use this function to turn on Stage's responses to events. 138 * @param {String|Array} type The event name or array that need to turn on/off. 139 * @param {Boolean} enabled Whether turn on or off the response method of stage DOM event. If not provided, default value is true. 140 * @returns {Stage} The Stage Object, chained call supported. 141 */ 142 enableDOMEvent: function(type, enabled){ 143 var me = this, 144 canvas = me.canvas, 145 types = typeof type === 'string' ? [type] : type, 146 enabled = enabled !== false, 147 handler = me._domListener || (me._domListener = function(e){me._onDOMEvent(e)}); 148 149 for(var i = 0; i < types.length; i++){ 150 var type = types[i]; 151 152 if(enabled){ 153 canvas.addEventListener(type, handler, false); 154 }else{ 155 canvas.removeEventListener(type, handler); 156 } 157 } 158 159 return me; 160 }, 161 162 /** 163 * @language=en 164 * DOM events handler function. This funciton will invoke events onto the visual object, which is on the position of the coordinate where the events is invoked. 165 * @private 166 */ 167 _onDOMEvent: function(e){ 168 var type = e.type, event = e, isTouch = type.indexOf('touch') == 0; 169 170 //calculate stageX/stageY 171 var posObj = e; 172 if(isTouch){ 173 var touches = e.touches, changedTouches = e.changedTouches; 174 posObj = (touches && touches.length) ? touches[0] : 175 (changedTouches && changedTouches.length) ? changedTouches[0] : null; 176 } 177 178 var x = posObj.pageX || posObj.clientX, y = posObj.pageY || posObj.clientY, 179 viewport = this.viewport || this.updateViewport(); 180 181 event.stageX = x = (x - viewport.left) / this.scaleX; 182 event.stageY = y = (y - viewport.top) / this.scaleY; 183 184 //鼠标事件需要阻止冒泡方法 Prevent bubbling on mouse events. 185 event.stopPropagation = function(){ 186 this._stopPropagationed = true; 187 }; 188 189 var obj = this.getViewAtPoint(x, y, true, false, true)||this, 190 canvas = this.canvas, target = this._eventTarget; 191 192 //fire mouseout/touchout event for last event target 193 var leave = type === 'mouseout'; 194 //当obj和target不同 且obj不是target的子元素时才触发out事件 fire out event when obj and target isn't the same as well as obj is not a child element to target. 195 if(target && (target != obj && (!target.contains || !target.contains(obj))|| leave)){ 196 var out = (type === 'touchmove') ? 'touchout' : 197 (type === 'mousemove' || leave || !obj) ? 'mouseout' : null; 198 if(out) { 199 var outEvent = Hilo.copy({}, event); 200 outEvent.type = out; 201 outEvent.eventTarget = target; 202 target._fireMouseEvent(outEvent); 203 } 204 event.lastEventTarget = target; 205 this._eventTarget = null; 206 } 207 208 //fire event for current view 209 if(obj && obj.pointerEnabled && type !== 'mouseout'){ 210 event.eventTarget = this._eventTarget = obj; 211 obj._fireMouseEvent(event); 212 } 213 214 //set cursor for current view 215 if(!isTouch){ 216 var cursor = (obj && obj.pointerEnabled && obj.useHandCursor) ? 'pointer' : ''; 217 canvas.style.cursor = cursor; 218 } 219 220 //fix android: `touchmove` fires only once 221 if(Hilo.browser.android && type === 'touchmove'){ 222 e.preventDefault(); 223 } 224 }, 225 226 /** 227 * @language=en 228 * Update the viewport (rendering area) which Stage show on the page. Invoke this function to update viewport when Stage canvas changes border, margin or padding properties. 229 * @returns {Object} The visible area of the Stage (the viewport property). 230 */ 231 updateViewport: function(){ 232 var canvas = this.canvas, viewport = null; 233 if(canvas.parentNode){ 234 viewport = this.viewport = Hilo.getElementRect(canvas); 235 } 236 return viewport; 237 }, 238 239 /** 240 * @language=en 241 * Resize the Stage. 242 * @param {Number} width The width of the new Stage. 243 * @param {Number} height The height of the new Stage. 244 * @param {Boolean} forceResize Whether forced to resize the Stage, means no matter the size of the Stage, force to change the size to keep Stage, canvas and window act at the same time. 245 */ 246 resize: function(width, height, forceResize){ 247 if(forceResize || this.width !== width || this.height !== height){ 248 this.width = width; 249 this.height = height; 250 this.renderer.resize(width, height); 251 this.updateViewport(); 252 } 253 } 254 255 }); 256