drgame-cc 1.0.45 → 1.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/drres/focus/focus_1/gesture.atlas +34 -0
  2. package/drres/focus/focus_1/gesture.atlas.meta +6 -0
  3. package/drres/focus/focus_1/gesture.json +1 -0
  4. package/drres/focus/focus_1/gesture.json.meta +10 -0
  5. package/drres/focus/focus_1/gesture.png +0 -0
  6. package/drres/{icons/pointer-yellow-60.png.meta → focus/focus_1/gesture.png.meta} +14 -14
  7. package/drres/focus/focus_1.meta +13 -0
  8. package/drres/focus/focus_2/gesture.atlas +41 -0
  9. package/drres/focus/focus_2/gesture.atlas.meta +6 -0
  10. package/drres/focus/focus_2/gesture.json +1 -0
  11. package/drres/focus/focus_2/gesture.json.meta +10 -0
  12. package/drres/focus/focus_2/gesture.png +0 -0
  13. package/drres/{icons/pointer-blue-60.png.meta → focus/focus_2/gesture.png.meta} +13 -13
  14. package/drres/focus/focus_2.meta +13 -0
  15. package/drres/focus/focus_3/gesture.atlas +34 -0
  16. package/drres/focus/focus_3/gesture.atlas.meta +6 -0
  17. package/drres/focus/focus_3/gesture.json +1 -0
  18. package/drres/focus/focus_3/gesture.json.meta +10 -0
  19. package/drres/focus/focus_3/gesture.png +0 -0
  20. package/drres/{icons/pointer-pink-60.png.meta → focus/focus_3/gesture.png.meta} +12 -12
  21. package/drres/focus/focus_3.meta +13 -0
  22. package/drres/focus/focus_4/gesture.atlas +20 -0
  23. package/drres/focus/focus_4/gesture.atlas.meta +6 -0
  24. package/drres/focus/focus_4/gesture.json +1 -0
  25. package/drres/focus/focus_4/gesture.json.meta +10 -0
  26. package/drres/focus/focus_4/gesture.png +0 -0
  27. package/drres/focus/focus_4/gesture.png.meta +38 -0
  28. package/drres/focus/focus_4.meta +13 -0
  29. package/drres/focus.meta +13 -0
  30. package/drres/prefab/ExitWin.prefab +2 -2
  31. package/drres/prefab/Toast.prefab +378 -378
  32. package/drscript/{ad/AdManager.ts.meta → BtnExtra.ts.meta} +1 -1
  33. package/drscript/Define.ts +17 -2
  34. package/drscript/{ad/AdSlot.ts.meta → Define.ts.meta} +1 -1
  35. package/drscript/{ad/AggregateAdProvider.ts.meta → ExitWin.ts.meta} +1 -1
  36. package/drscript/GameAnalytics.ts +37 -118
  37. package/drscript/{ad/IAdProvider.ts.meta → GameAnalytics.ts.meta} +1 -1
  38. package/drscript/HelperWin.ts.meta +10 -0
  39. package/drscript/Info.ts.meta +10 -0
  40. package/drscript/InternalUitl.ts.meta +10 -0
  41. package/drscript/KeyAdapter.ts +133 -0
  42. package/drscript/KeyAdapter.ts.meta +10 -0
  43. package/drscript/Loader.ts.meta +10 -0
  44. package/drscript/PlatformAdapter.ts +178 -192
  45. package/drscript/PlatformAdapter.ts.meta +10 -0
  46. package/drscript/README.md +91 -805
  47. package/drscript/README.md.meta +6 -0
  48. package/drscript/UIAdapter.ts +324 -0
  49. package/drscript/UIAdapter.ts.meta +10 -0
  50. package/drscript/WebBridge.ts +1 -1
  51. package/drscript/WebBridge.ts.meta +10 -0
  52. package/drscript/ad-sdk/reporters/superset-lewo.js +6 -0
  53. package/drscript/ad-sdk/types/enums.d.ts +1 -0
  54. package/drscript/ad-sdk.meta +13 -0
  55. package/drscript/ad.meta +13 -0
  56. package/drscript/component/AlertTip.ts +25 -0
  57. package/drscript/component/AlertTip.ts.meta +10 -0
  58. package/drscript/component/BtnExtra.ts +17 -0
  59. package/drscript/component/BtnExtra.ts.meta +10 -0
  60. package/drscript/{ExitWin.ts → component/ExitWin.ts} +6 -8
  61. package/drscript/component/ExitWin.ts.meta +10 -0
  62. package/drscript/{HelperWin.ts → component/HelperWin.ts} +5 -19
  63. package/drscript/component/HelperWin.ts.meta +10 -0
  64. package/drscript/component.meta +13 -0
  65. package/drscript/config.meta +13 -0
  66. package/drscript/core.meta +13 -0
  67. package/drscript/focus/FocusItem.ts +1 -19
  68. package/drscript/focus/FocusManager.ts +157 -198
  69. package/drscript/focus/FocusVisualLayer.ts +74 -163
  70. package/drscript/focus.meta +13 -0
  71. package/drscript/input/KeyboardMove.ts +40 -11
  72. package/drscript/input/KeyboardMove.ts.meta +10 -0
  73. package/drscript/input.meta +13 -0
  74. package/drscript/interface.meta +13 -0
  75. package/drscript/pinyin.meta +13 -0
  76. package/drscript/toast.meta +13 -0
  77. package/drscript/virtualgamepad.meta +13 -0
  78. package/package.json +1 -1
  79. package/drres/config/default.json +0 -108
  80. package/drres/config/default.json.meta +0 -6
  81. package/drres/icons/pointer-blue-60.png +0 -0
  82. package/drres/icons/pointer-pink-60.png +0 -0
  83. package/drres/icons/pointer-white-60.png +0 -0
  84. package/drres/icons/pointer-yellow-60.png +0 -0
  85. package/drscript/Info.ts +0 -78
  86. package/drscript/InternalUitl.ts +0 -65
  87. package/drscript/ad/AdManager.ts +0 -174
  88. package/drscript/ad/AdSlot.ts +0 -44
  89. package/drscript/ad/AggregateAdProvider.ts +0 -149
  90. package/drscript/ad/IAdProvider.ts +0 -39
  91. package/drscript/ad/MockAdProvider.ts +0 -36
  92. package/drscript/ad/MockAdProvider.ts.meta +0 -10
  93. package/drscript/ad/SelfAdProvider.ts +0 -41
  94. package/drscript/ad/SelfAdProvider.ts.meta +0 -10
  95. package/drscript/config/TvUiConfig.ts +0 -155
  96. package/drscript/config/TvUiConfig.ts.meta +0 -10
  97. package/drscript/config/TvUiConfigManager.ts +0 -133
  98. package/drscript/config/TvUiConfigManager.ts.meta +0 -10
  99. package/drscript/core/BackManager.ts +0 -38
  100. package/drscript/core/BackManager.ts.meta +0 -10
  101. package/drscript/core/NativeBridge.ts +0 -137
  102. package/drscript/core/NativeBridge.ts.meta +0 -10
  103. package/drscript/core/TvUi.ts +0 -69
  104. package/drscript/core/TvUi.ts.meta +0 -10
  105. package/drscript/core/TvUiRoot.ts +0 -137
  106. package/drscript/core/TvUiRoot.ts.meta +0 -10
  107. package/drscript/icon/IconConfig.ts +0 -7
  108. package/drscript/icon/IconConfig.ts.meta +0 -10
  109. package/drscript/icon/IconManager.ts +0 -97
  110. package/drscript/icon/IconManager.ts.meta +0 -10
  111. package/drscript/input/KeyCodeAdapter.ts +0 -23
  112. package/drscript/input/RemoteInputManager.ts +0 -105
  113. package/drscript/interface/ILobbyBridge.ts +0 -5
  114. package/drscript/toast/ToastComponent.ts +0 -35
  115. package/drscript/toast/ToastComponent.ts.meta +0 -10
  116. package/drscript/toast/ToastManager.ts +0 -66
  117. package/drscript/toast/ToastManager.ts.meta +0 -10
  118. package/drscript/toast/TvExitConfirmDialog.ts +0 -334
  119. package/drscript/toast/TvExitConfirmDialog.ts.meta +0 -10
  120. package/drscript/toast/UIFunctions.ts +0 -150
  121. package/drscript/toast/UIFunctions.ts.meta +0 -10
