<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>https://wiki2.lessokaji.com/index.php?action=history&amp;feed=atom&amp;title=%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8CycleGUI%E5%BF%AB%E9%80%9F%E5%BC%80%E5%8F%91UI%E7%95%8C%E9%9D%A2</id>
	<title>如何使用CycleGUI快速开发UI界面 - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="https://wiki2.lessokaji.com/index.php?action=history&amp;feed=atom&amp;title=%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8CycleGUI%E5%BF%AB%E9%80%9F%E5%BC%80%E5%8F%91UI%E7%95%8C%E9%9D%A2"/>
	<link rel="alternate" type="text/html" href="https://wiki2.lessokaji.com/index.php?title=%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8CycleGUI%E5%BF%AB%E9%80%9F%E5%BC%80%E5%8F%91UI%E7%95%8C%E9%9D%A2&amp;action=history"/>
	<updated>2026-05-16T15:09:34Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.40.0</generator>
	<entry>
		<id>https://wiki2.lessokaji.com/index.php?title=%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8CycleGUI%E5%BF%AB%E9%80%9F%E5%BC%80%E5%8F%91UI%E7%95%8C%E9%9D%A2&amp;diff=1028&amp;oldid=prev</id>
		<title>Artheru：​Initial bilingual draft (auto-published)</title>
		<link rel="alternate" type="text/html" href="https://wiki2.lessokaji.com/index.php?title=%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8CycleGUI%E5%BF%AB%E9%80%9F%E5%BC%80%E5%8F%91UI%E7%95%8C%E9%9D%A2&amp;diff=1028&amp;oldid=prev"/>
		<updated>2026-05-16T11:43:04Z</updated>

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