You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
9.0 KiB
263 lines
9.0 KiB
|
1 day ago
|
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());
|
||
|
|
}
|
||
|
|
}
|