import { Button, Color, EditBox, Label, Node, Overflow, UITransform, HorizontalTextAlignment, VerticalTextAlignment } from 'cc'; import { GmBuiltinPanelId, type IGmConfirmData, type IGmContentPanel, type IGmNumberStepperData, type IGmPanelContext, type IGmTextAreaData } from './GmTypes'; import { GmRegistry } from './GmRegistry'; /** * 在父节点下创建带 Label 的按钮。 */ function makeBtn(parent: Node, title: string, w: number, h: number, onClick: () => void): Node { const n = new Node(title); n.layer = parent.layer; const ut = n.addComponent(UITransform); ut.setContentSize(w, h); const lb = n.addComponent(Label); lb.string = title; lb.fontSize = 22; lb.horizontalAlign = HorizontalTextAlignment.CENTER; lb.verticalAlign = VerticalTextAlignment.CENTER; lb.color = Color.WHITE; n.addComponent(Button); n.on(Button.EventType.CLICK, onClick, null); parent.addChild(n); return n; } /** * 数字步进面板。 */ class GmNumberStepperPanel implements IGmContentPanel { private _root: Node | null = null; private _value = 0; private _mode: 'set' | 'add' = 'set'; private _lbValue: Label | null = null; private _lbMode: Label | null = null; private _ctx: IGmPanelContext | null = null; private _data: IGmNumberStepperData | null = null; open(parent: Node, ctx: IGmPanelContext): void { this.close(); this._ctx = ctx; this._data = ctx.command.panelData as IGmNumberStepperData; if (!this._data?.getValue || !this._data?.apply) { console.warn('[GM] numberStepper 缺少 panelData'); ctx.host.toast('面板配置错误'); ctx.goBack(); return; } this._value = Math.floor(Number(this._data.getValue()) || 0); this._mode = this._data.defaultMode ?? 'set'; this._root = new Node('numberStepper'); this._root.layer = parent.layer; const rootUt = this._root.addComponent(UITransform); rootUt.setContentSize(640, 400); parent.addChild(this._root); const title = new Node('title'); title.layer = parent.layer; title.addComponent(UITransform).setContentSize(600, 40); const tl = title.addComponent(Label); tl.string = ctx.command.title; tl.fontSize = 28; tl.horizontalAlign = HorizontalTextAlignment.CENTER; tl.color = Color.WHITE; title.setPosition(0, 160); this._root.addChild(title); const valNode = new Node('val'); valNode.layer = parent.layer; valNode.addComponent(UITransform).setContentSize(200, 50); this._lbValue = valNode.addComponent(Label); this._lbValue.fontSize = 36; this._lbValue.horizontalAlign = HorizontalTextAlignment.CENTER; this._lbValue.color = Color.YELLOW; valNode.setPosition(0, 80); this._root.addChild(valNode); const modeNode = new Node('mode'); modeNode.layer = parent.layer; modeNode.addComponent(UITransform).setContentSize(200, 30); this._lbMode = modeNode.addComponent(Label); this._lbMode.fontSize = 22; this._lbMode.horizontalAlign = HorizontalTextAlignment.CENTER; this._lbMode.color = Color.CYAN; modeNode.setPosition(0, 30); this._root.addChild(modeNode); const steps = [-100, -10, -1, 1, 10, 100]; for (let i = 0; i < steps.length; i++) { const s = steps[i]; const bx = -250 + i * 100; makeBtn(this._root, s > 0 ? `+${s}` : `${s}`, 88, 44, () => { this._value += s; this._clamp(); this._paint(); }).setPosition(bx, -30); } makeBtn(this._root, '切换模式', 140, 44, () => { this._mode = this._mode === 'set' ? 'add' : 'set'; this._paint(); }).setPosition(-120, -100); makeBtn(this._root, '应用', 140, 44, () => { this._data!.apply(this._value, this._mode); this._ctx!.refreshList(); this._ctx!.goBack(); }).setPosition(120, -100); makeBtn(this._root, '返回', 100, 40, () => this._ctx!.goBack()).setPosition(0, -160); this._paint(); } close(): void { if (this._root?.isValid) this._root.destroy(); this._root = null; this._lbValue = null; this._lbMode = null; this._ctx = null; this._data = null; } private _clamp(): void { const min = this._data?.min; const max = this._data?.max; if (min !== undefined && this._value < min) this._value = min; if (max !== undefined && this._value > max) this._value = max; } private _paint(): void { if (this._lbValue) this._lbValue.string = `${this._value}`; if (this._lbMode) this._lbMode.string = this._mode === 'set' ? '模式:设为' : '模式:增加'; } } /** * 文本区面板(存档 JSON 等)。 */ class GmTextAreaPanel implements IGmContentPanel { private _root: Node | null = null; private _edit: EditBox | null = null; private _ctx: IGmPanelContext | null = null; open(parent: Node, ctx: IGmPanelContext): void { this.close(); this._ctx = ctx; const data = ctx.command.panelData as IGmTextAreaData; if (!data?.apply) { console.warn('[GM] textArea 缺少 panelData'); ctx.host.toast('面板配置错误'); ctx.goBack(); return; } this._root = new Node('textArea'); this._root.layer = parent.layer; this._root.addComponent(UITransform).setContentSize(640, 480); parent.addChild(this._root); const title = new Node('title'); title.layer = parent.layer; title.addComponent(UITransform).setContentSize(600, 36); const tl = title.addComponent(Label); tl.string = ctx.command.title; tl.fontSize = 26; tl.horizontalAlign = HorizontalTextAlignment.CENTER; tl.color = Color.WHITE; title.setPosition(0, 200); this._root.addChild(title); const box = new Node('edit'); box.layer = parent.layer; box.addComponent(UITransform).setContentSize(580, 280); this._edit = box.addComponent(EditBox); this._edit.placeholder = data.placeholder ?? '粘贴内容'; this._edit.string = ''; box.setPosition(0, 20); this._root.addChild(box); makeBtn(this._root, '应用', 140, 44, () => { data.apply(this._edit?.string ?? ''); this._ctx!.refreshList(); this._ctx!.goBack(); }).setPosition(-100, -180); makeBtn(this._root, '返回', 140, 44, () => this._ctx!.goBack()).setPosition(100, -180); } close(): void { if (this._root?.isValid) this._root.destroy(); this._root = null; this._edit = null; this._ctx = null; } } /** * 二次确认面板。 */ class GmConfirmPanel implements IGmContentPanel { private _root: Node | null = null; private _ctx: IGmPanelContext | null = null; open(parent: Node, ctx: IGmPanelContext): void { this.close(); this._ctx = ctx; const data = ctx.command.panelData as IGmConfirmData; if (!data?.onConfirm) { console.warn('[GM] confirmQuick 缺少 panelData'); ctx.host.toast('面板配置错误'); ctx.goBack(); return; } this._root = new Node('confirm'); this._root.layer = parent.layer; this._root.addComponent(UITransform).setContentSize(560, 280); parent.addChild(this._root); const msg = new Node('msg'); msg.layer = parent.layer; msg.addComponent(UITransform).setContentSize(500, 120); const lb = msg.addComponent(Label); lb.string = data.message || ctx.command.title; lb.fontSize = 24; lb.horizontalAlign = HorizontalTextAlignment.CENTER; lb.verticalAlign = VerticalTextAlignment.CENTER; lb.overflow = Overflow.RESIZE_HEIGHT; lb.color = Color.WHITE; msg.setPosition(0, 40); this._root.addChild(msg); makeBtn(this._root, '确认', 140, 44, () => { data.onConfirm(); this._ctx!.refreshList(); this._ctx!.goBack(); }).setPosition(-100, -80); makeBtn(this._root, '取消', 140, 44, () => this._ctx!.goBack()).setPosition(100, -80); } close(): void { if (this._root?.isValid) this._root.destroy(); this._root = null; this._ctx = null; } } /** * 注册 mx 内置内容面板。 */ export class GmBuiltinPanels { private static _done = false; /** 幂等注册内置面板 */ static ensureRegistered(): void { if (this._done) return; this._done = true; GmRegistry.registerPanel(GmBuiltinPanelId.numberStepper, () => new GmNumberStepperPanel()); GmRegistry.registerPanel(GmBuiltinPanelId.textArea, () => new GmTextAreaPanel()); GmRegistry.registerPanel(GmBuiltinPanelId.confirmQuick, () => new GmConfirmPanel()); } }