@@ -1,116 +1,73 @@
1
- import { TvUiDirection } from "../config/TvUiConfig";
2
- import KeyCodeAdapter, { TvRemoteKey } from "../input/KeyCodeAdapter";
3
- import FocusScope from "./FocusScope";
4
-
5
- export interface FocusOptions {
6
- root?: cc.Node;
7
- scopeId?: string;
8
- focusId?: string;
9
- iconKey?: string;
10
- up?: string;
11
- down?: string;
12
- left?: string;
13
- right?: string;
14
- onClick?: Function;
15
- onFocus?: Function;
16
- onBlur?: Function;
17
- focusScale?: number;
18
- showCursor?: boolean;
19
- }
20
-
21
- export interface FocusCursorOptions {
22
- basePath?: string;
23
- normalPath?: string;
24
- focusPath?: string;
25
- corner?: string;
26
- width?: number;
27
- height?: number;
28
- offsetX?: number;
29
- offsetY?: number;
30
- }
1
+ import { KeyCode } from "../ad-sdk/ad-sdk";
31
2
 
32
3
  export interface FocusRuntimeItem {
33
4
  node: cc.Node;
34
- root?: cc.Node;
35
- scopeId: string;
36
- focusId: string;
37
- iconKey?: string;
38
- up?: string;
39
- down?: string;
40
- left?: string;
41
- right?: string;
42
- onClick?: Function;
43
5
  onFocus?: Function;
44
6
  onBlur?: Function;
45
- component?: any;
46
- baseScaleX: number;
47
- baseScaleY: number;
7
+ onClick?: Function;
48
8
  focusScale: number;
49
9
  showCursor: boolean;
10
+ offset: cc.Vec2;
50
11
  }
