如何使用CycleGUI快速开发UI界面
概述 / Overview
CycleGUI 是 MDCS 自研的 按需协程GUI 框架。本页给开发者一份 最小学习路径:开一个画面、画一点云、加一个按钮、读一个输入。
CycleGUI is MDCS's lazy-coroutine GUI framework. This page is the minimum learning path: open a panel, draw a cloud, add a button, accept an input.
1. 开一个画面 / Open a panel
using CycleGUI;
// 任何线程都可以 / from any thread
var painter = UI.GetPainter("my-debug");
painter.DrawText("Hello from algorithm thread");
`UI.GetPainter(name)` 拿到一个 命名画板;后续所有 `painter.DrawXxx` 累积到同一画面。 `UI.GetPainter(name)` returns a named painter; subsequent `DrawXxx` calls accumulate into the same view.
2. 画点云 / Draw a point cloud
var painter = UI.GetPainter("lidar-frame");
foreach (var pt in cachedLidar)
{
var c = pt.intensity > 0.8 ? Color.Red : Color.White;
painter.DrawDot3D(c,
new Vector3((float)(pt.d * Math.Cos(pt.th)),
(float)(pt.d * Math.Sin(pt.th)),
0));
}
CycleGUI 把所有 `Draw` 命令存到 当前画面;在 GUI 主线程下一次重绘时一次性渲染。 CycleGUI buffers all `Draw` commands for the current frame and renders them when the UI thread next repaints.
3. 加一个按钮 / Add a button
按钮 + 通用 UI 控件用 协程风格 —— 把 UI 逻辑写成 `IEnumerable<Cycle>` 由框架托管: Buttons and other widgets use coroutines:
UI.CastCoroutine(MyControl());
IEnumerable<Cycle> MyControl()
{
while (true)
{
var panel = UI.GetPanel("control");
if (panel.Button("Start scan"))
{
StartScan(); // 调用算法
}
if (panel.Button("Stop"))
{
StopScan();
}
yield return Cycle.Next; // 等下一帧
}
}
`UI.CastCoroutine` 把一个 `IEnumerable<Cycle>` 投递给 GUI 主线程;它只在需要重绘时被推进。 `UI.CastCoroutine` posts a coroutine to the UI thread; it advances only on repaint.
4. 读一个输入 / Read an input
IEnumerable<Cycle> ConfigPanel()
{
int speed = 600;
while (true)
{
var p = UI.GetPanel("config");
speed = p.IntInput("speed (mm/s)", speed, min: 0, max: 2000);
p.Text($"Current speed = {speed}");
if (p.Button("Apply"))
{
Configuration.conf.basicSpeed = speed;
}
yield return Cycle.Next;
}
}
5. 等用户输入 / Wait for user input
有时算法要等用户点确认才继续 —— 用 `yield return WaitForButton(...)`:
Sometimes the algorithm has to wait for user confirmation:
IEnumerable<Cycle> ApprovalFlow()
{
while (true)
{
var p = UI.GetPanel("approve");
p.Text("Pending: cart approach to station-17");
yield return p.WaitForButton("Approve"); // 阻塞协程直到点击
ConfirmApproach();
yield return Cycle.Seconds(1);
}
}
6. 跨线程 / 跨进程 / Cross-thread / cross-process
- CycleGUI 自动把任意线程的 `Draw` 调用 marshal到 UI 主线程。
- 同一 CycleGUI 服务可被多个进程连接;每个进程独立维护自己的 painter,UI 端可同时显示。
CycleGUI auto-marshals `Draw` calls onto the UI thread from any thread. Multiple processes can connect to one CycleGUI server simultaneously; each has its own painter, all visible together.
7. 录制与回放 / Record & replay
UI.StartRecording("session.cgr");
// ... 跑算法 + 各种 Draw ...
UI.StopRecording();
// 离线回放
UI.Replay("session.cgr");
最佳实践 / Best practices
- 命名 painter / panel 反映 数据来源(如 `lidar-front`、`slam-map`),不要根据 UI 位置命名。
- 协程内不要 sleep;改用 `yield return Cycle.Seconds(N)` 让框架调度。
- 重命名 painter 会丢历史 —— 如果用户在多次会话间想保留视图开关状态,名称保持稳定。
- 数据多时考虑节流 —— 即便是协程按需调用,每帧画百万点云仍会卡;用空间下采样或频率限制。
与 Detour / Clumsy / SimpleComposer 的集成 / Integration
- Detour: SLAM 中间状态默认在 `detour-slam` painter;可在 Detour UI 中开关。
- Clumsy: Movement 内部可调 `UI.GetPainter("movement-debug")` 加自定义可视化。
- SimpleComposer: CAD 画布本身就是一个 CycleGUI 实例,自定义工具通过协程加新的 layer。