feat: add basic components
- add text, box, and single-line input components - support input change, submit, and cancel callbacks - update example to compose the page from library components
This commit is contained in:
+48
-76
@@ -14,16 +14,41 @@ var textMeasurer = new TerminalTextMeasurer();
|
||||
var renderer = new DifferentialRenderer(terminalOutput, textMeasurer);
|
||||
using var runtime = new TuiRuntime(terminalInput, parser, renderer);
|
||||
|
||||
var component = new EchoComponent(runtime, textMeasurer, renderer);
|
||||
runtime.Add(component);
|
||||
runtime.SetFocus(component);
|
||||
var done = new ManualResetEventSlim();
|
||||
var eventsText = new Text("最近事件:\n- 无");
|
||||
var statusText = new Text();
|
||||
var input = new TinyTUI.Components.Input(textMeasurer) { Prompt = "请输入: " };
|
||||
|
||||
input.OnChanged = value => UpdateStatus(statusText, value, textMeasurer, renderer);
|
||||
input.OnSubmitted = value =>
|
||||
{
|
||||
AddEvent(eventsText, $"Submitted: {value}");
|
||||
input.Clear();
|
||||
};
|
||||
input.OnCanceled = () => done.Set();
|
||||
|
||||
UpdateStatus(statusText, input.Value, textMeasurer, renderer);
|
||||
|
||||
var page = new Container();
|
||||
page.Add(new Text("TinyTUI 基础组件示例"));
|
||||
page.Add(new Text("======================"));
|
||||
page.Add(new Text("测试方式: 输入文本 Backspace 删除 Enter 提交 Esc 退出"));
|
||||
page.Add(new Text(string.Empty));
|
||||
page.Add(new Box(statusText, textMeasurer) { Title = "状态" });
|
||||
page.Add(new Text(string.Empty));
|
||||
page.Add(new Box(input, textMeasurer) { Title = "Input" });
|
||||
page.Add(new Text(string.Empty));
|
||||
page.Add(new Box(eventsText, textMeasurer) { Title = "事件" });
|
||||
|
||||
runtime.Add(page);
|
||||
runtime.SetFocus(input);
|
||||
|
||||
terminalOutput.HideCursor();
|
||||
|
||||
try
|
||||
{
|
||||
runtime.Start();
|
||||
component.WaitForExit();
|
||||
done.Wait();
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -33,77 +58,24 @@ finally
|
||||
terminalOutput.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于手动验证 Runtime 的示例组件
|
||||
/// </summary>
|
||||
file sealed class EchoComponent(ITuiRuntime runtime, ITextMeasurer textMeasurer, DifferentialRenderer renderer) : IInputComponent
|
||||
static void UpdateStatus(
|
||||
Text statusText,
|
||||
string inputValue,
|
||||
ITextMeasurer textMeasurer,
|
||||
DifferentialRenderer renderer)
|
||||
{
|
||||
private readonly ManualResetEventSlim _done = new();
|
||||
private readonly List<string> _events = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<string> Render(int width)
|
||||
{
|
||||
var lines = new List<string>
|
||||
{
|
||||
"TinyTUI Runtime 示例",
|
||||
"====================",
|
||||
$"当前渲染宽度: {width}",
|
||||
$"全量重绘次数: {renderer.FullRedrawCount}",
|
||||
$"差分重绘次数: {renderer.DifferentialRedrawCount}",
|
||||
string.Empty,
|
||||
"测试方式:",
|
||||
"- 输入普通字符 验证 Text 事件会进入焦点组件",
|
||||
"- 按方向键 Enter Backspace Tab Delete 验证 Key 事件会进入焦点组件",
|
||||
"- 调整窗口大小 验证 Resize 事件会进入焦点组件并触发重渲染",
|
||||
"- 按 Esc 退出",
|
||||
string.Empty,
|
||||
"宽度示例:",
|
||||
$"字符串 \"abc\" 的终端宽度 = {textMeasurer.GetWidth("abc")}",
|
||||
$"字符串 \"中文\" 的终端宽度 = {textMeasurer.GetWidth("中文")}",
|
||||
$"字符串 \"😀\" 的终端宽度 = {textMeasurer.GetWidth("😀")}",
|
||||
$"截断前 \"abc中文😀xyz\" 的终端宽度 = {textMeasurer.GetWidth("abc中文😀xyz")}",
|
||||
$"截断到 8 列后的结果 = \"{textMeasurer.Truncate("abc中文😀xyz", 8)}\"",
|
||||
string.Empty,
|
||||
"最近事件:",
|
||||
};
|
||||
|
||||
lines.AddRange(_events.TakeLast(10));
|
||||
return lines;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void HandleInput(TuiInputEvent input)
|
||||
{
|
||||
if (input is { Kind: TuiInputEventKind.Key, Value: KeyNames.Escape })
|
||||
{
|
||||
_done.Set();
|
||||
return;
|
||||
}
|
||||
|
||||
_events.Add($"{input.Kind}: {FormatInput(input.Value)}");
|
||||
runtime.RequestRender();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待用户退出示例
|
||||
/// </summary>
|
||||
public void WaitForExit()
|
||||
{
|
||||
_done.Wait();
|
||||
}
|
||||
|
||||
private static string FormatInput(string data)
|
||||
{
|
||||
return string.Join(' ', data.Select(static c => c switch
|
||||
{
|
||||
'\r' => "\\r",
|
||||
'\n' => "\\n",
|
||||
'\t' => "\\t",
|
||||
'\b' => "\\b",
|
||||
'\e' => "\\e",
|
||||
_ when char.IsControl(c) => $"\\x{(int)c:x2}",
|
||||
_ => c.ToString(),
|
||||
}));
|
||||
}
|
||||
statusText.Value = string.Join(
|
||||
'\n',
|
||||
$"当前输入 = \"{inputValue}\"",
|
||||
$"当前输入宽度 = {textMeasurer.GetWidth(inputValue)}",
|
||||
$"字符串 \"abc中文😀xyz\" 宽度 = {textMeasurer.GetWidth("abc中文😀xyz")}",
|
||||
$"截断到 8 列 = \"{textMeasurer.Truncate("abc中文😀xyz", 8)}\"",
|
||||
$"全量重绘次数 = {renderer.FullRedrawCount}",
|
||||
$"差分重绘次数 = {renderer.DifferentialRedrawCount}");
|
||||
}
|
||||
|
||||
static void AddEvent(Text eventsText, string message)
|
||||
{
|
||||
var lines = eventsText.Value.Split('\n').Skip(1).Append($"- {message}").TakeLast(8);
|
||||
eventsText.Value = "最近事件:\n" + string.Join('\n', lines);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user