feat: initialize interfaces and model
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
- 默认使用最新的C#语法糖简化代码的写法
|
||||
- C#的summary注释使用中文,并且行尾不添加类似 `。` `,` 之类的中文符号
|
||||
@@ -0,0 +1,65 @@
|
||||
# TODO
|
||||
|
||||
## Success Criteria
|
||||
|
||||
最终成功应该具备这些能力:
|
||||
|
||||
- 能作为一个 C# TUI 类库被其他控制台程序引用。
|
||||
- 能启动和停止终端运行时,并在退出时恢复终端状态。
|
||||
- 能读取键盘输入,并把普通字符、方向键、组合键、回车、退格、Esc 等转换成统一事件。
|
||||
- 能处理粘贴输入,避免大段粘贴被拆成混乱的按键事件。
|
||||
- 能监听终端窗口尺寸变化,并触发重新布局和重新渲染。
|
||||
- 能用组件树描述界面,而不是让组件直接写控制台。
|
||||
- 能支持焦点管理,让当前焦点组件接收输入。
|
||||
- 能渲染基础组件,包括文本、容器、单行输入框、多行编辑器、选择列表和加载状态。
|
||||
- 能支持 overlay 弹层,用于菜单、弹窗、提示框和临时输入。
|
||||
- 能正确显示中文、emoji、ANSI 样式文本,并避免宽度计算错位。
|
||||
- 能进行全量渲染,保证最小版本可以稳定显示。
|
||||
- 能进行差分渲染,只刷新变化的行,减少闪烁。
|
||||
- 能提供一组示例程序,展示输入、编辑、列表选择、overlay 和动态刷新。
|
||||
- 能提供测试覆盖核心模块,包括输入解析、宽度计算、渲染输出和组件行为。
|
||||
- 能在 Windows Terminal / PowerShell 环境下稳定运行。
|
||||
|
||||
## Architecture
|
||||
|
||||
整体分成几个大模块:
|
||||
|
||||
- STDIO 模块:负责读取键盘输入、监听窗口尺寸变化、管理 raw mode 和生命周期。
|
||||
- STDOUT 模块:负责向终端输出文本和 ANSI 控制序列。
|
||||
- Input 模块:负责把原始输入转换成统一的按键、粘贴、控制事件。
|
||||
- Runtime 模块:负责管理组件树、焦点、事件分发和渲染调度。
|
||||
- Renderer 模块:负责把组件输出同步到终端,先全量渲染,后续升级成差分渲染。
|
||||
- Text Width 模块:负责计算终端显示宽度,处理 ANSI、中文、emoji、截断和换行。
|
||||
- Component 模块:负责提供 Text、Input、Container、Box 等基础 UI 组件。
|
||||
- Overlay 模块:负责弹层显示、遮盖合成、焦点切换和恢复。
|
||||
- Example 模块:负责验证框架 API 和真实交互流程。
|
||||
- Test 模块:负责覆盖输入解析、渲染输出、宽度计算和组件行为。
|
||||
|
||||
大致依赖方向:
|
||||
|
||||
```text
|
||||
Example
|
||||
-> Runtime
|
||||
-> Component
|
||||
-> Overlay
|
||||
-> Renderer
|
||||
-> STDOUT
|
||||
-> Text Width
|
||||
-> Input
|
||||
-> STDIO
|
||||
```
|
||||
|
||||
## Steps
|
||||
|
||||
1. 定义核心模块边界:STDIO/STDOUT、输入解析、运行时、渲染器、组件、示例。
|
||||
2. 实现终端 IO 模块,统一封装控制台输入、输出、尺寸和生命周期。
|
||||
3. 实现输入解析模块,把原始按键和控制序列转换成框架事件。
|
||||
4. 实现组件运行时,管理组件树、焦点、事件分发和渲染请求。
|
||||
5. 实现基础渲染器,先完成全量渲染和清屏重画。
|
||||
6. 实现文本宽度模块,统一处理 ANSI、中文、emoji 和截断。
|
||||
7. 实现差分渲染器,只更新变化的终端行。
|
||||
8. 实现基础组件集,包括 Text、Input、Container 和 Box。
|
||||
9. 实现 Overlay 模块,支持弹层显示、遮盖合成和焦点切换。
|
||||
10. 实现高级组件集,包括 Editor、SelectList、Markdown 和 Loader。
|
||||
11. 完善示例程序,用真实场景验证输入、渲染、焦点和 overlay。
|
||||
12. 补齐测试和基准,覆盖输入解析、宽度计算、渲染输出和组件行为。
|
||||
@@ -0,0 +1,58 @@
|
||||
namespace TinyTUI.Components;
|
||||
|
||||
/// <summary>
|
||||
/// 按添加顺序纵向渲染子组件的容器组件
|
||||
/// </summary>
|
||||
public class Container : IComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前子组件列表
|
||||
/// </summary>
|
||||
public List<IComponent> Children { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 向容器添加子组件
|
||||
/// </summary>
|
||||
public void Add(IComponent component)
|
||||
{
|
||||
Children.Add(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从容器移除子组件
|
||||
/// </summary>
|
||||
public void Remove(IComponent component)
|
||||
{
|
||||
Children.Remove(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除容器中的所有子组件
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Children.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual IReadOnlyList<string> Render(int width)
|
||||
{
|
||||
var lines = new List<string>();
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
lines.AddRange(child.Render(width));
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Invalidate()
|
||||
{
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace TinyTUI.Components;
|
||||
|
||||
/// <summary>
|
||||
/// 定义 TUI 组件树中的最小可渲染单元
|
||||
/// </summary>
|
||||
public interface IComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 按给定视口宽度将组件渲染为终端行
|
||||
/// </summary>
|
||||
IReadOnlyList<string> Render(int width);
|
||||
|
||||
/// <summary>
|
||||
/// 清除组件持有的渲染缓存状态
|
||||
/// </summary>
|
||||
void Invalidate() { }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using TinyTUI.Input;
|
||||
|
||||
namespace TinyTUI.Components;
|
||||
|
||||
/// <summary>
|
||||
/// 定义聚焦后可以接收标准化输入事件的组件
|
||||
/// </summary>
|
||||
public interface IInputComponent : IComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理运行时分发的标准化输入事件
|
||||
/// </summary>
|
||||
void HandleInput(TuiInputEvent input);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace TinyTUI.Input;
|
||||
|
||||
/// <summary>
|
||||
/// 将终端原始输入数据转换为标准化 TUI 输入事件
|
||||
/// </summary>
|
||||
public interface IInputParser
|
||||
{
|
||||
/// <summary>
|
||||
/// 将一段原始输入解析为零个或多个标准化输入事件
|
||||
/// </summary>
|
||||
IReadOnlyList<TuiInputEvent> Parse(string data);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace TinyTUI.Input;
|
||||
|
||||
/// <summary>
|
||||
/// 标识 TUI 运行时消费的高级输入事件类型
|
||||
/// </summary>
|
||||
public enum TuiInputEventKind
|
||||
{
|
||||
/// <summary>
|
||||
/// 可打印文本输入
|
||||
/// </summary>
|
||||
Text,
|
||||
|
||||
/// <summary>
|
||||
/// 非文本按键或组合键
|
||||
/// </summary>
|
||||
Key,
|
||||
|
||||
/// <summary>
|
||||
/// 成组的粘贴输入
|
||||
/// </summary>
|
||||
Paste,
|
||||
|
||||
/// <summary>
|
||||
/// 终端尺寸变化输入事件
|
||||
/// </summary>
|
||||
Resize,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示原始终端数据解析后的标准化输入事件
|
||||
/// </summary>
|
||||
public sealed record TuiInputEvent(TuiInputEventKind Kind, string Value);
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace TinyTUI.Overlay;
|
||||
|
||||
/// <summary>
|
||||
/// 控制已显示 overlay 的生命周期和可见性
|
||||
/// </summary>
|
||||
public interface IOverlayHandle
|
||||
{
|
||||
/// <summary>
|
||||
/// 永久隐藏并移除 overlay
|
||||
/// </summary>
|
||||
void Hide();
|
||||
|
||||
/// <summary>
|
||||
/// 临时切换 overlay 是否参与渲染和焦点处理
|
||||
/// </summary>
|
||||
void SetHidden(bool hidden);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using TinyTUI.Components;
|
||||
|
||||
namespace TinyTUI.Overlay;
|
||||
|
||||
/// <summary>
|
||||
/// 管理渲染在基础组件树之上的临时组件
|
||||
/// </summary>
|
||||
public interface IOverlayManager
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前是否存在至少一个 overlay
|
||||
/// </summary>
|
||||
bool HasOverlay { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 显示 overlay 组件并返回控制句柄
|
||||
/// </summary>
|
||||
IOverlayHandle Show(IComponent component);
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏最上层 overlay
|
||||
/// </summary>
|
||||
void HideTop();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace TinyTUI.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// 将渲染后的终端行同步到终端输出
|
||||
/// </summary>
|
||||
public interface IRenderer
|
||||
{
|
||||
/// <summary>
|
||||
/// 将给定的逻辑终端行渲染到当前终端视口
|
||||
/// </summary>
|
||||
void Render(IReadOnlyList<string> lines, TerminalSize size);
|
||||
|
||||
/// <summary>
|
||||
/// 清除渲染器状态 让下一次渲染从干净基线开始
|
||||
/// </summary>
|
||||
void Reset();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using TinyTUI.Components;
|
||||
|
||||
namespace TinyTUI.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// 协调组件状态 输入分发 渲染和终端生命周期
|
||||
/// </summary>
|
||||
public interface ITuiRuntime : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 向根组件树添加组件
|
||||
/// </summary>
|
||||
void Add(IComponent component);
|
||||
|
||||
/// <summary>
|
||||
/// 从根组件树移除组件
|
||||
/// </summary>
|
||||
void Remove(IComponent component);
|
||||
|
||||
/// <summary>
|
||||
/// 设置接收输入事件的组件
|
||||
/// </summary>
|
||||
void SetFocus(IComponent? component);
|
||||
|
||||
/// <summary>
|
||||
/// 请求一次渲染
|
||||
/// </summary>
|
||||
void RequestRender();
|
||||
|
||||
/// <summary>
|
||||
/// 启动 TUI 运行时
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// 停止 TUI 运行时并恢复终端状态
|
||||
/// </summary>
|
||||
void Stop();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace TinyTUI.Stdio;
|
||||
|
||||
/// <summary>
|
||||
/// 提供终端原始输入 尺寸变化通知和输入生命周期控制
|
||||
/// </summary>
|
||||
public interface ITerminalInput : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取最近一次记录的终端视口尺寸
|
||||
/// </summary>
|
||||
TerminalSize CurrentSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 在终端收到原始输入数据时触发
|
||||
/// </summary>
|
||||
event EventHandler<string>? DataReceived;
|
||||
|
||||
/// <summary>
|
||||
/// 在终端视口尺寸变化时触发
|
||||
/// </summary>
|
||||
event EventHandler<TerminalSize>? Resized;
|
||||
|
||||
/// <summary>
|
||||
/// 开始读取终端输入和尺寸变化事件
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// 停止读取终端输入并恢复输入相关的终端状态
|
||||
/// </summary>
|
||||
void Stop();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace TinyTUI.Stdout;
|
||||
|
||||
/// <summary>
|
||||
/// 提供渲染器和运行时使用的终端输出操作
|
||||
/// </summary>
|
||||
public interface ITerminalOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 向输出流写入原始文本或终端控制序列
|
||||
/// </summary>
|
||||
void Write(string value);
|
||||
|
||||
/// <summary>
|
||||
/// 将缓冲的输出刷新到终端
|
||||
/// </summary>
|
||||
void Flush();
|
||||
|
||||
/// <summary>
|
||||
/// 清空可见终端屏幕
|
||||
/// </summary>
|
||||
void ClearScreen();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏硬件光标
|
||||
/// </summary>
|
||||
void HideCursor();
|
||||
|
||||
/// <summary>
|
||||
/// 显示硬件光标
|
||||
/// </summary>
|
||||
void ShowCursor();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace TinyTUI;
|
||||
|
||||
/// <summary>
|
||||
/// 表示当前终端视口的字符单元尺寸
|
||||
/// </summary>
|
||||
public readonly record struct TerminalSize(int Columns, int Rows);
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace TinyTUI.Text;
|
||||
|
||||
/// <summary>
|
||||
/// 按终端字符单元宽度规则测量和裁剪字符串
|
||||
/// </summary>
|
||||
public interface ITextMeasurer
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取字符串占用的终端字符单元数量
|
||||
/// </summary>
|
||||
int GetWidth(string value);
|
||||
|
||||
/// <summary>
|
||||
/// 裁剪字符串使其渲染宽度不超过给定字符单元宽度
|
||||
/// </summary>
|
||||
string Truncate(string value, int maxWidth);
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
<Solution>
|
||||
<Folder Name="/_/">
|
||||
<File Path=".gitignore" />
|
||||
<File Path="AGENTS.md" />
|
||||
<File Path="Directory.Build.props" />
|
||||
<File Path="nuget.config" />
|
||||
<File Path="README.md" />
|
||||
<File Path="TODO.md" />
|
||||
</Folder>
|
||||
<Project Path="src/Example/Example.csproj" Id="6656c1ec-b220-4e7a-8875-5a97a85afbc1" />
|
||||
<Project Path="src/TinyTUI/TinyTUI.csproj" Id="f39199aa-6947-4951-b952-a1588045505e" />
|
||||
|
||||
Reference in New Issue
Block a user