ycc.js
Version:
Mini and powerful canvas engine for creating App or Game.
7,956 lines • 193 kB
JavaScript
/**
* @file Ycc.class.js
* @author xiaohei
* @date 2017/9/30
* @description Ycc.class文件
*
*/
/**
* 应用启动入口类,每个实例都与一个canvas绑定。
* 该canvas元素会被添加至HTML结构中,作为应用的显示舞台。
* @param config {Object} 整个ycc的配置项
* @param config.debugDrawContainer {Boolean} 是否显示所有UI的容纳区域
* @param config.useGesture {Boolean} 是否启用系统的手势库,默认启用
* @constructor
*/
var Ycc = function Ycc(config){
/**
* 绘图环境
* @type {CanvasRenderingContext2D}
*/
this.ctx = null;
/**
* 与ycc绑定的canvas元素
* @type {null}
*/
this.canvasDom = null;
/**
* Layer对象数组。包含所有的图层
* @type {Array}
*/
this.layerList = [];
/**
* 实例的快照管理模块
* @type {Ycc.PhotoManager}
*/
this.photoManager = null;
/**
* ycc的图层管理器
* @type {null}
*/
this.layerManager = null;
/**
* 系统心跳管理器
*/
this.ticker = null;
/**
* 调试模块
* @type {null}
*/
this.debugger = null;
/**
* 资源加载器
* @type {Ycc.Loader}
*/
this.loader = new Ycc.Loader();
/**
* 异步请求的封装
* @type {Ycc.Ajax}
*/
this.ajax = new Ycc.Ajax();
/**
* 基础绘图UI。这些绘图操作会直接作用于舞台。
* @type {Ycc.UI}
*/
this.baseUI = null;
/**
* 整个ycc的配置项
* @type {*|{}}
*/
this.config = config || {
debugDrawContainer:false,
useGesture:true
};
/**
* 是否移动端
* @type {boolean}
*/
this.isMobile = Ycc.utils.isMobile();
this.stageW = 0;
this.stageH = 0;
};
/**
* 获取舞台的宽
*/
Ycc.prototype.getStageWidth = function () {
return this.canvasDom.width;
};
/**
* 获取舞台的高
*/
Ycc.prototype.getStageHeight = function () {
return this.canvasDom.height;
};
/**
* 绑定canvas元素,一个canvas绑定一个ycc实例
* @param canvas
* @return {Ycc}
*/
Ycc.prototype.bindCanvas = function (canvas) {
this.canvasDom = canvas;
this.ctx = canvas.getContext('2d');
this.layerList = [];
this.photoManager = new Ycc.PhotoManager(this);
this.layerManager = new Ycc.LayerManager(this);
this.ticker = new Ycc.Ticker(this);
this.debugger = new Ycc.Debugger(this);
this.baseUI = new Ycc.UI(this);
this.init();
return this;
};
/**
* 类初始化
*/
Ycc.prototype.init = function () {
if(true===this.config.useGesture)
this._initStageGestureEvent();
};
/**
*
* 需求实现:初始化舞台的手势事件监听器
* 1、事件传递给所有图层
* 2、事件传递给最上层UI
* 3、pc端和移动端统一,pc端视为一个触摸点,而移动端可以存在多个触摸点
* 4、dragging、dragend事件,的最上层UI为dragstart时所指的UI
* 5、所有鼠标事件均由舞台转发,转发的坐标均为绝对坐标。
* `layer`和`ui`可以调用各自的`transformToLocal`方法,将绝对坐标转换为自己的相对坐标。
*
* @private
*/
Ycc.prototype._initStageGestureEvent = function () {
var self = this;
// 鼠标/触摸点开始拖拽时,所指向的UI对象,只用于单个触摸点的情况
//var dragstartUI = null;
// 鼠标/触摸点开始拖拽时,所指向的UI对象map,用于多个触摸点的情况
var dragstartUIMap = {};
var gesture = new Ycc.Gesture({target:this.ctx.canvas});
gesture.addListener('tap',gestureListener);
gesture.addListener('longtap',gestureListener);
gesture.addListener('doubletap',gestureListener);
gesture.addListener('swipeleft',gestureListener);
gesture.addListener('swiperight',gestureListener);
gesture.addListener('swipeup',gestureListener);
gesture.addListener('swipedown',gestureListener);
gesture.addListener('dragstart',dragstartListener);
gesture.addListener('dragging',draggingListener);
gesture.addListener('dragend',dragendListener);
// PC端专属事件
gesture.addListener('mousemove',gestureListener);
function dragstartListener(e) {
// 在canvas中的绝对位置
var x = parseInt(e.clientX - self.ctx.canvas.getBoundingClientRect().left),
y = parseInt(e.clientY - self.ctx.canvas.getBoundingClientRect().top);
var dragstartUI = self.getUIFromPointer(new Ycc.Math.Dot(x,y));
dragstartUI&&(dragstartUIMap[e.identifier]=dragstartUI);
// dragstartUI&&dragstartUI.belongTo.enableEventManager&&triggerUIEvent(e.type,x,y,dragstartUI);
triggerUIEventBubbleUp(e.type,x,y,dragstartUI);
triggerLayerEvent(e.type,x,y);
}
function draggingListener(e) {
// 在canvas中的绝对位置
var x = parseInt(e.clientX - self.ctx.canvas.getBoundingClientRect().left),
y = parseInt(e.clientY - self.ctx.canvas.getBoundingClientRect().top);
var dragstartUI = dragstartUIMap[e.identifier];
// dragstartUI&&dragstartUI.belongTo.enableEventManager&&triggerUIEvent(e.type,x,y,dragstartUI);
triggerUIEventBubbleUp(e.type,x,y,dragstartUI);
triggerLayerEvent(e.type,x,y);
}
function dragendListener(e) {
// 在canvas中的绝对位置
var x = parseInt(e.clientX - self.ctx.canvas.getBoundingClientRect().left),
y = parseInt(e.clientY - self.ctx.canvas.getBoundingClientRect().top);
var dragstartUI = dragstartUIMap[e.identifier];
triggerUIEventBubbleUp(e.type,x,y,dragstartUI);
triggerLayerEvent(e.type,x,y);
// wx端的一个bug
if (dragstartUI){
// dragstartUI.belongTo.enableEventManager && triggerUIEvent(e.type, x, y, dragstartUI);
dragstartUI = null;
dragstartUIMap[e.identifier]=null;
}
}
// 通用监听
function gestureListener(e) {
// console.log(e);
// 在canvas中的绝对位置
var x = parseInt(e.clientX - self.ctx.canvas.getBoundingClientRect().left),
y = parseInt(e.clientY - self.ctx.canvas.getBoundingClientRect().top);
var ui = self.getUIFromPointer(new Ycc.Math.Dot(x,y));
// triggerLayerEvent(e.type,x,y);
// ui&&ui.belongTo.enableEventManager&&triggerUIEvent(e.type,x,y,ui);
triggerUIEventBubbleUp(e.type,x,y,ui);
triggerLayerEvent(e.type,x,y);
}
function triggerLayerEvent(type,x,y){
for(var i=self.layerList.length-1;i>=0;i--){
var layer = self.layerList[i];
if(!layer.enableEventManager) continue;
layer.enableEventManager&&layer.triggerListener(type,new Ycc.Event({
type:type,
x:x,
y:y
}));
}
}
function triggerUIEvent(type,x,y,ui){
ui.triggerListener(type,new Ycc.Event({
x:x,
y:y,
type:type,
target:ui
}));
}
/**
* 冒泡触发UI的事件
* @param type
* @param x
* @param y
* @param ui
* @return {null}
*/
function triggerUIEventBubbleUp(type,x,y,ui) {
if(ui && ui.belongTo.enableEventManager){
// 触发ui的事件
ui.triggerListener(type,new Ycc.Event({x:x,y:y,type:type,target:ui}));
// 如果ui阻止了事件冒泡,则不触发其父级的事件
if(ui.stopEventBubbleUp) return;
// 触发父级ui的事件
ui.getParentList().reverse().forEach(function (fa) {
fa.triggerListener(type,new Ycc.Event({x:x,y:y,type:type,target:fa}));
});
}
}
};
/**
* 清除
*/
Ycc.prototype.clearStage = function () {
this.ctx.clearRect(0,0,this.getStageWidth(),this.getStageHeight());
};
/**
* 根据id查找图层
* @param id 图层id
* @return {Ycc.Layer}
*/
Ycc.prototype.findLayerById = function (id) {
for(var i =0;i<this.layerList.length;i++){
var layer = this.layerList[i];
if(layer.id===id)
return layer;
}
return null;
};
/**
* 根据id查找UI
* @param id UI的id
* @return {Ycc.UI}
*/
Ycc.prototype.findUiById = function (id) {
for(var i =0;i<this.layerList.length;i++){
var layer = this.layerList[i];
for(var j=0;j<layer.uiList.length;j++){
var ui = layer.uiList[j];
if(ui.id===id)
return ui;
}
}
return null;
};
/**
* 获取舞台中某个点所对应的最上层UI。
* @param dot {Ycc.Math.Dot} 点坐标,为舞台的绝对坐标
* @param uiIsShow {Boolean} 是否只获取显示在舞台上的UI,默认为true
* @return {UI}
*/
Ycc.prototype.getUIFromPointer = function (dot,uiIsShow) {
var self = this;
uiIsShow = Ycc.utils.isBoolean(uiIsShow)?uiIsShow:true;
// 从最末一个图层开始寻找
for(var j=self.layerList.length-1;j>=0;j--){
var layer = self.layerList[j];
if(uiIsShow&&!layer.show) continue;
var ui = layer.getUIFromPointer(dot,uiIsShow);
if(ui)
return ui;
}
return null;
};
;/**
* @file Ycc.utils.js
* @author xiaohei
* @desc
* 整个程序公用的基础工具模块
*
* @requires Ycc.init
*/
(function(Ycc){
Ycc.utils = {};
/**
* 合并两个对象。只会保留targetObj中存在的字段。
* @param targetObj 目标对象
* @param obj2 待合入的对象
* @param isDeepClone 是否进行深拷贝
* @return {{}} targetObj对象
*/
Ycc.utils.extend = function(targetObj, obj2,isDeepClone) {
if(isDeepClone)
obj2 = Ycc.utils.deepClone(obj2);
for (var i in targetObj) {
if(!targetObj.hasOwnProperty(i)) continue;
if (obj2 && typeof obj2[i] !=="undefined") {
targetObj[i] = obj2[i];
}
}
return targetObj;
};
/**
* 合并对象
* 将src所有的字段全部合并至target,若存在公有字段,则src会覆盖target对象的字段。
* 这个操作是浅拷贝。prototype内的属性不会被覆盖。
* @param target {object} 待覆盖的目标对象
* @param src {object} 源对象
* @return 返回target对象
*/
Ycc.utils.mergeObject = function(target,src){
src = src || {};
for(var key in src){
if(!src.hasOwnProperty(key)) continue;
if(typeof src[key]!=="undefined"){
target[key] = src[key];
}
}
return target;
};
/**
* 判断字符串
* @param str
* @return {boolean}
*/
Ycc.utils.isString = function(str) {
return typeof(str) === "string";
};
/**
* 判断数字
* @param str
* @return {boolean}
*/
Ycc.utils.isNum = function(str) {
return typeof(str) === "number";
};
/**
* 判断boolean
* @param str
* @return {boolean}
*/
Ycc.utils.isBoolean = function(str) {
return typeof(str) === "boolean";
};
/**
* 判断对象
* @param str
* @return {boolean}
*/
Ycc.utils.isObj = function(str) {
return typeof(str) === "object";
};
/**
* 判断函数
* @param str
* @return {boolean}
*/
Ycc.utils.isFn = function(str) {
return typeof(str) === "function";
};
/**
* 判断数组
* @param str
* @return {boolean}
*/
Ycc.utils.isArray = function(str) {
return Object.prototype.toString.call(str) === '[object Array]';
};
/**
* 检测是否是移动端
* @return {boolean}
*/
Ycc.utils.isMobile = function () {
var userAgentInfo = navigator.userAgent;
var Agents = ["Android", "iPhone",
"SymbianOS", "Windows Phone",
"iPad", "iPod"];
var flag = false;
for (var v = 0; v < Agents.length; v++) {
if (userAgentInfo.indexOf(Agents[v]) > 0) {
flag = true;
break;
}
}
return flag;
};
/**
* 深拷贝某个对象或者数组
* @param arrOrObj
* @return {*}
*/
Ycc.utils.deepClone = function(arrOrObj){
return (Ycc.utils.isArray(arrOrObj))? deepCopy(arrOrObj):deepExtend(arrOrObj);
function deepExtend(obj){
var tempObj = {};
for(var i in obj){
if(!obj.hasOwnProperty(i)) continue;
tempObj[i] = obj[i];
if(Ycc.utils.isArray(obj[i])){
tempObj[i] = deepCopy(obj[i]);
}else if(Ycc.utils.isObj(obj[i])){
tempObj[i] = deepExtend(obj[i]);
}else{
tempObj[i] = obj[i];
}
}
return tempObj;
}
function deepCopy(arr){
var newArr = [];
var v = null;
for(var i=0;i<arr.length;i++){
v = arr[i];
if(Ycc.utils.isArray(v))
newArr.push(deepCopy(v));
else if(Ycc.utils.isObj(v))
newArr.push(deepExtend(v));
else{
newArr.push(v);
}
}
return newArr;
}
};
/**
* 迷你模板,替换__field__,其中的`field`为`renderObj`中的字段
* 返回替换后的模板文本
* @param tpl 模板字符串
* @param renderObj 渲染的对象
* @return {string}
*/
Ycc.utils.renderTpl=function (tpl,renderObj) {
return tpl.replace(/__.+?__/g,function (txt) {
console.log('匹配到的文本-->',txt);
var key = txt.slice(2).slice(0,-2).trim();
if(renderObj[key]!==undefined)
return renderObj[key];
else
return txt;
});
};
/**
* 释放obj内存 只清空字段
* @param obj
*/
Ycc.utils.releaseObject = function (obj) {
for(var key in obj){
if(!obj.hasOwnProperty(key)) continue;
delete obj[key];
}
};
/**
* 释放arr内存 只清空元素
* @param arr
*/
Ycc.utils.releaseArray = function (arr) {
arr.length = 0;
};
})(Ycc);
;/**
* @file Ycc.Math.js
* @author xiaohei
* @date 2017/11/2
* @description Ycc.Math文件
*/
(function (Ycc) {
var extend = Ycc.utils.extend;
/**
* 数学表达式模块
* @constructor
*/
Ycc.Math = function () {};
/**
* 点
* @param x {number} x坐标
* @param y {number} y坐标
* @constructor
*//**
* 点
* @param [dot] {object}
* @param dot.x {number} x坐标
* @param dot.y {number} y坐标
* @constructor
*/
Ycc.Math.Dot = function (dot) {
/**
* x坐标
* @type {number}
*/
this.x = 0;
/**
* y坐标
* @type {number}
*/
this.y = 0;
var len = arguments.length;
if(len===1){
this.x = dot.x;
this.y = dot.y;
}else if(len===2){
this.x = arguments[0];
this.y = arguments[1];
}
};
/**
* 点是否在某个区域内
* @param rect {Ycc.Math.Rect} 区域
*/
Ycc.Math.Dot.prototype.isInRect = function (rect) {
return this.x>=rect.x&&this.x<=rect.x+rect.width && this.y>=rect.y && this.y<=rect.y+rect.height;
};
/**
* 判读两点位置是否相同
* @param dot
* @return {boolean}
*/
Ycc.Math.Dot.prototype.isEqual = function (dot) {
return this.x===dot.x && this.y===dot.y;
};
/**
* 点的加法/点的偏移量
* @param dot {Ycc.Math.Dot} 加的点
* @return {Ycc.Math.Dot} 返回一个新的点
*/
Ycc.Math.Dot.prototype.plus = function (dot) {
return new Ycc.Math.Dot(this.x+dot.x,this.y+dot.y);
};
/**
* 将当前点绕另外一个点旋转一定度数
* @param rotation 旋转角度
* @param anchorDot 锚点坐标
* @return 旋转后的点
*/
Ycc.Math.Dot.prototype.rotate = function (rotation,anchorDot) {
anchorDot=anchorDot||new Ycc.Math.Dot(0,0);
var dotX = this.x,dotY=this.y,anchorX=anchorDot.x,anchorY=anchorDot.y;
var dx = (dotX - anchorX)*Math.cos(rotation/180*Math.PI) - (dotY - anchorY)*Math.sin(rotation/180*Math.PI)+anchorX;
var dy = (dotY - anchorY)*Math.cos(rotation/180*Math.PI) + (dotX - anchorX)*Math.sin(rotation/180*Math.PI)+anchorY;
return new Ycc.Math.Dot(dx,dy);
};
/**
* 判断三点是否共线
* @param dot1
* @param dot2
* @param dot3
*/
Ycc.Math.Dot.threeDotIsOnLine = function (dot1,dot2,dot3) {
// 存在位置相同点肯定共线
if(dot1.isEqual(dot2) || dot1.isEqual(dot3) || dot2.isEqual(dot3))
return true;
// 三个点x一样
if(dot1.x===dot2.x&&dot2.x===dot3.x)
return true;
var k1 = Math.abs(dot1.y-dot2.y)/Math.abs(dot1.x-dot2.x);
var k2 = Math.abs(dot1.y-dot3.y)/Math.abs(dot1.x-dot3.x);
return k1===k2;
};
/**
* 区域
* @param startDot {Dot}
* @param width
* @param height
* @constructor
*//**
* 区域
* @param x
* @param y
* @param width
* @param height
* @constructor
*//**
* 区域
* @param rect
* @param rect.x
* @param rect.y
* @param rect.width
* @param rect.height
* @constructor
*/
Ycc.Math.Rect = function (rect) {
/**
* 构造器的引用
* @type {function}
*/
this.yccClass = Ycc.Math.Rect;
/**
* 左上角x坐标
* @type {number}
*/
this.x = 0;
/**
* 左上角y坐标
* @type {number}
*/
this.y = 0;
/**
* 区域宽
* @type {number}
*/
this.width = 0;
/**
* 区域高
* @type {number}
*/
this.height = 0;
var len = arguments.length;
if(len===1){
this.x = rect.x;
this.y = rect.y;
this.width = rect.width;
this.height = rect.height;
}else if(len===3){
this.x = arguments[0].x;
this.y = arguments[0].y;
this.width = arguments[1];
this.height = arguments[2];
}else if(len === 4){
this.x = arguments[0];
this.y = arguments[1];
this.width = arguments[2];
this.height = arguments[3];
}
this.toPositive();
};
/**
* 将矩形的长和宽转换为正数
*/
Ycc.Math.Rect.prototype.toPositive = function () {
var x0 = this.x,
y0 = this.y,
x1 = this.x + this.width,
y1 = this.y + this.height;
this.x = x0<x1?x0:x1;
this.y = y0<y1?y0:y1;
this.width = Math.abs(this.width);
this.height = Math.abs(this.height);
};
/**
* 获取区域的顶点列表
* @return {Ycc.Math.Dot[]}
*/
Ycc.Math.Rect.prototype.getVertices = function () {
return [
new Ycc.Math.Dot(this.x,this.y),
new Ycc.Math.Dot(this.x+this.width,this.y),
new Ycc.Math.Dot(this.x+this.width,this.y+this.height),
new Ycc.Math.Dot(this.x,this.y+this.height),
new Ycc.Math.Dot(this.x,this.y)
];
};
/**
* 根据顶点更新数值
* @param vertices
* @return {*}
*/
Ycc.Math.Rect.prototype.updateByVertices = function (vertices) {
if(!Ycc.utils.isArray(vertices))
return console.error('参数必须是数组!');
this.x = vertices[0].x;
this.y = vertices[0].y;
this.width = vertices[1].x-this.x;
this.height = vertices[2].y-this.y;
};
/**
* 向量构造函数
* @constructor
*/
Ycc.Math.Vector = function () {
this.x = 0;
this.y = 0;
this.z = 0;
if(arguments.length===3 || arguments.length===2){
this.x=arguments[0]||0;
this.y=arguments[1]||0;
this.z=arguments[2]||0;
}
if(arguments.length===1){
if(!Ycc.utils.isObj(arguments[0])) console.error('constructor need a objec as param!');
this.x=arguments[0].x||0;
this.y=arguments[0].y||0;
this.z=arguments[0].z||0;
}
};
/**
* 向量的点乘法
* @param v2 {Ycc.Math.Vector} 点乘向量
* @return {number}
*/
Ycc.Math.Vector.prototype.dot = function (v2) {
return this.x*v2.x+this.y*v2.y+this.z*v2.z;
};
/**
* 向量的叉乘法
* @param v2 {Ycc.Math.Vector} 叉乘向量
* @return {number}
*/
Ycc.Math.Vector.prototype.cross = function (v2) {
var res = new Ycc.Math.Vector();
res.x = this.y*v2.z-v2.y*this.z;
res.y = v2.x*this.z-this.x*v2.z;
res.z = this.x*v2.y-v2.x*this.y;
return res;
};
/**
* 获取向量的模长
* @return {number}
*/
Ycc.Math.Vector.prototype.getLength = function () {
return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z,2);
};
/**
* 矩阵的构造方法。
* @param data {array} 矩阵所有行拼接的数组
* @param m {number} 行数
* @param n {number} 列数
* @constructor
*/
Ycc.Math.Matrix = function (data,m,n) {
this.data = data;
this.m = m;
this.n = n;
};
/**
* 矩阵点乘法
* @param M {Ycc.Math.Matrix} 另一个矩阵
*/
Ycc.Math.Matrix.prototype.dot = function (M) {
if(M.m!==this.n || M.n!==this.m)
return console.error('两个矩阵的行数和列数不对应,不能相乘!');
var N = new Ycc.Math.Matrix([],this.m,this.m);
// 循环行
for(var i=1;i<=this.m;i++){
// 循环矩阵赋值
for(var k=1;k<=this.m;k++){
var temp =0;
// 循环列
for(var j=1;j<=this.n;j++){
temp += this.get(i,j)*M.get(j,k);
}
N.set(i,k,temp);
}
}
return N;
};
/**
* 获取矩阵i行j列的元素。
* 注:i,i下标从1开始
* @param i
* @param j
* @return {number}
*/
Ycc.Math.Matrix.prototype.get = function (i, j) {
return this.data[(i-1)*this.n+j-1];
};
/**
* 设置矩阵i行j列的元素为val
* 注:i,i下标从1开始
* @param i
* @param j
* @param val
*/
Ycc.Math.Matrix.prototype.set = function (i, j, val) {
this.data[(i-1)*this.n+j-1] = val;
};
})(Ycc);;/**
* @file Ycc.Tree.class.js
* @author xiaohei
* @date 2018/8/6
* @description Ycc.Tree.class文件
*/
(function (Ycc) {
// 节点的自增id
var nodeID = 1;
/**
* 存储所有树节点的引用,不允许重载
* 使用prototype好处:
* 1、所有Tree对象公用
* 2、JSON.stringify不会将此序列化,避免抛循环引用的错误
* key为$id val为Tree对象
* @type {{}}
*/
var nodeMap = {};
/**
* 树的构造函数
* 若参数为空,默认创建只有一个根节点的树
* @constructor
*/
Ycc.Tree = function() {
/**
* 节点的自增ID,不允许修改,且每个对象必须唯一
* @type {number}
*/
this.$id = nodeID++;
/**
* 节点的父节点ID,不允许修改
* @type {null|Ycc.Tree}
*/
this.$parentID = null;
/**
* 节点的子节点列表
* @type {Array}
*/
this.children = [];
/**
* 节点所携带的数据
* @type {any}
*/
this.data = null;
// 存入map中,方便通过id寻找
nodeMap[this.$id] = this;
};
/**
* 释放当前节点的内存,非递归
* @param treeNode
*/
Ycc.Tree.release = function (treeNode) {
// 删除父级children的引用
var pa = treeNode.getParent();
if(pa){
var children = pa.children;
var index = children.indexOf(treeNode);
if(index!==-1)
children[index]=null;
}
// 删除nodeMap引用
delete nodeMap[treeNode.$id];
/**
* 节点的子节点列表
* @type {Array}
*/
treeNode.children.length = 0;
/**
* 节点所携带的数据
* @type {any}
*/
treeNode.data = null;
};
/**
* 获取nodeMap表
* @return {{}}
*/
Ycc.Tree.getNodeMap = function () {
return nodeMap;
};
/**
* 获取nodeMap表
* @return {{}}
*/
Ycc.Tree.prototype.getNodeMap = function () {
return nodeMap;
};
/**
* 获取父级
* @return {Ycc.Tree}
*/
Ycc.Tree.prototype.getParent = function () {
if(!this.$parentID) return null;
return nodeMap[this.$parentID];
};
/**
* 添加一颗子树
* @param tree
*/
Ycc.Tree.prototype.addChildTree = function (tree) {
if(tree.$parentID) return console.error("sub tree's parent has exist! can't add!",tree);
tree.$parentID = this.$id;
this.children.push(tree);
return this;
};
/**
* 删除一颗子树,只能删除直接子节点
* @param tree
* @return {*}
*/
Ycc.Tree.prototype.removeChildTree = function (tree) {
var index = this.children.indexOf(tree);
if(index===-1) return this;
this.children.splice(index,1);
return this;
};
/**
* 获取树的深度
* @return {number}
*/
Ycc.Tree.prototype.getDepth = function () {
var self = this;
var dep = 1;
if(self.children.length>0){
for(var i=0;i<self.children.length;i++){
var subDep = self.children[i].getDepth();
dep = subDep+1>dep?subDep+1:dep;
}
}
return dep;
};
/**
* 树的迭代器,返回集中常用的迭代方法
* @param option 暂时不用
* @return {{each: each, leftChildFirst: leftChildFirst, rightChildFirst: rightChildFirst, depthDown: depthDown}}
*/
Ycc.Tree.prototype.itor = function (option) {
var self = this;
/**
* 父代优先遍历
* 先遍历父代,再依次遍历子代
* 若cb返回true,则停止遍历
* @param cb
* @return {boolean}
*/
function each(cb) {
if(cb.call(self,self)) return true;
if(self.children.length>0){
for(var i=0;i<self.children.length;i++){
if(self.children[i].itor().each(cb)) return true;
}
}
return false;
}
/**
* 左树优先遍历
* 只要最左边的树不为空就继续遍历其子树,最后遍历根节点
* 若cb返回true,则停止遍历
*
* @param cb
* @return {boolean}
*/
function leftChildFirst(cb) {
if(self.children.length>0){
for(var i=0;i<self.children.length;i++){
if(self.children[i].itor().leftChildFirst(cb)) return true;
}
}
if(cb.call(self,self)) return true;
}
/**
* 右树优先遍历
* 只要最右边的树不为空就继续遍历其子树,最后遍历根节点
* 若cb返回true,则停止遍历
*
* @param cb
* @return {boolean}
*/
function rightChildFirst(cb) {
if(self.children.length>0){
for(var i=self.children.length-1;i>=0;i--){
if(self.children[i].itor().rightChildFirst(cb)) return true;
}
}
if(cb.call(self,self)) return true;
}
/**
* 根据当前节点,按层级依次向下遍历
* 这只是depthDownByNodes的特殊情况
* 若cb返回true,则停止遍历
*
* @param cb(node)
* @return {boolean}
*/
function depthDown(cb) {
depthDownByNodes([self],cb);
}
/**
* 根据所给的节点列表,按层级依次向下遍历
* 若cb返回true,则停止遍历
*
* @param nodes {Ycc.Tree[]}
* @param cb(node,layer)
* @param [layer] {number} 当前nodes列表所在的层级,可选参数
* @return {boolean|number}
*/
function depthDownByNodes(nodes,cb,layer){
if(nodes.length===0)
return true;
layer=layer||0;
layer++;
var nextNodes = [];
// 是否停止遍历下一层的标志位
var breakFlag = false;
for(var i=0;i<nodes.length;i++) {
var rvl = cb.call(self, nodes[i], layer);
// 如果返回为true,则表示停止遍历下一层
// 如果返回为-1,则表示当前节点的所有子孙节点不再遍历
if (rvl===true) {
breakFlag = true;
break;
}else if(rvl===-1)
continue;
nextNodes = nextNodes.concat(nodes[i].children);
}
if(breakFlag){
nextNodes = [];
}
return depthDownByNodes(nextNodes,cb,layer);
}
return {
each:each,
leftChildFirst:leftChildFirst,
rightChildFirst:rightChildFirst,
depthDown:depthDown
};
};
/**
* 转化为节点列表
* @return {Ycc.Tree[]}
*/
Ycc.Tree.prototype.toNodeList = function () {
var list = [];
this.itor().depthDown(function (node) {
list.push(node);
});
return list;
};
/**
* 获取节点列表按照层级的分类
* key为层级,val为Ycc.Tree列表
* @return {{}}
*/
Ycc.Tree.prototype.getNodeListGroupByLayer = function () {
var list = {};
this.itor().depthDown(function (node,layer) {
if(!list[layer])
list[layer] = [];
list[layer].push(node);
});
return list;
};
/**
* 获取节点的所有父级,靠近节点的父级排序在后
* @return {Ycc.Tree[]}
*/
Ycc.Tree.prototype.getParentList = function () {
var node = this;
var list = [];
while(node.$parentID){
var parent = nodeMap[node.$parentID];
list.unshift(parent);
node = parent;
}
return list;
};
/**
* 获取节点的所有兄弟节点,包括自身
* @return {Ycc.Tree[]}
*/
Ycc.Tree.prototype.getBrotherList = function () {
var list = [];
if(!this.$parentID)
list = [this];
else
list = nodeMap[this.$parentID].children;
return list;
};
/////////////////////////// static Methods
/**
* 根据传入的json构造一棵树
*
* 若节点有数据必须包含data字段
* 若节点有子节点必须包含children字段,且为数组
* 只关注data和children字段,其他字段将忽略
*
* @param json {object} json对象,示例:{data,children}
* @return {Ycc.Tree}
*/
Ycc.Tree.createByJSON = function (json) {
var root = new Ycc.Tree();
root.data = json.data;
if(Ycc.utils.isArray(json.children) && json.children.length>0){
for(var i=0;i<json.children.length;i++){
root.addChildTree(Ycc.Tree.createByJSON(json.children[i]));
}
}
return root;
};
/**
* 根据传入的节点列表构造一棵树
*
* 只关注字段id,parentID
*
* 构造成功后将生成新的$id,$parentID,且所有字段都将放入data中,包括id和parentID
*
* @param nodes {Array[]} json对象数组
* @return {Ycc.Tree}
*/
Ycc.Tree.createByNodes = function (nodes) {
if(!Ycc.utils.isArray(nodes) || nodes.length===0)
return console.error('need an Array as param!');
var root = null;
var treeNodes = [];
nodes.forEach(function (node) {
var treeNode = new Ycc.Tree();
treeNode.data = node;
treeNodes.push(treeNode);
if(!Ycc.utils.isNum(node.parentID) && !node.parentID)
root = treeNode;
});
treeNodes.forEach(function (treeNode) {
treeNodes.forEach(function (subNode) {
if(subNode.data.parentID === treeNode.data.id){
treeNode.addChildTree(subNode);
}
});
});
return root;
};
})(Ycc);;/**
* @file Ycc.Graph.class.js
* @author xiaohei
* @date 2018/8/14
* @description Ycc.Graph.class文件
*
* 图的结构类,有向图、无向图等
*/
(function (Ycc) {
// 节点唯一id
var vid = 1;
// 边的唯一id
var eid = 1;
// 图的唯一id
var gid = 1;
// 节点map key为$id val为有向顶点/无向顶点
var vMap = {};
// 边map key为$id val为有向边/无向边
var eMap = {};
/**
* 图的结构类
* @constructor
*/
Ycc.Graph = function (type) {
/**
* 图的分类 1--有向图 2--无向图
* @type {number}
*/
this.type = type||1;
/**
* 图的id
* @type {number}
*/
this.$id = gid++;
/**
* 图包含的顶点
* @type {Ycc.Graph.DirectedV[] | Ycc.Graph.UnDirectedV[]}
*/
this.vList = [];
/**
* 图包含的边
* @type {Ycc.Graph.E[]}
*/
this.eList = [];
};
/**
* 获取顶点的map
* @return {{}}
*/
Ycc.Graph.prototype.getMapV = function () {
return vMap;
};
/**
* 获取边的map
* @return {{}}
*/
Ycc.Graph.prototype.getMapE = function () {
return eMap;
};
/**
* 广度优先搜索
* @param vArrId {array} 顶点$id数组,代表从哪些顶点开始遍历
* @param cb {function} 回调函数
* 若回调函数返回true,则遍历结束
* @param [vSearchedId] 已遍历的$id数组
*
* @return {boolean}
*/
Ycc.Graph.prototype.bfs = function (vArrId,cb,vSearchedId) {
vArrId = vArrId||[];
vSearchedId = vSearchedId || [];
// 递归结束条件
if(vSearchedId.length===this.vList.length)
return true;
// 若未结束,但长度为0,说明图中存在孤立部分,任取一个孤立部分的顶点,继续遍历
if(vArrId.length===0){
var tempID = null;
for(var j=0;j<this.vList.length;j++){
if(vSearchedId.indexOf(this.vList[j].$id)===-1){
tempID=this.vList[j].$id;
break;
}
}
return this.bfs([tempID],cb,vSearchedId);
}
// 修改已遍历的顶点
var v = null;
for(var i=0;i<vArrId.length;i++){
v = vMap[vArrId[i]];
if(cb.call(this,v)) return true;
vSearchedId.push(v.$id);
}
// 下一层需要遍历的节点
var next = [];
for(var k=0;k<vArrId.length;k++){
v = vMap[vArrId[k]];
var temp = v.getAccessibleIds().filter(function (id) {
return vSearchedId.indexOf(id)===-1;
});
next = next.concat(temp);
}
return this.bfs(next,cb,vSearchedId);
};
/**
* 图的深度优先遍历
* @param vStartID 从哪个顶点开始遍历
* @param cb
* @param vSearchedId 已遍历的$id数组
*/
Ycc.Graph.prototype.dfs = function (vStartID,cb,vSearchedId) {
vSearchedId = vSearchedId || [];
// 修改已遍历的顶点
var v = vMap[vStartID];
vSearchedId.push(v.$id);
if(cb.call(this,v)) return true;
// 遍历可达的节点
var accessibleIds = v.getAccessibleIds();
for(var k=0;k<accessibleIds.length;k++){
var next = accessibleIds[k];
if(vSearchedId.indexOf(next)===-1){
if(this.dfs(next,cb,vSearchedId))
return true;
}
}
// 递归结束条件
if(vSearchedId.length===this.vList.length)
return true;
// 若递归未结束,说明图中存在孤立部分,任取一个孤立部分的顶点,继续遍历
var tempID = null;
for(var j=0;j<this.vList.length;j++){
if(vSearchedId.indexOf(this.vList[j].$id)===-1){
tempID=this.vList[j].$id;
break;
}
}
return this.dfs(tempID,cb,vSearchedId);
};
/**
* 创建一个有向图
* @static
* @param vArr {Array} 顶点列表,示例:[{id,data,...}]
* @param eArr {Array} 边列表,示例:[{fromId,toId,data,...}]
*/
Ycc.Graph.createDirectedGraph = function (vArr,eArr) {
var graph = new Ycc.Graph(1);
var vList = graph.vList;
var eList = graph.eList;
vArr.forEach(function (v) {
vList.push(new Ycc.Graph.DirectedV(v));
});
eArr.forEach(function (e) {
var from = null,to=null;
var edge = new Ycc.Graph.DirectedE();
for(var i =0;i<vList.length;i++){
// 两个都找到了就跳出去
if(from && to) break;
var v = vList[i];
if(v.data.id === e.fromId){
from = v;
v.outIDs.push(edge.$id);
}
if(v.data.id === e.toId){
to = v;
v.inIDs.push(edge.$id);
}
}
edge.init(from.$id,to.$id,e);
eList.push(edge);
});
return graph;
};
/**
* 有向图中的顶点类
* @param data 节点的数据
* @constructor
*/
Ycc.Graph.DirectedV = function (data) {
/**
* 节点id
* @type {number}
*/
this.$id = vid++;
/**
* 节点所携带的数据
* @type {any}
*/
this.data = data;
/**
* 节点的入边$id列表
* @type {number[]}
*/
this.inIDs = [];
/**
* 节点的出边$id列表
* @type {number[]}
*/
this.outIDs = [];
// 放入map,方便查找
vMap[this.$id] = this;
};
/**
* 获取有向图中,某个节点指向的节点ID列表
* @return {Array}
*/
Ycc.Graph.DirectedV.prototype.getAccessibleIds = function () {
var ids = [];
this.outIDs.forEach(function (id) {
ids.push(eMap[id].toID);
});
return ids;
};
/**
* 有向图中的边类
* @constructor
*/
Ycc.Graph.DirectedE = function () {
/**
* 边的id
* @type {number}
*/
this.$id = eid++;
/**
* 边所携带的数据,比如权重
* @type {any}
*/
this.data = null;
/**
* 边的起点$id
* @type {number}
*/
this.fromID = null;
/**
* 边的终点$id
* @type {number}
*/
this.toID = null;
// 放入map,方便查找
eMap[this.$id] = this;
};
/**
* 有向图边的初始化
* @param fromID
* @param toID
* @param data
*/
Ycc.Graph.DirectedE.prototype.init = function (fromID, toID, data) {
this.fromID = fromID;
this.toID = toID;
this.data = data;
};
/**
* 创建一个无向图
* @static
* @param vArr {Array} 顶点列表,示例:[{id,data,...}]
* @param eArr {Array} 边列表,ids为边关联的两个顶点id列表,长度为2,示例:[{ids,data,...}]
*/
Ycc.Graph.createUnDirectedGraph = function (vArr,eArr) {
var graph = new Ycc.Graph(2);
var vList = graph.vList;
var eList = graph.eList;
vArr.forEach(function (v) {
vList.push(new Ycc.Graph.UnDirectedV(v));
});
eArr.forEach(function (e) {
// 边的节点id列表
var ids = [];
var edge = new Ycc.Graph.UnDirectedE();
for(var i =0;i<vList.length;i++){
// 遍历的顶点
var v = vList[i];
// 两个都找到了就跳出去
if(ids.length===2) break;
if(v.data.id === e.ids[0] || v.data.id === e.ids[1]){
ids.push(v.$id);
v.eIDs.push(edge.$id);
}
}
edge.init(ids,e);
eList.push(edge);
});
return graph;
};
/**
* 无向图中的顶点类
* @param data {any} 顶点携带的数据
* @constructor
*/
Ycc.Graph.UnDirectedV = function (data) {
/**
* 节点id
* @type {number}
*/
this.$id = vid++;
/**
* 节点所携带的数据
* @type {any}
*/
this.data = data;
/**
* 节点的边$id列表
* @type {number[]}
*/
this.eIDs = [];
// 放入map,方便查找
vMap[this.$id] = this;
};
/**
* 获取节点的可达节点id列表
* @return {number[]}
*/
Ycc.Graph.UnDirectedV.prototype.getAccessibleIds = function () {
var accessibleIds=[];
for(var i=0;i<this.eIDs.length;i++){
// 边
var edge = eMap[this.eIDs[i]];
if(edge.vIDs[0]===this.$id){
accessibleIds.push(edge.vIDs[1]);
}
if(edge.vIDs[1]===this.$id){
accessibleIds.push(edge.vIDs[0]);
}
}
return accessibleIds;
};
/**
* 无向图中的边类
* @constructor
*/
Ycc.Graph.UnDirectedE = function () {
/**
* 边的id
* @type {number}
*/
this.$id = eid++;
/**
* 边所携带的数据,比如权重
* @type {any}
*/
this.data = null;
/**
* 关联的两个顶点$id数组 长度为2
* @type {number[]}
*/
this.vIDs = [];
// 放入map,方便查找
eMap[this.$id] = this;
};
/**
* 无向图中的边类初始化
* @param ids
* @param data
*/
Ycc.Graph.UnDirectedE.prototype.init = function (ids, data) {
this.vIDs = ids;
this.data = data;
};
})(Ycc);;/**
* @file Ycc.Ticker.class.js
* @author xiaohei
* @date 2017/10/26
* @description Ycc.Ticker.class文件
*/
(function (Ycc) {
/**
* 系统心跳管理类。
* 管理系统的心跳;自定义帧事件的广播;帧更新图层的更新等。
*
* 注:
* 心跳间隔时间为1e3/60;
* 无论帧率为多少,心跳间隔时间不变;
* 总帧数<=总心跳次数;
* 只有当总帧数*每帧的理论时间小于总心跳时间,帧的监听函数才会触发,以此来控制帧率;
*
* @param yccInstance
* @constructor
*/
Ycc.Ticker = function (yccInstance) {
this.yccClass = Ycc.Ticker;
/**
* ycc实例的引用
* @type {Ycc}
*/
this.yccInstance = yccInstance;
/**
* 启动时间戳
* @type {number}
*/
this.startTime = performance.now();
/**
* 上一帧刷新的时间戳
* @type {number}
*/
this.lastFrameTime = this.startTime;
/**
* 当前帧与上一帧的刷新的时间差
* @type {number}
*/
this.deltaTime = 0;
/**
* 当前帧与上一帧时间差的期望值(根据帧率计算而来的)
* @type {number}
*/
this.deltaTimeExpect = 0;
/**
* 实际帧间隔与期望帧间隔的时间比
* @type {number}
*/
this.deltaTimeRatio = 1;
/**
* 所有帧时间差的总和
* @type {number}
*/
this.deltaTimeTotalValue = 0;
/**
* 所有自定义的帧监听函数列表
* @type {function[]}
*/
this.frameListenerList = [];
/**
* 默认帧率
* @type {number}
*/
this.defaultFrameRate = 60;
/**
* 默认帧间隔
* @type {number}
*/
this.defaultDeltaTime = 1e3/this.defaultFrameRate;
/**
* 总帧数
* @type {number}
*/
this.frameAllCount = 0;
/**
* 总心跳次数
* @type {number}
*/
this.timerTickCount = 0;
/**
* 定时器ID。用于停止心跳。
* @type {number}
* @private
*/
this._timerId = 0;
/**
* 心跳是否已经启动
* @type {boolean}
* @private
*/
this._isRunning = false;
};
/**
* 定时器开始
* @param [frameRate] 心跳频率,即帧率
*/
Ycc.Ticker.prototype.start = function (frameRate) {
var self = this;
if(self._isRunning){
return;
}
// 正常设置的帧率
frameRate = frameRate?frameRate:self.defaultFrameRate;
// 每帧理论的间隔时间
self.deltaTimeExpect = 1000/frameRate;
var timer = requestAnimationFrame || webkitRequestAnimationFrame || mozRequestAnimationFrame || oRequestAnimationFrame || msRequestAnimationFrame;
// 初始帧数量设为0
self.frameAllCount = 0;
// timer兼容
timer || (timer = function(e) {
return setTimeout(e, 1e3 / 60);
}
);
// 启动时间
self.startTime = performance.now();
// 启动心跳
// self._timerId = timer.call(window, cb);
self._timerId = timer(cb);
self._isRunning = true;
// 心跳回调函数。约60fps
function cb() {
// 当前时间
var curTime = self.timerTickCount===0?self.startTime:performance.now();
// 总的心跳数加1
self.timerTickCount++;
// 总的心跳时间
var tickTime = curTime - self.startTime;
// 所有帧刷新总时间,理论值
var frameTime = self.frameAllCount * self.deltaTimeExpect;
// 当总帧数*每帧的理论时间小于总心跳时间,触发帧的回调
if(tickTime > frameTime || "undefined"!==typeof wx){
// 总帧数加1
self.frameAllCount++;
// 执行所有自定义的帧监听函数
self.broadcastFrameEvent();
// 执行所有图层的帧监听函数
self.broadcastToLayer();
// 两帧的时间差
self.deltaTime = performance.now()-self.lastFrameTime;
// 帧间隔缩放比
self.deltaTimeRatio = self.deltaTime/self.deltaTimeExpect;
// 帧时间差的总和(忽略第一帧)
self.frameAllCount>1&&(self.deltaTimeTotalValue +=self.deltaTime);
if(self.deltaTime/self.deltaTimeExpect>3){
console.warn("第%d帧:",self.frameAllCount);
console.warn("该帧率已低于正常值的1/3!若相邻帧持续警告,请适当降低帧率,或者提升刷新效率!","正常值:",frameRate," 当前值:",1000/self.deltaTime);
}
// 设置上一帧刷新时间
self.lastFrameTime += self.deltaTime;
}
// 递归调用心跳函数
// self._timerId = timer.call(window,cb);
self._timerId = timer(cb);
}
};
/**
* 停止心跳
*/
Ycc.Ticker.prototype.stop = function () {
var stop = cancelAnimationFrame || webkitCancelAnimationFrame || mozCancelAnimationFrame || oCancelAnimationFrame;
stop || (stop = function (id) {
return clearTimeout(id);
});
stop(this._timerId);
this._isRunning = false;
};
/**
* 给每帧添加自定义的监听函数
* @param listener
*/
Ycc.Ticker.prototype.addFrameListener = function (listener) {
this.frameListenerList.push(listener);
};
/**
* 移除某个监听函数
* @param listener
*/
Ycc.Ticker.prototype.removeFrameListener = function (listener) {
var index = this.frameListenerList.indexOf(listener);
if(index!==-1)
this.frameListenerList.splice(index,1);
};
/**
* 执行所有自定义的帧监听函数
*/
Ycc.Ticker.prototype.broadcastFrameEvent = function () {
for(var i =0;i<this.frameListenerList.length;i++){
var listener = this.frameListenerList[i];
Ycc.utils.isFn(listener) && listener();
}
};
/**
* 执行所有图层的监听函数
*/
Ycc.Ticker.prototype.broadcastToLayer = function () {
for(var i = 0;i<this.yccInstance.layerList.length;i++){
var layer = this.yccInstance.layerList[i];
layer.show && layer.enableFrameEvent && layer.onFrameComing();
}
};
})(Ycc);;/**
* @file Ycc.Debugger.class.js
* @author xiaohei
* @date 2018/10/24
* @description Ycc.Debugger.class文件
*/
(function (Ycc) {
/**
* ycc的调试模块
* @constructor
*/
Ycc.Debugger = function (yccInstance) {
this.yccClass = Ycc.Debugger;
/**
* ycc的实例
*/
this.yccInstance = yccInstance;
/**
* 信息面板显示的UI 帧间隔
* @type {Ycc.UI}
*/
this.deltaTime = null;
/**
* 信息面板显示的UI 帧间隔期望值
* @type {Ycc.UI}
*/
this.deltaTimeExpect = null;
/**
* 信息面板显示的UI 总帧数
* @type {Ycc.UI}
*/
this.frameAllCount = null;
/**
* 信息面板显示的UI 帧间隔平均值
* @type {Ycc.UI}
*/
this.deltaTimeAverage = null;
/**
* 当前帧渲染耗时
* @type {Ycc.UI}
*/
this.renderTime = null;
/**
* 当前帧渲染的所有UI个数
* @type {Ycc.UI}
*/
this.renderUiCount = null;
this.totalJSHeapSize = null;
this.usedJSHeapSize = null;
this.jsHeapSizeLimit = null;
/**
* 调试面板所显示的字段,示例:[{name,cb,ui}]
* @type {Array}
*/
this.fields = [];
/**
* 调试面板的容纳区
* @type {Ycc.UI.Rect}
*/
this.rect = new Ycc.UI.Rect({
name:'debuggerRect',
rect:new Ycc.Math.Rect(10,10,200,140),
color:'rgba(255,255,0,0.5)'
});
/**
* 调试面板的图层
*/
this.layer = yccInstance.layerManager.newLayer({
name:"debug图层"
});
this.init();
};
Ycc.Debugger.prototype.init = function () {
var self = this;
this.yccInstance.ticker.addFrameListener(function () {
self.updateInfo();
});
};
/**
* 显示调试面板
*/
Ycc.Debugger.prototype.showDebugPanel = function () {
var layer = this.layer;
if(layer.uiList.indexOf(this.rect)===-1)
layer.addUI(this.rect);
};
/**
* 更新面板的调试信息
*/
Ycc.Debugger.prototype.updateInfo = function () {
// 强制使debug面板置顶
var layerList = this.yccInstance.layerList;
var index = layerList.indexOf(this.layer);
// bug 调试layer必须存在
if(index===-1) return;
if(index+1!==layerList.length){
layerList.splice(index,1);
layerList.push(this.layer);
}
this.rect.rect.height = this.fields.length*20;
this.fields.forEach(function (field) {
field.ui.content = field.name+' '+field.cb();
});
};
/**
* 添加一个信息项
* @param name
* @param cb() {function}
* cb必须返回一个值,这个值将直接填入
*
*/
Ycc.Debugger.prototype.addField = function (name, cb) {
var index = this.fields.length;
var ui = new Ycc.UI.SingleLineText({
content:"usedJSHeapSize "+cb(),
fontSize:'12px',
rect:new Ycc.Math.Rect(0,20*index,100,20),
color:'green'
});
this.fields.push({name:name,cb:cb,ui:ui});
this.rect.addChild(ui);
};
/**
* 将调试面板添加至某个图层
* @param layer {Ycc.Layer}
*/
Ycc.Debugger.prototype.addToLayer = function (layer) {
if(layer.uiList.indexOf(this.rect)===-1)
layer.addUI(this.rect);
};
/**
* 更新某个调试字段的回调函数
* @param name
* @param cb
*/
Ycc.Debugger.prototype.updateField = function (name,cb) {
for(var i=0;i<this.fields.length;i++){
if(this.fields[i].name===name){
this.fields[i].cb=null;
this.fields[i].cb=cb;
return;
}
}
};
/**
* 调试日志信息类
* @param message
* @constructor
*/
Ycc.Debugger.Log = function (message) {
this.message = '[Ycc logger]=> '+message;
};
/**
* 调试错误信息类
* @param message
* @constructor
*/
Ycc.Debugger.Error = function (message) {
this.message = '[Ycc error]=> '+message;
};
})(Ycc);;/**
* @file Ycc.Loader.class.js
* @author xiaohei
* @date 2017/10/9
* @description Ycc.Loader.class文件
*/
(function (Ycc) {
/**
* ycc实例的资源加载类
* @constructor
*/
Ycc.Loader = function () {
this.yccClass = Ycc.Loader;
/**
* 异步模块
* @type {Ycc.Ajax}
*/
this.ajax = new Ycc.Ajax();
/**
* 基础地址,末尾必须有斜线'/'
* @type {string}
*/
this.basePath = '';
};
/**
* 并发加载资源
* @param resArr
* @param [resArr.name] 资源名称,方便查找
* @param resArr.url 资源的url
* @param [resArr.type] 资源类型 image,audio,默认为image
* @param [resArr.res] 资源加载完成后,附加给该字段
* @param endCb 资源加载结束的回调
* @param [progressCb] 资源加载进度的回调
* @param [endResArr] 用于存储加载已结束的音频,一般不用传值
* @param [endResMap] 用于存储加载已结束的音频map,一般不用传值。注:map的key是根据name字段生成的
*/
Ycc.Loader.prototype.loadResParallel = function (resArr, endCb, progressCb,endResArr,endResMap) {
endResArr = endResArr || [];
endResMap = endResMap || {};
for(var i=0;i<resArr.length;i++){
var curRes = resArr[i];
var successEvent = "load";
var errorEvent = "error";
curRes.type = curRes.type || 'image';
if(curRes.type==='image'){
curRes.res = new Image();
curRes.res.src = curRes.url;
}
if(curRes.type==='audio'){
successEvent = 'loadedmetadata';
curRes.res = new Audio();
curRes.res.src = curRes.url;
curRes.res.preload = "load";
}
curRes.res.addEventListener(successEvent,listener(curRes,i,true));
curRes.res.addEventListener(errorEvent,listener(curRes,i,false));
function listener(curRes,index,error) {
return function () {
endResArr.push(curRes);
if(typeof curRes.name!=='undefined') endResMap[curRes.name] = curRes.res;
Ycc.utils.isFn(progressCb) && progressCb(curRes,error,index);
if(resArr.length===endResArr.length){
endCb(endResArr,endResMap);
}
};
}
}
};
/**
* 依次加载资源
* @param resArr
* @param [resArr.name] 资源名称,方便查找
* @param resArr.url 资源的url
* @param [resArr.type] 资源类型 image,audio
* @param [resArr.res] 资源加载完成后,附加给该字段
* @param endCb 资源加载结束的回调
* @param [progressCb] 资源加载进度的回调
* @param [endResArr] 用于存储加载已结束的音频,一般不用传值
* @param [endResMap] 用于存储加载已结束的音频map,一般不用传值。注:map的key是根据name字段生成的
*/
Ycc.Loader.prototype.loadResOneByOne = function (resArr, endCb, progressCb,endResArr,endResMap) {
endResArr = endResArr || [];
endResMap = endResMap || {};
if(resArr.length===endResArr.length){
endCb(endResArr,endResMap);
return;
}
var self = this;
// 当前加载的下标
var index = endResArr.length;
var curRes = resArr[index];
var successEvent = "load";
var errorEvent = "error";
curRes.type = curRes.type || 'image';
var timerId = 0;
if(curRes.type==='image'){
curRes.res = new Image();
curRes.res.src = self.basePath + curRes.url;
curRes.res.addEventListener(successEvent,onSuccess);
curRes.res.addEventListener(errorEvent,onError);
// 超时提示只针对图片
timerId = setTimeout(function () {
curRes.res.removeEventListener(successEvent,onSuccess);
curRes.res.removeEventListener(errorEvent,onSuccess);
onError({message:"获取资源超时!"});
},curRes.timeout||10000);
}else if(curRes.type==='audio'){
// 兼容wx端
if("undefined"!==typeof wx){
curRes.res = new Audio();
curRes.res.src = self.basePath + curRes.url;
successEvent = 'loadedmetadata';
errorEvent = 'error';
curRes.res.addEventListener(successEvent,onSuccess);
curRes.res.addEventListener(errorEvent,onError);
return;
}
curRes.res = new AudioPolyfill();
if(!curRes.res.context){
onError({message:"浏览器不支持AudioContext!"});
return;
}
console.log(self.basePath + curRes.url);
self.ajax.get(self.basePath + curRes.url,(function (curRes) {
return function (arrayBuffer) {
curRes.res.context.decodeAudioData(arrayBuffer, function(buf) {
curRes.res.buf=buf;
onSuccess();
}, onError);
}
})(curRes),onError,'arraybuffer');
}
function onSuccess() {
console.log('loader:',curRes.name,'success');
clearTimeout(timerId);
if(curRes.type==='image' || ("undefined"!==typeof wx && curRes.type==='audio' )){
curRes.res.removeEventListener(successEvent,onSuccess);
curRes.res.removeEventListener(errorEvent,onError);
}
endResArr.push(curRes);
if(typeof curRes.name!=='undefined') endResMap[curRes.name] = curRes.res;
Ycc.utils.isFn(progressCb) && progressCb(curRes,null,index);
self.loadResOneByOne(resArr,endCb,progressCb,endResArr,endResMap);
}
function onError(e) {
console.log('loader:',curRes.name,'error');
clearTimeout(timerId);
if(curRes.type==='image' || ("undefined"!==typeof wx && curRes.type==='audio' )){
curRes.res.removeEventListener(successEvent,onSuccess);
curRes.res.removeEventListener(errorEvent,onError);
}
endResArr.push(curRes);
if(typeof curRes.name!=='undefined') endResMap[curRes.name] = curRes.res;
Ycc.utils.isFn(progressCb) && progressCb(curRes,e,index);
self.loadResOneByOne(resArr,endCb,progressCb,endResArr,endResMap);
}
};
/**
* 获取资源
* @param resArr
* @param name
*/
Ycc.Loader.prototype.getResByName = function (name,resArr) {
for(var i=0;i<resArr.length;i++){
if(resArr[i].name===name)
return resArr[i];
}
return null;
};
/**
* audio元素的垫片,此类是私有类
* @constructor
*/
function AudioPolyfill(){
this.currentTime = 0;
/**
* 播放器
* @type {null}
*/
this.source = null;
/**
* 是否正在播放
* @type {boolean}
*/
this.running = false;
}
/**
* audio api环境
* 公用属性
* @readonly
*/
AudioPolyfill.prototype.context = (("undefined"!==typeof AudioContext)&&new AudioContext()) ||
(("undefined"!==typeof webkitAudioContext)&&new webkitAudioContext());
///("undefined"!==typeof AudioContext || "undefined"!==typeof webkitAudioContext) && new (AudioContext || webkitAudioContext)();
/**
* 播放音效
* 根据currentTime播放
*/
AudioPolyfill.prototype.play = function () {
var context = this.context;
if(!context) return;
this.running = true;
// 先stop
this.source && this.source.stop();
var source = context.createBufferSource();
source.buffer = this.buf;
source.connect(context.destination);
source.start(this.currentTime);
this.source = source;
};
/**
* 暂停音效
*/
AudioPolyfill.prototype.pause = function () {
var context = this.context;
if(!context) return;
this.running = false;
this.source.stop();
this.source = null;
};
})(Ycc);;/**
* @file Ycc.Ajax.class.js
* @author xiaohei
* @date 2018/10/31
* @description Ycc.Ajax.class文件
*/
(function (Ycc) {
/**
* 异步加载类
* @constructor
*/
Ycc.Ajax = function () {
this.yccClass = Ycc.Ajax;
};
/**
* ajax get请求
* @param url
* @param successCb 成功的回调函数
* @param errorCb 失败的回调函数
* @param responseType
*/
/**
* ajax get请求
* @param option
* @param option.url
* @param option.successCb
* @param option.successCb
* @param option.responseType
*/
Ycc.Ajax.prototype.get = function (option) {
var self = this;
var url='',
successCb,
errorCb,
responseType='json';
if(arguments.length===1){
url='';
successCb=option.successCb;
errorCb=option.errorCb;
responseType='json';
}else {
url=arguments[0];
successCb=arguments[1];
errorCb=arguments[2];
responseType=arguments[3];
}
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = responseType;
// Decode asynchronously
request.onload = function() {
successCb.call(self,request.response);
};
request.onerror = function (e) {
errorCb.call(self,e);
};
request.send();
};
})(Ycc);;/**
* @file Ycc.Event.class.js
* @author xiaohei
* @date 2017/11/20
* @description Ycc.Event.class文件
*/
(function (Ycc) {
/**
* 事件的数据结构类
* @param type {String} 事件类型
* @constructor
*/
Ycc.Event = function (type) {
this.yccClass = Ycc.Event;
/**
* 事件类型
* @type {string|Object}
*/
this.type = type?type:"";
/**
* 鼠标或点击位置
* @type {number}
*/
this.x=0;
/**
* 鼠标或点击位置
* @type {number}
*/
this.y=0;
/**
* ycc事件所对应的原始事件
*/
this.originEvent = null;
/**
* 鼠标按下的ycc事件
* @type {Ycc.Event}
*/
// this.mouseDownYccEvent = null;
/**
* 鼠标抬起的ycc事件
* @type {Ycc.Event}
*/
// this.mouseUpYccEvent = null;
/**
* 是否阻止向下传递。默认为false,不阻止
* @type {boolean}
*/
this.stop = false;
/**
* 事件触发的对象
* @type {Ycc.UI|null}
*/
this.target = null;
/**
* 事件触发时,鼠标的坐标与UI的坐标差。
* 即(e.x-target.x,e.y-target.y),
* 该属性只在事件类型为mousedown、dragstart、dragging时有效
* @type {Ycc.Math.Dot|null}
*/
// this.targetDeltaPosition = null;
if(Ycc.utils.isObj(type)){
Ycc.utils.extend(this,type);
}
};
/**
* 鼠标按下事件,全局保存,若存在,则表明鼠标处于按下状态
* @type {Ycc.Event}
*/
// Ycc.Event.mouseDownEvent = null;
/**
* 鼠标抬起事件,全局保存,若存在,则表明鼠标处于抬起状态
* @type {Ycc.Event}
*/
// Ycc.Event.mouseUpEvent = null;
/**
* 拖拽开始的标志位
* @type {null}
*/
// Ycc.Event.dragStartFlag = false;
})(Ycc);;/**
* @file Ycc.Listener.class.js
* @author xiaohei
* @date 2017/11/16
* @description Ycc.Listener.class文件
*/
(function (Ycc) {
/**
* 事件监听类。供Layer及UI类继承
* @constructor
*/
Ycc.Listener = function () {
this.yccClass = Ycc.Listener;
/**
* 所有的监听器。key为type,val为listener数组。
* @type {{}}
*/
this.listeners = {};
/**
* 被阻止的事件类型。key为type,val为boolean
* @type {{}}
*/
this.stopType = {};
/**
* 被禁用的事件类型。key为type,val为boolean
* @type {{}}
*/
this.disableType = {};
/**
* 是否阻止所有的事件触发
* @type {boolean}
*/
this.stopAllEvent = false;
/**
* 点击 的监听。默认为null
* @type {function}
*/
this.onclick = null;
/**
* 鼠标按下 的监听。默认为null
* @type {function}
*/
this.onmousedown = null;
/**
* 鼠标抬起 的监听。默认为null
* @type {function}
*/
this.onmouseup = null;
/**
* 鼠标移动 的监听。默认为null
* @type {function}
*/
this.onmousemove = null;
/**
* 拖拽开始 的监听。默认为null
* @type {function}
*/
this.ondragstart = null;
/**
* 拖拽 的监听。默认为null
* @type {function}
*/
this.ondragging = null;
/**
* 拖拽结束 的监听。默认为null
* @type {function}
*/
this.ondragend = null;
/**
* 鼠标移入 的监听。默认为null
* @type {function}
*/
this.onmouseover = null;
/**
* 鼠标移出 的监听。默认为null
* @type {function}
*/
this.onmouseout = null;
/**
* 触摸开始 的监听。默认为null
* @type {function}
*/
this.ontouchstart = null;
/**
* 触摸移动 的监听。默认为null
* @type {function}
*/
this.ontouchmove = null;
/**
* 触摸结束 的监听。默认为null
* @type {function}
*/
this.ontouchend = null;
/**
* 点击事件 的监听。默认为null
* @type {function}
*/
this.ontap = null;
};
/**
* 释放某个监听器的内存
* 将其所有引用属性设为null,等待GC
* @static
* @param listener
*/
Ycc.Listener.release = function (listener) {
// 临时变量
var key = null;
listener.yccClass = null;
/**
* 所有的监听器。key为type,val为listener数组。
* @type {{}}
*/
for(key in listener.listeners){
if(!listener.listeners.hasOwnProperty(key)) continue;
listener.listeners[key].length=0;
delete listener.listeners[key];
}
// listener.listeners = null;
/**
* 被阻止的事件类型。key为type,val为boolean
* @type {{}}
*/
Ycc.utils.releaseObject(listener.stopType);
// listener.stopType = null;
/**
* 被禁用的事件类型。key为type,val为boolean
* @type {{}}
*/
Ycc.utils.releaseObject(listener.disableType);
// listener.disableType = null;
/**
* 是否阻止所有的事件触发
* @type {boolean}
*/
listener.stopAllEvent = true;
/**
* 点击 的监听。默认为null
* @type {function}
*/
listener.onclick = null;
/**
* 鼠标按下 的监听。默认为null
* @type {function}
*/
listener.onmousedown = null;
/**
* 鼠标抬起 的监听。默认为null
* @type {function}
*/
listener.onmouseup = null;
/**
* 鼠标移动 的监听。默认为null
* @type {function}
*/
listener.onmousemove = null;
/**
* 拖拽开始 的监听。默认为null
* @type {function}
*/
listener.ondragstart = null;
/**
* 拖拽 的监听。默认为null
* @type {function}
*/
listener.ondragging = null;
/**
* 拖拽结束 的监听。默认为null
* @type {function}
*/
listener.ondragend = null;
/**
* 鼠标移入 的监听。默认为null
* @type {function}
*/
listener.onmouseover = null;
/**
* 鼠标移出 的监听。默认为null
* @type {function}
*/
listener.onmouseout = null;
/**
* 触摸开始 的监听。默认为null
* @type {function}
*/
listener.ontouchstart = null;
/**
* 触摸移动 的监听。默认为null
* @type {function}
*/
listener.ontouchmove = null;
/**
* 触摸结束 的监听。默认为null
* @type {function}
*/
listener.ontouchend = null;
/**
* 点击事件 的监听。默认为null
* @type {function}
*/
listener.ontap = null;
};
/**
* 添加某个类型的监听器
* @param type {string}
* @param listener {function}
*/
Ycc.Listener.prototype.addListener = function (type, listener) {
var ls = this.listeners[type];
if(!ls)
this.listeners[type] = [];
this.listeners[type].indexOf(listener) === -1 && this.listeners[type].push(listener);
};
/**
* 阻止某个事件类型继续传递
* @param type
*/
Ycc.Listener.prototype.stop = function (type) {
this.stopType[type] = true;
};
/**
* 触发某一类型的监听器
* @param type
* @param data
*/
Ycc.Listener.prototype.triggerListener = function (type,data) {
if(this.stopAllEvent) return;
if(this.disableType[type]) return;
if(!this.stopType[type])
Ycc.utils.isFn(this["on"+type]) && this["on"+type].apply(this,Array.prototype.slice.call(arguments,1));
var ls = this.listeners && this.listeners[type];
if(!ls || !Ycc.utils.isArray(ls)) return;
for(var i=0;i<ls.length;i++){
if(!this.stopType[type])
ls[i].apply(this,Array.prototype.slice.call(arguments,1));
}
};
/**
* 移除某个类型的监听器
* @param type
* @param listener
*/
Ycc.Listener.prototype.removeListener = function (type,listener) {
var ls = this.listeners[type];
if(!ls || !Ycc.utils.isArray(ls)) return;
for(var i=0;i<ls.length;i++){
if(ls[i]===listener) {
ls.splice(i,1);
return;
}
}
};
/**
* 禁止某个事件触发
* @param type
*/
Ycc.Listener.prototype.disableEvent = function (type) {
this.disableType[type] = true;
};
/**
* 恢复某个事件的触发
* @param type
*/
Ycc.Listener.prototype.resumeEvent = function (type) {
this.disableType[type] = false;
};
})(Ycc);;/**
* @file Ycc.TouchLifeTracer.class.js
* @author xiaohei
* @date 2018/6/12
* @description Ycc.TouchLifeTracer.class文件
* @requires Ycc.Listener
*/
(function (Ycc) {
/**
* touch事件的生命周期类
* @constructor
* @private
* */
var TouchLife = (function () {
var id = 0;
return function () {
/**
* 生命周期的id
* @type {number}
* */
this.id=id++;
/**
* 开始的touch事件
* @type {Touch}
* */
this.startTouchEvent = null;
/**
* 结束的touch事件
* @type {Touch}
* */
this.endTouchEvent = null;
/**
* 结束的touch事件
* @type {Touch[]}
* */
this.moveTouchEventList = [];
/**
* 开始时间
* @type {number}
*/
this.startTime = Date.now();
/**
* 结束时间
* @type {number}
*/
this.endTime = 0;
};
})();
/**
* touch事件追踪器
* @param opt
* @param opt.target 被追踪的dom对象
* @extends Ycc.Listener
* @constructor
*/
Ycc.TouchLifeTracer = function(opt) {
Ycc.Listener.call(this);
/**
* 追踪的对象
* */
this.target = opt.target;
/**
* 作用于target的所有生命周期,包含存活和死亡的周期
* */
this._lifeList = [];
/**
* 当前存活的生命周期,正在与target接触的触摸点生命周期
* */
this.currentLifeList = [];
/**
* 当前对象的touch
* @type {Array}
*/
this.targetTouches = [];
/**
* 当前target所有的touch
* @type {Array}
*/
this.touches = [];
/**
* 当前改变的所有touch
* @type {Array}
*/
this.changedTouches = [];
/**
* 某个生命周期开始
* @type {function}
* @param callback(life)
* */
this.onlifestart = null;
/**
* 某个生命周期状态变更
* @type {function}
* @param callback(life)
* */
this.onlifechange = null;
/**
* 某个生命周期开始
* @type {function}
* @param callback(life)
* */
this.onlifeend = null;
/**
* 添加生命周期
* @param life {TouchLife} 生命周期
* @return {*}
*/
this.addLife = function (life) {
this._lifeList.push(life);
};
/**
* 根据identifier查找生命周期,此方法只能在生命周期内使用
* @param identifier
* @return {*}
*/
this.findCurrentLifeByTouchID = function (identifier) {
for(var i=0;i<this.currentLifeList.length;i++){
var life = this.currentLifeList[i];
if(life.startTouchEvent.identifier===identifier)
return life;
}
};
/**
* 根据touchID删除当前触摸的生命周期
* @param identifier
* @return {boolean}
*/
this.deleteCurrentLifeByTouchID = function (identifier) {
for(var i=0;i<this.currentLifeList.length;i++){
var life = this.currentLifeList[i];
if(life.startTouchEvent.identifier===identifier){
this.currentLifeList.splice(i,1);
return true;
}
}
return false;
};
/**
* 初始化
*/
this.init = function () {
var self = this;
this.target.addEventListener("touchstart",function (e) {
e.preventDefault();
self.syncTouches(e);
var life = new TouchLife();
life.startTouchEvent = e.changedTouches[0];
self.addLife(life);
self.currentLifeList.push(life);
// self.onlifestart && self.onlifestart(life);
self.triggerListener('lifestart',life);
});
this.target.addEventListener('touchmove',function (e) {
e.preventDefault();
self.syncTouches(e);
var touches = e.changedTouches;
for(var i=0;i<touches.length;i++){
var touch = touches[i];
var life = self.findCurrentLifeByTouchID(touch.identifier);
var index = self.indexOfTouchFromMoveTouchEventList(life.moveTouchEventList,touch);
if(index===-1)
life.moveTouchEventList.push(touch);
else
life.moveTouchEventList[index]=touch;
// self.onlifechange && self.onlifechange(life);
self.triggerListener('lifechange',life);
}
});
this.target.addEventListener('touchend',function (e) {
e.preventDefault();
self.syncTouches(e);
var touch = e.changedTouches[0];
var life = self.findCurrentLifeByTouchID(touch.identifier);
life.endTouchEvent = touch;
life.endTime = Date.now();
self.deleteCurrentLifeByTouchID(touch.identifier);
// self.onlifeend && self.onlifeend(life);
self.triggerListener('lifeend',life);
});
};
this.init();
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.TouchLifeTracer.prototype,Ycc.Listener.prototype);
/**
* 同步当前HTML元素的touches
* @param e 原生的touch事件。touchstart、end、move ...
*/
Ycc.TouchLifeTracer.prototype.syncTouches = function (e) {
this.touches = [];
this.changedTouches = [];
this.targetTouches = [];
var i=0;
var touches=[];
touches = e.touches;
for(i=0;i<touches.length;i++){
this.touches.push(touches[i]);
}
touches = e.changedTouches;
for(i=0;i<e.changedTouches.length;i++){
this.changedTouches.push(touches[i]);
}
touches = e.targetTouches;
for(i=0;i<e.targetTouches.length;i++){
this.targetTouches.push(touches[i]);
}
};
/**
* 寻找移动过的接触点
*/
Ycc.TouchLifeTracer.prototype.indexOfTouchFromMoveTouchEventList = function (moveTouchEventList,touch) {
for(var i=0;i<moveTouchEventList.length;i++){
if(touch.identifier===moveTouchEventList[i].identifier)
return i;
}
return -1;
};
})(Ycc);;/**
* @file Ycc.Gesture.class.js
* @author xiaohei
* @date 2018/6/19
* @description Ycc.Gesture.class文件
* 移动端的手势类,封装简单的手势操作,操作只对target元素生效,若需要转发给Ycc.UI,则需要自己处理
* @requires Ycc.TouchLifeTracer
*/
(function (Ycc) {
/**
*
* @param option
* @param option.target 手势触发的HTML对象
* @extends Ycc.Listener
* @constructor
*/
Ycc.Gesture = function (option) {
Ycc.Listener.call(this);
this.yccClass = Ycc.Gesture;
this.option = option;
/**
* 长按事件的定时器id
* @type {null}
* @private
*/
this._longTapTimeout = null;
this._init();
};
Ycc.Gesture.prototype = new Ycc.Listener();
Ycc.Gesture.prototype._init = function () {
if(Ycc.utils.isMobile()){
console.log('mobile gesture init...');
this._initForMobile();
}else{
console.log('pc gesture init...');
this._initForPC();
}
};
/**
* 初始化移动端的手势
* @private
*/
Ycc.Gesture.prototype._initForMobile = function () {
var self = this;
var tracer = new Ycc.TouchLifeTracer({target:this.option.target});
// 上一次触摸、当前触摸
var preLife,curLife;
// 是否阻止事件
var prevent = {
tap:false,
swipe:false
};
tracer.onlifestart = function (life) {
self.triggerListener('tap',self._createEventData(life.startTouchEvent,'tap'));
self.triggerListener('log','tap triggered');
// 触发拖拽开始事件
self.triggerListener('dragstart',self._createEventData(life.startTouchEvent,'dragstart'));
// 多个触摸点的情况
if(tracer.currentLifeList.length>1){
self.triggerListener('log','multi touch start ...');
self.triggerListener('multistart',tracer.currentLifeList);
prevent.tap = false;
prevent.swipe = false;
clearTimeout(this._longTapTimeout);
// 缩放、旋转只取最先接触的两个点即可
preLife = tracer.currentLifeList[0];
curLife = tracer.currentLifeList[1];
return this;
}
// 只有一个触摸点的情况
prevent.tap = false;
prevent.swipe = false;
//长按事件
this._longTapTimeout = setTimeout(function () {
self.triggerListener('longtap',self._createEventData(life.startTouchEvent,'longtap'));
},750);
};
tracer.onlifechange = function (life) {
// 只要存在移动的接触点,就触发dragging事件
life.moveTouchEventList.forEach(function (moveEvent) {
self.triggerListener('dragging',self._createEventData(moveEvent,'dragging'));
});
if(tracer.currentLifeList.length>1){
prevent.tap=true;
prevent.swipe=true;
self.triggerListener('log','multi touch move ...');
self.triggerListener('multichange',preLife,curLife);
var rateAndAngle = self.getZoomRateAndRotateAngle(preLife,curLife);
if(Ycc.utils.isNum(rateAndAngle.rate)){
self.triggerListener('zoom',rateAndAngle.rate);
self.triggerListener('log','zoom triggered',rateAndAngle.rate);
}
if(Ycc.utils.isNum(rateAndAngle.angle)){
self.triggerListener('rotate',rateAndAngle.angle);
self.triggerListener('log','rotate triggered',rateAndAngle.angle);
}
return this;
}
// 只有一个触摸点的情况
if(life.moveTouchEventList.length>0){
var firstMove = life.startTouchEvent;
var lastMove = Array.prototype.slice.call(life.moveTouchEventList,-1)[0];
// 如果触摸点按下期间存在移动行为,且移动距离大于10,则认为该操作不是tap、longtap
if(Math.abs(lastMove.pageX-firstMove.pageX)>10 || Math.abs(lastMove.pageY-firstMove.pageY)>10){
prevent.tap=true;
clearTimeout(this._longTapTimeout);
}
}
};
tracer.onlifeend = function (life) {
self.triggerListener('dragend',self._createEventData(life.endTouchEvent,'dragend'));
// 若某个触摸结束,当前触摸点个数为1,说明之前的操作为多点触控。这里发送多点触控结束事件
if(tracer.currentLifeList.length===1){
return self.triggerListener('multiend',preLife,curLife);
}
if(tracer.currentLifeList.length===0){
// 开始和结束时间在300ms内,认为是tap事件
if(!prevent.tap && life.endTime-life.startTime<300){
// 取消长按事件
clearTimeout(this._longTapTimeout);
// 两次点击在300ms内,并且两次点击的范围在10px内,则认为是doubletap事件
if(preLife && life.endTime-preLife.endTime<300 && Math.abs(preLife.endTouchEvent.pageX-life.endTouchEvent.pageX)<10&& Math.abs(preLife.endTouchEvent.pageY-life.endTouchEvent.pageY)<10){
self.triggerListener('doubletap',self._createEventData(life.endTouchEvent,'doubletap'));
self.triggerListener('log','doubletap triggered');
preLife = null;
return this;
}
preLife=life;
return this;
}
// 如果触摸点按下期间存在移动行为,且移动范围大于30px,触摸时间在200ms内,则认为该操作是swipe
if(!prevent.swipe && life.endTime-life.startTime<300 ){
console.log('swipe');
var firstMove = life.startTouchEvent;
var lastMove = Array.prototype.slice.call(life.moveTouchEventList,-1)[0];
if(Math.abs(lastMove.pageX-firstMove.pageX)>30 || Math.abs(lastMove.pageY-firstMove.pageY)>30){
var type = 'swipe'+self._getSwipeDirection(firstMove.pageX,firstMove.pageY,lastMove.pageX,lastMove.pageY);
self.triggerListener('log',type);
self.triggerListener(type,self._createEventData(life.endTouchEvent,type));
}
return this;
}
}
};
};
/**
* pc端的初始化,pc端只有一个鼠标,操作相对简单
* @private
*/
Ycc.Gesture.prototype._initForPC = function () {
var self = this;
// 鼠标按下的yccEvent
var mouseDownEvent = null;
// 鼠标抬起的yccEvent
var mouseUpEvent = null;
// 拖动是否触发的标志
var dragStartFlag = false;
// 记录上一次点击事件,用于判断doubletap
var preTap = null;
// 记录长按的计时ID,用于判断longtap
var longTapTimeoutID = -1;
// 拖拽过程中的生命周期ID
var identifier = 0;
/**
* 需求实现:
* 1、事件传递给所有图层
* 2、事件传递给最上层UI
* 3、记录按下时鼠标的位置,及按下时鼠标所指的UI
* */
this.option.target.addEventListener('mousedown',function (e) {
// console.log(e.type,'...');
e.identifier=++identifier;
mouseDownEvent = self._createEventData(e);
longTapTimeoutID = setTimeout(function () {
console.log('longtap',Date.now(),'...');
self.triggerListener('log','long tap ...');
self.triggerListener('longtap',self._createEventData(mouseDownEvent,'longtap'));
},750);
});
/**
* 需求实现:
* 1、事件传递给所有图层
* 2、事件传递给最上层UI
* 3、如果move时,鼠标为按下状态,触发一次所有图层的dragstart事件
* 4、如果move时,鼠标为按下状态,触发一次 鼠标按下时UI 的dragstart事件
* 5、如果move时,鼠标为按下状态,触发所有图层的dragging事件
* 6、如果move时,鼠标为按下状态,触发 鼠标按下时UI 的dragging事件
* */
this.option.target.addEventListener('mousemove',function (e) {
// console.log(e.type,'....',self);
self.triggerListener('mousemove',self._createEventData(e,'mousemove'));
// 如果鼠标正处于按下状态,则模拟触发dragging事件
if(mouseDownEvent){
// 判断是否真的移动,是否真的存在拖拽
if(mouseDownEvent && e.clientX===mouseDownEvent.clientX&&e.clientY===mouseDownEvent.clientY) return;
// 解决webkit内核mouseup自动触发mousemove的BUG
if(mouseUpEvent && e.clientX===mouseDownEvent.clientX&&e.clientY===mouseDownEvent.clientY) return;
// 取消长按事件
clearTimeout(longTapTimeoutID);
// dragging之前,触发一次dragstart事件
if(!dragStartFlag){
self.triggerListener('dragstart',self._createEventData(mouseDownEvent,'dragstart'));
// 设置标志位
dragStartFlag = true;
}
e.identifier=identifier;
self.triggerListener('dragging',self._createEventData(e,'dragging'));
}else{
// 取消长按事件
clearTimeout(longTapTimeoutID);
}
});
/**
* 需求实现:
* 1、事件传递给所有图层
* 2、事件传递给 鼠标按下时所指的UI
* 3、清除按下时鼠标的位置,及按下时鼠标所指的UI
* */
this.option.target.addEventListener('mouseup',function (e) {
// console.log(e.type,'...');
e.identifier=identifier;
mouseUpEvent = self._createEventData(e);
// 如果存在拖拽标志位,抬起鼠标时需要发送dragend事件
if(dragStartFlag){
// 取消长按事件
clearTimeout(longTapTimeoutID);
self.triggerListener('dragend',self._createEventData(e,'dragend'));
// 如果鼠标按下期间存在移动行为,且移动范围大于30px,按下时间在300ms内,则认为该操作是swipe
if(dragStartFlag&&mouseUpEvent.createTime-mouseDownEvent.createTime<300 ){
if(Math.abs(mouseUpEvent.pageX-mouseDownEvent.pageX)>30 || Math.abs(mouseUpEvent.pageY-mouseDownEvent.pageY)>30){
var type = 'swipe'+self._getSwipeDirection(mouseDownEvent.pageX,mouseDownEvent.pageY,mouseUpEvent.pageX,mouseUpEvent.pageY);
console.log('swipe',type);
self.triggerListener('log',type);
self.triggerListener(type,self._createEventData(mouseDownEvent,type));
}
}
dragStartFlag = false;
mouseDownEvent = null;
return null;
}
//不存在拖拽事件,且开始按下鼠标和结束时间在300ms内,认为是tap事件
if(!dragStartFlag&&mouseDownEvent && mouseUpEvent.createTime-mouseDownEvent.createTime<300){
// 取消长按事件
clearTimeout(longTapTimeoutID);
var curTap = self._createEventData(mouseDownEvent,'tap');
self.triggerListener('tap',curTap);
self.triggerListener('log','tap triggered');
// 两次点击在300ms内,并且两次点击的范围在10px内,则认为是doubletap事件
if(preTap && curTap.createTime-preTap.createTime<300 && Math.abs(preTap.pageX-curTap.pageX)<10&& Math.abs(preTap.pageY-curTap.pageY)<10){
self.triggerListener('doubletap',self._createEventData(curTap,'doubletap'));
self.triggerListener('log','doubletap triggered');
preTap = null;
return this;
}
preTap=curTap;
mouseDownEvent = null;
return this;
}
});
// 若鼠标超出舞台,给所有图层广播一个mouseup事件,解决拖拽超出舞台的问题。
// this.option.target.addEventListener("mouseout",function (e) {
// var yccEvent = new Ycc.Event({
// type:"mouseup",
// x:parseInt(e.clientX - self.ctx.canvas.getBoundingClientRect().left),
// y:parseInt(e.clientY - self.ctx.canvas.getBoundingClientRect().top)
// });
// if(yccEvent.x>parseInt(this.width)) yccEvent.x = parseInt(this.width);
// if(yccEvent.x<0) yccEvent.x=0;
// if(yccEvent.y>parseInt(this.height)) yccEvent.y = parseInt(this.height);
// if(yccEvent.y<0) yccEvent.y=0;
//
// for(var i=self.layerList.length-1;i>=0;i--){
// var layer = self.layerList[i];
// if(!layer.enableEventManager) continue;
// layer.triggerListener(yccEvent.type,yccEvent);
// }
// });
};
/**
* 构造筛选事件中的有用信息
* @param event {MouseEvent | TouchEvent} 鼠标事件或者触摸事件
* @param [type] {String} 事件类型,可选
* @return {{target: null, clientX: number, clientY: number, pageX: number, pageY: number, screenX: number, screenY: number, force: number}}
* @private
*/
Ycc.Gesture.prototype._createEventData = function (event,type) {
var data={
/**
* 事件类型
*/
type:"",
/**
* 事件触发对象
*/
target:null,
/**
* 事件的生命周期ID,只在拖拽过程中存在,存在时此值大于-1
* PC端表示mousedown直至mouseup整个周期
* mobile端表示touchstart直至touchend整个周期
*/
identifier:-1,
clientX:0,
clientY:0,
pageX:0,
pageY:0,
screenX:0,
screenY:0,
force:1,
/**
* 创建时间
*/
createTime:Date.now()
};
data = Ycc.utils.extend(data,event);
data.type=type;
return data;
};
/**
* 获取某个触摸点的swipe方向
* @private
*/
Ycc.Gesture.prototype._getSwipeDirection = function (x1,y1,x2,y2) {
return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'left' : 'right') : (y1 - y2 > 0 ? 'up' : 'down');
};
/**
* 获取缩放比例
* @param preLife
* @param curLife
* @return {number}
* @private
*/
Ycc.Gesture.prototype.getZoomRateAndRotateAngle = function (preLife, curLife) {
this.triggerListener('log','preLife');
this.triggerListener('log',preLife);
this.triggerListener('log','curLife');
this.triggerListener('log',curLife);
var x0=preLife.startTouchEvent.pageX,
y0=preLife.startTouchEvent.pageY,
x1=curLife.startTouchEvent.pageX,
y1=curLife.startTouchEvent.pageY;
var preMoveTouch = preLife.moveTouchEventList.length>0?preLife.moveTouchEventList[preLife.moveTouchEventList.length-1]:preLife.startTouchEvent;
var curMoveTouch = curLife.moveTouchEventList.length>0?curLife.moveTouchEventList[curLife.moveTouchEventList.length-1]:curLife.startTouchEvent;
var x0move=preMoveTouch.pageX,
y0move=preMoveTouch.pageY,
x1move=curMoveTouch.pageX,
y1move=curMoveTouch.pageY;
var vector0 = new Ycc.Math.Vector(x1-x0,y1-y0),
vector1 = new Ycc.Math.Vector(x1move-x0move,y1move-y0move);
var angle = Math.acos(vector1.dot(vector0)/(vector1.getLength()*vector0.getLength()))/Math.PI*180;
return {
rate:vector1.getLength()/vector0.getLength(),
angle:angle*(vector1.cross(vector0).z>0?-1:1)
};//(new Ycc.Math.Vector(x1move-x0move,y1move-y0move).getLength())/(new Ycc.Math.Vector(x1-x0,y1-y0).getLength());
};
})(Ycc);
;/**
* @file Ycc.Layer.class.js
* @author xiaohei
* @date 2017/11/16
* @description Ycc.Layer.class文件
* @requires Ycc.Listener
*/
(function (Ycc) {
var layerIndex = 0;
/**
* 图层类。
* 每新建一个图层,都会新建一个canvas元素。
* 每个图层都跟这个canvas元素绑定。
* @param yccInstance {Ycc} ycc实例
* @param option {object} 配置项
* @param option.enableEventManager {boolean} 是否监听舞台事件
*
* @constructor
* @extends Ycc.Listener
*/
Ycc.Layer = function(yccInstance,option){
Ycc.Listener.call(this);
option = option || {};
/**
* 类型
*/
this.yccClass = Ycc.Layer;
/**
* 存储图层中的所有UI。UI的顺序,即为图层中的渲染顺序。
* @type {Ycc.UI[]}
*/
this.uiList = [];
/**
* 该图层ui的总数(只在渲染之后赋值)
* @type {number}
*/
this.uiCountRecursion = 0;
/**
* ycc实例的引用
*/
this.yccInstance = yccInstance;
/**
* 当前图层的绘图环境
* @type {CanvasRenderingContext2D}
*/
this.ctx = null;
/**
* 图层id
*/
this.id = layerIndex++;
/**
* 图层类型。
* `ui`表示用于绘图的图层。`tool`表示辅助的工具图层。`text`表示文字图层。
* 默认为`ui`。
*/
this.type = "ui";
/**
* 图层中的文字。仅当图层type为text时有值。
* @type {string}
*/
this.textValue = "";
/**
* 图层名称
* @type {string}
*/
this.name = option.name?option.name:"图层_"+this.type+"_"+this.id;
/**
* 图层位置的x坐标。默认与舞台左上角重合
* @type {number}
*/
this.x = 0;
/**
* 图层位置的Y坐标。默认与舞台左上角重合
* @type {number}
*/
this.y = 0;
/**
* 图层宽
* @type {number}
*/
this.width = yccInstance.getStageWidth();
/**
* 图层高
* @type {number}
*/
this.height = yccInstance.getStageHeight();
/**
* 图层是否显示
*/
this.show = true;
/**
* 是否监听舞台的事件。用于控制舞台事件是否广播至图层。默认关闭
* @type {boolean}
*/
this.enableEventManager = false;
/**
* 是否接收每帧更新的通知。默认为false
* @type {boolean}
*/
this.enableFrameEvent = false;
/**
* 若接收通知,此函数为接收通知的回调函数。当且仅当enableFrameEvent为true时生效
* @type {function}
*/
this.onFrameComing = function () {};
// 覆盖参数
Ycc.utils.extend(this,option);
// 初始化
this.init();
};
Ycc.Layer.prototype = new Ycc.Listener();
Ycc.Layer.prototype.constructor = Ycc.Layer;
/**
* 释放layer的内存,等待GC
* 将所有引用属性置为null
* @param layer
*/
Ycc.Layer.release = function (layer) {
Ycc.Listener.release(layer);
/**
* 类型
*/
layer.yccClass = null;
/**
* 存储图层中的所有UI。UI的顺序,即为图层中的渲染顺序。
* @type {Ycc.UI[]}
*/
layer.uiList = null;
/**
* ycc实例的引用
*/
layer.yccInstance = null;
/**
* 图层是否显示
*/
layer.show = false;
/**
* 是否监听舞台的事件。用于控制舞台事件是否广播至图层。默认关闭
* @type {boolean}
*/
layer.enableEventManager = false;
/**
* 是否接收每帧更新的通知。默认为false
* @type {boolean}
*/
layer.enableFrameEvent = false;
/**
* 若接收通知,此函数为接收通知的回调函数。当且仅当enableFrameEvent为true时生效
* @type {function}
*/
layer.onFrameComing = null;
};
/**
* 初始化
* @return {null}
*/
Ycc.Layer.prototype.init = function () {
var self = this;
// 初始化图层属性
this.ctx = this.yccInstance.ctx;
// 初始化画布属性
self._setCtxProps();
// 初始化图层事件
// self._initEvent();
};
/**
* 事件的初始化。此方法已废弃,改由舞台转发事件
* <br> 注:如果鼠标按下与抬起的位置有变动,默认不会触发click事件。
* @private
*/
Ycc.Layer.prototype._initEvent = function () {
var self = this;
// 记录鼠标按下的事件
var mouseDownYccEvent = null;
// 记录鼠标抬起的事件
var mouseUpYccEvent = null;
// 鼠标是否已经移动
var mouseHasMove = false;
// 是否有拖拽事件触发的标志位
var dragFlag = false;
this.addListener("click",function (e) {
// 如果鼠标已经改变了位置,那么click事件不触发
if(mouseHasMove) return;
defaultMouseListener(e);
});
this.addListener("mousedown",function (e) {
mouseHasMove = false;
dragFlag = false;
mouseDownYccEvent = e;
defaultMouseListener(e);
});
this.addListener("mouseup",function (e) {
if(dragFlag){
var dragendEvent = new Ycc.Event({
type:"dragend",
x:e.x,
y:e.y,
mouseDownYccEvent:mouseDownYccEvent
});
self.triggerListener("dragend",dragendEvent);
if(mouseDownYccEvent&&mouseDownYccEvent.target){
self.target = mouseDownYccEvent.target;
self.target.triggerListener("dragend",dragendEvent);
}
}
e.mouseDownYccEvent = mouseDownYccEvent = null;
mouseUpYccEvent = e;
defaultMouseListener(e);
});
this.addListener("mousemove",mouseMoveListener);
/**
* 图层中鼠标移动的监听器
* @param e {Ycc.Event}
*/
function mouseMoveListener(e) {
// 判断事件是否已经被阻止
if(e.stop) return;
// 判断是否真的移动
if(mouseDownYccEvent && e.x===mouseDownYccEvent.x&&e.y===mouseDownYccEvent.y) return;
// 解决webkit内核mouseup自动触发mousemove的BUG
if(mouseUpYccEvent && e.x===mouseUpYccEvent.x&&e.y===mouseUpYccEvent.y) {
return;
}
// 设置已经移动的标志位
mouseHasMove = true;
// 如果鼠标已经按下,则表示拖拽事件。
if(mouseDownYccEvent){
// 1.拖拽之前,触发一次dragstart事件
if(!dragFlag){
var dragStartEvent = new Ycc.Event({
type:"dragstart",
x:mouseDownYccEvent.x,
y:mouseDownYccEvent.y,
mouseDownYccEvent:mouseDownYccEvent
});
// 先触发图层的拖拽事件,该事件没有target属性
self.triggerListener(dragStartEvent.type,dragStartEvent);
if(mouseDownYccEvent.target){
dragStartEvent.target = mouseDownYccEvent.target;
dragStartEvent.target.triggerListener(dragStartEvent.type,dragStartEvent);
}
}
// 2.修改拖拽已经发生的标志位
dragFlag = true;
// 3.触发dragging事件
var draggingEvent = new Ycc.Event({
type:"dragging",
x:e.x,
y:e.y,
mouseDownYccEvent:mouseDownYccEvent
});
// 先触发图层的拖拽事件,该事件没有target属性
self.triggerListener(draggingEvent.type,draggingEvent);
if(mouseDownYccEvent.target){
draggingEvent.target = mouseDownYccEvent.target;
draggingEvent.target.triggerListener(draggingEvent.type,draggingEvent);
}
// 触发拖拽事件时,不再触发鼠标的移动事件,所以此处直接返回
return null;
}
// 下面处理普通的鼠标移动事件
for(var i = self.uiList.length-1;i>=0;i--){
var ui = self.uiList[i];
// 图层内部UI的相对坐标
var dot = new Ycc.Math.Dot(e.x - ui.belongTo.x,e.y - ui.belongTo.y);
// 如果位于rect内,触发事件,并阻止继续传递
if(dot.isInRect(ui.rect)){
e.stop = true;
e.mouseDownYccEvent = mouseDownYccEvent;
e.mouseUpYccEvent = mouseUpYccEvent;
e.target = ui;
ui.triggerListener(e.type,e);
break;
}
}
}
/**
* 默认的事件监听器。默认鼠标事件触发点位于rect内,事件才转发给UI。
* @todo 其他事件需要考虑图层坐标
* @param e {Ycc.Event} ycc事件,e中的坐标值x、y为绝对坐标
*/
function defaultMouseListener(e) {
if(e.stop) return;
for(var i = self.uiList.length-1;i>=0;i--){
var ui = self.uiList[i];
var dot = new Ycc.Math.Dot(e.x,e.y);
// 如果位于rect内,并且事件未被阻止,触发事件,并阻止继续传递
if(ui.rect && dot.isInRect(ui.getAbsolutePosition()) && e.stop===false){
e.stop = true;
e.mouseDownYccEvent = mouseDownYccEvent;
e.mouseUpYccEvent = mouseUpYccEvent;
e.target = ui;
e.targetDeltaPosition = new Ycc.Math.Dot(e.x-ui.getAbsolutePosition().x,e.y-ui.getAbsolutePosition().y);
ui.triggerListener(e.type,e);
break;
}
}
}
/**
* 直接将事件转发给UI。不做任何处理。
* @param e
*/
function broadcastDirect(e) {
e.mouseDownYccEvent = mouseDownYccEvent;
for(var i = 0;i<self.uiList.length;i++){
var ui = self.uiList[i];
ui.triggerListener(e.type,e);
}
}
};
/**
* 设置画布所有的属性
*/
Ycc.Layer.prototype._setCtxProps = function (props) {
var self = this;
var ctxConfig = {
fontStyle:"normal",
fontVariant:"normal",
fontWeight:"normal",
fontSize:"16px",
fontFamily:"微软雅黑",
font:"16px 微软雅黑",
textBaseline:"hanging",
fillStyle:"red",
strokeStyle:"blue"
};
ctxConfig = Ycc.utils.extend(ctxConfig,props);
ctxConfig.font = [ctxConfig.fontStyle,ctxConfig.fontVariant,ctxConfig.fontWeight,ctxConfig.fontSize,ctxConfig.fontFamily].join(" ");
for(var key in ctxConfig){
if(!ctxConfig.hasOwnProperty(key)) continue;
self.ctx[key] = ctxConfig[key];
}
};
/**
* 清除图层
*/
Ycc.Layer.prototype.clear = function () {
this.ctx.clearRect(0,0,this.width,this.height);
};
/**
* 清空图层内的所有UI
*/
Ycc.Layer.prototype.removeAllUI = function () {
this.uiList.forEach(function (ui) {
Ycc.UI.release(ui);
});
this.uiList.length=0;
};
/**
* 删除自身
*/
Ycc.Layer.prototype.removeSelf = function () {
this.removeAllUI();
this.yccInstance.layerManager.deleteLayer(this);
Ycc.Layer.release(this);
};
/**
* 渲染图层至舞台
*/
Ycc.Layer.prototype.renderToStage = function () {
if(this.show)
this.yccInstance.ctx.drawImage(this.ctx.canvas,0,0,this.width,this.height);
};
/**
* 添加一个UI图形至图层,如果设置了beforUI,该UI会被添加至该UI之前
* @param ui {Ycc.UI} UI图形
* @param beforeUI {Ycc.UI|null} UI图形
*/
Ycc.Layer.prototype.addUI = function (ui,beforeUI) {
var self = this;
// 遍历所有节点,初始化
ui.itor().each(function (child) {
child.init(self);
});
if(!beforeUI)
return this.uiList.push(ui);
var index = this.uiList.indexOf(beforeUI);
if(index===-1)
return this.uiList.push(ui);
this.uiList.splice(index,0,ui);
};
/**
* 删除图层内的某个UI图形,及其子UI
* @param ui
*/
Ycc.Layer.prototype.removeUI = function (ui) {
if(!ui) return false;
var index = this.uiList.indexOf(ui);
if(index===-1) return false;
Ycc.UI.release(ui);
this.uiList[index]=null;
this.uiList.splice(index,1);
return true;
};
/**
* 渲染Layer。
* <br>注意:并没有渲染至舞台。
*/
Ycc.Layer.prototype.render = function () {
for(var i=0;i<this.uiList.length;i++){
if(!this.uiList[i].show) continue;
this.uiList[i].render();
}
};
/**
* 重绘图层。
* <br>注意:并没有渲染至舞台。
*/
Ycc.Layer.prototype.reRender = function () {
// this.clear();
var self = this;
self.uiCountRecursion=0;
for(var i=0;i<this.uiList.length;i++){
if(!this.uiList[i].show) continue;
//this.uiList[i].__render();
// 按树的层次向下渲染
this.uiList[i].itor().depthDown(function (ui, level) {
//console.log(level,ui);
self.uiCountRecursion++;
if(ui.show)
ui.__render();
else
return -1;
});
}
// 兼容wx端,wx端多一个draw API
self.ctx.draw && self.ctx.draw();
};
/**
* 获取图层中某个点所对应的最上层UI,最上层UI根据层级向下遍历,取层级最深的可见UI。
* @param dot {Ycc.Math.Dot} 点坐标,为舞台的绝对坐标
* @param uiIsShow {Boolean} 是否只获取显示在舞台上的UI,默认为true
* @return {UI}
*/
Ycc.Layer.prototype.getUIFromPointer = function (dot,uiIsShow) {
uiIsShow = Ycc.utils.isBoolean(uiIsShow)?uiIsShow:true;
var self = this;
var temp = null;
/*for(var i =self.uiList.length-1;i>=0;i--){
var ui = self.uiList[i];
if(uiIsShow&&!ui.show) continue;
// 右子树优先寻找
ui.itor().rightChildFirst(function (child) {
console.log(child);
// 跳过不可见的UI
if(uiIsShow&&!child.show) return false;
// 如果位于rect内,此处根据绝对坐标比较
if(dot.isInRect(child.getAbsolutePosition())){
console.log(child,222);
temp = child;
return true;
}
});
}*/
var tempLevel = 0;
for(var i=0;i<this.uiList.length;i++){
var ui = self.uiList[i];
if(uiIsShow&&!ui.show) continue;
//this.uiList[i].__render();
// 按树的层次向下遍历
this.uiList[i].itor().depthDown(function (child, level) {
// 跳过不可见的UI,返回-1阻止继续遍历其子UI
if(uiIsShow&&!child.show) return -1;
// 如果位于rect内,并且层级更深,则暂存,此处根据绝对坐标比较
if(child.containDot(dot) && level>=tempLevel){
temp = child;
tempLevel=level;
}
});
}
return temp;
};
/**
* 根据图层坐标,将图层内某个点的相对坐标(相对于图层),转换为舞台的绝对坐标
* @param dotOrArr {Ycc.Math.Dot | Ycc.Math.Dot[]}
* @return {Ycc.Math.Dot | Ycc.Math.Dot[]}
*/
Ycc.Layer.prototype.transformToAbsolute = function (dotOrArr) {
var res = null;
if(Ycc.utils.isArray(dotOrArr)){
res = [];
for(var i=0;i<dotOrArr.length;i++){
var resDot = new Ycc.Math.Dot(0,0);
var dot = dotOrArr[i];
resDot.x=this.x+dot.x;
resDot.y=this.y+dot.y;
res.push(resDot);
}
return res;
}
res = new Ycc.Math.Dot(0,0);
res.x = this.x+(dotOrArr.x);
res.y = this.y+(dotOrArr.y);
return res;
};
/**
* 根据图层坐标,将某个点的绝对坐标,转换为图层内的相对坐标
* @param dotOrArr {Ycc.Math.Dot | Ycc.Math.Dot[]}
* @return {Ycc.Math.Dot | Ycc.Math.Dot[]}
*/
Ycc.Layer.prototype.transformToLocal = function (dotOrArr) {
var res = null;
if(Ycc.utils.isArray(dotOrArr)){
res = [];
for(var i=0;i<dotOrArr.length;i++){
var resDot = new Ycc.Math.Dot(0,0);
var dot = dotOrArr[i];
resDot.x=dot.x-this.x;
resDot.y=dot.y-this.y;
res.push(resDot);
}
return res;
}
res = new Ycc.Math.Dot(0,0);
res.x = (dotOrArr.x)-this.x;
res.y = (dotOrArr.y)-this.y;
return res;
};
})(Ycc);;/**
* @file Ycc.Layer.class.js
* @author xiaohei
* @date 2017/10/23
* @description Ycc.LayerManager.class文件
*/
(function (Ycc) {
/**
* Ycc的图层管理类。每个图层管理器都与一个canvas舞台绑定。
* @param yccInstance {Ycc} ycc实例
* @constructor
*/
Ycc.LayerManager = function (yccInstance) {
/**
* ycc实例
*/
this.yccInstance = yccInstance;
/**
* 保存渲染时间,主要是reReader方法的耗时,开发者可以在每次reRender调用后获取该值
* @type {number}
* @readonly
*/
this.renderTime = 0;
/**
* 保存渲染的UI个数,主要是reReader方法中的UI个数,开发者可以在每次reRender调用后获取该值
* @type {number}
* @readonly
*/
this.renderUiCount = 0;
};
Ycc.LayerManager.prototype.init = function () {
};
/**
* 新建图层
* @param config
*/
Ycc.LayerManager.prototype.newLayer = function (config) {
var layer = new Ycc.Layer(this.yccInstance,config);
this.yccInstance.layerList.push(layer);
return layer;
};
/**
* 删除图层。
* @param layer
*/
Ycc.LayerManager.prototype.deleteLayer = function (layer) {
var layerList = this.yccInstance.layerList;
for(var i = 0;i<layerList.length;i++){
if(layerList[i].id === layer.id){
this.yccInstance.layerList.splice(i,1);
return layer;
}
}
return layer;
};
/**
* 删除所有图层
*/
Ycc.LayerManager.prototype.deleteAllLayer = function () {
for(var i=0;i<this.yccInstance.layerList.length;i++){
var layer = this.yccInstance.layerList[i];
layer.removeAllUI();
Ycc.Layer.release(layer);
layer = null;
}
this.yccInstance.layerList=[];
};
/**
* 重新将所有图层绘制至舞台。不显示的图层也会更新。
*/
Ycc.LayerManager.prototype.reRenderAllLayerToStage = function () {
var t1 = Date.now();
this.renderUiCount = 0;
this.yccInstance.clearStage();
for(var i=0;i<this.yccInstance.layerList.length;i++){
var layer = this.yccInstance.layerList[i];
// 该图层是否可见
if(!layer.show) continue;
layer.reRender();
this.renderUiCount+=layer.uiCountRecursion;
}
this.renderTime = Date.now()-t1;
};
/**
* 只允许某一个图层接收舞台事件
* @param layer {Layer} 允许接收事件的图层
*/
Ycc.LayerManager.prototype.enableEventManagerOnly = function (layer) {
if(!layer) return false;
for(var i=0;i<this.yccInstance.layerList.length;i++) {
this.yccInstance.layerList[i].enableEventManager = false;
}
layer.enableEventManager = true;
return this;
};
/**
* 允许所有图层接收舞台事件
* @param enable
* @return {Ycc.LayerManager}
*/
Ycc.LayerManager.prototype.enableEventManagerAll = function (enable) {
for(var i=0;i<this.yccInstance.layerList.length;i++) {
this.yccInstance.layerList[i].enableEventManager = enable;
}
return this;
};
/**
* 根据json数组绘制所有图层
* @param jsonArray {Array} json数组,示例:[{option,ui[]}]
* @return {*}
*/
Ycc.LayerManager.prototype.renderAllLayerByJsonArray = function (jsonArray) {
if(!Ycc.utils.isArray(jsonArray)){
return console.error('jsonArray is not an Array!',jsonArray);
}
var self = this;
for(var i=0;i<jsonArray.length;i++){
var layerConfig = jsonArray[i];
if(!Ycc.utils.isObj(layerConfig)){
return console.error('item in jsonArray should be an Object!',layerConfig);
}
var layer = self.newLayer(layerConfig.option);
for(var j=0;j<layerConfig.ui.length;j++){
var uiConfig = layerConfig.ui[j];
layer.addUI(new Ycc.UI[uiConfig.type](uiConfig.option));
}
layer.render();
}
};
})(Ycc);;/**
* @file Ycc.PhotoManager.class.js
* @author xiaohei
* @date 2017/9/30
* @description Ycc.PhotoManager.class文件。
* Ycc实例的快照管理类
*
*/
(function (Ycc){
/**
* 照片数据结构类
* @param imageData
* @constructor
*/
var Photo = function(imageData) {
this.imageData = imageData;
this.createTime = new Date();
this.id = this.createTime.getTime();
};
/**
* Ycc实例的快照管理类
* @param yccInstance {Ycc}
* @constructor
*/
Ycc.PhotoManager = function (yccInstance) {
this.yccInstance = yccInstance;
this.ctx = yccInstance.ctx;
this._photos = [];
};
/**
* 保存快照,即保存当前的原子图形渲染步骤
*/
Ycc.PhotoManager.prototype.takePhoto = function () {
this._photos.push(new Photo(this.ctx.getImageData(0,0,this.yccInstance.getStageWidth(),this.yccInstance.getStageHeight())));
return this;
};
/**
* 获取保存的历史照片
* @returns {Array}
*/
Ycc.PhotoManager.prototype.getHistoryPhotos = function () {
return this._photos;
};
/**
* 显示照片
* @param photo {Photo}
* @returns {Photo}
*/
Ycc.PhotoManager.prototype.showPhoto = function (photo) {
this.ctx.putImageData(photo.imageData,0,0);
return photo;
};
/**
* 显示最后一次保存的快照
* @returns {boolean}
*/
Ycc.PhotoManager.prototype.showLastPhoto = function () {
var len = this._photos.length;
var photo = false;
if(len>=1){
photo = this._photos[len-1];
this.showPhoto(photo);
}
return photo;
};
/**
* 删除照片
* @param photoId 照片的id
* @returns {*}
*/
Ycc.PhotoManager.prototype.delPhotoById = function (photoId) {
var tempPhotos = this._photos.slice(0);
for(var i=0;i<tempPhotos.length;i++){
if(tempPhotos[i].id === photoId){
tempPhotos.splice(i+1,1);
this._photos = tempPhotos;
return tempPhotos[i];
}
}
return false;
};
})(Ycc);;/**
* @file ycc.ui.js
* @author xiaohei
* @date 2016/4/1
*
* @desc
* 控制所有的绘图基本操作,保存所有的绘图步骤,并提供回退、前进、清空、快照等方法。
* 不管理图形颜色、阴影、文字等内容。
* 所有图形绘制都为同步操作
*
* @requires Ycc
* @requires Ycc.utils
* */
(function(Ycc){
// 检查依赖项
if(!Ycc || !Ycc.utils){
return console.error("Error: Ycc.ui needs module `Ycc` and `Ycc.utils`!");
}
// 方法别名
var isObj = Ycc.utils.isObj,
isArr = Ycc.utils.isArray,
isString = Ycc.utils.isString,
isNum = Ycc.utils.isNum,
extend = Ycc.utils.extend;
/*******************************************************************************
* 定义所需要的类
******************************************************************************/
/**
* UI类,提供绘图基本的原子图形和组合图形。
* 每个UI类的对象都跟一个Ycc绑定。
*
* @param yccInstance {Ycc}
* @constructor
*/
Ycc.UI = function(yccInstance){
this.yccClass = Ycc.UI;
/**
* 保存的快照,每个元素都是`getImageData`的返回值
* @type {Array}
* @private
*/
this._photos = [];
/**
* 当前绘图环境
*/
this.ctx = yccInstance.ctx;
/**
* 当前绘图环境的宽
*/
this.ctxWidth = yccInstance.getStageWidth();
/**
* 当前绘图环境的高
*/
this.ctxHeight = yccInstance.getStageHeight();
};
/*******************************************************************************
* 定义UI类的基础图形,不带rect容器的图形
******************************************************************************/
/**
* 文字
* @param positionDot
* @param content
* @param [fill]
* @returns {Ycc.UI}
*/
Ycc.UI.prototype.text = function (positionDot,content,fill) {
this.ctx.save();
if(fill)
this.ctx.fillText(content,positionDot[0],positionDot[1]);
else
this.ctx.strokeText(content,positionDot[0],positionDot[1]);
this.ctx.restore();
return this;
};
/**
* 画线
* @param dot1
* @param dot2
* @returns {Ycc.UI}
*/
Ycc.UI.prototype.line = function (dot1, dot2) {
this.ctx.save();
this.ctx.beginPath();
this.ctx.moveTo(dot1[0], dot1[1]);
this.ctx.lineTo(dot2[0], dot2[1]);
this.ctx.stroke();
this.ctx.closePath();
this.ctx.restore();
return this;
};
/**
* 矩形
* @param left_top_dot
* @param right_bottom_dot
* @param fill
*/
Ycc.UI.prototype.rect=function (left_top_dot,right_bottom_dot,fill){
this.ctx.save();
this.ctx.beginPath();
this.ctx.rect(left_top_dot[0],left_top_dot[1],right_bottom_dot[0]-left_top_dot[0],right_bottom_dot[1]-left_top_dot[1]);
this.ctx.closePath();
if(!fill)
this.ctx.stroke();
else
this.ctx.fill();
this.ctx.restore();
return this;
};
/**
* 椭圆
* @param centrePoint {Dot} 椭圆中心点
* @param width {Number} 长半轴
* @param height {Number} 短半轴
* @param rotateAngle {Number} 旋转角
* @param fill {Boolean} 是否填充
*/
Ycc.UI.prototype.ellipse = function(centrePoint,width,height,rotateAngle,fill) {
this.ctx.save();
var r = (width > height) ? width : height;
// 计算压缩比例
var ratioX = width / r;
var ratioY = height / r;
// 默认旋转中心位于画布左上角,需要改变旋转中心点
this.ctx.translate(centrePoint[0],centrePoint[1]);
this.ctx.rotate(parseInt(rotateAngle)*Math.PI/180);
// 再变换回原来的旋转中心点
this.ctx.translate(-centrePoint[0],-centrePoint[1]);
this.ctx.scale(ratioX, ratioY);
this.ctx.beginPath();
this.ctx.arc(centrePoint[0] / ratioX, centrePoint[1]/ ratioY, r, 0, 2 * Math.PI, false);
this.ctx.closePath();
if(!fill)
this.ctx.stroke();
else
this.ctx.fill();
this.ctx.restore();
return this;
};
/**
* 圆弧
* @param centrePoint 圆心
* @param r 半径
* @param startAngle 起始角
* @param endAngle 结束角
* @param [counterclockwise] 方向
*/
Ycc.UI.prototype.circleArc = function (centrePoint, r,startAngle,endAngle,counterclockwise) {
this.ctx.save();
this.ctx.beginPath();
this.ctx.arc(
centrePoint[0],
centrePoint[1],
r,
startAngle/180*Math.PI,
endAngle/180*Math.PI,
counterclockwise
);
this.ctx.stroke();
this.ctx.closePath();
this.ctx.restore();
return this;
};
/**
* 扇形
* @param centrePoint 圆心
* @param r 半径
* @param startAngle 起始角
* @param endAngle 结束角
* @param [fill] 是否填充
* @param [counterclockwise] 方向
*/
Ycc.UI.prototype.sector = function (centrePoint, r,startAngle,endAngle,fill,counterclockwise) {
this.ctx.save();
this.ctx.beginPath();
this.ctx.lineTo(centrePoint[0],centrePoint[1]);
this.ctx.arc(centrePoint[0],centrePoint[1],r,startAngle*Math.PI/180,endAngle*Math.PI/180,counterclockwise);
this.ctx.lineTo(centrePoint[0],centrePoint[1]);
this.ctx.closePath();
if(!fill)
this.ctx.stroke();
else
this.ctx.fill();
this.ctx.restore();
return this;
};
/**
* 根据多个点画折线,可以用此方法实现跟随鼠标
* @param pointList {Array} Dot数组,即二维数组
*/
Ycc.UI.prototype.foldLine = function (pointList) {
if(pointList.length<2) return console.error("Error: 参数错误!");
this.ctx.save();
this.ctx.beginPath();
this.ctx.moveTo(pointList[0][0],pointList[0][1]);
for(var i =0;i<pointList.length;i++){
this.ctx.lineTo(pointList[i][0],pointList[i][1]);
}
this.ctx.stroke();
this.ctx.restore();
return this;
};
/**
* 圆
* @param centrePoint 圆心
* @param r 半径
* @param fill 是否填充
*/
Ycc.UI.prototype.circle = function(centrePoint, r, fill) {
this.ellipse(centrePoint,r,r,0,fill);
return this;
};
/**
* 绘制图片
* @param img {Image} 图片路径
* @param left_top_dot {Array} 左上角坐标
*/
Ycc.UI.prototype.image = function (img,left_top_dot){
var self = this;
left_top_dot = left_top_dot || [0,0];
self.ctx.save();
self.ctx.beginPath();
self.ctx.drawImage(img, left_top_dot[0], left_top_dot[1]); // 设置对应的图像对象,以及它在画布上的位置
self.ctx.closePath();
self.ctx.restore();
return this;
};
/**
* 给定宽度,获取能容纳的最长字符串
* @param content {string}
* @param width {number}
* @return {string}
* @private
*/
Ycc.UI.prototype._getMaxLenContent = function (content,width) {
var out = content;
var outW = 0;
if(this.ctx.measureText(content).width<=width)
return content;
for(var i = 0;i<content.length;i++){
out = content.slice(0,i);
outW = this.ctx.measureText(out).width;
if(outW>width){
return content.slice(0,i-1);
}
}
};
/*******************************************************************************
* 定义UI类的控制方法
******************************************************************************/
/**
* 缩放绘图,对之后的所有操作都有效
* @param scaleX
* @param scaleY
* @returns {Ycc.UI}
*/
Ycc.UI.prototype.scale = function (scaleX, scaleY) {
this.ctx.scale(scaleX,scaleY);
return this;
};
/**
* 清除画布
*/
Ycc.UI.prototype.clear=function () {
var defaultSet = {
//默认填充颜色
fillStyle:"#fff"
};
this.ctx.fillStyle = defaultSet.fillStyle;
this.ctx.save();
this.ctx.beginPath();
this.ctx.rect(0,0,this.ctxWidth,this.ctxHeight);
this.ctx.fill();
this.ctx.closePath();
this.ctx.restore();
return this;
};
})(Ycc);;/**
* @file Ycc.UI.Base.class.js
* @author xiaohei
* @date 2017/11/15
* @description Ycc.UI.Base.class文件。
* 所有容器UI的基础类
* @requires Ycc.UI
* @requires Ycc.utils
*/
(function (Ycc) {
var uid = 0;
/**
* 所有UI类的基类。
* <br> 所有UI都必须遵循先计算、后绘制的流程
* 所有UI的rect坐标均为相对坐标,且相对于父级,若没有父级,则相对于图层
* UI只有在被添加至图层时,即layer.addUI(ui),UI的绘图环境ctx才会被初始化
*
* @constructor
* @extends Ycc.Listener Ycc.Tree
*/
Ycc.UI.Base = function (option) {
Ycc.Listener.call(this);
Ycc.Tree.call(this);
/**
* 构造器的引用,Ycc中的每个类都有此属性
*/
this.yccClass = Ycc.UI.Base;
/**
* UI的唯一ID
* @type {number}
*/
this.id = uid++;
/**
* UI实例的名字
* @type {string}
*/
this.name = "";
/**
* 绘图环境
* @type {null}
*/
this.ctx = null;
/**
* UI的绘图区域
* @type {Ycc.Math.Rect}
*/
this.rect = new Ycc.Math.Rect();
/**
* x相对父级的位置
* @type {number}
*/
this.x = 0;
/**
* y相对父级的位置
* @type {number}
*/
this.y = 0;
/**
* UI对象的锚点坐标。相对坐标。相对于rect的x
* 锚点坐标主要用于图形的旋转、平移、缩放
* @type {number}
*/
this.anchorX = 0;
/**
* UI对象的锚点坐标。相对坐标。相对于rect的y
* 锚点坐标主要用于图形的旋转、平移、缩放
* @type {number}
*/
this.anchorY = 0;
/**
* 相对于锚点的旋转角度
* @type {number}
*/
this.rotation = 0;
/**
* 容纳区的背景色
* @type {string}
*/
this.rectBgColor = "rgba(0,0,0,0)";
/**
* 容纳区的边框宽度
* @type {number}
*/
this.rectBorderWidth = 0;
/**
* 容纳区边框颜色
* @type {string}
*/
this.rectBorderColor = "#000";
/**
* 是否显示
* @type {boolean}
*/
this.show = true;
/**
* 默认情况下,UI阻止事件冒泡,但不会阻止事件传播给图层
* @type {boolean}
*/
this.stopEventBubbleUp = true;
/**
* 线条宽度
* @type {number}
*/
this.lineWidth = 1;
/**
* 填充颜色
* @type {string}
*/
this.fillStyle = "black";
/**
* 线条颜色
* @type {string}
*/
this.strokeStyle = "black";
/**
* UI所属的图层
* @type {Ycc.Layer}
*/
this.belongTo = null;
/**
* 用户自定义的数据
* @type {null}
*/
this.userData = null;
/**
* 是否显示缩放之前的位置
* @type {boolean}
*/
this.isShowRotateBeforeUI = false;
/**
* 基础绘图UI
* @type {Ycc.UI}
*/
this.baseUI = null;
/**
* 初始化之前的hook
* @type {function}
*/
this.beforeInit = null;
/**
* 初始化之后的hook
* @type {function}
*/
this.afterInit = null;
/**
* 计算属性前的hook
* @type {function}
*/
this.oncomputestart = null;
/**
* 计算属性后的hook
* @type {function}
*/
this.oncomputeend = null;
/**
* 渲染前的hook
* @type {function}
*/
this.onrenderstart = null;
/**
* 渲染后的hook
* @type {function}
*/
this.onrenderend = null;
this.extend(option);
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.Base.prototype,Ycc.Listener.prototype);
Ycc.utils.mergeObject(Ycc.UI.Base.prototype,Ycc.Tree.prototype);
/**
* 在某个图层中初始化UI
* @param layer {Layer} 图层
*/
Ycc.UI.Base.prototype.init = function (layer) {
Ycc.utils.isFn(this.beforeInit) && this.beforeInit();
this.belongTo = layer;
this.ctx = layer.ctx;
// 初始化时计算一次属性
this.computeUIProps();
Ycc.utils.isFn(this.afterInit) && this.afterInit();
};
/**
* 计算UI的各种属性。此操作必须在绘制之前调用。
* <br> 计算与绘制分离的好处是,在绘制UI之前就可以提前确定元素的各种信息,从而判断是否需要绘制。
* @override
*/
Ycc.UI.Base.prototype.computeUIProps = function () {
};
/**
* 渲染容纳区rect的背景色
* @param absoluteRect {Ycc.Math.Rect} 容纳区的绝对位置
*/
Ycc.UI.Base.prototype.renderRectBgColor = function (absoluteRect) {
var rect = absoluteRect;
var dots = this.getAbsolutePositionPolygon();
if(!dots||dots.length===0) return console.log(new Ycc.Debugger.Log("no polygon coordirates!").message);
this.ctx.save();
this.ctx.fillStyle = this.rectBgColor;
this.ctx.beginPath();
this.ctx.moveTo(dots[0].x,dots[0].y);
for(var i=1;i<dots.length-1;i++)
this.ctx.lineTo(dots[i].x,dots[i].y);
// this.ctx.rect(rect.x,rect.y,rect.width,rect.height);
this.ctx.closePath();
this.ctx.fill();
this.ctx.restore();
rect = null;
};
/**
* 渲染容纳区rect的边框
* @param absoluteRect {Ycc.Math.Rect} 容纳区的绝对位置
*/
Ycc.UI.Base.prototype.renderRectBorder = function (absoluteRect) {
// 边框宽度为0,不渲染
if(this.rectBorderWidth<=0) return;
var rect = absoluteRect;
this.ctx.save();
this.ctx.strokeStyle = this.rectBorderColor;
this.ctx.strokeWidth = this.rectBorderWidth;
this.ctx.beginPath();
this.ctx.rect(rect.x,rect.y,rect.width,rect.height);
this.ctx.closePath();
this.ctx.stroke();
this.ctx.restore();
rect = null;
};
/**
* 绘制UI平移、旋转之前的位置,用虚线绘制
* 需要子UI重载
* @param [ctx] 绘图环境,非必传
*/
Ycc.UI.Base.prototype.renderDashBeforeUI = function (ctx) {
};
/**
* 删除自身。
* 若子类包含多个UI,需要重载
*/
Ycc.UI.Base.prototype.removeSelf = function () {
this.belongTo.removeUI(this);
};
/**
* 添加子ui
* @param ui
* @return {Ycc.UI.Base}
*/
Ycc.UI.Base.prototype.addChild = function (ui) {
if(this.belongTo)
ui.init(this.belongTo);
this.addChildTree(ui);
return this;
};
/**
* 删除子ui
* @param ui
*/
Ycc.UI.Base.prototype.removeChild = function (ui) {
this.removeChildTree(ui);
Ycc.UI.release(ui);
return this;
};
/**
* 坐标系的缩放和旋转。
* 先缩放、再旋转。
* @todo 子类渲染前需要调用此方法
* @todo 多边形替换rect后,此方法废弃,不再调用
*/
Ycc.UI.Base.prototype.scaleAndRotate = function () {
// 坐标系缩放
// this.ctx.scale(this.scaleX,this.scaleY);
var rect = this.getAbsolutePositionRect();
// 坐标系旋转
this.ctx.translate(this.anchorX+rect.x,this.anchorY+rect.y);
this.ctx.rotate(this.rotation*Math.PI/180);
this.ctx.translate(-this.anchorX-rect.x,-this.anchorY-rect.y);
};
/**
* 判断ui是否存在于舞台之外,渲染时可以不予渲染
* 在compute之后使用,判断更准确
* @return {boolean}
*/
Ycc.UI.Base.prototype.isOutOfStage = function () {
var stageW = this.belongTo.yccInstance.getStageWidth();
var stageH = this.belongTo.yccInstance.getStageHeight();
var absolute = this.getAbsolutePosition();
return absolute.x>stageW
|| (absolute.x+absolute.width<0)
|| absolute.y>stageH
|| (absolute.y+absolute.height<0);
};
/**
* 递归释放内存,等待GC
* 将所有引用属性设为null
* @param uiNode ui节点
*/
Ycc.UI.release = function (uiNode) {
uiNode.itor().leftChildFirst(function (ui) {
// console.log('release '+(!!ui.yccClass?ui.yccClass.name:''),ui);
if(ui.yccClass===Ycc.UI.Image || ui.yccClass===Ycc.UI.ImageFrameAnimation)
ui.res=null;
// 释放Tree的内存
Ycc.Tree.release(ui);
// 释放listener内存
Ycc.Listener.release(ui);
/////////////// 释放UI内存
ui.yccInstance=null;
/**
* 构造器的引用,Ycc中的每个类都有此属性
*/
ui.yccClass = null;
/**
* 绘图环境
* @type {null}
*/
ui.ctx = null;
/**
* UI所属的图层
* @type {Ycc.Layer}
*/
ui.belongTo = null;
/**
* 用户自定义的数据
* @type {null}
*/
ui.userData = null;
/**
* 基础绘图UI
* @type {Ycc.UI}
*/
ui.baseUI = null;
/**
* 初始化之前的hook
* @type {function}
*/
ui.beforeInit = null;
/**
* 初始化之后的hook
* @type {function}
*/
ui.afterInit = null;
/**
* 计算属性前的hook
* @type {function}
*/
ui.oncomputestart = null;
/**
* 计算属性后的hook
* @type {function}
*/
ui.oncomputeend = null;
/**
* 渲染前的hook
* @type {function}
*/
ui.onrenderstart = null;
/**
* 渲染后的hook
* @type {function}
*/
ui.onrenderend = null;
});
};
/**
* 渲染至绘图环境。
* <br> 注意:重写此方法时,不能修改UI类的属性。修改属性,应该放在computeUIProps方法内。
* @param [ctx] 绘图环境。可选
* @override
*/
Ycc.UI.Base.prototype.render = function (ctx) {
};
/**
* 绘制UI的容器(红色小方框)
* @param absoluteRect {Ycc.Math.Rect} UI的绝对坐标
* @private
*/
Ycc.UI.Base.prototype._renderContainer = function (absoluteRect) {
var rect = absoluteRect;
this.ctx.save();
this.ctx.beginPath();
this.ctx.strokeStyle = "#ff0000";
this.ctx.rect(rect.x,rect.y,rect.width,rect.height);
this.ctx.closePath();
this.ctx.stroke();
this.ctx.restore();
rect=null;
};
/**
* 渲染统一调用的入口,供图层统一调用。
* 新增渲染过程中的hook函数。
* 此方法不允许重载、覆盖
* @param [ctx] 绘图环境。可选
* @private
*/
Ycc.UI.Base.prototype.__render = function (ctx) {
this.triggerListener('computestart',new Ycc.Event("computestart"));
var error = this.computeUIProps();
this.triggerListener('computeend',new Ycc.Event("computeend"));
if(error) return console.error(error.message);
// 超出舞台时,不予渲染
if(this.isOutOfStage())
return;
var absolutePosition = this.getAbsolutePositionRect();
// 绘制UI的背景,rectBgColor
this.renderRectBgColor(absolutePosition);
// 绘制容纳区的边框
this.renderRectBorder(absolutePosition);
// 绘制旋转平移之前的UI
this.renderDashBeforeUI();
// 全局UI配置项,是否绘制UI的容器
if(this.belongTo.yccInstance.config.debugDrawContainer){
this._renderContainer(absolutePosition);
}
this.triggerListener('renderstart',new Ycc.Event("renderstart"));
this.render(ctx);
this.triggerListener('renderend',new Ycc.Event("renderend"));
};
/**
* 给定宽度,获取能容纳的最长单行字符串
* @param content {string} 文本内容
* @param width {number} 指定宽度
* @return {string}
*/
Ycc.UI.Base.prototype.getMaxContentInWidth = function (content, width) {
var out = content;
var outW = 0;
if(this.ctx.measureText(content).width<=width)
return content;
for(var i = 0;i<content.length;i++){
out = content.slice(0,i+1);
outW = this.ctx.measureText(out).width;
if(outW>width){
return content.slice(0,i);
}
}
};
/**
* 合并参数
* @param option
* @return {Ycc.UI}
*/
Ycc.UI.Base.prototype.extend = function (option) {
option = option || {};
for(var key in this){
if(typeof option[key]!=="undefined"){
this[key] = option[key];
}
}
return this;
};
/**
* 克隆ui
* @return {Ycc.UI}
*/
Ycc.UI.Base.prototype.clone = function () {
var ui = new this.yccClass();
ui.extend(this);
return ui;
};
/**
* 获取UI平移、旋转之后位置的多边形区域,子UI需覆盖此方法
*/
Ycc.UI.Base.prototype.getAbsolutePositionPolygon = function () {};
/**
* 获取容纳UI的矩形区域,子UI可以覆盖此方法
* 注:此区域未经过旋转
* @return {Ycc.Math.Rect}
*/
Ycc.UI.Base.prototype.getAbsolutePositionRect = function () {
var pos = this.getAbsolutePosition();
return new Ycc.Math.Rect(pos.x,pos.y,this.rect.width,this.rect.height);
};
/**
* 获取UI的绝对坐标,主要考虑图层坐标
* 注:此区域未经过旋转
* @return {Ycc.Math.Rect}
*/
Ycc.UI.Base.prototype.getAbsolutePosition = function(){
var pos = new Ycc.Math.Rect();
var pa = this.getParent();
pos.width = this.rect.width;
pos.height = this.rect.height;
if(!pa){
pos.x = this.rect.x+this.belongTo.x;
pos.y = this.rect.y+this.belongTo.y;
}else{
var paRect = pa.getAbsolutePosition();
pos.x = this.rect.x+paRect.x;
pos.y = this.rect.y+paRect.y;
}
return pos;
};
/**
* 根据图层坐标和UI位置坐标,将某个点的相对坐标(相对于UI的父级),转换为舞台的绝对坐标
* @param dotOrArr {Ycc.Math.Dot | Ycc.Math.Dot[]}
* @return {Ycc.Math.Dot | Ycc.Math.Dot[]}
*/
Ycc.UI.Base.prototype.transformToAbsolute = function (dotOrArr) {
var self = this;
if(Ycc.utils.isArray(dotOrArr)){
return dotOrArr.map(function (item) {
return self.transformToAbsolute(item);
});
}
var pa = this.getParent();
if(!pa){
return new Ycc.Math.Dot(dotOrArr.x+this.belongTo.x,dotOrArr.y+this.belongTo.y);
}else{
var paAbsolute = pa.getAbsolutePosition();
return new Ycc.Math.Dot(dotOrArr.x+paAbsolute.x,dotOrArr.y+paAbsolute.y);
}
};
/**
* 根据图层坐标和UI位置坐标,将某个点的绝对坐标,转换为相对于UI父级的相对坐标
* @param dotOrArr {Ycc.Math.Dot | Ycc.Math.Dot[]}
* @return {Ycc.Math.Dot | Ycc.Math.Dot[]}
*/
Ycc.UI.Base.prototype.transformToLocal = function (dotOrArr) {
var res = null;
var absolutePos = this.getAbsolutePosition();
if(Ycc.utils.isArray(dotOrArr)){
res = [];
for(var i=0;i<dotOrArr.length;i++){
var resDot = new Ycc.Math.Dot(0,0);
var dot = dotOrArr[i];
resDot.x=dot.x-(absolutePos.x)+this.x;
resDot.y=dot.y-(absolutePos.y)+this.y;
res.push(resDot);
}
return res;
}
res = new Ycc.Math.Dot(0,0);
// 本地坐标需加上当前的x、y
res.x = dotOrArr.x-(absolutePos.x)+this.x;
res.y = dotOrArr.y-(absolutePos.y)+this.y;
return res;
};
/**
* 判断某个点是否在UI组件内
* 默认根据ui的容纳区rect字段进行判断,子UI可以覆盖此方法
* @param dot {Ycc.Math.Dot} 某个点的绝对坐标
* @return {boolean}
*/
Ycc.UI.Base.prototype.containDot = function (dot) {
return dot.isInRect(this.getAbsolutePosition());
};
/**
* 根据当前的锚点、旋转角度获取某个点的转换之后的坐标
* @param dot {Ycc.Math.Dot|Ycc.Math.Dot[]} 需要转换的点,该点为相对坐标,相对于当前UI的父级
* @return {Ycc.Math.Dot} 转换后的点,该点为绝对坐标
*/
Ycc.UI.Base.prototype.transformByRotate = function (dot) {
var self = this;
// 点数组的转换
if(Ycc.utils.isArray(dot)){
return dot.map(function (itemDot) {
return self.transformByRotate(itemDot);
});
}
var res = new Ycc.Math.Dot();
// 位置的绝对坐标
var pos = this.getAbsolutePosition();
// pos={x:0,y:0};
var dotX = dot.x;
var dotY = dot.y;
// 坐标旋转
var dx = (dotX - this.anchorX)*Math.cos(this.rotation/180*Math.PI) - (dotY - this.anchorY)*Math.sin(this.rotation/180*Math.PI)+this.anchorX;
var dy = (dotY - this.anchorY)*Math.cos(this.rotation/180*Math.PI) + (dotX - this.anchorX)*Math.sin(this.rotation/180*Math.PI)+this.anchorY;
// res.x=dx+pos.x;
// res.y=dy+pos.y;
res = this.transformToAbsolute({x:dx,y:dy});
return res;
};
})(Ycc);;/**
* @file Ycc.UI.Polygon.class.js
* @author xiaohei
* @date 2019/4/26
* @description Ycc.UI.Polygon.class文件
*/
(function (Ycc) {
/**
* 多边形UI
* 位置坐标x、y为只读属性,且为相对坐标,默认取多边形的第一个顶点坐标
* @constructor
* @extends Ycc.UI.Base
* @param option {object} 所有可配置的配置项
* @param option.fill {boolean} 是否填充绘制,false表示描边绘制
* @param option.coordinates {Ycc.Math.Dot[]} 多边形点坐标的数组,为保证图形能够闭合,起点和终点必须相等。注意:点列表的坐标为相对坐标
*/
Ycc.UI.Polygon = function Polygon(option) {
option = option || {};
Ycc.UI.Base.call(this, option);
this.yccClass = Ycc.UI.Polygon;
/**
* 位置x
* @type {number}
* @readonly
*/
this.x=0;
/**
* 位置y
* @type {number}
* @readonly
*/
this.y=0;
/**
* 是否填充
* @type {boolean}
*/
this.fill = true;
/**
* 颜色
* @type {string}
*/
this.fillStyle = "blue";
/**
* 光线投射模式 1-升级模式 2-普通模式
* @type {number}
*/
this.noneZeroMode = 1;
/**
* 是否绘制点的下标
* @type {boolean}
*/
this.isDrawIndex = false;
/**
* 多边形点坐标的数组,为保证图形能够闭合,起点和终点必须相等
* @type {null}
*/
this.coordinates=option.coordinates||[];
// 合并属性
this.extend(option);
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.Polygon.prototype, Ycc.UI.Base.prototype);
Ycc.UI.Polygon.prototype.constructor = Ycc.UI.Polygon;
/**
* 计算UI的各种属性。此操作必须在绘制之前调用。
* <br> 计算与绘制分离的好处是,在绘制UI之前就可以提前确定元素的各种信息,从而判断是否需要绘制。
* @override
*/
Ycc.UI.Polygon.prototype.computeUIProps = function () {
// 计算相对位置
if(this.coordinates[0])
this.x=this.coordinates[0].x,this.y=this.coordinates[0].y;
};
/**
* 渲染至ctx
* @param ctx
*/
Ycc.UI.Polygon.prototype.render = function (ctx) {
var self = this;
ctx = ctx || self.ctx;
if(!self.ctx){
console.error("[Ycc error]:","ctx is null !");
return;
}
// console.log('render');
// 绘制旋转缩放之前的UI
ctx.save();
ctx.fillStyle = this.fillStyle;
ctx.strokeStyle = this.strokeStyle;
this.renderPath();
this.fill?ctx.fill():ctx.stroke();
ctx.restore();
};
/**
* 根据coordinates绘制路径
* 只绘制路径,不填充、不描边
* 继承的子类若不是多边形,需要重载此方法
*/
Ycc.UI.Polygon.prototype.renderPath = function (ctx) {
if(this.coordinates.length===0) return;
var self = this;
ctx = ctx || self.ctx;
var start = this.transformByRotate(this.coordinates[0]);
ctx.beginPath();
ctx.moveTo(start.x,start.y);
for(var i=0;i<this.coordinates.length-1;i++){
var dot = this.transformByRotate(this.coordinates[i]);
if(this.isDrawIndex) ctx.fillText(i+'',dot.x-10,dot.y+10);
ctx.lineTo(dot.x,dot.y);
}
ctx.closePath();
};
/**
* 获取当前UI平移、旋转之后位置的多边形区域
* @return {Ycc.Math.Dot[]} 返回多边形点的坐标数组
* @override
*/
Ycc.UI.Polygon.prototype.getAbsolutePositionPolygon = function () {
var self = this;
return this.coordinates.map(function (point) {
return self.transformByRotate(point);
});
};
/**
* 获取UI的绝对坐标,只计算图层坐标和UI的位置坐标x、y
* 不考虑UI的缩放和旋转,缩放旋转可通过其他方法转换
* @param [pos] {Ycc.Math.Dot} 获取到的位置对象,非必传
* @return {Ycc.Math.Dot}
* @override
*/
Ycc.UI.Polygon.prototype.getAbsolutePosition = function(pos){
pos = pos || new Ycc.Math.Dot(this.x,this.y);
var pa = this.getParent();
if(!pa){
// 最顶层的UI需要加上图层的坐标
pos.x = this.x+this.belongTo.x;
pos.y = this.y+this.belongTo.y;
}else{
var paPos = pa.getAbsolutePosition();
pos.x += paPos.x;
pos.y += paPos.y;
}
return pos;
};
/**
* 获取能容纳当前UI的最小方形区域
* @return {Ycc.Math.Rect}
*/
Ycc.UI.Polygon.prototype.getAbsolutePositionRect = function () {
if(!this.coordinates[0]) return console.log(new Ycc.Debugger.Error('need compute prop coordinates').message);
var start = this.coordinates[0];
var minx=start.x,miny=start.y,maxx=start.x,maxy=start.y;
for(var i=0;i<this.coordinates.length-1;i++){
var dot = this.coordinates[i];
if(dot.x<minx) minx=dot.x;
if(dot.x>=maxx) maxx=dot.x;
if(dot.y<miny) miny=dot.y;
if(dot.y>=maxy) maxy=dot.y;
}
var posAbsolute = this.transformToAbsolute({x:minx,y:miny});
return new Ycc.Math.Rect(posAbsolute.x,posAbsolute.y,maxx-minx,maxy-miny);
};
/**
* 绘制旋转缩放之前的UI
* @override
*/
Ycc.UI.Polygon.prototype.renderDashBeforeUI = function (ctx) {
if(!this.isShowRotateBeforeUI || this.coordinates.length===0) return;
var self = this;
ctx = ctx || self.ctx;
var pa = this.getParent();
var paPos =pa? pa.getAbsolutePosition():new Ycc.Math.Dot();
var start = new Ycc.Math.Dot(this.coordinates[0].x+paPos.x,this.coordinates[0].y+paPos.y);
ctx.save();
// 虚线
ctx.setLineDash([10]);
ctx.beginPath();
ctx.moveTo(start.x,start.y);
for(var i=0;i<self.coordinates.length-1;i++){
var dot = self.coordinates[i];
ctx.lineTo(dot.x+paPos.x,dot.y+paPos.y);
}
ctx.closePath();
ctx.strokeStyle = this.strokeStyle;
ctx.stroke();
ctx.restore();
};
/**
* 重载基类的包含某个点的函数,用于点击事件等的响应
* 两种方法:
* 方法一:经过该点的水平射线与多边形的焦点数,即Ray-casting Algorithm
* 方法二:某个点始终位于多边形逆时针向量的左侧、或者顺时针方向的右侧即可判断,算法名忘记了
* 此方法采用方法一,并假设该射线平行于x轴,方向为x轴正方向
* @param dot {Ycc.Math.Dot} 需要判断的点,绝对坐标
* @param noneZeroMode {Number} 是否noneZeroMode 1--启用 2--关闭 默认启用
* 从这个点引出一根“射线”,与多边形的任意若干条边相交,计数初始化为0,若相交处被多边形的边从左到右切过,计数+1,若相交处被多边形的边从右到左切过,计数-1,最后检查计数,如果是0,点在多边形外,如果非0,点在多边形内
* @return {boolean}
*/
Ycc.UI.Polygon.prototype.containDot = function (dot,noneZeroMode) {
// 默认启动none zero mode
noneZeroMode=noneZeroMode||this.noneZeroMode;
var _dot = dot;
var x = _dot.x,y=_dot.y;
var crossNum = 0;
// 点在线段的左侧数目
var leftCount = 0;
// 点在线段的右侧数目
var rightCount = 0;
for(var i=0;i<this.coordinates.length-1;i++){
var start = this.transformByRotate(this.coordinates[i]);
var end = this.transformByRotate(this.coordinates[i+1]);
// 起点、终点斜率不存在的情况
if(start.x===end.x) {
// 因为射线向右水平,此处说明不相交
if(x>start.x) continue;
// 从左侧贯穿
if((end.y>start.y&&y>=start.y && y<=end.y)){
leftCount++;
// console.log('++1');
crossNum++;
}
// 从右侧贯穿
if((end.y<start.y&&y>=end.y && y<=start.y)) {
rightCount++;
// console.log('++1');
crossNum++;
}
continue;
}
// 斜率存在的情况,计算斜率
var k=(end.y-start.y)/(end.x-start.x);
// 交点的x坐标
var x0 = (y-start.y)/k+start.x;
// 因为射线向右水平,此处说明不相交
if(x>x0) continue;
if((end.x>start.x&&x0>=start.x && x0<=end.x)){
// console.log('++2');
crossNum++;
if(k>=0) leftCount++;
else rightCount++;
}
if((end.x<start.x&&x0>=end.x && x0<=start.x)) {
// console.log('++2');
crossNum++;
if(k>=0) rightCount++;
else leftCount++;
}
}
// console.log('polygon',dot,noneZeroMode,crossNum,crossNum%2,leftCount,rightCount);
return noneZeroMode===1?leftCount-rightCount!==0:crossNum%2===1;
};
})(Ycc);;/**
* @file Ycc.UI.Ellipse.class.js
* @author xiaohei
* @date 2017/11/24
* @description Ycc.UI.Ellipse.class文件
*/
(function (Ycc) {
/**
* 椭圆
* @param option {object} 所有可配置的配置项
* @param option.rect {Ycc.Math.Rect} 容纳区。会根据属性设置动态修改。
* @param option.fill=false {boolean} 填充or描边
* @param option.color=black {string} 圆的颜色
* @param option.point {Ycc.Math.Dot} 圆心位置
* @param option.width=20 {number} 长轴
* @param option.height=10 {number} 短轴
* @param option.angle=0 {number} 椭圆绕其中心的自转角度
* 注:通过rotation设置的旋转角度只会旋转椭圆的中心点,此处的angle是将椭圆本身围绕中心点旋转。
* 此处的理解,可以结合地球绕太阳旋转,angle表示自转角度,rotation表示公转角度。
* @constructor
* @extends Ycc.UI.Polygon
*/
Ycc.UI.Ellipse = function Ellipse(option) {
Ycc.UI.Polygon.call(this,option);
this.yccClass = Ycc.UI.Ellipse;
this.point = new Ycc.Math.Dot();
this.width = 20;
this.height = 10;
/**
* 椭圆自转角度
* @type {number}
*/
this.angle = 0;
// centrePoint,width,height,rotateAngle,fill
this.color = "black";
this.fill = false;
this.extend(option);
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.Ellipse.prototype,Ycc.UI.Polygon.prototype);
Ycc.UI.Ellipse.prototype.constructor = Ycc.UI.Ellipse;
/**
* 计算UI的各种属性。此操作必须在绘制之前调用。
* <br> 计算与绘制分离的好处是,在绘制UI之前就可以提前确定元素的各种信息,从而判断是否需要绘制。
* @override
*/
Ycc.UI.Ellipse.prototype.computeUIProps = function () {
var x=this.point.x,
y=this.point.y,
a=this.width/2,
b=this.height/2,
angle = this.angle/180*Math.PI;
var yMax = Math.sqrt(Math.cos(angle)*Math.cos(angle)*b*b+Math.sin(angle)*Math.sin(angle)*a*a) + y;
var yMin = -yMax+2*y;
var xMax = Math.sqrt(Math.cos(angle)*Math.cos(angle)*a*a+Math.sin(angle)*Math.sin(angle)*b*b) + x;
var xMin = -xMax+2*x;
this.rect.x = xMin;
this.rect.y = yMin;
this.rect.width = xMax-xMin;
this.rect.height = yMax-yMin;
// 计算多边形坐标
this.coordinates= this.rect.getVertices();
// 计算相对位置
this.x=this.point.x,this.y=this.point.y;
};
/**
* 绘制
*/
Ycc.UI.Ellipse.prototype.render = function () {
var width = this.width,
rotateAngle=this.angle,
height=this.height;
var point = this.transformByRotate(this.point);
this.ctx.save();
var r = (width > height) ? width : height;
// 计算压缩比例
var ratioX = width / r;
var ratioY = height / r;
// 默认旋转中心位于画布左上角,需要改变旋转中心点
this.ctx.translate(point.x,point.y);
this.ctx.rotate(parseInt(rotateAngle)*Math.PI/180);
// 再变换回原来的旋转中心点
this.ctx.translate(-point.x,-point.y);
// this.ctx.scale(1, 1);
this.ctx.scale(ratioX, ratioY);
this.ctx.beginPath();
this.ctx.arc(point.x / ratioX, point.y/ ratioY, r/2, 0, 2 * Math.PI, false);
this.ctx.closePath();
this.ctx.fillStyle = this.ctx.strokeStyle = this.color;
if(!this.fill)
this.ctx.stroke();
else
this.ctx.fill();
this.ctx.restore();
};
/**
* 判断是否在椭圆内
* @param dot 绝对坐标
* @param noneZeroMode
* @override
*/
Ycc.UI.Ellipse.prototype.containDot = function (dot,noneZeroMode) {
var point = this.transformByRotate(this.point);
// 椭圆自转角度
dot = new Ycc.Math.Dot(dot).rotate(-this.angle,point);
return Math.pow((dot.x-point.x)/this.width*2,2)+Math.pow((dot.y-point.y)/this.height*2,2)<=1;
};
/**
* 渲染旋转平移之前的位置
* @param ctx
* @override
*/
Ycc.UI.Ellipse.prototype.renderDashBeforeUI=function (ctx) {
if(!this.isShowRotateBeforeUI || this.coordinates.length===0) return;
var width = this.width,
rotateAngle=this.angle,
height=this.height;
var pa = this.getParent();
var point = pa?pa.transformToAbsolute(this.point):this.point;
this.ctx.save();
var r = (width > height) ? width : height;
// 计算压缩比例
var ratioX = width / r;
var ratioY = height / r;
this.ctx.scale(ratioX, ratioY);
this.ctx.beginPath();
this.ctx.arc(point.x / ratioX, point.y/ ratioY, r/2, 0, 2 * Math.PI, false);
this.ctx.closePath();
this.ctx.strokeStyle = this.color;
this.ctx.setLineDash([10]);
this.ctx.stroke();
this.ctx.restore();
}
})(Ycc);;/**
* @file Ycc.UI.Circle.class.js
* @author xiaohei
* @date 2017/11/23
* @description Ycc.UI.Circle.class文件
*/
(function (Ycc) {
/**
* 圆
* @param option {object} 所有可配置的配置项
* @param option.rect {Ycc.Math.Rect} 容纳区。会根据属性设置动态修改。
* @param option.fill=true {boolean} 填充or描边
* @param option.color=black {string} 圆的颜色
* @param option.point {Ycc.Math.Dot} 圆心位置,相对坐标
* @param option.r=10 {number} 圆的半径
* @constructor
* @extends Ycc.UI.Polygon
*/
Ycc.UI.Circle = function Circle(option) {
Ycc.UI.Polygon.call(this,option);
this.yccClass = Ycc.UI.Circle;
this.point = null;
this.r = 10;
this.color = "black";
this.fill = true;
this.extend(option);
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.Circle.prototype,Ycc.UI.Polygon.prototype);
/**
* 计算UI的各种属性。此操作必须在绘制之前调用。
* <br> 计算与绘制分离的好处是,在绘制UI之前就可以提前确定元素的各种信息,从而判断是否需要绘制。
* @override
*/
Ycc.UI.Circle.prototype.computeUIProps = function () {
if(!this.point) return new Ycc.Debugger.Error('Circle need prop point');
var x=this.point.x,
y=this.point.y,
r=this.r;
this.rect = new Ycc.Math.Rect(x-r,y-r,2*r,2*r);
// 计算多边形坐标
this.coordinates= this.rect.getVertices();
// 计算相对位置
this.x=this.point.x,this.y=this.point.y;
};
/**
* 绘制
* @override
*/
Ycc.UI.Circle.prototype.render = function () {
var point = this.transformByRotate(this.point);
this.ctx.save();
this.ctx.beginPath();
this.ctx.fillStyle = this.color;
this.ctx.strokeStyle = this.color;
this.ctx.arc(
point.x,
point.y,
this.r,
0,
2*Math.PI
);
this.ctx.closePath();
if(!this.fill)
this.ctx.stroke();
else
this.ctx.fill();
this.ctx.restore();
};
/**
* 绘制旋转缩放之前的UI
* @override
*/
Ycc.UI.Circle.prototype.renderDashBeforeUI = function (ctx) {
var self = this;
ctx = this.ctx;
var pa = this.getParent();
var point = pa?pa.transformToAbsolute(this.point):this.point;
ctx.save();
// 虚线
ctx.setLineDash([10]);
ctx.beginPath();
this.ctx.arc(
point.x,
point.y,
this.r,
0,
2*Math.PI
);
ctx.closePath();
ctx.strokeStyle = this.color;
ctx.stroke();
ctx.restore();
};
/**
* 判断是否在圆内
* @param dot 绝对坐标
* @param noneZeroMode
* @override
*/
Ycc.UI.Circle.prototype.containDot = function (dot,noneZeroMode) {
var point = this.transformByRotate(this.point);
return Math.pow(dot.x-point.x,2)+Math.pow(dot.y-point.y,2)<=Math.pow(this.r,2);
};
})(Ycc);;/**
* @file Ycc.UI.Image.class.js
* @author xiaohei
* @date 2017/11/22
* @description Ycc.UI.Image.class文件
*/
(function (Ycc) {
/**
* 图片UI
* @param option {object} 所有可配置的配置项
* @param option.rect {Ycc.Math.Rect} 容纳区。会根据属性设置动态修改。
* @param option.fillMode=none {string} 填充方式
* <br> none -- 无填充方式。左上角对齐,超出隐藏,不修改rect大小。
* <br> repeat -- 重复。左上角对齐,重复平铺图片,不修改rect大小,超出隐藏。
* <br> scale -- 缩放。左上角对齐,缩放至整个rect区域,不修改rect大小。
* <br> scaleRepeat -- 先缩放再重复。左上角对齐,缩放至某个rect区域,再重复填充整个rect区域,不修改rect大小。
* <br> auto -- 自动。左上角对齐,rect大小自动适配图片。若图片超出rect,会动态修改rect大小。
* <br> scale9Grid -- 9宫格模式填充。左上角对齐,中间区域将拉伸,不允许图片超出rect区域大小,不会修改rect大小。
* @param option.res {Image} 需要填充的图片资源。注:必须已加载完成。
* @param option.mirror {Number} 将图片镜像绘制方式
* <br> 0 -- 无
* <br> 1 -- 上下颠倒
* <br> 2 -- 左右翻转
* <br> 3 -- 上下左右颠倒
* @param option.scale9GridRect {Ycc.Math.Rect} 9宫格相对于res图片的中间区域,当且仅当fillMode为scale9Grid有效。
* @constructor
* @extends Ycc.UI.Polygon
*/
Ycc.UI.Image = function Image(option) {
Ycc.UI.Polygon.call(this,option);
this.yccClass = Ycc.UI.Image;
/**
* 填充方式
* <br> none -- 无填充方式。左上角对齐,超出隐藏,不修改rect大小。
* <br> repeat -- 重复。左上角对齐,重复平铺图片,不修改rect大小,超出隐藏。
* <br> scale -- 缩放。左上角对齐,缩放至整个rect区域,不修改rect大小。
* <br> auto -- 自动。左上角对齐,rect大小自动适配图片。若图片超出rect,会动态修改rect大小。
* <br> scale9Grid -- 9宫格模式填充。左上角对齐,中间区域将拉伸,不允许图片超出rect区域大小,不会修改rect大小。 *
* @type {string}
*/
this.fillMode = "none";
/**
* 需要填充的图片资源。注:必须已加载完成。
* @type {Image}
*/
this.res = null;
/**
* 图片颠倒方式
* <br> 0 -- 无
* <br> 1 -- 左右颠倒
* <br> 2 -- 上下翻转
* <br> 3 -- 上下左右颠倒
* @type {number}
*/
this.mirror = 0;
/**
* 9宫格相对于res图片的中间区域,当且仅当fillMode为scale9Grid有效。
* @type {Ycc.Math.Rect}
*/
this.scale9GridRect=null;
/**
* 缩放重复模式下,原始图片的缩放区域,当且仅当fillMode为scaleRepeat有效。
* @type {null}
*/
this.scaleRepeatRect = null;
this.extend(option);
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.Image.prototype,Ycc.UI.Polygon.prototype);
Ycc.UI.Image.prototype.constructor = Ycc.UI.Image;
/**
* 计算UI的各种属性。此操作必须在绘制之前调用。
* <br> 计算与绘制分离的好处是,在绘制UI之前就可以提前确定元素的各种信息,从而判断是否需要绘制。
* @override
*/
Ycc.UI.Image.prototype.computeUIProps = function () {
if(this.fillMode === "auto"){
this.rect.width = this.res.width;
this.rect.height = this.res.height;
}
// 计算多边形坐标
this.coordinates= this.rect.getVertices();
// 计算相对位置
this.x=this.rect.x,this.y=this.rect.y;
};
/**
* 处理镜像
* @param rect {Ycc.Math.Rect} 计算之后的图片容纳区
* @private
*/
Ycc.UI.Image.prototype._processMirror = function (rect) {
if(this.mirror===1){
this.ctx.scale(-1, 1);
this.ctx.translate(-rect.x*2-rect.width,0);
}
if(this.mirror===2){
this.ctx.scale(1, -1);
this.ctx.translate(0,-rect.y*2-rect.height);
}
if(this.mirror===3){
this.ctx.scale(-1, -1);
this.ctx.translate(-rect.x*2-rect.width,-rect.y*2-rect.height);
}
};
/**
* 绘制
*/
Ycc.UI.Image.prototype.render = function () {
this.ctx.save();
// this.scaleAndRotate();
var rect = this.getAbsolutePositionRect();//this.rect;
var img = this.res;
// 局部变量
var i,j,wCount,hCount,xRest,yRest;
// 坐标系旋转
var absoluteAnchor = this.transformToAbsolute({x:this.anchorX,y:this.anchorY});
this.ctx.translate(absoluteAnchor.x,absoluteAnchor.y);
this.ctx.rotate(this.rotation*Math.PI/180);
this.ctx.translate(-absoluteAnchor.x,-absoluteAnchor.y);
this._processMirror(rect);
if(this.fillMode === "none")
this.ctx.drawImage(this.res,0,0,rect.width,rect.height,rect.x,rect.y,rect.width,rect.height);
else if(this.fillMode === "scale")
this.ctx.drawImage(this.res,0,0,img.width,img.height,rect.x,rect.y,rect.width,rect.height);
else if(this.fillMode === "auto"){
this.ctx.drawImage(this.res,0,0,img.width,img.height,rect.x,rect.y,rect.width,rect.height);
}else if(this.fillMode === "repeat"){
// x,y方向能容纳的img个数
wCount = parseInt(rect.width/img.width)+1;
hCount = parseInt(rect.height/img.height)+1;
for(i=0;i<wCount;i++){
for(j=0;j<hCount;j++){
xRest = img.width;
yRest = img.height;
if(i===wCount-1)
xRest = rect.width-i*img.width;
if(j===hCount-1)
yRest = rect.height-j*img.height;
this.ctx.drawImage(this.res,
0,0,
xRest,yRest,
rect.x+img.width*i,rect.y+img.height*j,
xRest,yRest);
}
}
}else if(this.fillMode === "scaleRepeat"){
// x,y方向能容纳的img个数
wCount = parseInt(rect.width/this.scaleRepeatRect.width)+1;
hCount = parseInt(rect.height/this.scaleRepeatRect.height)+1;
for(i=0;i<wCount;i++){
for(j=0;j<hCount;j++){
xRest = this.scaleRepeatRect.width;
yRest = this.scaleRepeatRect.height;
if(i===wCount-1)
xRest = rect.width-i*this.scaleRepeatRect.width;
if(j===hCount-1)
yRest = rect.height-j*this.scaleRepeatRect.height;
this.ctx.drawImage(this.res,
0,0,
img.width,img.height,
rect.x+this.scaleRepeatRect.width*i,rect.y+this.scaleRepeatRect.height*j,
xRest,yRest);
}
}
}else if(this.fillMode === "scale9Grid"){
var centerRect = this.scale9GridRect;
var grid = [];
var src,dest;
// 第1块
grid[0]={};
grid[0].src = new Ycc.Math.Rect(0,0,centerRect.x,centerRect.y);
grid[0].dest = new Ycc.Math.Rect(rect.x,rect.y,centerRect.x,centerRect.y);
// 第3块
grid[2]={};
grid[2].src = new Ycc.Math.Rect(centerRect.x+centerRect.width,0,img.width-centerRect.x-centerRect.width,centerRect.y);
grid[2].dest = new Ycc.Math.Rect(rect.width-grid[2].src.width+rect.x,rect.y,grid[2].src.width,grid[2].src.height);
// 第7块
grid[6]={};
grid[6].src = new Ycc.Math.Rect(0,centerRect.y+centerRect.height,centerRect.x,img.height-centerRect.y-centerRect.height);
grid[6].dest = new Ycc.Math.Rect(rect.x,rect.y+rect.height-grid[6].src.height,grid[6].src.width,grid[6].src.height);
// 第9块
grid[8]={};
grid[8].src = new Ycc.Math.Rect(centerRect.x+centerRect.width,centerRect.y+centerRect.height,img.width-centerRect.x-centerRect.width,img.height-centerRect.y-centerRect.height);
grid[8].dest = new Ycc.Math.Rect(rect.width-grid[8].src.width+rect.x,rect.y+rect.height-grid[8].src.height,grid[8].src.width,grid[8].src.height);
// 第2块
grid[1]={};
grid[1].src = new Ycc.Math.Rect(centerRect.x,0,centerRect.width,centerRect.y);
grid[1].dest = new Ycc.Math.Rect(grid[0].dest.x+grid[0].dest.width,rect.y,rect.width-grid[0].dest.width-grid[2].dest.width,centerRect.y);
// 第4块
grid[3]={};
grid[3].src = new Ycc.Math.Rect(grid[0].src.x,centerRect.y,grid[0].src.width,centerRect.height);
grid[3].dest = new Ycc.Math.Rect(grid[0].dest.x,grid[0].dest.y+grid[0].dest.height,grid[0].dest.width,rect.height-grid[0].dest.height-grid[6].dest.height);
// 第6块
grid[5]={};
grid[5].src = new Ycc.Math.Rect(grid[2].src.x,centerRect.y,grid[2].src.width,centerRect.height);
grid[5].dest = new Ycc.Math.Rect(grid[2].dest.x,grid[3].dest.y,grid[2].dest.width,grid[3].dest.height);
// 第8块
grid[7]={};
grid[7].src = new Ycc.Math.Rect(grid[1].src.x,grid[6].src.y,centerRect.width,grid[6].src.height);
grid[7].dest = new Ycc.Math.Rect(grid[1].dest.x,grid[6].dest.y,grid[1].dest.width,grid[6].dest.height);
// 第5块
grid[4]={};
grid[4].src = new Ycc.Math.Rect(centerRect.x,centerRect.y,centerRect.width,centerRect.height);
grid[4].dest = new Ycc.Math.Rect(grid[1].dest.x,grid[5].dest.y,grid[1].dest.width,grid[5].dest.height);
for(var k=0;k<grid.length;k++){
if(!grid[k]) continue;
src = grid[k].src;
dest = grid[k].dest;
this.ctx.drawImage(this.res,
// 源
src.x,src.y,src.width,src.height,
// 目标
dest.x,dest.y,dest.width,dest.height
);
}
}
this.ctx.restore();
};
})(Ycc);;/**
* @file Ycc.UI.ImageFrameAnimation.js
* @author xiaohei
* @date 2018/9/29
* @description Ycc.UI.ImageFrameAnimation文件
*/
(function (Ycc) {
/**
* 图片序列帧动画的UI
* @param option {object} 所有可配置的配置项
* @param option.rect {Ycc.Math.Rect} 容纳区。会将显示区的内容缩放至这个区域。
* @param option.res {Image} 需要填充的图片资源。注:必须已加载完成。
* @param option.frameSpace {Number} 序列帧播放的帧间隔。默认为1,即每帧都更换图片
* @param option.firstFrameRect {Number} 首帧的显示区。该区域相对于原始图片,且之后帧显示区将按照这个区域的width递推
* @param option.frameRectCount {Number} 帧显示区的递推个数。该个数相对于原始图片,表示之后帧显示区的递推个数
* @param option.autoplay {Boolean} 自动播放
* @param option.mirror {Number} 将图片镜像绘制方式
* <br> 0 -- 无
* <br> 1 -- 上下颠倒
* <br> 2 -- 左右翻转
* <br> 3 -- 上下左右颠倒
* @constructor
* @extends Ycc.UI.Polygon
*/
Ycc.UI.ImageFrameAnimation = function ImageFrameAnimation(option) {
Ycc.UI.Polygon.call(this,option);
this.yccClass = Ycc.UI.ImageFrameAnimation;
/**
* 需要填充的图片资源。注:必须已加载完成。
* @type {Image}
*/
this.res = null;
/**
* 序列帧播放的帧间隔。默认为1,即每帧都更换图片
* @type {number}
*/
this.frameSpace = 1;
/**
* 首帧的显示区
* @type {null|Ycc.Math.Rect}
*/
this.firstFrameRect = null;
/**
* 动画开始时系统的总帧数
* @type {number}
*/
this.startFrameCount = 0;
/**
* 帧显示区的递推个数
* @type {number}
*/
this.frameRectCount = 1;
/**
* 是否自动播放
* @type {boolean}
*/
this.autoplay = false;
/**
* 是否真正播放
* @type {boolean}
*/
this.isRunning = false;
/**
* 图片颠倒方式
* <br> 0 -- 无
* <br> 1 -- 左右颠倒
* <br> 2 -- 上下翻转
* <br> 3 -- 上下左右颠倒
* @type {number}
*/
this.mirror = 0;
this.extend(option);
// 初始化
this.isRunning = this.autoplay;
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.ImageFrameAnimation.prototype,Ycc.UI.Polygon.prototype);
Ycc.UI.ImageFrameAnimation.prototype.constructor = Ycc.UI.ImageFrameAnimation;
/**
* 计算UI的各种属性。此操作必须在绘制之前调用。
* <br> 计算与绘制分离的好处是,在绘制UI之前就可以提前确定元素的各种信息,从而判断是否需要绘制。
* @override
*/
Ycc.UI.ImageFrameAnimation.prototype.computeUIProps = function () {
// 计算多边形坐标
this.coordinates= this.rect.getVertices();
// 计算相对位置
this.x=this.rect.x,this.y=this.rect.y;
};
/**
* 处理镜像
* @param rect {Ycc.Math.Rect} 计算之后的图片容纳区
* @private
*/
Ycc.UI.ImageFrameAnimation.prototype._processMirror = function (rect) {
if(this.mirror===1){
this.ctx.scale(-1, 1);
this.ctx.translate(-rect.x*2-rect.width,0);
}
if(this.mirror===2){
this.ctx.scale(1, -1);
this.ctx.translate(0,-rect.y*2-rect.height);
}
if(this.mirror===3){
this.ctx.scale(-1, -1);
this.ctx.translate(-rect.x*2-rect.width,-rect.y*2-rect.height);
}
};
/**
* 绘制
*/
Ycc.UI.ImageFrameAnimation.prototype.render = function () {
// 绝对坐标
var rect = this.getAbsolutePositionRect();
// 获取当前显示第几个序列图,由于默认播放第一帧图片,这里直接渲染第二帧图片
var index = parseInt((this.belongTo.yccInstance.ticker.frameAllCount-this.startFrameCount)/this.frameSpace)%this.frameRectCount+1;
// 若没开始播放,默认只绘制第一个序列帧
if(!this.isRunning || index>=this.frameRectCount)
index=0;
// 绘制
this.ctx.save();
this.scaleAndRotate();
// 处理镜像属性
this._processMirror(rect);
this.ctx.drawImage(this.res,
this.firstFrameRect.x+this.firstFrameRect.width*index,this.firstFrameRect.y,this.firstFrameRect.width,this.firstFrameRect.height,
rect.x,rect.y,rect.width,rect.height);
this.ctx.restore();
};
/**
* 开始播放
*/
Ycc.UI.ImageFrameAnimation.prototype.start = function () {
this.startFrameCount = this.belongTo.yccInstance.ticker.frameAllCount;
this.isRunning = true;
};
/**
* 停止播放
*/
Ycc.UI.ImageFrameAnimation.prototype.stop = function () {
this.isRunning = false;
};
})(Ycc);
;/**
* @file Ycc.UI.Line.class.js
* @author xiaohei
* @date 2017/11/17
* @description Ycc.UI.Line.class文件
*/
(function (Ycc) {
/**
* 线段。可设置属性如下
* @param option {object} 所有可配置的配置项
* @param option.start {Ycc.Math.Dot} 起点
* @param option.end {Ycc.Math.Dot} 终点
* @param option.width=1 {number} 线条宽度
* @param option.color="black" {string} 线条颜色
* @constructor
* @extends Ycc.UI.Polygon
*/
Ycc.UI.Line = function Line(option) {
Ycc.UI.Polygon.call(this,option);
this.yccClass = Ycc.UI.Line;
this.start = new Ycc.Math.Dot(0,0);
this.end = new Ycc.Math.Dot(0,0);
this.width = 1;
this.color = "black";
this.extend(option);
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.Line.prototype,Ycc.UI.Polygon.prototype);
Ycc.UI.Line.prototype.constructor = Ycc.UI.Line;
/**
* 计算UI的各种属性。此操作必须在绘制之前调用。
* <br> 计算与绘制分离的好处是,在绘制UI之前就可以提前确定元素的各种信息,从而判断是否需要绘制。
* @override
*/
Ycc.UI.Line.prototype.computeUIProps = function () {
this.rect.x = this.start.x<this.end.x?this.start.x:this.end.x;
this.rect.y = this.start.y<this.end.y?this.start.y:this.end.y;
this.rect.width = Math.abs(this.start.x-this.end.x);
this.rect.height = Math.abs(this.start.y-this.end.y);
// 计算多边形坐标
var x1=this.start.x,y1=this.start.y,x2=this.end.x,y2=this.end.y;
// 垂直线段的斜率
var k = -(x2-x1)/(y2-y1);
if(y2-y1===0) {
this.coordinates=[
{x:this.start.x,y:this.start.y-this.width/2},
{x:this.start.x,y:this.start.y+this.width/2},
{x:this.end.x,y:this.end.y+this.width/2},
{x:this.end.x,y:this.end.y-this.width/2},
{x:this.start.x,y:this.start.y-this.width/2},
];
}else {
var cx1 = x1-Math.pow(Math.pow(this.width/2,2)/(k*k+1),0.5);
var cx2 = x1+Math.pow(Math.pow(this.width/2,2)/(k*k+1),0.5);
var cx3 = x2+Math.pow(Math.pow(this.width/2,2)/(k*k+1),0.5);
var cx4 = x2-Math.pow(Math.pow(this.width/2,2)/(k*k+1),0.5);
var cy1 = k*(cx1-x1)+y1;
var cy2 = k*(cx2-x1)+y1;
var cy3 = k*(cx3-x2)+y2;
var cy4 = k*(cx4-x2)+y2;
// 计算多边形坐标
// 计算多边形坐标
this.coordinates=[
{x:cx1,y:cy1},
{x:cx2,y:cy2},
{x:cx3,y:cy3},
{x:cx4,y:cy4},
{x:cx1,y:cy1},
];
}
// 计算相对位置
this.x=this.start.x,this.y=this.start.y;
this.strokeStyle = this.color;
this.fillStyle = this.color;
};
/**
* 绘制函数与Polygon相同
*/
// Ycc.UI.Line.prototype.render = function () {
//
// var pa = this.getParent();
// var start = pa?pa.transformToAbsolute(this.start):this.start;
// var end = pa?pa.transformToAbsolute(this.end):this.end;
// this.ctx.save();
// this.ctx.strokeStyle = this.color;
// this.ctx.lineWidth = this.width;
//
// this.ctx.beginPath();
// this.ctx.moveTo(start.x, start.y);
// this.ctx.lineTo(end.x, end.y);
// this.ctx.stroke();
// this.ctx.closePath();
// this.ctx.restore();
// };
})(Ycc);;/**
* @file Ycc.UI.BrokenLine.class.js
* @author xiaohei
* @date 2017/11/24
* @description Ycc.UI.BrokenLine.class文件
*/
(function (Ycc) {
/**
* 线段。可设置属性如下
* @param option {object} 所有可配置的配置项
* @param option.rect {Ycc.Math.Rect} 容纳区。会根据属性设置动态修改。该坐标是相对于图层的坐标
* @param option.pointList {Ycc.Math.Dot[]} Dot数组。该坐标是相对于图层的坐标
* @param option.width=1 {number} 线条宽度
* @param option.color="black" {string} 线条颜色
* @param option.smooth=false {boolean} 线条是否平滑
* @constructor
* @extends Ycc.UI.Polygon
*/
Ycc.UI.BrokenLine = function BrokenLine(option) {
Ycc.UI.Polygon.call(this,option);
this.yccClass = Ycc.UI.BrokenLine;
this.pointList = [];
this.width = 1;
this.color = "black";
this.smooth = false;
this.extend(option);
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.BrokenLine.prototype,Ycc.UI.Polygon.prototype);
/**
* 计算UI的各种属性。此操作必须在绘制之前调用。
* <br> 计算与绘制分离的好处是,在绘制UI之前就可以提前确定元素的各种信息,从而判断是否需要绘制。
* @override
*/
Ycc.UI.BrokenLine.prototype.computeUIProps = function () {
if(this.pointList.length===0) {
this.rect.x = 0;
this.rect.y = 0;
this.rect.width = 0;
this.rect.height = 0;
return null;
}
var minx=this.pointList[0].x,miny=this.pointList[0].y,maxx=this.pointList[0].x,maxy=this.pointList[0].y;
for(var i =0;i<this.pointList.length;i++){
var point = this.pointList[i];
if(point.x<minx)
minx = point.x;
else if(point.x>maxx)
maxx = point.x;
if(point.y<miny)
miny = point.y;
else if(point.y>maxy)
maxy = point.y;
}
this.rect.x = minx;
this.rect.y = miny;
this.rect.width = maxx-minx;
this.rect.height = maxy-miny;
// 计算多边形坐标
this.coordinates= this.rect.getVertices();
// 计算相对位置
this.x=this.rect.x,this.y=this.rect.y;
};
/**
* 绘制
*/
Ycc.UI.BrokenLine.prototype.render = function () {
if(this.pointList.length<2) return null;
// 父级
var pa = this.getParent();
this.ctx.save();
this.ctx.strokeStyle = this.color;
this.ctx.strokeWidth = this.width;
this.ctx.beginPath();
// 因为直接操作舞台,所以绘制之前需要转换成舞台绝对坐标
// var pointList = pa?pa.transformToAbsolute(this.pointList):this.pointList;
var pointList = this.transformByRotate(this.pointList);
if(this.smooth)
this._smoothLineRender(pointList);
else
this._normalRender(pointList);
this.ctx.closePath();
this.ctx.restore();
};
/**
* 普通绘制
* @param pointList
* @private
*/
Ycc.UI.BrokenLine.prototype._normalRender = function (pointList) {
this.ctx.moveTo(pointList[0].x, pointList[0].y);
for(var i =1;i<pointList.length;i++){
this.ctx.lineTo(pointList[i].x, pointList[i].y);
}
this.ctx.stroke();
};
/**
* 绘制平滑的曲线
* 方案一:使用三次贝塞尔曲线
* 原理:
* 1、任意顶点的两个控制点连线始终平行于x轴
* 2、它们的连线始终经过顶点
* 3、两个控制点距离顶点的长度,根据顶点的相邻顶点在x轴方向上的距离乘以某个系数来确定
* 4、这两个控制点分属于不同的两条曲线,分别是起点的控制点和终点的控制点
* 5、第一个顶点和最后一个顶点只有一个控制点
* @param pointList {Ycc.Math.Dot[]} 经过转换后的舞台绝对坐标点列表
*/
Ycc.UI.BrokenLine.prototype._smoothLineRender = function (pointList) {
// 获取生成曲线的两个控制点和两个顶点,N个顶点可以得到N-1条曲线
var list = getCurveList(pointList);
// 调用canvas三次贝塞尔方法bezierCurveTo逐一绘制
this.ctx.beginPath();
for(var i=0;i<list.length;i++){
this.ctx.moveTo(list[i].start.x,list[i].start.y);
this.ctx.bezierCurveTo(list[i].dot1.x,list[i].dot1.y,list[i].dot2.x,list[i].dot2.y,list[i].end.x,list[i].end.y);
}
this.ctx.stroke();
/**
* 获取曲线的绘制列表,N个顶点可以得到N-1条曲线
* @return {Array}
*/
function getCurveList(pointList) {
// 长度比例系数
var lenParam = 1/3;
// 存储曲线列表
var curveList = [];
// 第一段曲线控制点1为其本身
curveList.push({
start:pointList[0],
end:pointList[1],
dot1:pointList[0],
dot2:null
});
for(var i=1;i<pointList.length-1;i++){
var cur = pointList[i];
var next = pointList[i+1];
var pre = pointList[i-1];
// 上一段曲线的控制点2
var p1 = new Ycc.Math.Dot(cur.x-lenParam*(Math.abs(cur.x-pre.x)),cur.y);
// 当前曲线的控制点1
var p2 = new Ycc.Math.Dot(cur.x+lenParam*(Math.abs(cur.x-next.x)),cur.y);
// 上一段曲线的控制点2
curveList[i-1].dot2 = p1;
curveList.push({
start:cur,
end:next,
dot1:p2,
dot2:null
});
}
// 最后一段曲线的控制点2为其本身
curveList[curveList.length-1].dot2=pointList[pointList.length-1];
return curveList;
}
};
/**
* 绘制平滑的曲线
* 方案二:使用三次贝塞尔曲线
* 原理:
* 1、顶点两端的控制点连线始终经过顶点
* 2、控制点连线始终垂直于两向量的夹角平分线
* 3、两个控制点距离顶点的长度,根据顶点的相邻顶点在x轴方向上的距离乘以某个系数来确定
* 4、这两个控制点分属于不同的两条曲线,分别是起点的控制点和终点的控制点
* 5、第一个顶点和最后一个顶点只有一个控制点
* @param pointList {Ycc.Math.Dot[]} 经过转换后的舞台绝对坐标点列表
*/
Ycc.UI.BrokenLine.prototype._smoothLineRender02 = function (pointList) {
/**
* 获取曲线的绘制列表,N个顶点可以得到N-1条曲线
* @return {Array}
*/
function getCurveList() {
var lenParam = 1/3;
var curveList = [];
// 第一段曲线控制点1为其本身
curveList.push({
start:pointList[0],
end:pointList[1],
dot1:pointList[0],
dot2:null
});
for(var i=1;i<pointList.length-1;i++){
var cur = pointList[i];
var next = pointList[i+1];
var pre = pointList[i-1];
// 上一段曲线的控制点2
var p1 = new Ycc.Math.Dot(cur.x-lenParam*(Math.abs(cur.x-pre.x)),cur.y);
// 当前曲线的控制点1
var p2 = new Ycc.Math.Dot(cur.x+lenParam*(Math.abs(cur.x-next.x)),cur.y);
// 上一段曲线的控制点2
curveList[i-1].dot2 = p1;
curveList.push({
start:cur,
end:next,
dot1:p2,
dot2:null
});
}
// 最后一段曲线的控制点2为其本身
curveList[curveList.length-1].dot2=pointList[pointList.length-1];
return curveList;
}
};
})(Ycc);;/**
* @file Ycc.UI.MultiLineText.class.js
* @author xiaohei
* @date 2017/11/16
* @description Ycc.UI.MultiLineText.class文件
*/
(function (Ycc) {
/**
* 多行文本UI
* @constructor
* @extends Ycc.UI.Base
* @param option {object} 所有可配置的配置项
* @param option.content="" {string} 内容
* @param option.color=black {string} 颜色
* @param option.rect {Ycc.Math.Rect} 文字的绘制区域。若超出长度,此区域会被修改
* @param option.wordBreak=break-all {string} 水平方向文字超出换行
* <br>`break-all` 超出即换行
* <br>`break-word` 在单词处换行
* <br>`no-break` 不换行,超出即隐藏
* <br>默认为`no-break`
* @param option.overflow=auto {string} 垂直方向超出rect之后的显示方式
* <br> `hidden` -- 直接隐藏
* <br> `auto` -- 修改rect大小,完全显示
*/
Ycc.UI.MultiLineText = function MultiLineText(option) {
Ycc.UI.Polygon.call(this,option);
this.yccClass = Ycc.UI.MultiLineText;
// /**
// * 配置项
// */
// this.option = Ycc.utils.extend({
// content:"",
// fontSize:"16px",
// lineHeight:24,
// fill:true,
// color:"black",
// rect:null,
// wordBreak:"break-all",
// overflow:"auto"
// },option);
/**
* 显示在文本框中的文本行。私有属性,不允许修改。
* @type {string[]}
*/
this.displayLines = [];
this.content = "";
this.fontSize = "16px";
this.lineHeight = 24;
this.fill = true;
this.color = "black";
this.wordBreak = "break-all";
this.overflow = "auto";
this.extend(option);
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.MultiLineText.prototype,Ycc.UI.Polygon.prototype);
Ycc.UI.MultiLineText.prototype.constructor = Ycc.UI.MultiLineText;
/**
* 计算UI的各种属性。此操作必须在绘制之前调用。
* <br> 计算与绘制分离的好处是,在绘制UI之前就可以提前确定元素的各种信息,从而判断是否需要绘制。
* @override
*/
Ycc.UI.MultiLineText.prototype.computeUIProps = function () {
var self = this;
var config = this;
// 文本行
var lines = this.content.split(/(?:\r\n|\r|\n)/);
// 待显示的文本行
this.displayLines = getRenderLines();
if(config.overflow === "auto"){
config.rect.height = config.lineHeight*this.displayLines.length;
}
// 计算多边形坐标
this.coordinates= this.rect.getVertices();
// 计算相对位置
this.x=this.rect.x,this.y=this.rect.y;
/**
* 获取需要实际绘制的文本行数组
*/
function getRenderLines(){
switch (config.wordBreak){
case "no-break":
case "break-all":
return getBreakAllRenderLines();
break;
case "break-word":
return getBreakWordRenderLines();
break;
default:
return getBreakAllRenderLines();
}
/**
* 获取break-all时的绘制文本行
*/
function getBreakAllRenderLines(){
var res = [];
for(var i = 0;i<lines.length;i++){
var line = lines[i];
var _lines = dealLine(line);
res = res.concat(_lines);
}
return res;
/**
* 递归处理单行超长的情况
* @param line
*/
function dealLine(line){
var subLines = [];
var lineW = self.ctx.measureText(line).width;
// 若没有超长
if(lineW<=config.rect.width){
subLines.push(line);
return subLines;
}
for(var j=0;j<line.length;j++){
var part = line.slice(0,j+1);
var partW = self.ctx.measureText(part).width;
if(partW>config.rect.width){
var subLine = line.slice(0,j-1);
subLines.push(subLine);
var restLine = line.slice(j-1);
// 递归处理剩下的字符串
subLines = subLines.concat(dealLine(restLine));
return subLines;
}
}
}
}
/**
* 获取break-word时的绘制文本行
*/
function getBreakWordRenderLines(){
var res = [];
for(var i = 0;i<lines.length;i++){
var line = lines[i];
var _lines = dealLine(line);
res = res.concat(_lines);
}
return res;
/**
* 递归处理单行超长的情况
* @param line
*/
function dealLine(line){
var subLines = [];
var lineW = self.ctx.measureText(line).width;
// 若没有超长
if(lineW<=config.rect.width){
subLines.push(line);
return subLines;
}
// 记录最后一次空格出现的位置
var spacePosition = 0;
for(var j=0;j<line.length;j++){
var part = line.slice(0,j+1);
var partW = self.ctx.measureText(part).width;
var curChar = line[j];
if(curChar===" ")
spacePosition = j;
if(partW>config.rect.width){
var subLine = "";
var restLine = "";
// 若当前字符为空格
if(curChar===" " || spacePosition===0){
subLine = line.slice(0,j);
subLines.push(subLine);
restLine = line.slice(j);
// 递归处理剩下的字符串
subLines = subLines.concat(dealLine(restLine));
return subLines;
}
// 当前字符不为空格
subLine = line.slice(0,spacePosition);
subLines.push(subLine);
restLine = line.slice(spacePosition);
// 递归处理剩下的字符串
subLines = subLines.concat(dealLine(restLine));
return subLines;
}
}
}
}
}
};
/**
* 渲染至ctx
* @param ctx
*/
Ycc.UI.MultiLineText.prototype.render = function (ctx) {
var self = this;
self.ctx = ctx || self.ctx;
if(!self.ctx){
console.error("[Ycc error]:","ctx is null !");
return;
}
// 引用
var config = this;
// 绝对坐标的rect
var rect = this.getAbsolutePositionRect();
this.ctx.save();
this.ctx.fillStyle = config.color;
this.ctx.strokeStyle = config.color;
// 坐标系旋转
var absoluteAnchor = this.transformToAbsolute({x:this.anchorX,y:this.anchorY});
this.ctx.translate(absoluteAnchor.x,absoluteAnchor.y);
this.ctx.rotate(this.rotation*Math.PI/180);
this.ctx.translate(-absoluteAnchor.x,-absoluteAnchor.y);
// 绘制
for(var i = 0;i<self.displayLines.length;i++){
var x = rect.x;
var y = rect.y + i*config.lineHeight;
if(y+config.lineHeight>rect.y+rect.height){
break;
}
this.ctx.fillText(self.displayLines[i],x,y);
}
this.ctx.restore();
};
})(Ycc);;/**
* @file Ycc.UI.Rect.class.js
* @author xiaohei
* @date 2017/11/17
* @description Ycc.UI.Rect.class文件
*/
(function (Ycc) {
/**
* 方块
* @param option {object} 所有可配置的配置项
* @param option.rect {Ycc.Math.Rect} 容纳区。会根据属性设置动态修改。
* @param option.fill=true {boolean} 填充or描边
* @param option.color=black {string} 方块颜色
* @constructor
* @extends Ycc.UI.Polygon
*/
Ycc.UI.Rect = function Rect(option) {
Ycc.UI.Polygon.call(this,option);
this.yccClass = Ycc.UI.Rect;
/**
* 配置项
*/
// this.option = Ycc.utils.extend({
// rect:null,
// fill:true,
// color:"black"
// },option);
this.fill = true;
this.color = "black";
this.extend(option);
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.Rect.prototype,Ycc.UI.Polygon.prototype);
/**
* 计算UI的各种属性。此操作必须在绘制之前调用。
* <br> 计算与绘制分离的好处是,在绘制UI之前就可以提前确定元素的各种信息,从而判断是否需要绘制。
* @override
*/
Ycc.UI.Rect.prototype.computeUIProps = function () {
// 计算多边形坐标
this.coordinates = this.rect.getVertices();
// 赋值位置信息
this.x = this.rect.x,this.y=this.rect.y;
};
/**
* 绘制
*/
Ycc.UI.Rect.prototype.render = function (ctx) {
var self = this;
ctx = ctx || self.ctx;
if(!self.ctx){
console.error("[Ycc error]:","ctx is null !");
return;
}
ctx.save();
ctx.fillStyle = this.color;
ctx.strokeStyle = this.color;
this.renderPath();
this.fill?ctx.fill():ctx.stroke();
ctx.restore();
};
})(Ycc);;/**
* @file Ycc.UI.CropRect.class.js
* @author xiaohei
* @date 2017/11/29
* @description Ycc.UI.CropRect.class文件
*/
(function (Ycc) {
/**
* 裁剪框
* @param option {object} 所有可配置的配置项
* @param option.rect {Ycc.Math.Rect} 容纳区。会根据属性设置动态修改。
* @param option.fill=true {boolean} 填充or描边
* @constructor
* @extends Ycc.UI.Polygon
*/
Ycc.UI.CropRect = function CropRect(option) {
Ycc.UI.Polygon.call(this,option);
this.yccClass = Ycc.UI.CropRect;
/**
* 控制点的大小
* @type {number}
*/
this.ctrlSize = 6;
/**
* 拖拽是否允许超出舞台
* @type {boolean}
*/
this.enableDragOut = false;
/**
* 是否填充
* @type {boolean}
*/
this.fill = true;
/**
* 左上角 的控制点
* @type {Ycc.Math.Rect}
*/
this.ctrlRect1 = new Ycc.Math.Rect();
/**
* 右上角 的控制点
* @type {Ycc.Math.Rect}
*/
this.ctrlRect2 = new Ycc.Math.Rect();
/**
* 左下角 的控制点
* @type {Ycc.Math.Rect}
*/
this.ctrlRect3 = new Ycc.Math.Rect();
/**
* 右下角 的控制点
* @type {Ycc.Math.Rect}
*/
this.ctrlRect4 = new Ycc.Math.Rect();
/**
* 选框选择后,下方按钮的配置项
* @type {Ycc.UI.SingleLineText[]}
*/
this.btns = [];
/**
* 下方按钮的高度
* @type {number}
*/
this.btnHeight = 38;
/**
* 下方按钮左右的间距
* @type {number}
*/
this.btnVerticalPadding = 10;
/**
* 是否将名字显示在左上角
* @type {boolean}
*/
this.showName = true;
this.extend(option);
this._initUI();
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.CropRect.prototype,Ycc.UI.Polygon.prototype);
/**
* 是否显示框选后的操作按钮
* @param show {Boolean} `true`--显示 `false`--隐藏
*/
Ycc.UI.CropRect.prototype.showBtns = function (show) {
if(this.btns.length>0){
for(var i =0;i<this.btns.length;i++){
if(Ycc.utils.isObj(this.btns[i])){
this.btns[i].show = show;
}
}
this.belongTo.yccInstance.layerManager.reRenderAllLayerToStage();
}
};
/**
* 设置区块的操作按钮
* @param btns
*/
Ycc.UI.CropRect.prototype.setCtrlBtns = function (btns) {
var self = this;
// 添加文字按钮到图层
if(btns.length!==0){
btns.forEach(function (config) {
// 默认参数
config = Ycc.utils.extend({
content:"",
rectBgColor:"#666",
color:"#fff",
ontap:function () {}
},config);
var btn = new Ycc.UI.SingleLineText(config);
btn.addListener("mouseover",function () {
this.color="#ccc";
this.belongTo.yccInstance.layerManager.reRenderAllLayerToStage();
});
btn.addListener("mouseout",function () {
this.color="#fff";
this.belongTo.yccInstance.layerManager.reRenderAllLayerToStage();
});
self.btns.push(btn);
self.belongTo.addUI(btn);
})
}
};
/**
* 计算UI的各种属性。此操作必须在绘制之前调用。
*/
Ycc.UI.CropRect.prototype.computeUIProps = function () {
// 设置画布属性再计算,否则计算内容长度会有偏差
this.belongTo._setCtxProps(this);
var rect = this.rect;
// 操作按钮的总长度
var totalW = 0;
// 循环内临时变量
var tempW = 0;
// 操作按钮左上角的起点坐标
var x0 = this.rect.x;
var y0 = this.rect.y+this.rect.height;
// 舞台的宽高
var stageW = this.belongTo.yccInstance.getStageWidth();
var stageH = this.belongTo.yccInstance.getStageHeight();
// 计算控制点的属性
this.ctrlRect1 = (new Ycc.Math.Rect(rect.x,rect.y,this.ctrlSize,this.ctrlSize));
this.ctrlRect2 = (new Ycc.Math.Rect(rect.x+rect.width-this.ctrlSize,rect.y,this.ctrlSize,this.ctrlSize));
this.ctrlRect3 = (new Ycc.Math.Rect(rect.x,rect.y+rect.height-this.ctrlSize,this.ctrlSize,this.ctrlSize));
this.ctrlRect4 = (new Ycc.Math.Rect(rect.x+rect.width-this.ctrlSize,rect.y+rect.height-this.ctrlSize,this.ctrlSize,this.ctrlSize));
// 设置操作按钮的属性,并求其总长度
if(this.btns.length===0) return;
for(var k=0;k<this.btns.length;k++){
var btn = this.btns[k];
btn.overflow="auto";
btn.xAlign="center";
btn.rect.width=parseInt(this.ctx.measureText(btn.content).width+2*this.btnVerticalPadding);
btn.rect.height = this.btnHeight;
totalW += btn.rect.width+1;
}
// 操作按钮越界后的处理
if(x0+totalW>stageW)
x0 = stageW-totalW;
if(y0+this.btnHeight>stageH){
// 上边也放不下
if(this.rect.y<this.btnHeight){
y0 = 0;
}else{
y0 = this.rect.y-this.btnHeight-2;
}
}
// 计算操作按钮的位置
for(var i=0;i<this.btns.length;i++){
var ui = this.btns[i];
ui.rect.x=x0+tempW;
ui.rect.y=y0+2;
tempW += ui.rect.width+1;
}
// 计算多边形坐标
this.coordinates= this.rect.getVertices();
// 计算相对位置
this.x=this.rect.x,this.y=this.rect.y;
};
/**
* 初始化UI
* @private
*/
Ycc.UI.CropRect.prototype._initUI = function () {
var self = this;
//拖拽开始时的坐标信息
var startPos = null;
//拖拽开始时的位置信息
var startRect = null;
this.userData = this.userData?this.userData:{};
this.addListener("dragstart",function (e) {
console.log('dragstart',e);
// self.showBtns(false);
// 标识第几个变换控制点
this.userData.ctrlStart = 0;
// 拖拽开始时选框的位置
this.userData.dragStartPosition = new Ycc.Math.Rect(this.rect);
// 拖拽开始时鼠标的位置
this.userData.dragStartMousePosition = new Ycc.Math.Dot(e);
var dot = new Ycc.Math.Dot(e);
startPos = dot;
startRect = new Ycc.Math.Rect(self.rect);
for(var i=1;i<=4;i++){
if(dot.isInRect(this["ctrlRect"+i])){
this.userData.ctrlStart = i;
return null;
}
}
});
this.addListener("dragging",function (e) {
// 拖拽开始的起点位置
var r = this.userData.dragStartPosition;
// 拖拽开始时鼠标的位置
var m = this.userData.dragStartMousePosition;
// 选框位置信息的暂存值
var x,y,width,height;
// 控制点的拖拽事件
if(this.userData.ctrlStart<=4&&this.userData.ctrlStart>=1){
// 拖动左上角控制点
if(this.userData.ctrlStart===1 ){
x = e.x-(m.x-r.x);
y = e.y-(m.y-r.y);
width = r.width-(e.x-r.x);
height = r.height-(e.y-r.y);
if(x<=r.x+r.width-this.ctrlSize*2)
this.rect.x = x;
if(y<=r.y+r.height-this.ctrlSize*2)
this.rect.y = y;
}
// 拖动右上角控制点
if(this.userData.ctrlStart===2){
x = r.x;
y = e.y-(m.y-r.y);
width = e.x-r.x+(r.width+r.x-m.x);
height = r.height-(e.y-r.y);
this.rect.x = x;
if(y<=r.y+r.height-this.ctrlSize*2)
this.rect.y = y;
}
// 拖动左下角控制点
if(this.userData.ctrlStart===3){
x = e.x-(m.x-r.x);
y = r.y;
width = r.width-(e.x-r.x);
height = e.y - r.y + (r.height+r.y-m.y);
if(x<=r.x+r.width-this.ctrlSize*2)
this.rect.x = x;
this.rect.y = y;
}
// 拖动右下角控制点
if(this.userData.ctrlStart===4){
this.rect.x = r.x;
this.rect.y = r.y;
this.rect.width = (e.x-r.x)+(r.x+r.width-m.x);
this.rect.height = (e.y-r.y)+(r.y+r.height-m.y);
}
if(width>=this.ctrlSize*2)
this.rect.width = width;
if(height>=this.ctrlSize*2)
this.rect.height = height;
}else{
// 选框的拖拽事件
this.rect.x = startRect.x+e.x-startPos.x;
this.rect.y = startRect.y+e.y-startPos.y;
}
// 处理选择器的最小宽度
if(this.rect.width<=this.ctrlSize*2){
this.rect.width = this.ctrlSize*2;
}
// 处理选择器的最小高度
if(this.rect.height<=this.ctrlSize*2){
this.rect.height = this.ctrlSize*2;
}
// 处理不允许拖拽出舞台
if(!this.enableDragOut){
var stageW = this.belongTo.yccInstance.getStageWidth();
var stageH = this.belongTo.yccInstance.getStageHeight();
if(this.rect.x<=0) this.rect.x=0;
if(this.rect.y<=0) this.rect.y=0;
if(this.rect.x+this.rect.width>=stageW){
this.rect.x = stageW-this.rect.width;
}
if(this.rect.y+this.rect.height>=stageH){
this.rect.y = stageH-this.rect.height;
}
}
/**
* @todo 此处是否在UI内渲染,有待考虑
*/
this.belongTo.yccInstance.layerManager.reRenderAllLayerToStage();
});
this.addListener("dragend",function (e) {
self.showBtns(true);
});
};
/**
* 绘制
*/
Ycc.UI.CropRect.prototype.render = function () {
var rect = this.rect;
this.ctx.save();
this.ctx.fillStyle = this.fillStyle;
this.ctx.strokeStyle = this.strokeStyle;
this.ctx.beginPath();
this.ctx.rect(rect.x,rect.y,rect.width,rect.height);
this.ctx.closePath();
if(!this.fill)
this.ctx.stroke();
else
this.ctx.fill();
this.ctx.beginPath();
this.ctx.rect(this.ctrlRect1.x,this.ctrlRect1.y,this.ctrlRect1.width,this.ctrlRect1.height);
this.ctx.rect(this.ctrlRect2.x,this.ctrlRect2.y,this.ctrlRect2.width,this.ctrlRect2.height);
this.ctx.rect(this.ctrlRect3.x,this.ctrlRect3.y,this.ctrlRect3.width,this.ctrlRect3.height);
this.ctx.rect(this.ctrlRect4.x,this.ctrlRect4.y,this.ctrlRect4.width,this.ctrlRect4.height);
this.ctx.closePath();
this.ctx.fill();
this.ctx.restore();
// 绘制名字
this.ctx.save();
this.ctx.beginPath();
this.ctx.fillStyle="blue";
this.ctx.font="normal normal bold 14px Arial";
this.ctx.fillText(this.name,rect.x+10,rect.y+10);
this.ctx.closePath();
this.ctx.restore();
};
/**
* 删除自身及操作按钮
*/
Ycc.UI.CropRect.prototype.removeSelf = function () {
this.belongTo.removeUI(this);
this.btns.forEach(function (t) {
t.removeSelf();
});
};
})(Ycc);;/**
* @file Ycc.UI.SingleLineText.class.js
* @author xiaohei
* @date 2017/11/15
* @description Ycc.UI.SingleLineText.class文件
*/
(function (Ycc) {
/**
* 绘制单行文本
* @constructor
* @extends Ycc.UI.Base
* @param option {object} 所有可配置的配置项
* @param option.content="" {string} 内容
* @param option.color=black {string} 颜色
* @param option.rect {Ycc.Math.Rect} 容纳区。会根据属性设置动态修改。位置坐标x,y为rect的x,y
* @param option.overflow=auto {string} 水平方向超出rect之后的显示方式
* <br> `hidden` -- 直接隐藏
* <br> `auto` -- 修改rect大小,完全显示
* @return {Ycc.UI}
*/
Ycc.UI.SingleLineText = function SingleLineText(option) {
Ycc.UI.Polygon.call(this,option);
this.yccClass = Ycc.UI.SingleLineText;
/**
* 区域内显示的文本
* @type {string}
*/
this.displayContent = "";
/**
* 期望绘制的文本内容
* @type {string}
*/
this.content = "";
/**
* 文字大小
* @type {string}
*/
this.fontSize = "16px";
/**
* 文字描边or填充
* @type {boolean}
*/
this.fill = true;
/**
* 文字颜色
* @type {string}
*/
this.color = "black";
/**
* 文字在区域内x方向的排列方式。 `left` or `center`
* @type {string}
*/
this.xAlign = "left";
/**
* 文字在区域内y方向的排列方式
* @type {string}
*/
this.yAlign = "center";
/**
* 文字超出后的处理方式。 `auto` or `hidden`
* @type {string}
*/
this.overflow = "auto";
this.extend(option);
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.SingleLineText.prototype,Ycc.UI.Polygon.prototype);
Ycc.UI.SingleLineText.prototype.constructor = Ycc.UI.SingleLineText;
/**
* 计算UI的各种属性。此操作必须在绘制之前调用。
* <br> 计算与绘制分离的好处是,在绘制UI之前就可以提前确定元素的各种信息,从而判断是否需要绘制。
* @override
*/
Ycc.UI.SingleLineText.prototype.computeUIProps = function () {
var self = this;
// 设置画布属性再计算,否则计算内容长度会有偏差
self.belongTo._setCtxProps(self);
// 内容的长度
var contentWidth = this.ctx.measureText(this.content).width;
this.displayContent = this.content;
// 长度超长时的处理
if(contentWidth>this.rect.width){
if(this.overflow === "hidden"){
self.displayContent = self.getMaxContentInWidth(this.content,this.rect.width);
}else if(this.overflow === "auto"){
this.rect.width = contentWidth;
}
}
if(this.overflow === "hidden"){
if(contentWidth>this.rect.width)
self.displayContent = self.getMaxContentInWidth(this.content,this.rect.width);
}else if(this.overflow === "auto"){
if(contentWidth>this.rect.width){
this.rect.width = contentWidth;
}
if(parseInt(this.fontSize)>this.rect.height){
this.rect.height = parseInt(this.fontSize);
}
}
// 计算多边形坐标
this.coordinates= this.rect.getVertices();
// 计算相对位置
this.x=this.rect.x,this.y=this.rect.y;
};
/**
* 渲染至ctx
* @param ctx
*/
Ycc.UI.SingleLineText.prototype.render = function (ctx) {
var self = this;
// 设置画布属性再计算,否则计算内容长度会有偏差
self.belongTo._setCtxProps(self);
self.ctx = ctx || self.ctx;
if(!self.ctx){
console.error("[Ycc error]:","ctx is null !");
return;
}
// 文字的绘制起点
var x,y;
// 字体大小
var fontSize = parseInt(self.fontSize);
// 配置项
var option = this;
// 绝对坐标
var rect = this.getAbsolutePositionRect();
x = rect.x;
var textWidth = this.ctx.measureText(this.displayContent).width;
if(this.xAlign==="center"){
x+=(rect.width-textWidth)/2;
}
if(this.xAlign==="right"){
x+=(rect.width-textWidth);
}
y = rect.y;
if(fontSize>rect.height){
return console.warn("[Ycc warning] : ","行高不够,或者文字太大!",option);
}
// 上下居中
if(option.yAlign==="center"){
y = y+rect.height/2-fontSize/2;
}
this.ctx.save();
// this.scaleAndRotate();
// 坐标系旋转
var absoluteAnchor = this.transformToAbsolute({x:this.anchorX,y:this.anchorY});
this.ctx.translate(absoluteAnchor.x,absoluteAnchor.y);
this.ctx.rotate(this.rotation*Math.PI/180);
this.ctx.translate(-absoluteAnchor.x,-absoluteAnchor.y);
this.ctx.fillStyle = option.color;
this.ctx.strokeStyle = option.color;
// this.baseUI.text([x,y],self.displayContent,option.fill);
this.ctx.fillText(self.displayContent,x,y);
this.ctx.restore();
};
})(Ycc);;/**
* @file Ycc.UI.ComponentButton.class.js
* @author xiaohei
* @date 2018/11/27
* @description Ycc.UI.ComponentButton.class文件
*/
(function (Ycc) {
/**
* 按钮组件
* 组件自身也是一个UI,所以option包含ui.base的所有属性
* @param option {Object}
* @param option.rect {Ycc.Math.Rect} 相对于父级按钮的位置,继承于base
* @param option.rectBgColor {String} 按钮区域的背景色,继承于base
* @param option.rectBorderWidth {Number} 按钮区域的边框宽度,继承于base
* @param option.rectBorderColor {String} 按钮区域的边框颜色,继承于base
* @param option.backgroundImageRes {String} 按钮区域的图片资源
* @param option.text {String} 按钮内的文字
* @param option.textColor {String} 按钮内的文字的颜色
* @constructor
*/
Ycc.UI.ComponentButton = function ComponentButton(option) {
Ycc.UI.Base.call(this,option);
this.yccClass = Ycc.UI.ComponentButton;
/**
* 容纳区的边框宽度
* @type {number}
*/
this.rectBorderWidth = 1;
/**
* 容纳区边框颜色
* @type {string}
*/
this.rectBorderColor = "#000";
/**
* 背景图资源
* @type {null}
*/
this.backgroundImageRes = null;
/**
* 按钮内的文字
* @type {string}
*/
this.text = "";
/**
* 按钮文字颜色
* @type {string}
*/
this.textColor = "black";
/**
* 背景
* @type {null}
* @private
*/
this.__bgUI = null;
/**
* 文字
* @type {null}
* @private
*/
this.__textUI = null;
this.extend(option);
this.__componentInit();
};
// 继承prototype
Ycc.utils.mergeObject(Ycc.UI.ComponentButton.prototype,Ycc.UI.Base.prototype);
Ycc.UI.ComponentButton.prototype.constructor = Ycc.UI.ComponentButton;
/**
* 组件初始化
* @private
*/
Ycc.UI.ComponentButton.prototype.__componentInit = function () {
if(this.backgroundImageRes){
this.__bgUI = new Ycc.UI.Image({
rect:new Ycc.Math.Rect(0,0,this.rect.width,this.rect.height),
fillMode:'scale',
res:this.backgroundImageRes,
stopEventBubbleUp:false
});
this.addChild(this.__bgUI);
}
if(this.text!==""){
this.__textUI = new Ycc.UI.SingleLineText({
rect:new Ycc.Math.Rect(0,0,this.rect.width,this.rect.height),
content:this.text,
color:this.textColor,
xAlign:'center',
stopEventBubbleUp:false
});
this.addChild(this.__textUI);
}
};
/**
* 更新属性
*/
Ycc.UI.Base.prototype.computeUIProps = function () {
if(this.__bgUI){
this.__bgUI.rect.width = this.rect.width;
this.__bgUI.rect.height = this.rect.height;
this.__bgUI.res = this.backgroundImageRes;
}
if(this.__textUI){
this.__textUI.rect.width = this.rect.width;
this.__textUI.rect.height = this.rect.height;
this.__textUI.content = this.text;
this.__textUI.color = this.textColor;
}
};
})(Ycc);;/**
* @file Ycc.polyfill.wx.js
* @author xiaohei
* @date 2018/11/19
* @description Ycc.polyfill.wx文件
*/
// 兼容微信
if("undefined"!== typeof wx){
// Ycc.prototype.getStageWidth = function () {
//
// };
/*Ycc.Gesture.prototype._init = function () {
console.log('wx gesture init');
};*/
Ycc.utils.isMobile = function () {
console.log('wx isMobile');
return true;
};
if("undefined"!== typeof performance){
performance.now = function () {
return Date.now();
};
}
};;/**
* @file Ycc.polyfill.export.js
* @author xiaohei
* @date 2018/12/11
* @description Ycc.polyfill.export文件
*
* 导出兼容文件,兼容npm模块的加载模式
*/
;if(typeof exports==="object"&&typeof module!=="undefined"){
module.exports=Ycc;
}else if(typeof define==="function"){
define("Ycc",Ycc)
}else{
var g;
if(typeof window!=="undefined"){g=window}
else if(typeof global!=="undefined"){g=global}
else if(typeof self!=="undefined"){g=self}
else{g=this}
g.Ycc = Ycc;
}