2018-08-08 15:57:32 +08:00

472 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Hilo
* Copyright 2015 alibaba.com
* Licensed under the MIT License
*/
/**
* @class View类是所有可视对象或组件的基类。
* @mixes EventMixin
* @borrows EventMixin#on as #on
* @borrows EventMixin#off as #off
* @borrows EventMixin#fire as #fire
* @param {Object} properties 创建对象的属性参数。可包含此类所有可写属性。
* @module hilo/view/View
* @requires hilo/core/Hilo
* @requires hilo/core/Class
* @requires hilo/event/EventMixin
* @requires hilo/geom/Matrix
* @requires hilo/util/util
* @property {String} id 可视对象的唯一标识符。
* @property {Number} x 可视对象的x轴坐标。默认值为0。
* @property {Number} y 可视对象的y轴坐标。默认值为0。
* @property {Number} width 可视对象的宽度。默认值为0。
* @property {Number} height 可视对象的高度。默认值为0。
* @property {Number} alpha 可视对象的透明度。默认值为1。
* @property {Number} rotation 可视对象的旋转角度。默认值为0。
* @property {Boolean} visible 可视对象是否可见。默认为可见即true。
* @property {Number} pivotX 可视对象的中心点的x轴坐标。默认值为0。
* @property {Number} pivotY 可视对象的中心点的y轴坐标。默认值为0。
* @property {Number} scaleX 可视对象在x轴上的缩放比例。默认为不缩放即1。
* @property {Number} scaleY 可视对象在y轴上的缩放比例。默认为不缩放即1。
* @property {Matrix} transform 可视对象的transform属性如果设置将忽略x, y, scaleX, scaleY, rotation. pivotX, pivotY 属性。默认null。
* @property {Boolean} pointerEnabled 可视对象是否接受交互事件。默认为接受交互事件即true。
* @property {Object} background 可视对象的背景样式。可以是CSS颜色值、canvas的gradient或pattern填充。
* @property {Graphics} mask 可视对象的遮罩图形。
* @property {Number} tint 可视对象的附加颜色默认0xFFFFFF只支持WebGL模式。
* @property {String|Function} align 可视对象相对于父容器的对齐方式。取值可查看Hilo.align枚举对象。
* @property {Container} parent 可视对象的父容器。只读属性。
* @property {Number} depth 可视对象的深度也即z轴的序号。只读属性。
* @property {Drawable} drawable 可视对象的可绘制对象。供高级开发使用。
* @property {Array} boundsArea 可视对象的区域顶点数组。格式为:[{x:10, y:10}, {x:20, y:20}]。
*/
var View = (function(){
return Class.create(/** @lends View.prototype */{
Mixes: EventMixin,
constructor: function(properties){
properties = properties || {};
this.id = this.id || properties.id || Hilo.getUid("View");
util.copy(this, properties, true);
},
tint:0xffffff,
id: null,
x: 0,
y: 0,
width: 0,
height: 0,
alpha: 1,
rotation: 0,
visible: true,
pivotX: 0,
pivotY: 0,
scaleX: 1,
scaleY: 1,
pointerEnabled: true,
background: null,
mask: null,
align: null,
drawable: null,
boundsArea: null,
parent: null,
depth: -1,
transform: null,
blendMode:'source-over',
/**
* 返回可视对象的舞台引用。若对象没有被添加到舞台则返回null。
* @returns {Stage} 可视对象的舞台引用。
*/
getStage: function(){
var obj = this, parent;
while(parent = obj.parent) obj = parent;
//NOTE: don't use `instanceof` to prevent circular module requirement.
//But it's not a very reliable way to check it's a stage instance.
if(obj.canvas) return obj;
return null;
},
/**
* 返回可视对象缩放后的宽度。
* @returns {Number} 可视对象缩放后的宽度。
*/
getScaledWidth: function(){
return this.width * this.scaleX;
},
/**
* 返回可视对象缩放后的高度。
* @returns {Number} 可视对象缩放后的高度。
*/
getScaledHeight: function(){
return this.height * this.scaleY;
},
/**
* 添加此对象到父容器。
* @param {Container} container 一个容器。
* @param {Uint} index 要添加到索引位置。
* @returns {View} 可视对象本身。
*/
addTo: function(container, index){
if(typeof index === 'number') container.addChildAt(this, index);
else container.addChild(this);
return this;
},
/**
* 从父容器里删除此对象。
* @returns {View} 可视对象本身。
*/
removeFromParent: function(){
var parent = this.parent;
if(parent) parent.removeChild(this);
return this;
},
/**
* 获取可视对象在舞台全局坐标系内的外接矩形以及所有顶点坐标。
* @returns {Array} 可视对象的顶点坐标数组vertexs。另vertexs还包含属性
* <ul>
* <li><b>x</b> - 可视对象的外接矩形x轴坐标。</li>
* <li><b>y</b> - 可视对象的外接矩形y轴坐标。</li>
* <li><b>width</b> - 可视对象的外接矩形的宽度。</li>
* <li><b>height</b> - 可视对象的外接矩形的高度。</li>
* </ul>
*/
getBounds: function(){
var w = this.width, h = this.height,
mtx = this.getConcatenatedMatrix(),
poly = this.boundsArea || [{x:0, y:0}, {x:w, y:0}, {x:w, y:h}, {x:0, y:h}],
vertexs = [], point, x, y, minX, maxX, minY, maxY;
for(var i = 0, len = poly.length; i < len; i++){
point = mtx.transformPoint(poly[i], true, true);
x = point.x;
y = point.y;
if(i == 0){
minX = maxX = x;
minY = maxY = y;
}else{
if(minX > x) minX = x;
else if(maxX < x) maxX = x;
if(minY > y) minY = y;
else if(maxY < y) maxY = y;
}
vertexs[i] = point;
}
vertexs.x = minX;
vertexs.y = minY;
vertexs.width = maxX - minX;
vertexs.height = maxY - minY;
return vertexs;
},
/**
* 获取可视对象相对于其某个祖先(默认为最上层容器)的连接矩阵。
* @param {View} ancestor 可视对象的相对的祖先容器。
* @private
*/
getConcatenatedMatrix: function(ancestor){
var mtx = new Matrix(1, 0, 0, 1, 0, 0);
for(var o = this; o != ancestor && o.parent; o = o.parent){
var cos = 1, sin = 0,
rotation = o.rotation % 360,
pivotX = o.pivotX, pivotY = o.pivotY,
scaleX = o.scaleX, scaleY = o.scaleY,
transform = o.transform;
if(transform) {
mtx.concat(transform);
}
else{
if(rotation){
var r = rotation * Math.PI / 180;
cos = Math.cos(r);
sin = Math.sin(r);
}
if(pivotX != 0) mtx.tx -= pivotX;
if(pivotY != 0) mtx.ty -= pivotY;
var pos = o.getAlignPosition();
mtx.concat(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, pos.x, pos.y);
}
}
return mtx;
},
getAlignPosition: function(){
var parent = this.parent;
var align = this.align;
var x = this.x;
var y = this.y;
if(parent && this.align){
if(typeof align === 'function'){
return this.align();
}
var w = this.width, h = this.height,
pw = parent.width, ph = parent.height;
switch(align){
case 'TL':
x = 0;
y = 0;
break;
case 'T':
x = pw - w >> 1;
y = 0;
break;
case 'TR':
x = pw - w;
y = 0;
break;
case 'L':
x = 0;
y = ph - h >> 1;
break;
case 'C':
x = pw - w >> 1;
y = ph - h >> 1;
break;
case 'R':
x = pw - w;
y = ph - h >> 1;
break;
case 'BL':
x = 0;
y = ph - h;
break;
case 'B':
x = pw - w >> 1;
y = ph - h;
break;
case 'BR':
x = pw - w;
y = ph - h;
break;
}
}
return {
x:x,
y:y
};
},
/**
* 检测由x和y参数指定的点是否在其外接矩形之内。
* @param {Number} x 要检测的点的x轴坐标。
* @param {Number} y 要检测的点的y轴坐标。
* @param {Boolean} usePolyCollision 是否使用多边形碰撞检测。默认为false。
* @returns {Boolean} 点是否在可视对象之内。
*/
hitTestPoint: function(x, y, usePolyCollision){
var bound = this.getBounds(),
hit = x >= bound.x && x <= bound.x + bound.width &&
y >= bound.y && y <= bound.y + bound.height;
if(hit && usePolyCollision){
hit = pointInPolygon(x, y, bound);
}
return hit;
},
/**
* 检测object参数指定的对象是否与其相交。
* @param {View} object 要检测的可视对象。
* @param {Boolean} usePolyCollision 是否使用多边形碰撞检测。默认为false。
*/
hitTestObject: function(object, usePolyCollision){
var b1 = this.getBounds(),
b2 = object.getBounds(),
hit = b1.x <= b2.x + b2.width && b2.x <= b1.x + b1.width &&
b1.y <= b2.y + b2.height && b2.y <= b1.y + b1.height;
if(hit && usePolyCollision){
hit = polygonCollision(b1, b2);
}
return !!hit;
},
/**
* 可视对象的基本渲染实现用于框架内部或高级开发使用。通常应该重写render方法。
* @param {Renderer} renderer 渲染器。
* @param {Number} delta 渲染时时间偏移量。
* @protected
*/
_render: function(renderer, delta){
if((!this.onUpdate || this.onUpdate(delta) !== false) && renderer.startDraw(this)){
renderer.transform(this);
this.render(renderer, delta);
renderer.endDraw(this);
}
},
/**
* 冒泡鼠标事件
*/
_fireMouseEvent:function(e){
e.eventCurrentTarget = this;
this.fire(e);
// 处理mouseover事件 mouseover不需要阻止冒泡
// handle mouseover event, mouseover needn't stop propagation.
if(e.type == "mousemove"){
if(!this.__mouseOver){
this.__mouseOver = true;
var overEvent = util.copy({}, e);
overEvent.type = "mouseover";
this.fire(overEvent);
}
}
else if(e.type == "mouseout"){
this.__mouseOver = false;
}
// 向上冒泡
// handle event propagation
var parent = this.parent;
if(!e._stopped && !e._stopPropagationed && parent){
if(e.type == "mouseout" || e.type == "touchout"){
if(!parent.hitTestPoint(e.stageX, e.stageY, true)){
parent._fireMouseEvent(e);
}
}
else{
parent._fireMouseEvent(e);
}
}
},
/**
* 更新可视对象此方法会在可视对象渲染之前调用。此函数可以返回一个Boolean值。若返回false则此对象不会渲染。默认值为null。
* 限制如果在此函数中改变了可视对象在其父容器中的层级当前渲染帧并不会正确渲染而是在下一渲染帧。可在其父容器的onUpdate方法中来实现。
* @type Function
* @default null
*/
onUpdate: null,
/**
* 可视对象的具体渲染逻辑。子类可通过覆盖此方法实现自己的渲染。
* @param {Renderer} renderer 渲染器。
* @param {Number} delta 渲染时时间偏移量。
*/
render: function(renderer, delta){
renderer.draw(this);
},
/**
* 返回可视对象的字符串表示。
* @returns {String} 可视对象的字符串表示。
*/
toString: function(){
return Hilo.viewToString(this);
}
});
/**
* @private
*/
function pointInPolygon(x, y, poly){
var cross = 0, onBorder = false, minX, maxX, minY, maxY;
for(var i = 0, len = poly.length; i < len; i++){
var p1 = poly[i], p2 = poly[(i+1)%len];
if(p1.y == p2.y && y == p1.y){
p1.x > p2.x ? (minX = p2.x, maxX = p1.x) : (minX = p1.x, maxX = p2.x);
if(x >= minX && x <= maxX){
onBorder = true;
continue;
}
}
p1.y > p2.y ? (minY = p2.y, maxY = p1.y) : (minY = p1.y, maxY = p2.y);
if(y < minY || y > maxY) continue;
var nx = (y - p1.y)*(p2.x - p1.x) / (p2.y - p1.y) + p1.x;
if(nx > x) cross++;
else if(nx == x) onBorder = true;
//当射线和多边形相交
if(p1.x > x && p1.y == y){
var p0 = poly[(len+i-1)%len];
//当交点的两边在射线两旁
if(p0.y < y && p2.y > y || p0.y > y && p2.y < y){
cross ++;
}
}
}
return onBorder || (cross % 2 == 1);
}
/**
* @private
*/
function polygonCollision(poly1, poly2){
var result = doSATCheck(poly1, poly2, {overlap:-Infinity, normal:{x:0, y:0}});
if(result) return doSATCheck(poly2, poly1, result);
return false;
}
/**
* @private
*/
function doSATCheck(poly1, poly2, result){
var len1 = poly1.length, len2 = poly2.length,
currentPoint, nextPoint, distance,
min1, max1, min2, max2, dot, overlap, normal = {x:0, y:0};
for(var i = 0; i < len1; i++){
currentPoint = poly1[i];
nextPoint = poly1[(i < len1-1 ? i+1 : 0)];
normal.x = currentPoint.y - nextPoint.y;
normal.y = nextPoint.x - currentPoint.x;
distance = Math.sqrt(normal.x * normal.x + normal.y * normal.y);
normal.x /= distance;
normal.y /= distance;
min1 = max1 = poly1[0].x * normal.x + poly1[0].y * normal.y;
for(var j = 1; j < len1; j++){
dot = poly1[j].x * normal.x + poly1[j].y * normal.y;
if(dot > max1) max1 = dot;
else if(dot < min1) min1 = dot;
}
min2 = max2 = poly2[0].x * normal.x + poly2[0].y * normal.y;
for(j = 1; j < len2; j++){
dot = poly2[j].x * normal.x + poly2[j].y * normal.y;
if(dot > max2) max2 = dot;
else if(dot < min2) min2 = dot;
}
if(min1 < min2){
overlap = min2 - max1;
normal.x = -normal.x;
normal.y = -normal.y;
}else{
overlap = min1 - max2;
}
if(overlap >= 0){
return false;
}else if(overlap > result.overlap){
result.overlap = overlap;
result.normal.x = normal.x;
result.normal.y = normal.y;
}
}
return result;
}
})();