如何使用CycleGUI快速开发UI界面

来自MDCS wiki2
跳到导航 跳到搜索


概述 / 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。

相关页面 / See also