# CycleGUI 实用示例 ## 示例 1:最小应用 最简可运行 CycleGUI 应用: ```csharp using CycleGUI; using CycleGUI.Terminals; LocalTerminal.SetTitle("Hello CycleGUI"); LocalTerminal.AddMenuItem("Exit", LocalTerminal.Terminate); LocalTerminal.Start(); GUI.PromptPanel(pb => { pb.Panel.ShowTitle("Hello"); pb.Label("Hello from CycleGUI!"); if (pb.Button("Exit")) pb.Panel.Exit(); }); ``` ## 示例 2:多面板导航(LearnCycleGUI 模式) 主面板按钮打开子面板,子面板停靠在左侧: ```csharp Panel mainPanel = null; Panel settingsPanel = null; List activePanels = new(); mainPanel = GUI.PromptPanel(pb => { pb.Panel.ShowTitle("Main"); if (pb.Button("Open Settings")) { if (!activePanels.Contains(settingsPanel)) { settingsPanel = GUI.DeclarePanel() .ShowTitle("Settings") .InitPosRelative(mainPanel, 0, 16, 0, 1) .SetDefaultDocking(Panel.Docking.Left); settingsPanel.Define(CreateSettingsHandler()); activePanels.Add(settingsPanel); } else settingsPanel.BringToFront(); } }); PanelBuilder.CycleGUIHandler CreateSettingsHandler() { float volume = 0.5f; bool darkMode = true; int quality = 1; return pb => { pb.SliderFloat("Volume", ref volume, 0, 1); pb.Toggle("Dark Mode", ref darkMode); pb.RadioButtons("Quality", new[] { "Low", "Medium", "High" }, ref quality); pb.Separator(); if (pb.Closing()) { activePanels.Remove(settingsPanel); pb.Panel.Exit(); } }; } ``` ## 示例 3:模态对话框 阻塞式确认对话框,返回用户选择: ```csharp bool confirmed = false; GUI.PromptAndWaitPanel(pb => { pb.Panel.ShowTitle("Confirm").Modal(true).AutoSize(true); pb.Label("Are you sure you want to delete?"); pb.SameLine(); if (pb.Button("Yes", distinct: "yes")) { confirmed = true; pb.Panel.Exit(); } pb.SameLine(); if (pb.Button("No", distinct: "no")) { pb.Panel.Exit(); } }); if (confirmed) { /* proceed */ } ``` ## 示例 4:实时数据面板 后台线程更新数据,面板实时刷新: ```csharp float temperature = 20f; float humidity = 50f; bool running = true; var panel = GUI.DeclarePanel() .ShowTitle("Sensor Monitor") .InitSize(350, 200) .SetDefaultDocking(Panel.Docking.Right); panel.Define(pb => { pb.MiniPlot("Temperature", temperature); pb.MiniPlot("Humidity", humidity); pb.RealtimePlot("Temp Chart", temperature); pb.Separator(); if (pb.Button("Stop")) running = false; pb.Panel.Repaint(); // 实时数据:handler 内持续 Repaint }); Task.Run(() => { var rng = new Random(); while (running) { temperature = 20f + (float)rng.NextDouble() * 5; humidity = 50f + (float)rng.NextDouble() * 10; Thread.Sleep(100); // 也可在此 panel.Repaint() 替代 handler 内 Repaint,二选一 } }); ``` ## 示例 5:表格展示与交互 ```csharp string[] names = { "Alice", "Bob", "Charlie", "Diana" }; float[] scores = { 95, 87, 73, 91 }; bool[] enabled = { true, true, false, true }; panel.Define(pb => { pb.Table("students", new[] { "Name", "Score", "Enabled", "Action" }, names.Length, (row, i) => { row.Label(names[i]); row.Label(scores[i].ToString("F1")); row.Checkbox(ref enabled[i]); if (row.ButtonGroup("", new[] { "Edit", "Delete" }, out int act)) { if (act == 0) Console.WriteLine($"Edit {names[i]}"); if (act == 1) Console.WriteLine($"Delete {names[i]}"); } }, height: 200, enableSearch: true, title: "Student Records"); }); ``` ## 示例 6:3D 场景 - 加载模型和点云 ```csharp using CycleGUI; using CycleGUI.API; using System.Numerics; // 加载模型类 Workspace.AddProp(new LoadModel { name = "car_model", detail = new Workspace.ModelDetail(File.ReadAllBytes("car.glb")) { Scale = 0.001f, Center = new Vector3(0, 0, 0), } }); // 放置两个实例 Workspace.AddProp(new PutModelObject { name = "car_a", clsName = "car_model", newPosition = new Vector3(0, 0, 0), newQuaternion = Quaternion.Identity, }); Workspace.AddProp(new PutModelObject { name = "car_b", clsName = "car_model", newPosition = new Vector3(5, 0, 0), newQuaternion = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathF.PI / 2), }); // 添加地面点云 var gridPoints = new List(); var gridColors = new List(); for (float x = -10; x <= 10; x += 0.5f) for (float y = -10; y <= 10; y += 0.5f) { gridPoints.Add(new Vector4(x, y, 0, 2)); gridColors.Add(0xFF808080); } Workspace.AddProp(new PutPointCloud { name = "ground_grid", xyzSzs = gridPoints.ToArray(), colors = gridColors.ToArray(), newPosition = Vector3.Zero, }); // 设置相机 new SetCamera { lookAt = new Vector3(2.5f, 0, 0), altitude = MathF.PI / 4, azimuth = -MathF.PI / 3, distance = 15f, }.IssueToAllTerminals(); ``` ## 示例 7:Painter 实时调试绘制 ```csharp var painter = Painter.GetPainter("debug_overlay"); Task.Run(() => { float t = 0; while (true) { painter.Clear(); // 绘制坐标轴 painter.DrawLine(Color.Red, Vector3.Zero, Vector3.UnitX * 2, 2f, Painter.ArrowType.End); painter.DrawLine(Color.Green, Vector3.Zero, Vector3.UnitY * 2, 2f, Painter.ArrowType.End); painter.DrawLine(Color.Blue, Vector3.Zero, Vector3.UnitZ * 2, 2f, Painter.ArrowType.End); // 绘制旋转点 float x = MathF.Cos(t) * 3; float y = MathF.Sin(t) * 3; painter.DrawDot(Color.Yellow, new Vector3(x, y, 0), 5f); painter.DrawText(Color.White, new Vector3(x, y, 0.3f), $"({x:F1}, {y:F1})"); // 绘制轨迹线 for (int i = 0; i < 20; i++) { float t1 = t - i * 0.1f; float t2 = t - (i + 1) * 0.1f; painter.DrawLine(Color.FromArgb(255 - i * 12, 255, 255, 0), new Vector3(MathF.Cos(t1) * 3, MathF.Sin(t1) * 3, 0), new Vector3(MathF.Cos(t2) * 3, MathF.Sin(t2) * 3, 0)); } t += 0.05f; Thread.Sleep(33); } }); ``` ## 示例 8:对象选择与 Guizmo 操作 ```csharp SelectObject selectOp = null; GuizmoAction guizmoOp = null; string selectedObject = null; void StartSelect() { selectOp = new SelectObject(); selectOp.feedback = (results, op) => { if (results.Length > 0) { selectedObject = results[0].name; Console.WriteLine($"Selected: {selectedObject}"); } }; selectOp.Start(); } void StartGuizmo() { if (selectedObject == null) return; selectOp?.End(); guizmoOp = new GuizmoAction { dof = GuizmoAction.GuizmoDof.PlanarXYYaw, realtimeResult = true, }; guizmoOp.feedback = (results, op) => { foreach (var (name, pos, rot) in results) Console.WriteLine($"Moved {name} to {pos}"); }; guizmoOp.finished = () => StartSelect(); guizmoOp.Start(); } ``` ## 示例 9:拾取坐标绘制线段 ```csharp Vector3? lineStart = null; var getPos = new GetPosition { Name = "Draw Line", method = GetPosition.PickMode.GridPlane, }; getPos.feedback = (wp, op) => { if (lineStart == null) { lineStart = wp.mouse_pos; } else { Workspace.AddProp(new PutStraightLine { name = $"line_{DateTime.Now.Ticks}", start = lineStart.Value, end = wp.mouse_pos, color = Color.Cyan, width = 2, arrowType = Painter.ArrowType.End, }); lineStart = null; } }; getPos.Start(); ``` ## 示例 10:Medulla2 风格的 IOObject 面板 Medulla2 中每个设备对象(IOObject)通过 `OpenUI` 打开独立的控制面板: ```csharp public class Camera3DIOObject : IOObject { private Panel uiPanel; private bool displaying = false; public void OpenUI() { displaying = true; uiPanel = GUI.DeclarePanel() .ShowTitle($"Camera: {Name}") .SetDefaultDocking(Panel.Docking.Right); uiPanel.Define(pb => { if (pb.Button("Capture")) TakeSnapshot(); pb.Toggle("Live View", ref displaying); pb.MiniPlot("FPS", currentFps); if (pb.Closing()) { displaying = false; Painter.GetPainter(Name).Clear(); uiPanel.Exit(); } }); } public void CloseUI() { displaying = false; Painter.GetPainter(Name).Clear(); uiPanel?.Exit(); } public void draw() { if (!displaying) return; var pp = Painter.GetPainter(Name); pp.Clear(); foreach (var pt in cachedPoints) { var cc = Color.FromArgb(pt.r, pt.g, pt.b); pp.DrawDot(cc, new Vector3(pt.X, pt.Y, pt.Z) / 1000f, 1); } } } ``` ## 示例 11:工具栏面板 ```csharp var toolbar = GUI.DeclarePanel() .ShowTitle(null) .AsToolbarPanel(38, Panel.ToolbarAnchor.RightTop); toolbar.Define(pb => { var tpb = new ToolbarPanelBuilder(pb); tpb.Label($"{IconFonts.ForkAwesome.Cube} Scene"); tpb.Separator(); if (tpb.Button($"{IconFonts.ForkAwesome.Play} Run", distinct: "tb-run")) StartSimulation(); if (tpb.Button($"{IconFonts.ForkAwesome.Stop} Stop", distinct: "tb-stop")) StopSimulation(); tpb.PopMenuButton($"{IconFonts.ForkAwesome.Camera} View", new[] { new MenuItem("Front", () => new SetCamera { azimuth = 0, altitude = MathF.PI/2 }.IssueToDefault()), new MenuItem("Top", () => new SetCamera { azimuth = 0, altitude = 0 }.IssueToDefault()), new MenuItem("Reset", () => new SetCamera().IssueToDefault()), }); }); ``` ## 示例 12:同时启用 Local + Web 终端 ```csharp static void Main(string[] args) { LocalTerminal.SetTitle("Dual Terminal App"); LocalTerminal.SetIcon(LoadIcon(), "DualApp"); LocalTerminal.AddMenuItem("Exit", LocalTerminal.Terminate); LocalTerminal.Start(); // Web 终端在后台启动 Task.Run(() => { var htdocs = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "htdocs"); if (Directory.Exists(htdocs)) LeastServer.AddServingFiles("/", htdocs); // 添加自定义 API LeastServer.AddGetHandler("/api/status", () => JsonConvert.SerializeObject(new { status = "running", time = DateTime.Now })); WebTerminal.Use(port: 8081, ico: LoadIcon()); }); // 远程连接时显示的面板 Terminal.RegisterRemotePanel(terminal => CreateMainPanel(terminal)); // 本地面板 GUI.PromptPanel(CreateMainPanel(GUI.defaultTerminal)); } static PanelBuilder.CycleGUIHandler CreateMainPanel(Terminal t) { return pb => { pb.Panel.ShowTitle("Control Panel"); pb.Label($"Terminal: {t.GetType().Name}"); // 同样的 UI 在两个终端上都可见... }; } // 图标统一用 .ico 字节(也可 File.ReadAllBytes 从磁盘读) // 需 csproj: ;exe 图标另用 static byte[] LoadIcon() { var asm = Assembly.GetExecutingAssembly(); using var s = asm.GetManifestResourceStream( asm.GetManifestResourceNames().First(p => p.Contains(".ico"))); return new BinaryReader(s).ReadBytes((int)s.Length); } ``` ## 示例 13:Painter 填充多边形 ```csharp var painter = Painter.GetPainter("polygon_demo"); painter.Clear(); // 绘制地面上的三角形 var triangle = new[] { new Vector2(0, 0), new Vector2(1, 0), new Vector2(0.5f, 0.866f), }; painter.DrawPolygonFilled(triangle, trans: new Vector3(0, 0, 0), quat: Quaternion.Identity, borderColor: Color.White, fillColor: Color.FromArgb(128, Color.Green)); // 拉伸为柱体 painter.DrawPolygonFilled(triangle, trans: new Vector3(3, 0, 0), quat: Quaternion.Identity, borderColor: Color.Yellow, fillColor: Color.FromArgb(100, Color.Blue), thickness: 1.5f); ``` ## 示例 14:Medulla2 插件结构 Medulla2 插件 DLL 必须暴露 `MainIOObject` 类: ```csharp // MyPlugin/MainIOObject.cs public class MainIOObject : IOObject { public object init(string typeName, dynamic[] args) { // 根据 typeName 创建设备实例 if (typeName == "sensor_a") return new SensorA((string)args[0], (int)args[1]); return null; } } // MyPlugin/SensorA.cs public class SensorA : IOObject { public SensorA(string port, int baudRate) { /* init */ } [IOObjectUtility] public void Calibrate() { /* 显示为 UI 按钮 */ } [IOObjectMonitor] public float Temperature => ReadTemp(); } ``` csproj 引用 refasmer 生成的参考程序集: ```xml $(ReleaseDir)\RefMedullaCore.dll ``` 输出到插件目录: ```xml $(DebugDir)\plugins ``` 加载方式(在 .iocmd 脚本中): ``` sensor = io load plugins/MyPlugin.dll mySensor = sensor init sensor_a COM3 115200 ```