using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public enum KeyboardLayout
{
Default,
Numeric,
Hex,
Pinyin // 拼音键盘(支持中文输入)
}
///
/// 动态生成自定义键盘并将按键事件转发给 InputDialog
/// 用法:把该脚本挂在 CustomKeyboardPanel 上,面板内部应包含一个用于放按键的容器(可不设置则使用自身)。
///
public class CustomKeyboard : MonoBehaviour
{
public Transform KeysContainer; // 按键放置容器
[Tooltip("如果设置了 KeyPrefab,则在运行时使用该 prefab 来生成按键;如果为空,脚本会创建简单的 GameObject(仅在运行时)。")]
public GameObject KeyPrefab;
public Vector2 KeySize = new Vector2(60, 60);
public Vector2 KeySpacing = new Vector2(6, 6);
private object _target; // 可以是 InputDialog 或 KeyboardInputAdapter
private KeyboardLayout _layout = KeyboardLayout.Default;
private bool _isPasswordMode = false;
private bool _isUpperCase = false; // 拼音键盘大小写切换
private GridLayoutGroup _grid;
// 拼音输入相关
private string _pinyinBuffer = ""; // 拼音缓冲区
private Transform _candidateContainer; // 候选字容器
public Action OnKeyboardSizeChanged; // 键盘大小变化回调
// 翻页相关
private int _currentPage = 0; // 当前页码(0基索引)
private const int _candidatesPerPage = 7; // 每页显示的候选字数量
private string[] _allCandidates = null; // 当前拼音的所有候选字
void Awake()
{
// 如果 KeysContainer 未设置,尝试查找;否则 InputDialog.CreateDynamicKeyboard() 已设置
if (KeysContainer == null)
{
var child = this.transform.Find("KeysContainer");
if (child != null)
KeysContainer = child;
else
KeysContainer = this.transform;
}
_grid = KeysContainer.GetComponent();
// 如果容器本身没有 GridLayoutGroup 则添加一个(仅在这里需要,动态创建时已添加)
if (_grid == null)
{
_grid = KeysContainer.gameObject.AddComponent();
_grid.cellSize = KeySize;
_grid.spacing = KeySpacing;
}
}
public void SetTarget(object target, KeyboardLayout layout, bool isPassword)
{
_target = target;
_layout = layout;
_isPasswordMode = isPassword;
_isUpperCase = false;
_pinyinBuffer = "";
RebuildKeys();
}
public void RebuildKeys()
{
// 注意: 修改 Prefab Asset(例如在 Project 视图中打开的 Prefab)时,
// 直接对其 Transform 做 SetParent/Destroy 会导致 Unity 抛出错误:
// "Setting the parent of a transform which resides in a Prefab Asset is disabled to prevent data corruption"
// 因此此方法只会在运行时(Play mode)修改场景实例的子对象。
if (!Application.isPlaying)
{
Debug.Log("RebuildKeys skipped: only runs in Play mode to avoid editing prefab assets. For editing prefab assets use the Editor -> Prefab editing workflow.");
return;
}
// 清除已有按键(运行时安全)
for (int i = KeysContainer.childCount - 1; i >= 0; --i)
{
var child = KeysContainer.GetChild(i);
if (Application.isPlaying) Destroy(child.gameObject);
else DestroyImmediate(child.gameObject);
}
switch (_layout)
{
case KeyboardLayout.Numeric:
BuildNumeric();
break;
case KeyboardLayout.Hex:
BuildHex();
break;
case KeyboardLayout.Pinyin:
BuildPinyin();
break;
default:
BuildDefault();
break;
}
// add control row: Clear Backspace Confirm
BuildControlRow();
}
private void BuildKey(string label, Action onClick)
{
// 优先使用 KeyPrefab(需在 Inspector 中赋值),在运行时用 Instantiate 创建实例并设置父物体——这是安全的。
GameObject go = null;
if (KeyPrefab != null)
{
go = Instantiate(KeyPrefab, KeysContainer, false);
go.name = "Key_" + label;
// 如果 prefab 没有自动设置文本,需要查找并设置
var tmp = go.GetComponentInChildren();
if (tmp != null) tmp.text = label;
var btn = go.GetComponent