ui_base.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. import { _decorator, assetManager, Button, Component, EventHandler, EventTouch, find, isValid, Node, Prefab, Toggle, ToggleContainer } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. /***
  4. * @en internal class, used for handling node event.
  5. * @zh 内部类,用于节点事件监听
  6. *
  7. * */
  8. @ccclass('tgxNodeEventAgent')
  9. class __NodeEventAgent__ extends Component {
  10. /***
  11. * @en recieve button click event and deliver them to the real handlers.
  12. * @zh 接受按钮事件,并转发给真正的处理函数
  13. * */
  14. onButtonClicked(evt: EventTouch, customEventData) {
  15. let btn = (evt.target as Node).getComponent(Button);
  16. let clickEvents = btn.clickEvents;
  17. for (let i = 0; i < clickEvents.length; ++i) {
  18. let h = clickEvents[i];
  19. if (h.customEventData == customEventData) {
  20. let cb = h['$cb$'];
  21. let target = h['$target$']
  22. let args = h['$args$'];
  23. cb.apply(target, [btn, args]);
  24. }
  25. }
  26. }
  27. /***
  28. * @en recieve toggle event and deliver them to the real handlers.
  29. * @zh 接受Toggle事件,并转发给真正的处理函数
  30. * */
  31. onToggleEvent(toggle: Toggle, customEventData) {
  32. let checkEvents = toggle.checkEvents;
  33. //if (toggle['_toggleContainer']) {
  34. // checkEvents = toggle['_toggleContainer'].checkEvents;
  35. //}
  36. for (let i = 0; i < checkEvents.length; ++i) {
  37. let h = checkEvents[i];
  38. if (h.customEventData == customEventData) {
  39. let cb = h['$cb$'];
  40. let target = h['$target$']
  41. let args = h['$args$'];
  42. cb.apply(target, [toggle, args]);
  43. }
  44. }
  45. }
  46. }
  47. let _id:number=0;
  48. export default class ui_base {
  49. private static _clss:Set<any>=new Set();
  50. private static _uis: ui_base[] = [];
  51. /***
  52. * @en hide and destroy all ui panel.
  53. * @zh 隐藏并销毁所有UI面板
  54. * */
  55. public static closeAll() {
  56. while (this._uis.length) {
  57. this._uis[0].close();
  58. }
  59. this._clss.clear();
  60. }
  61. //
  62. public static closeAndReleaseAll(){
  63. while (this._uis.length) {
  64. this._uis[0].closeAndRelease();
  65. }
  66. this._clss.clear();
  67. }
  68. //update all ui, called by UI.
  69. public static updateAll(dt:number) {
  70. for (let i = 0; i < this._uis.length; ++i) {
  71. let ctrl = this._uis[i];
  72. if (ctrl.node && isValid(ctrl.node)) {
  73. this._uis[i].onUpdate(dt);
  74. }
  75. }
  76. }
  77. private static _addCls(cls:any):void{
  78. if(!cls) return;this._clss.add(cls);
  79. }
  80. private static _removeCls(cls:any):void{
  81. if(!cls) return;this._clss.delete(cls);
  82. }
  83. private static _hasCls(cls:any):boolean{
  84. return this._clss.has(cls);
  85. }
  86. private _id: number = 0;
  87. private _bundle:string;
  88. private _prefab: string;
  89. private _layer: number;
  90. private _layout: any;
  91. private _cls:any;
  92. protected node: Node;
  93. private _destroyed:boolean = false;
  94. /***
  95. * @en the instance id to indicate an unique ui panel.
  96. * @zh 实例ID,用于标记一个唯一面板实例
  97. * */
  98. public get id(): number {return this._id;}
  99. /***
  100. * @en url of the prefab used by this ui panel.
  101. * @zh 本UI使用prefab路径
  102. * */
  103. public get prefab(): string {return this._prefab;}
  104. public get bundle():string{return this._bundle;}
  105. /***
  106. * @en layer of this ui panel.
  107. * @zh 本UI所在的UI层级
  108. * */
  109. public get layer(): number {return this._layer;}
  110. /***
  111. * @en layout of this ui panel.
  112. * @zh 本UI组件
  113. * */
  114. public get layout(): Component { return this._layout;}
  115. public getLayout<T extends Component>():T{ return this._layout as T;}
  116. constructor(bundle:string,prefab: string, layer: number, layoutCls: any) {
  117. this._cls = null;
  118. this._bundle=bundle;
  119. this._prefab = prefab;
  120. this._layer = layer;
  121. this._layout = layoutCls;
  122. this._id = _id++;
  123. }
  124. //setup this ui,called by UIMgr.
  125. private _setup(cls:any,node: Node,...data: any[]) {
  126. ui_base._uis.push(this);
  127. this._cls=cls;
  128. (ui_base as any)._addCls(this._cls);
  129. this.node = node;
  130. if (this._layout) this._layout = this.node.getComponent(this._layout);
  131. //notify sub class to handle something.
  132. //节点创建完毕,调用子类的处理函数。
  133. this.onCreated(...data);
  134. //check whether it has been destroyed, if has, hide it.
  135. //检查是否为已销毁,如果已销毁,则走销毁流程
  136. if(this._destroyed) this.close();
  137. }
  138. /**
  139. * @en hide and destroy this ui panel.
  140. * @zh 隐藏并销毁此UI面板
  141. * */
  142. public close(){
  143. this._resolve_close?.();
  144. this._resolve_close=null;
  145. this._destroyed = true;
  146. if(!this.node) return;
  147. this.node.removeFromParent();
  148. for (let i = 0; i < ui_base._uis.length; ++i) {
  149. if (ui_base._uis[i] == this) {
  150. ui_base._uis.splice(i, 1);
  151. break;
  152. }
  153. }
  154. this.onDispose();
  155. this.node.destroy();
  156. this.node = null;
  157. (ui_base as any)._removeCls(this._cls);
  158. this._cls=null;
  159. }
  160. public closeAndRelease(){
  161. this.close();
  162. assetManager.getBundle(this._bundle)?.release(this._prefab);
  163. }
  164. private _resolve_close: (() => void) | null = null;
  165. /**等待此ui关闭*/
  166. public wait_close():Promise<void>{
  167. if(this._resolve_close) return;
  168. return new Promise((resolve) => {this._resolve_close = resolve;});
  169. }
  170. /**
  171. * @en add button event handler
  172. * @zh 添加按钮事件
  173. * @param relativeNodePath to indicate a button node, can pass `string`|`Node`|`Button` here.
  174. * @param cb will be called when event emits. method format:(btn:Button,args:any)=>void
  175. * @param target the `this` argument of `cb`
  176. * */
  177. onButtonEvent(relativeNodePath: string | Node | Button, cb: Function, target?: any, args?: any) {
  178. let buttonNode: Node = null;
  179. if (relativeNodePath instanceof Node) {
  180. buttonNode = relativeNodePath;
  181. }
  182. else if (relativeNodePath instanceof Button) {
  183. buttonNode = relativeNodePath.node;
  184. }
  185. else {
  186. buttonNode = find(relativeNodePath, this.node);
  187. }
  188. if (!buttonNode) {
  189. return null;
  190. }
  191. //添加转发器
  192. let agent = this.node.getComponent(__NodeEventAgent__);
  193. if (!agent) {
  194. agent = this.node.addComponent(__NodeEventAgent__);
  195. }
  196. let btn = buttonNode.getComponent(Button);
  197. let clickEvents = btn.clickEvents;
  198. let handler = new EventHandler();
  199. handler.target = this.node;
  200. handler.component = 'tgxNodeEventAgent';
  201. handler.handler = 'onButtonClicked';
  202. handler.customEventData = '' + _id++;
  203. //附加额外信息 供事件转发使用
  204. handler['$cb$'] = cb;
  205. handler['$target$'] = target;
  206. handler['$args$'] = args;
  207. clickEvents.push(handler);
  208. btn.clickEvents = clickEvents;
  209. }
  210. /**
  211. * @en remove button event handler
  212. * @zh 移除按钮事件
  213. * @param relativeNodePath to indicate a button node, can pass `string`|`Node`|`Button` here.
  214. * @param cb will be called when event emits.
  215. * @param target the `this` argument of `cb`
  216. * */
  217. offButtonEvent(relativeNodePath: string | Node | Button, cb: Function, target: any) {
  218. let buttonNode: Node = null;
  219. if (relativeNodePath instanceof Node) {
  220. buttonNode = relativeNodePath;
  221. }
  222. else if (relativeNodePath instanceof Button) {
  223. buttonNode = relativeNodePath.node;
  224. }
  225. else {
  226. buttonNode = find(relativeNodePath, this.node);
  227. }
  228. if (!buttonNode) {
  229. return; ``
  230. }
  231. let agent = this.node.getComponent(__NodeEventAgent__);
  232. if (!agent) {
  233. return;
  234. }
  235. let btn = buttonNode.getComponent(Button);
  236. if (!btn) {
  237. return;
  238. }
  239. let clickEvents = btn.clickEvents;
  240. for (let i = 0; i < clickEvents.length; ++i) {
  241. let h = clickEvents[i];
  242. if (h['$cb$'] == cb && h['$target$'] == target) {
  243. clickEvents.splice(i, 1);
  244. btn.clickEvents = clickEvents;
  245. break;
  246. }
  247. }
  248. }
  249. /**
  250. * @en add toggle event handler
  251. * @zh 添加Toggle事件
  252. * @param relativeNodePath to indicate a button node, can pass `string`|`Node`|`Button` here.
  253. * @param cb will be called when event emits. method format:(btn:Toggle,args:any)=>void
  254. * @param target the `this` argument of `cb`
  255. */
  256. onToggleEvent(relativeNodePath: string | Node | Toggle | ToggleContainer, cb: Function, target?: any, args?: any) {
  257. let buttonNode: Node = null;
  258. if (relativeNodePath instanceof Node) {
  259. buttonNode = relativeNodePath;
  260. }
  261. else if (relativeNodePath instanceof Toggle) {
  262. buttonNode = relativeNodePath.node;
  263. }
  264. else if (relativeNodePath instanceof ToggleContainer) {
  265. buttonNode = relativeNodePath.node;
  266. }
  267. else {
  268. buttonNode = find(relativeNodePath, this.node);
  269. }
  270. if (!buttonNode) {
  271. return null;
  272. }
  273. //添加转发器
  274. let agent = this.node.getComponent(__NodeEventAgent__);
  275. if (!agent) agent = this.node.addComponent(__NodeEventAgent__);
  276. let btn = buttonNode.getComponent(Toggle) as any;
  277. if (!btn) btn = buttonNode.getComponent(ToggleContainer) as any;
  278. let checkEvents = btn.checkEvents;
  279. let handler = new EventHandler();
  280. handler.target = this.node;
  281. handler.component = 'tgxNodeEventAgent';
  282. handler.handler = 'onToggleEvent';
  283. handler.customEventData = '' + _id++;
  284. //附加额外信息 供事件转发使用
  285. handler['$cb$'] = cb;
  286. handler['$target$'] = target;
  287. handler['$args$'] = args;
  288. checkEvents.push(handler);
  289. btn.checkEvents = checkEvents;
  290. }
  291. /**
  292. * @en remove toggle event handler
  293. * @zh 移除Toggle事件
  294. * @param relativeNodePath to indicate a button node, can pass `string`|`Node`|`Button` here.
  295. * @param cb will be called when event emits. method format:(btn:Toggle,args:any)=>void
  296. * @param target the `this` argument of `cb`
  297. * */
  298. offToggleEvent(relativeNodePath: string | Node | Toggle | ToggleContainer, cb: Function, target: any) {
  299. let buttonNode: Node = null;
  300. if (relativeNodePath instanceof Node) {
  301. buttonNode = relativeNodePath;
  302. }
  303. else if (relativeNodePath instanceof Toggle) {
  304. buttonNode = relativeNodePath.node;
  305. }
  306. else if (relativeNodePath instanceof ToggleContainer) {
  307. buttonNode = relativeNodePath.node;
  308. }
  309. else {
  310. buttonNode = find(relativeNodePath, this.node);
  311. }
  312. if (!buttonNode) {
  313. return null;
  314. }
  315. //添加转发器
  316. let agent = this.node.getComponent(__NodeEventAgent__);
  317. if (!agent) {
  318. return;
  319. }
  320. let btn = buttonNode.getComponent(Toggle) as any;
  321. if (!btn) {
  322. btn = buttonNode.getComponent(ToggleContainer) as any;
  323. }
  324. let checkEvents = btn.checkEvents;
  325. for (let i = 0; i < checkEvents.length; ++i) {
  326. let h = checkEvents[i];
  327. if (h['$cb$'] == cb && h['$target$'] == target) {
  328. checkEvents.splice(i, 1);
  329. btn.checkEvents = checkEvents;
  330. break;
  331. }
  332. }
  333. }
  334. //子类的所有操作,需要在这个函数之后。
  335. protected onCreated(...data: any[]){}
  336. //当界面销毁时调用
  337. protected onDispose(){}
  338. //
  339. protected onUpdate(dt?:number) { }
  340. }