using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using FairyView;
using StandardScene.Utils;
namespace StandardScene.ExtendDevice.ButtonBox
{
///
/// 按钮盒配置管理(CycleGUI 版,替代原 WinForms ButtonBoxManager 窗体)。
///
public class ButtonBoxManager
{
private const string DataFileName = "ButtonBoxConfig.json";
private string DataFilePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DataFileName);
private readonly object SaveLock = new object();
private Panel _panel;
private List _boxes = new List();
private int _selectedBoxIdx = -1;
private int _selectedBtnIdx = -1;
private string _status = "";
private string _boxIp = "";
private string _boxPort = "502";
private string _boxIndex = "";
private int _typeIdx;
private string[] _typeNames = Array.Empty();
private string _btnIndex = "";
private string _triggerMission = "";
private string _triggerMethod = "";
private string _triggerParams = "";
private string _triggerDelay = "0";
private int _triggerStateIdx;
private readonly string[] _triggerStateNames = Enum.GetNames(typeof(ButtonState));
private static ButtonBoxManager _instance;
public static void OpenViewer() => Open();
public static void Open() => (_instance ??= new ButtonBoxManager()).OpenCore();
private void OpenCore()
{
if (_panel != null)
{
try { _panel.BringToFront(); return; }
catch { _panel = null; }
}
_typeNames = DiscoverTypes();
LoadData();
_selectedBoxIdx = -1;
_selectedBtnIdx = -1;
ClearBoxFields();
ClearButtonFields();
var panel = GUI.DeclarePanel()
.ShowTitle("按钮盒管理")
.ForceFloat()
.InitSize(1200, 760)
.InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f);
_panel = panel;
panel.IfTerminalQuit(() => { if (_panel == panel) _panel = null; });
panel.Define(pb =>
{
if (pb.Closing())
{
SaveData();
panel.Exit();
_panel = null;
return;
}
pb.SeparatorText("按钮盒");
pb.Table("btnbox-list", new[] { "盒编码", "IP", "端口", "类型", "盒操作" },
_boxes.Count, (row, i) =>
{
var b = _boxes[i];
row.Label($"{b.Index}");
row.Label(b.Ip);
row.Label($"{b.Port}");
row.Label(b.Type);
if (row.ButtonGroup(new[] { "选盒" }, new[] { "选择该按钮盒" }) == 0)
{
_selectedBoxIdx = i;
_selectedBtnIdx = -1;
LoadBoxFields(b);
ClearButtonFields();
}
}, height: FairyUiHelper.TableHeightPx(8), enableSearch: true);
pb.SeparatorText("按钮盒编辑");
var (ip, _) = pb.TextInput("1. IP", _boxIp, alwaysReturnString: true);
_boxIp = ip;
pb.SameLine(12);
var (port, _) = pb.TextInput("2. 端口", _boxPort, alwaysReturnString: true);
_boxPort = port;
var (idx, _) = pb.TextInput("3. 盒编码", _boxIndex, alwaysReturnString: true);
_boxIndex = idx;
if (_typeNames.Length > 0)
pb.DropdownBox("4. 类型", _typeNames, ref _typeIdx);
if (pb.Button("添加按钮盒", distinct: "bb-add-box")) AddBox();
pb.SameLine(8);
if (pb.Button("保存按钮盒", distinct: "bb-save-box")) SaveBox();
pb.SameLine(8);
if (pb.Button("删除按钮盒", distinct: "bb-del-box")) ConfirmDeleteBox();
var buttons = GetSelectedBox()?.Buttons ?? new List();
pb.SeparatorText($"按钮列表(盒 #{GetSelectedBox()?.Index ?? 0},共 {buttons.Count} 个)");
pb.Table("btn-list", new[] { "按钮编码", "触发状态", "延迟", "Mission", "Method", "按钮操作" },
buttons.Count, (row, i) =>
{
var btn = buttons[i];
row.Label($"{btn.Index}");
row.Label(btn.TriggerState);
row.Label($"{btn.TriggerDelay}");
row.Label(btn.TriggerMission);
row.Label(btn.TriggerMethod);
if (row.ButtonGroup(new[] { "选按钮" }, new[] { "选择该按钮" }) == 0)
{
_selectedBtnIdx = i;
LoadButtonFields(btn);
}
}, height: FairyUiHelper.TableHeightPx(6), enableSearch: true);
pb.SeparatorText("按钮编辑");
var (bIdx, _) = pb.TextInput("5. 按钮编码", _btnIndex, alwaysReturnString: true);
_btnIndex = bIdx;
pb.DropdownBox("6. 触发状态", _triggerStateNames, ref _triggerStateIdx);
var (delay, _) = pb.TextInput("7. 触发延迟", _triggerDelay, alwaysReturnString: true);
_triggerDelay = delay;
var (mission, _) = pb.TextInput("8. TriggerMission", _triggerMission, alwaysReturnString: true);
_triggerMission = mission;
var (method, _) = pb.TextInput("9. TriggerMethod", _triggerMethod, alwaysReturnString: true);
_triggerMethod = method;
var (parms, _) = pb.TextInput("10. TriggerMethodParams", _triggerParams, alwaysReturnString: true);
_triggerParams = parms;
if (pb.Button("添加按钮", distinct: "bb-add-btn")) AddButton();
pb.SameLine(8);
if (pb.Button("保存按钮", distinct: "bb-save-btn")) SaveButton();
pb.SameLine(8);
if (pb.Button("删除按钮", distinct: "bb-del-btn")) ConfirmDeleteButton();
if (!string.IsNullOrEmpty(_status))
{
pb.Separator();
pb.Label(_status);
}
});
}
private ButtonBoxModel GetSelectedBox() =>
_selectedBoxIdx >= 0 && _selectedBoxIdx < _boxes.Count ? _boxes[_selectedBoxIdx] : null;
private ButtonModel GetSelectedButton()
{
var box = GetSelectedBox();
return box != null && _selectedBtnIdx >= 0 && _selectedBtnIdx < box.Buttons.Count
? box.Buttons[_selectedBtnIdx] : null;
}
private string[] DiscoverTypes()
{
try
{
var names = Simple3.Utils.UiTypeDiscovery.AllTypes()
.Where(t => t.IsClass && !t.IsAbstract
&& t.Namespace == typeof(BasicButtonBox).Namespace
&& t.IsSubclassOf(typeof(BasicButtonBox)))
.Select(t => t.Name)
.OrderBy(n => n)
.ToArray();
return names.Length > 0 ? names : new[] { "BasicButtonBox" };
}
catch
{
return new[] { "BasicButtonBox" };
}
}
private void LoadData()
{
try
{
if (!File.Exists(DataFilePath)) { _boxes = new List(); return; }
var json = File.ReadAllText(DataFilePath, Encoding.UTF8);
_boxes = string.IsNullOrWhiteSpace(json)
? new List()
: json.JsonTo>() ?? new List();
}
catch (Exception ex)
{
_boxes = new List();
_status = $"加载失败: {ex.Message}";
}
}
private void SaveData()
{
var json = _boxes.ToJson();
var path = DataFilePath;
Task.Run(() =>
{
try
{
lock (SaveLock)
File.WriteAllText(path, json, Encoding.UTF8);
}
catch (Exception ex)
{
_status = $"保存失败: {ex.Message}";
_panel?.Repaint();
}
});
}
private void LoadBoxFields(ButtonBoxModel b)
{
_boxIp = b.Ip;
_boxPort = b.Port.ToString();
_boxIndex = b.Index.ToString();
_typeIdx = Math.Max(0, Array.IndexOf(_typeNames, b.Type));
if (_typeIdx < 0) _typeIdx = 0;
}
private void ClearBoxFields()
{
_boxIp = "";
_boxPort = "502";
_boxIndex = "";
_typeIdx = 0;
}
private void LoadButtonFields(ButtonModel b)
{
_btnIndex = b.Index.ToString();
_triggerMission = b.TriggerMission ?? "";
_triggerMethod = b.TriggerMethod ?? "";
_triggerParams = b.TriggerMethodParams ?? "";
_triggerDelay = b.TriggerDelay.ToString();
_triggerStateIdx = Math.Max(0, Array.IndexOf(_triggerStateNames, b.TriggerState));
}
private void ClearButtonFields()
{
_btnIndex = "";
_triggerMission = "";
_triggerMethod = "";
_triggerParams = "";
_triggerDelay = "0";
_triggerStateIdx = 0;
}
private void AddBox()
{
if (!TryParseBoxInput(out var index, out var ip, out var port, out var type, false, out var err))
{ FairyUiHelper.Alert("错误", err); return; }
if (_boxes.Any(b => b.Index == index)) { FairyUiHelper.Alert("错误", $"编码 {index} 已存在"); return; }
if (_boxes.Any(b => b.Ip == ip)) { FairyUiHelper.Alert("错误", $"IP {ip} 已存在"); return; }
var box = new ButtonBoxModel { Index = index, Ip = ip, Port = port, Type = type };
_boxes.Add(box);
_selectedBoxIdx = _boxes.Count - 1;
SaveData();
_status = "已添加按钮盒";
_panel?.Repaint();
}
private void SaveBox()
{
var cur = GetSelectedBox();
if (cur == null) { FairyUiHelper.Alert("提示", "请先选择按钮盒"); return; }
if (!TryParseBoxInput(out var index, out var ip, out var port, out var type, true, out var err))
{ FairyUiHelper.Alert("错误", err); return; }
if (_boxes.Any(b => b.Index == index && b != cur)) { FairyUiHelper.Alert("错误", $"编码 {index} 已存在"); return; }
if (_boxes.Any(b => b.Ip == ip && b != cur)) { FairyUiHelper.Alert("错误", $"IP {ip} 已存在"); return; }
cur.Index = index; cur.Ip = ip; cur.Port = port; cur.Type = type;
SaveData();
FairyUiHelper.Alert("提示", "保存成功");
_panel?.Repaint();
}
private void ConfirmDeleteBox()
{
var cur = GetSelectedBox();
if (cur == null) { FairyUiHelper.Alert("提示", "请选择要删除的按钮盒"); return; }
FairyUiHelper.ConfirmThen($"删除编码为 {cur.Index} 的按钮盒?", () =>
{
_boxes.Remove(cur);
_selectedBoxIdx = -1;
_selectedBtnIdx = -1;
ClearBoxFields();
ClearButtonFields();
SaveData();
_status = "已删除按钮盒";
_panel?.Repaint();
});
}
private void AddButton()
{
var box = GetSelectedBox();
if (box == null) { FairyUiHelper.Alert("提示", "请先选择按钮盒"); return; }
if (!TryParseButtonInput(out var index, out var delay, out var err))
{ FairyUiHelper.Alert("错误", err); return; }
if (box.Buttons.Any(b => b.Index == index)) { FairyUiHelper.Alert("错误", $"按钮编码 {index} 已存在"); return; }
box.Buttons.Add(new ButtonModel
{
Index = index,
TriggerMission = _triggerMission.Trim(),
TriggerMethod = _triggerMethod.Trim(),
TriggerMethodParams = _triggerParams.Trim(),
TriggerState = _triggerStateNames[_triggerStateIdx],
TriggerDelay = delay
});
_selectedBtnIdx = box.Buttons.Count - 1;
SaveData();
_status = "已添加按钮";
_panel?.Repaint();
}
private void SaveButton()
{
var box = GetSelectedBox();
var btn = GetSelectedButton();
if (box == null || btn == null) { FairyUiHelper.Alert("提示", "请先选择按钮"); return; }
if (!TryParseButtonInput(out var index, out var delay, out var err))
{ FairyUiHelper.Alert("错误", err); return; }
if (box.Buttons.Any(b => b.Index == index && b != btn)) { FairyUiHelper.Alert("错误", $"按钮编码 {index} 已存在"); return; }
btn.Index = index;
btn.TriggerMission = _triggerMission.Trim();
btn.TriggerMethod = _triggerMethod.Trim();
btn.TriggerMethodParams = _triggerParams.Trim();
btn.TriggerState = _triggerStateNames[_triggerStateIdx];
btn.TriggerDelay = delay;
SaveData();
FairyUiHelper.Alert("提示", "保存成功");
_panel?.Repaint();
}
private void ConfirmDeleteButton()
{
var box = GetSelectedBox();
var btn = GetSelectedButton();
if (box == null || btn == null) { FairyUiHelper.Alert("提示", "请选择要删除的按钮"); return; }
FairyUiHelper.ConfirmThen($"删除编码为 {btn.Index} 的按钮?", () =>
{
box.Buttons.Remove(btn);
_selectedBtnIdx = -1;
ClearButtonFields();
SaveData();
_status = "已删除按钮";
_panel?.Repaint();
});
}
private bool TryParseBoxInput(out int index, out string ip, out int port, out string type, bool requireSelection, out string err)
{
index = 0; ip = ""; port = 502; type = ""; err = "";
ip = string.IsNullOrWhiteSpace(_boxIp) ? "192.168.1.100" : _boxIp.Trim();
if (!IsValidIp(ip)) { err = "无效的 IP 地址"; return false; }
if (!int.TryParse(_boxPort.Trim(), out port)) { err = "端口必须是数字"; return false; }
if (!int.TryParse(_boxIndex.Trim(), out index))
{
index = _boxes.Count > 0 ? _boxes.Max(b => b.Index) + 1 : 1;
if (!string.IsNullOrWhiteSpace(_boxIndex.Trim())) { err = "编码必须是数字"; return false; }
}
type = _typeNames.Length > 0 ? _typeNames[_typeIdx] : "BasicButtonBox";
if (requireSelection && GetSelectedBox() == null) { err = "请先选择按钮盒"; return false; }
return true;
}
private bool TryParseButtonInput(out int index, out ushort delay, out string err)
{
index = 0; delay = 0; err = "";
var box = GetSelectedBox();
if (!int.TryParse(_btnIndex.Trim(), out index))
{
index = box?.Buttons.Count > 0 ? box.Buttons.Max(b => b.Index) + 1 : 1;
if (!string.IsNullOrWhiteSpace(_btnIndex.Trim())) { err = "按钮编码必须是数字"; return false; }
}
if (!ushort.TryParse(string.IsNullOrWhiteSpace(_triggerDelay) ? "0" : _triggerDelay.Trim(), out delay))
{ err = "触发延迟必须是 0-65535"; return false; }
return true;
}
private bool IsValidIp(string ip)
{
if (string.IsNullOrWhiteSpace(ip)) return false;
var pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
return Regex.IsMatch(ip, pattern) && IPAddress.TryParse(ip, out var addr)
&& addr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork;
}
}
}