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; } } }