import SPromise from './SPromise'; import Node = Laya.Node; import Sprite = Laya.Sprite; import UIComponent = Laya.UIComponent; import LayoutBox = Laya.LayoutBox; import Handler = Laya.Handler; import Tween = Laya.Tween; import Point = Laya.Point; import Skeleton = Laya.Skeleton; import Event = Laya.Event; import { GameUtils as utils } from "./GameUtils"; export module UIPlugin{ /** * 该方法允许使用多层路径来取得子节点 * @param parent 父节点 * @param childName 子节点名字,支持多层路径,使用'/'作为目录分隔符 */ export function getChildByName(parent:Sprite, childName:string):Sprite{ let ary = childName.split('/'); let crtComp:Sprite = parent; for(let i of ary){ crtComp = crtComp.getChildByName(i) as Sprite; if(!crtComp)return; } return crtComp; } /** * 在一个组件上,根据其子元素的name生成同名var * @param comp 操作对象 * @param names 欲生成的变量数组,支持多层嵌套,如'container/label' * @param varOwner var所挂载到的对象,为空则表示挂载到comp参数上面 */ export function generateVarByName(comp:Sprite, names:string[], varOwner?){ if(!varOwner)varOwner = comp; for(let i of names) { let ary = i.split('/'); let c = getChildByName(comp, i); if(c) { varOwner[ary[ary.length - 1]] = c; } } } /** * 从一个子节点中取出上面挂载的脚本 * @param parent 子节点所属节点 * @param childName 子节点名字,支持多层路径,使用'/'作为目录分隔符 * @param comp 脚本类别 */ export function getChildsComponent(parent:Sprite, childName:string, comp:any){ let c = getChildByName(parent, childName); if(c)return c.getComponent(comp); } /** * 获取一个容器中的可见子节点列表 */ export function getVisibleChildren(target:Node){ let r:Sprite[] = []; for (var i=0,n=target.numChildren;i < n;i++){ let item = target.getChildAt(i) as Sprite; if (item.visible){ r.push(item); } } return r; } /** * 生成一个SPromise对象,该对象会在scene的‘onViewCreated’事件派发后被resolve * @param scene */ export function createInitPromise(scene:Laya.Scene):SPromise{ let r = new SPromise(); scene.once('onViewCreated', r, r.resolve); return r; } /** * 刷新一个layoutBox子类对象的尺寸及相对位置 */ export function layoutBoxRefresh(target:Sprite){ if(target instanceof LayoutBox) { target.refresh(); } } /** 获得一个显示对象的锚点 */ export function getAnchorPoint(node:Sprite):Point{ let ax = utils.isNull(node['anchorX']) ? 0 : node['anchorX']; let ay = utils.isNull(node['anchorY']) ? 0 : node['anchorY']; return new Point(ax, ay); } /** 对齐两个显示对象 * @param target 需要调整位置的对象 * @param alignTo 将target对齐到的目标节点或位置 * @param offset 对齐之后还需要做的位移量 * @param alignType 对齐类型 */ export function alignUI(target:Sprite, alignTo:Sprite | Point, offset?:Point, alignType:'leftTop'|'center'='center'){ let useGlobalPos = (alignTo instanceof Sprite) && (target.parent != alignTo.parent); if(alignTo instanceof Sprite) { let [halfW, halfH, ap] = [alignTo.width / 2, alignTo.height / 2, getAnchorPoint(alignTo)]; let localPos = new Point(alignTo.x - ap.x * alignTo.width, alignTo.y - ap.y * alignTo.height); if(alignType === 'center') { localPos.x += halfW; localPos.y += halfH; } if(useGlobalPos) { alignTo = (alignTo.parent as Sprite).localToGlobal(localPos); alignTo = (target.parent as Sprite).globalToLocal(alignTo); } else { alignTo = localPos; } } let [w, h, ap] = [target.width, target.height, getAnchorPoint(target)]; let posX = alignTo.x + w * ap.x; let posY = alignTo.y + h * ap.y; if(alignType === 'center') { posX -= w / 2; posY -= h / 2; } if(offset instanceof Point) { posX += offset.x; posY += offset.y; } target.x = posX; target.y = posY; } /** * 获取一个节点在全局坐标系下的边框信息 * @param node 操作节点目标 * @param saveTo 计算结果保存到的Rectangle对象,若为空则会返回一个全新的Rectangle对象 */ export function getNodeGlobalRect(node:UIComponent, saveTo?:Laya.Rectangle):Laya.Rectangle{ let r = saveTo ? saveTo : new Laya.Rectangle(); let [w,h,ap] = [node.width, node.height, getAnchorPoint(node)]; r.width = w * node.globalScaleX; r.height = h * node.globalScaleY; let globalPos = (node.parent as Laya.Sprite).localToGlobal(new Laya.Point(node.x - ap.x * w, node.y - ap.y * h)); r.x = globalPos.x; r.y = globalPos.y; return r; } /** * 该方法可用来替代Laya.EventDispatcher.on方法添加事件侦听器并提供一些额外功能 * @param target 需要添加事件侦听器的对象 * @param type 事件类别 * @param caller 事件侦听器调用者 * @param listener 事件侦听器 * @param args 事件侦听器接收参数 * @param once 是否执行一次后就删除 * @param unshift 是否将侦听器排到第一顺位执行 */ export function addEventHandler(target:Laya.EventDispatcher, type:string, caller:any, listener:Function, args?:any[], once:boolean=false, unshift:boolean=false){ target._createListener(type,caller,listener,args,once); if(unshift) { let eList = target['_$0__events'][type]; if(_.isArray(eList)) { let thisHandler = eList.pop(); eList.unshift(thisHandler); } } } /** 在全局坐标系下,根据相对位置来布局一个视图节点,未设置任何相对坐标的话就会显示在中心位置 */ export function layoutGlobalViewInRelativePos(target:UIComponent){ let [sw, sh, dw, dh, ap] = [Laya.stage.width, Laya.stage.height, target.width * target.scaleX, target.height * target.scaleY, getAnchorPoint(target)]; //处理旋转状态Dialog的居中 let rotation = target.rotation / 90; if(rotation == 1 || rotation == -1){ [dw, dh] = [dh, dw]; } if(!isNaN(target.centerX)) { target.x = Math.round((sw / 2 - (0.5 - ap.x) * dw)) + target.centerX; } else if(!isNaN(target.left)){ target.x = target.left; } else if(!isNaN(target.right)){ target.x = sw - dw - target.right; } else{ target.x = Math.round((sw / 2 - (0.5 - ap.x) * dw)); } if(!isNaN(target.centerY)){ target.y = Math.round((sh / 2 - (0.5 - ap.y) * dh)) + target.centerY; } else if(!isNaN(target.top)){ target.y = target.top; } else if(!isNaN(target.bottom)){ target.y = sh - dh - target.bottom; } else{ target.y = Math.round((sh / 2 - (0.5 - ap.y) * dh)); } } /** 创建一个播放多次的Tween * @param target Tween.to的第一个参数 * @param props Tween.to的第二个参数 * @param duration Tween.to的第三个参数 * @param loopTime 循环次数,一次播放完毕算是一次循环,该值为0时表示无限循环 * @param yoyo 是否在播放完一次缓动后逆序播放一次,这样的话,一来一回算是一次循环 * * @returns Laya.Tween对象,带有一个reset方法可以重置target缓动属性回初始状态 */ export function loopTween(target, props, duration:number, loopTime:number=0, yoyo:boolean=false):Tween{ let startState = {}; for(let p in props) { startState[p] = target[p]; } let onComp; let loopFinish = Handler.create(undefined, ()=>{ if(--loopTime != 0) { loopTween(target, props, duration, loopTime, yoyo); } else { r['reset'](true); } }); let r = Laya.Pool.getItemByClass("tween", Tween); //这个Tween对象的reset方法被调用时可以将target重置回初始状态 r['reset'] = (recover:boolean)=>{ for(let p in startState) { target[p] = startState[p]; } if(recover)this.recover(); } if(yoyo) { onComp = Handler.create(r, r.to, [target, startState, duration, undefined, loopFinish]) } else { onComp = loopFinish; } r.to(target, props, duration, undefined, onComp); return r; } /** 安全地播放一个SK动画 */ export function safePlayAnimation(animationNode:Skeleton, loop:boolean=false, onPlay?:()=>void){ if(!animationNode['_aniPath']){ console.error('safePlayAnimation::您不能播放一个未设置URL的SK动画'); return; } let play = ()=>{ onPlay && onPlay(); animationNode.play(0, loop); } if(animationNode.templet) { play(); } else { animationNode['_complete'] = Handler.create(undefined, play); } } /** * 判断一个全局坐标系下的点是否落在目标节点内 * @param globalPos * @param node */ export function isGlobalPosInNode(globalPos:Point, node:Sprite):boolean{ let localPos = node.globalToLocal(globalPos); return node.getSelfBounds().contains(localPos.x, localPos.y); } /** 拖拽控制器 */ export class Dragger{ private _target:Sprite; private _onDragEnd:(globalPos:Point, isCancel?:boolean)=>void; private _removeFromParent:boolean; static _ins:Dragger; static instance(){ if(!Dragger._ins)Dragger._ins = new Dragger(); return Dragger._ins; } /** * 开始全屏幕拖拽 * @param target 拖拽目标 * @param createDragTarget 创建拖拽目标的函数,如果不传,则拖动target自身 * @param onDragEnd 拖拽结束回调函数 */ startDrag(target:Sprite, createDragTarget?:()=>Sprite, onDragEnd?:(globalPos:Point, isCancel?:boolean)=>void){ let stage = Laya.stage; stage.offAllCaller(this); this._target = createDragTarget ? createDragTarget() : target; this._onDragEnd = onDragEnd; Laya.MouseManager.instance.disableMouseEvent = true; stage.on(Event.MOUSE_MOVE, this, this.updateTargetPos); stage.on(Event.MOUSE_OUT, this, this._onMouseUp); stage.on(Event.MOUSE_UP, this, this._onMouseUp); if(this._target != target) { stage.addChild(this._target); } this.updateTargetPos(); } stopDrag(isCancel:boolean = false){ if(!this._target)return; let stage = Laya.stage; stage.offAllCaller(this); Laya.MouseManager.instance.disableMouseEvent = false; if(this._target.parent === stage) { this._target.destroy(); } this._onDragEnd && this._onDragEnd(this.crtPos, isCancel); this._target = undefined; } updateTargetPos(){ let t = this._target; let s = Laya.stage; let p = t.parent as Sprite; if(!p)return; alignUI(t, p === s ? this.crtPos : p.globalToLocal(this.crtPos)); } /** * 为一个节点增加拖拽监视器,一旦在该节点上按下手指并移动一段距离后就会触发拖拽操作 * @param target 拖拽目标 * @param threshold 拖拽触发阙值。若该值为空则会视为手指滑出target范围外即触发拖拽操作 * @param createDragTarget 创建拖拽目标的函数,如果不传,则拖动target自身 * @param onDragStart 拖拽开始回调函数 * @param onDragEnd 拖拽结束回调函数 */ addDragWatcher(target:Sprite, threshold?:Point, createDragTarget?:()=>Sprite, onDragStart?:(target:Sprite)=>void, onDragEnd?:(globalPos:Point, isCancel?:boolean)=>void){ if(threshold) { //TODO } else { target.on(Event.MOUSE_OUT, this, this._triggerDrag, [target, createDragTarget, onDragStart, onDragEnd]); } target.on(Event.MOUSE_UP, this, this._stopWatch); } get crtPos(){ return new Point(Laya.stage.mouseX, Laya.stage.mouseY); } private _triggerDrag(target, createDragTarget, onDragStart, onDragEnd){ target.offAllCaller(this); onDragStart && onDragStart(target); this.startDrag(target, createDragTarget, onDragEnd); } private _stopWatch(e:Event){ e.currentTarget.offAllCaller(this); } private _onMouseUp(e:Event){ this.stopDrag(); } } /** * 获得一个可以作为Tween.to方法的第四个参数使用的EaseBackIn的缓动方法 * @param gradient 坡度。该值越大则缓动过程速度变化越快 * @param amplitude 振幅。该值越大则缓动属性回弹的幅度越大 */ export function generateEaseBackInFuc(gradient:number=2, amplitude:number=1.7){ //方法所使用公式抄自官方的Ease.backIn方法 return function(t,b,c,d){ t = t / d; let r = c * Math.pow(t, gradient) *((amplitude+1)*t-amplitude) + b; return r; } } }