51
12
 
52
13
  type FocusListener = (item: FocusRuntimeItem | null, previous?: FocusRuntimeItem | null) => void;
53
14
 
15
+ interface ScopeInfo {
16
+ scopeId: string;
17
+ lastNodeUuid: string;
18
+ /** 绑定的窗口/场景节点(用于返回键自动处理) */
19
+ node?: cc.Node;
20
+ /** 节点类型 */
21
+ nodeType?: "window" | "scene";
22
+ /** 自定义返回回调 */
23
+ onBack?: () => boolean | void;
24
+ }
25
+
54
26
  export default class FocusManager {
55
27
  private static readonly FOCUS_SCALE_ACTION_TAG: number = 9911;
56
28
  private static readonly BASE_SCALE_X_KEY: string = "__tvFocusBaseScaleX";
57
29
  private static readonly BASE_SCALE_Y_KEY: string = "__tvFocusBaseScaleY";
58
30
  private static items: FocusRuntimeItem[] = [];
59
- private static itemMap: { [focusId: string]: FocusRuntimeItem } = {};
60
- private static currentFocusId: string = "";
61
- private static scopes: FocusScope[] = [new FocusScope("page")];
62
- private static activeScopeId: string = "page";
31
+ private static itemMap: { [uuid: string]: FocusRuntimeItem } = {};
32
+ private static scopeMap: { [uuid: string]: string } = {};
33
+ private static currentNodeUuid: string = "";
34
+ private static scopes: ScopeInfo[] = [];
35
+ private static activeScopeId: string = "";
63
36
  private static listeners: FocusListener[] = [];
64
- private static cursorOptions: FocusCursorOptions | null = null;
65
37
 
66
- public static setCursor(options: FocusCursorOptions | null) {
67
- this.cursorOptions = options;
68
- }
38
+ private static readonly DEFAULT_FOCUS_SCALE = 1.08;
69
39
 
70
- public static getCursor(): FocusCursorOptions | null {
71
- return this.cursorOptions;
72
- }
73
-
74
- public static register(target: cc.Node | any, options: FocusOptions = {}) {
40
+ public static register(target: cc.Node | any, options: { scopeId?: string; onFocus?: Function; onBlur?: Function; onClick?: Function; focusScale?: number; showCursor?: boolean; offset?: cc.Vec2 } = {}): void {
75
41
  let node = this.resolveNode(target);
76
42
  if (!node) return;
77
43
  let component = this.resolveComponent(target, node);
78
- let previousItem = this.findItemByNode(node);
79
- let previousBaseScale = previousItem ? { x: previousItem.baseScaleX, y: previousItem.baseScaleY } : null;
80
44
  this.unregister(node);
81
45
 
82
- let focusId = options.focusId || (component && component.focusId) || node.uuid || this.createFocusId(node);
83
- this.unregisterById(focusId);
84
- let baseScale = this.getNodeBaseScale(node, previousBaseScale);
46
+ let uuid = node.uuid;
47
+ this.getNodeBaseScale(node);
48
+
49
+ let scopeId = options.scopeId || (component && component.scopeId) || this.activeScopeId;
50
+ this.scopeMap[uuid] = scopeId;
85
51
 
86
52
  let item: FocusRuntimeItem = {
87
53
  node: node,
88
- root: options.root,
89
- scopeId: options.scopeId || (component && component.scopeId) || this.activeScopeId || "page",
90
- focusId: focusId,
91
- iconKey: options.iconKey || (component && component.iconKey) || "",
92
- up: options.up || (component && component.up) || "",
93
- down: options.down || (component && component.down) || "",
94
- left: options.left || (component && component.left) || "",
95
- right: options.right || (component && component.right) || "",
96
- onClick: options.onClick,
97
54
  onFocus: options.onFocus || (component && component.onFocus),
98
55
  onBlur: options.onBlur || (component && component.onBlur),
99
- component: component,
100
- baseScaleX: baseScale.x,
101
- baseScaleY: baseScale.y,
102
- focusScale: options.focusScale || (component && component.focusScale) || 1.08,
103
- showCursor: options.showCursor !== false
56
+ onClick: options.onClick,
57
+ focusScale: options.focusScale || (component && component.focusScale) || this.DEFAULT_FOCUS_SCALE,
58
+ showCursor: options.showCursor !== false,
59
+ offset: options.offset || cc.v2(0, 0)
104
60
  };
105
61
 
106
62
  this.items.push(item);
107
- this.itemMap[item.focusId] = item;
63
+ this.itemMap[uuid] = item;
108
64
  this.ensureFocus();
109
65
  }
110
66
 
111
67
  public static unregister(target: cc.Node | any) {
112
68
  let node = this.resolveNode(target);
113
69
  if (!node) return;
70
+ delete this.scopeMap[node.uuid];
114
71
  for (let i = this.items.length - 1; i >= 0; i--) {
115
72
  if (this.items[i].node == node) {
116
73
  this.removeItemAt(i);
@@ -123,21 +80,25 @@ export default class FocusManager {
123
80
  if (!root) return;
124
81
  for (let i = this.items.length - 1; i >= 0; i--) {
125
82
  let item = this.items[i];
126
- if (item.root == root || this.isChildOf(item.node, root)) {
83
+ if (this.isChildOf(item.node, root)) {
84
+ delete this.scopeMap[item.node.uuid];
127
85
  this.removeItemAt(i);
128
86
  }
129
87
  }
130
88
  this.ensureFocus();
131
89
  }
132
90
 
133
- public static unregisterById(focusId: string) {
134
- if (!focusId) return;
91
+ private static unregisterByUuid(uuid: string) {
92
+ if (!uuid) return;
135
93
  for (let i = this.items.length - 1; i >= 0; i--) {
136
- if (this.items[i].focusId == focusId) this.removeItemAt(i);
94
+ if (this.items[i].node.uuid == uuid) {
95
+ delete this.scopeMap[uuid];
96
+ this.removeItemAt(i);
97
+ }
137
98
  }
138
99
  }
139
100
 
140
- public static registerButtons(root: cc.Node, options: FocusOptions = {}) {
101
+ public static registerButtons(root: cc.Node) {
141
102
  if (!root) return;
142
103
  this.unregisterByRoot(root);
143
104
  this.walk(root, (node: cc.Node) => {
@@ -145,31 +106,36 @@ export default class FocusManager {
145
106
  let button = node.getComponent(cc.Button);
146
107
  if (!button) return;
147
108
  this.register(node, {
148
- root: root,
149
- scopeId: options.scopeId || this.activeScopeId,
150
- iconKey: options.iconKey,
151
- focusScale: options.focusScale || 1.08,
109
+ scopeId: this.activeScopeId,
110
+ focusScale: this.DEFAULT_FOCUS_SCALE,
152
111
  onClick: () => this.invokeButton(button)
153
112
  });
154
113
  });
114
+
155
115
  this.ensureFocus();
156
116
  }
157
117
 
158
- public static pushScope(scopeId: string, root?: cc.Node, firstFocus?: string | cc.Node) {
118
+ public static pushScope(scopeId: string, options?: { node?: cc.Node; nodeType?: "window" | "scene"; onBack?: () => boolean | void; skipEnsureFocus?: boolean }) {
159
119
  scopeId = scopeId || "modal";
160
120
  let currentScope = this.getActiveScope();
161
- if (currentScope) currentScope.lastFocusId = this.currentFocusId;
121
+ if (currentScope) currentScope.lastNodeUuid = this.currentNodeUuid;
162
122
  for (let i = 0; i < this.scopes.length; i++) {
163
123
  if (this.scopes[i].scopeId == scopeId) {
164
124
  this.scopes.splice(i, 1);
165
125
  break;
166
126
  }
167
127
  }
168
- this.scopes.push(new FocusScope(scopeId, root));
128
+ this.scopes.push({ scopeId, lastNodeUuid: "", node: options?.node, nodeType: options?.nodeType, onBack: options?.onBack });
169
129
  this.activeScopeId = scopeId;
170
- this.setFocusById("", true);
171
- if (firstFocus) this.focus(firstFocus);
172
- this.ensureFocus();
130
+ this.clearFocus();
131
+ if (!options?.skipEnsureFocus) this.ensureFocus();
132
+ }
133
+
134
+ /** 移除作用域并清理其下所有注册焦点 */
135
+ public static removeScope(node: cc.Node) {
136
+ if (!node) return;
137
+ this.popScope(node.uuid);
138
+ this.unregisterByRoot(node);
173
139
  }
174
140
 
175
141
  public static popScope(scopeId?: string) {
@@ -181,50 +147,77 @@ export default class FocusManager {
181
147
  break;
182
148
  }
183
149
  }
150
+ // 清理已弹出作用域下的所有条目
151
+ for (let i = this.items.length - 1; i >= 0; i--) {
152
+ if (this.scopeMap[this.items[i].node.uuid] == target) {
153
+ this.removeItemAt(i);
154
+ }
155
+ }
184
156
  let active = this.scopes[this.scopes.length - 1];
185
- this.activeScopeId = active ? active.scopeId : "page";
186
- let restored = active && active.lastFocusId ? this.focus(active.lastFocusId) : false;
157
+ this.activeScopeId = active ? active.scopeId : "";
158
+ let restored = active && active.lastNodeUuid ? this.focus(active.lastNodeUuid) : false;
187
159
  if (!restored) {
188
- this.setFocusById("", true);
160
+ this.clearFocus();
189
161
  this.ensureFocus();
190
162
  }
191
163
  }
192
164
 
165
+ /** 获取最顶层绑定了节点的作用域 */
166
+ public static getTopNodeScope(): ScopeInfo | null {
167
+ for (let i = this.scopes.length - 1; i >= 0; i--) {
168
+ if (this.scopes[i].node) return this.scopes[i];
169
+ }
170
+ return null;
171
+ }
172
+
173
+ /** 获取所有作用域(只读副本) */
174
+ public static getScopes(): ReadonlyArray<ScopeInfo> {
175
+ return this.scopes;
176
+ }
177
+
193
178
  public static reset() {
194
179
  let old = this.getCurrentFocusItem();
195
180
  if (old) this.setFocusVisual(old, false, true);
196
181
  this.items = [];
197
182
  this.itemMap = {};
198
- this.currentFocusId = "";
199
- this.scopes = [new FocusScope("page")];
200
- this.activeScopeId = "page";
183
+ this.scopeMap = {};
184
+ this.currentNodeUuid = "";
185
+ this.scopes = [];
186
+ this.activeScopeId = "";
201
187
  this.emitFocusChanged(null, old);
202
188
  }
203
189
 
204
- public static focus(target: string | cc.Node): boolean {
190
+ public static focus(target: string | cc.Node, silent: boolean = false): boolean {
205
191
  this.cleanup();
206
192
  if (!target) return false;
207
- let item: FocusRuntimeItem | null = null;
208
- if (typeof target == "string") {
209
- item = this.itemMap[target];
210
- } else {
211
- for (let i = 0; i < this.items.length; i++) {
212
- if (this.items[i].node == target) {
213
- item = this.items[i];
214
- break;
215
- }
193
+ let uuid = typeof target == "string" ? target : target.uuid;
194
+ let item = this.itemMap[uuid];
195
+ if (!item || !this.isItemActive(item) || !this.isInActiveScope(item)) return false;
196
+
197
+ let previous: FocusRuntimeItem | null = this.getCurrentFocusItem();
198
+ if (uuid == this.currentNodeUuid && uuid) {
199
+ let current = this.getCurrentFocusItem();
200
+ if (current) {
201
+ this.setFocusVisual(current, true);
202
+ // 总是触发位置更新,即使静默模式也要更新视觉位置
203
+ this.emitFocusChanged(current, previous || current);
216
204
  }
205
+ return true;
217
206
  }
218
- if (!item || !this.isItemActive(item) || !this.isInActiveScope(item)) return false;
219
- this.setFocusById(item.focusId);
207
+ if (previous) this.setFocusVisual(previous, false);
208
+ this.currentNodeUuid = uuid || "";
209
+ let current: FocusRuntimeItem | null = this.getCurrentFocusItem();
210
+ if (current) {
211
+ let scope = this.getActiveScope();
212
+ if (scope) scope.lastNodeUuid = current.node.uuid;
213
+ this.setFocusVisual(current, true);
214
+ }
215
+ // 总是触发位置更新,确保视觉层收到通知
216
+ this.emitFocusChanged(current, previous);
220
217
  return true;
221
218
  }
222
219
 
223
- public static focusNode(node: cc.Node): boolean {
224
- return this.focus(node);
225
- }
226
-
227
- public static move(direction: TvUiDirection): boolean {
220
+ public static move(direction: KeyCode): boolean {
228
221
  this.cleanup();
229
222
  let current = this.getCurrentFocusItem();
230
223
  if (!current) {
@@ -232,22 +225,19 @@ export default class FocusManager {
232
225
  return !!this.getCurrentFocusItem();
233
226
  }
234
227
 
235
- let explicitId = current[direction];
236
- if (explicitId && this.focus(explicitId)) return true;
237
-
238
228
  let vector = this.getDirectionVector(direction);
239
- let currentCenter = this.getWorldCenter(current.node);
229
+ let currentCenter = this.getWorldCenter(current.node).add(current.offset);
240
230
  let best: FocusRuntimeItem | null = null;
241
231
  let bestScore = Number.MAX_VALUE;
242
232
 
243
233
  for (let i = 0; i < this.items.length; i++) {
244
234
  let item = this.items[i];
245
235
  if (item == current || !this.isItemActive(item) || !this.isInActiveScope(item)) continue;
246
- let center = this.getWorldCenter(item.node);
236
+ let center = this.getWorldCenter(item.node).add(item.offset);
247
237
  let dx = center.x - currentCenter.x;
248
238
  let dy = center.y - currentCenter.y;
249
- if (vector.x != 0 && dx * vector.x <= 8) continue;
250
- if (vector.y != 0 && dy * vector.y <= 8) continue;
239
+ if (vector.x != 0 && dx * vector.x <= 0) continue;
240
+ if (vector.y != 0 && dy * vector.y <= 0) continue;
251
241
  let primary = vector.x != 0 ? Math.abs(dx) : Math.abs(dy);
252
242
  let secondary = vector.x != 0 ? Math.abs(dy) : Math.abs(dx);
253
243
  let score = primary + secondary * 2;
@@ -258,7 +248,7 @@ export default class FocusManager {
258
248
  }
259
249
 
260
250
  if (best) {
261
- this.setFocusById(best.focusId);
251
+ this.focus(best.node.uuid);
262
252
  return true;
263
253
  }
264
254
  return this.moveByOrder(direction == "left" || direction == "down" ? -1 : 1);
@@ -272,40 +262,18 @@ export default class FocusManager {
272
262
  item.onClick();
273
263
  return true;
274
264
  }
275
- if (item.component && item.component.onConfirm) {
276
- item.component.onConfirm();
265
+ let focusItem = item.node.getComponent("FocusItem") as any;
266
+ if (focusItem && focusItem.onConfirm) {
267
+ focusItem.onConfirm();
277
268
  return true;
278
269
  }
279
270
  this.invokeButton(item.node.getComponent(cc.Button));
280
271
  return true;
281
272
  }
282
273
 
283
- public static back(): boolean {
284
- return false;
285
- }
286
-
287
- public static handleKey(keyCode: number): boolean {
288
- let key = KeyCodeAdapter.toRemoteKey(keyCode);
289
- if (key == TvRemoteKey.Ok) return this.confirm();
290
- if (key == TvRemoteKey.Left) return this.move("left");
291
- if (key == TvRemoteKey.Right) return this.move("right");
292
- if (key == TvRemoteKey.Up) return this.move("up");
293
- if (key == TvRemoteKey.Down) return this.move("down");
294
- if (key == TvRemoteKey.Back) return this.back();
295
- return false;
296
- }
297
-
298
- public static clickCurrent(): boolean {
299
- return this.confirm();
300
- }
301
-
302
274
  public static getCurrentFocusItem(): FocusRuntimeItem | null {
303
- let item = this.itemMap[this.currentFocusId];
304
- return this.isItemActive(item) ? item : null;
305
- }
306
-
307
- public static getActiveScopeId(): string {
308
- return this.activeScopeId;
275
+ let item = this.itemMap[this.currentNodeUuid];
276
+ return (item && this.isItemActive(item) && this.isInActiveScope(item)) ? item : null;
309
277
  }
310
278
 
311
279
  public static onFocusChanged(listener: FocusListener) {
@@ -318,34 +286,20 @@ export default class FocusManager {
318
286
  if (index >= 0) this.listeners.splice(index, 1);
319
287
  }
320
288
 
321
- private static setFocusById(focusId: string, silent: boolean = false) {
322
- if (focusId == this.currentFocusId && focusId) {
323
- let current = this.getCurrentFocusItem();
324
- if (current && !silent) {
325
- this.setFocusVisual(current, true);
326
- this.emitFocusChanged(current, current);
327
- }
328
- return;
329
- }
289
+ public static clearFocus() {
330
290
  let previous: FocusRuntimeItem | null = this.getCurrentFocusItem();
331
291
  if (previous) this.setFocusVisual(previous, false);
332
- this.currentFocusId = focusId || "";
333
- let current: FocusRuntimeItem | null = this.getCurrentFocusItem();
334
- if (current) {
335
- let scope = this.getActiveScope();
336
- if (scope) scope.lastFocusId = current.focusId;
337
- this.setFocusVisual(current, true);
338
- }
339
- if (!silent) this.emitFocusChanged(current, previous);
340
- if (silent && previous) this.emitFocusChanged(null, previous);
292
+ this.currentNodeUuid = "";
293
+ if (previous) this.emitFocusChanged(null, previous);
341
294
  }
342
295
 
343
296
  private static setFocusVisual(item: FocusRuntimeItem, focused: boolean, immediate: boolean = false) {
344
297
  if (!item || !item.node || !item.node.isValid) return;
345
298
  this.setButtonSpriteVisual(item, focused);
346
299
  item.node.stopActionByTag(this.FOCUS_SCALE_ACTION_TAG);
347
- let targetScaleX = focused ? item.baseScaleX * item.focusScale : item.baseScaleX;
348
- let targetScaleY = focused ? item.baseScaleY * item.focusScale : item.baseScaleY;
300
+ let baseScale = this.getNodeBaseScale(item.node);
301
+ let targetScaleX = focused ? baseScale.x * item.focusScale : baseScale.x;
302
+ let targetScaleY = focused ? baseScale.y * item.focusScale : baseScale.y;
349
303
  if (immediate) {
350
304
  item.node.setScale(targetScaleX, targetScaleY);
351
305
  } else {
@@ -353,8 +307,8 @@ export default class FocusManager {
353
307
  action.setTag(this.FOCUS_SCALE_ACTION_TAG);
354
308
  item.node.runAction(action);
355
309
  }
356
- if (focused && item.onFocus) item.onFocus.call(item.component || item.node);
357
- if (!focused && item.onBlur) item.onBlur.call(item.component || item.node);
310
+ if (focused && item.onFocus) item.onFocus();
311
+ if (!focused && item.onBlur) item.onBlur();
358
312
  }
359
313
 
360
314
  private static setButtonSpriteVisual(item: FocusRuntimeItem, focused: boolean) {
@@ -368,18 +322,34 @@ export default class FocusManager {
368
322
  if (spriteFrame) sprite.spriteFrame = spriteFrame;
369
323
  }
370
324
 
325
+ private static _ensureFocusRetry: number = 0;
326
+ private static readonly _ENSURE_FOCUS_MAX_RETRY = 3;
327
+
371
328
  private static ensureFocus() {
372
329
  this.cleanup();
373
330
  if (this.getCurrentFocusItem()) return;
374
331
  let activeScope = this.getActiveScope();
375
- if (activeScope && activeScope.lastFocusId && this.focus(activeScope.lastFocusId)) return;
332
+ if (activeScope && activeScope.lastNodeUuid && this.focus(activeScope.lastNodeUuid)) return;
376
333
  for (let i = 0; i < this.items.length; i++) {
377
334
  let item = this.items[i];
378
335
  if (this.isItemActive(item) && this.isInActiveScope(item)) {
379
- this.setFocusById(item.focusId);
336
+ this.focus(item.node.uuid);
337
+ this._ensureFocusRetry = 0;
380
338
  return;
381
339
  }
382
340
  }
341
+ // 有注册的按钮但暂无活跃节点,可能是节点尚未完全初始化,延迟重试
342
+ if (this.items.length > 0 && this._ensureFocusRetry < this._ENSURE_FOCUS_MAX_RETRY) {
343
+ this._ensureFocusRetry++;
344
+ setTimeout(() => {
345
+ this.ensureFocus();
346
+ }, 50);
347
+ return;
348
+ }
349
+ // 超过重试次数/无注册项,清除焦点状态
350
+ this._ensureFocusRetry = 0;
351
+ this.currentNodeUuid = "";
352
+ this.emitFocusChanged(null, null);
383
353
  }
384
354
 
385
355
  private static moveByOrder(step: number): boolean {
@@ -390,13 +360,13 @@ export default class FocusManager {
390
360
  if (activeItems.length <= 0) return false;
391
361
  let index = 0;
392
362
  for (let i = 0; i < activeItems.length; i++) {
393
- if (activeItems[i].focusId == this.currentFocusId) {
363
+ if (activeItems[i].node.uuid == this.currentNodeUuid) {
394
364
  index = i;
395
365
  break;
396
366
  }
397
367
  }
398
368
  let next = (index + step + activeItems.length) % activeItems.length;
399
- this.setFocusById(activeItems[next].focusId);
369
+ this.focus(activeItems[next].node.uuid);
400
370
  return true;
401
371
  }
402
372
 
@@ -404,10 +374,11 @@ export default class FocusManager {
404
374
  let item = this.items[index];
405
375
  if (!item) return;
406
376
  this.setFocusVisual(item, false, true);
407
- delete this.itemMap[item.focusId];
377
+ delete this.itemMap[item.node.uuid];
378
+ delete this.scopeMap[item.node.uuid];
408
379
  this.items.splice(index, 1);
409
- if (this.currentFocusId == item.focusId) {
410
- this.currentFocusId = "";
380
+ if (this.currentNodeUuid == item.node.uuid) {
381
+ this.currentNodeUuid = "";
411
382
  this.emitFocusChanged(null, item);
412
383
  }
413
384
  }
@@ -426,10 +397,10 @@ export default class FocusManager {
426
397
  }
427
398
 
428
399
  private static isInActiveScope(item?: FocusRuntimeItem | null): boolean {
429
- return !!item && item.scopeId == this.activeScopeId;
400
+ return !!item && this.scopeMap[item.node.uuid] == this.activeScopeId;
430
401
  }
431
402
 
432
- private static getActiveScope(): FocusScope | null {
403
+ private static getActiveScope(): ScopeInfo | null {
433
404
  for (let i = this.scopes.length - 1; i >= 0; i--) {
434
405
  if (this.scopes[i].scopeId == this.activeScopeId) return this.scopes[i];
435
406
  }
@@ -441,10 +412,10 @@ export default class FocusManager {
441
412
  return cc.v2(box.x + box.width / 2, box.y + box.height / 2);
442
413
  }
443
414
 
444
- private static getDirectionVector(direction: TvUiDirection): cc.Vec2 {
445
- if (direction == "left") return cc.v2(-1, 0);
446
- if (direction == "right") return cc.v2(1, 0);
447
- if (direction == "up") return cc.v2(0, 1);
415
+ private static getDirectionVector(direction: KeyCode): cc.Vec2 {
416
+ if (direction == KeyCode.Left) return cc.v2(-1, 0);
417
+ if (direction == KeyCode.Right) return cc.v2(1, 0);
418
+ if (direction == KeyCode.Up) return cc.v2(0, 1);
448
419
  return cc.v2(0, -1);
449
420
  }
450
421
 
@@ -479,15 +450,7 @@ export default class FocusManager {
479
450
  return target;
480
451
  }
481
452
 
482
- private static findItemByNode(node: cc.Node): FocusRuntimeItem | null {
483
- if (!node) return null;
484
- for (let i = 0; i < this.items.length; i++) {
485
- if (this.items[i].node == node) return this.items[i];
486
- }
487
- return null;
488
- }
489
-
490
- private static getNodeBaseScale(node: cc.Node, preferred?: { x: number, y: number } | null): { x: number, y: number } {
453
+ private static getNodeBaseScale(node: cc.Node): { x: number, y: number } {
491
454
  let nodeAny: any = node as any;
492
455
  let storedX = nodeAny[this.BASE_SCALE_X_KEY];
493
456
  let storedY = nodeAny[this.BASE_SCALE_Y_KEY];
@@ -495,7 +458,7 @@ export default class FocusManager {
495
458
  return { x: storedX, y: storedY };
496
459
  }
497
460
 
498
- let baseScale = preferred || {
461
+ let baseScale = {
499
462
  x: this.readScale(node, "scaleX"),
500
463
  y: this.readScale(node, "scaleY")
501
464
  };
@@ -516,10 +479,6 @@ export default class FocusManager {
516
479
  return typeof value == "number" && isFinite(value);
517
480
  }
518
481
 
519
- private static createFocusId(node: cc.Node): string {
520
- return "focus_" + (node.name || "node") + "_" + this.items.length;
521
- }
522
-
523
482
  private static walk(node: cc.Node, visitor: (node: cc.Node) => void) {
524
483
  visitor(node);
525
484
  for (let i = 0; i < node.childrenCount; i++) {
@@ -535,4 +494,4 @@ export default class FocusManager {
535
494
  }
536
495
  return false;
537
496
  }
538
- }
497
+ }