feat: add overlay layout options

- add configurable overlay sizing and anchors

- support non-capturing overlays and focus handles

- demonstrate overlay options in Example
This commit is contained in:
chuan
2026-06-04 00:44:28 +08:00
parent 2ab090d724
commit 8a71a27944
11 changed files with 450 additions and 31 deletions
+51 -3
View File
@@ -21,6 +21,7 @@ var statusText = new Text();
var loader = new Loader(textMeasurer) { Message = "运行中" };
using var loaderCancellation = new CancellationTokenSource();
var input = new TinyTUI.Components.Input(textMeasurer) { Prompt = "请输入: " };
IOverlayHandle? tipHandle = null;
input.OnChanged = value => UpdateStatus(statusText, value, textMeasurer, renderer);
input.OnSubmitted = value =>
@@ -29,15 +30,46 @@ input.OnSubmitted = value =>
{
case "/help":
AddEvent(eventsText, "Overlay: help opened");
runtime.ShowOverlay(new HelpOverlay(runtime, textMeasurer));
runtime.ShowOverlay(
new HelpOverlay(runtime, textMeasurer),
new OverlayOptions
{
Width = OverlaySize.Percent(45),
MinWidth = 42,
Anchor = OverlayAnchor.TopRight,
Margin = new OverlayMargin(1),
});
break;
case "/tip":
tipHandle?.Hide();
tipHandle = runtime.ShowOverlay(
new TipOverlay(textMeasurer),
OverlayOptions.NonCapturingTooltip());
AddEvent(eventsText, "Overlay: non-capturing tip opened");
break;
case "/select":
AddEvent(eventsText, "Overlay: select opened");
runtime.ShowOverlay(new SelectOverlay(runtime, message => AddEvent(eventsText, message), textMeasurer));
runtime.ShowOverlay(
new SelectOverlay(runtime, message => AddEvent(eventsText, message), textMeasurer),
new OverlayOptions
{
Width = 34,
Anchor = OverlayAnchor.LeftCenter,
Margin = new OverlayMargin(1),
MaxHeight = 9,
});
break;
case "/edit":
AddEvent(eventsText, "Overlay: editor opened");
runtime.ShowOverlay(new EditorOverlay(runtime, message => AddEvent(eventsText, message), textMeasurer));
runtime.ShowOverlay(
new EditorOverlay(runtime, message => AddEvent(eventsText, message), textMeasurer),
new OverlayOptions
{
Width = OverlaySize.Percent(70),
MinWidth = 52,
Anchor = OverlayAnchor.BottomCenter,
Margin = new OverlayMargin(1),
});
break;
case "":
AddEvent(eventsText, "Submitted empty input");
@@ -62,6 +94,7 @@ AddAll(
"操作说明\n" +
"- 输入文本后 Enter 提交\n" +
"- /help 打开帮助弹层\n" +
"- /tip 打开不抢焦点的右上角提示\n" +
"- /select 打开可交互选择列表\n" +
"- /edit 打开需要焦点的多行编辑器\n" +
"- Esc 退出"),
@@ -169,6 +202,21 @@ file sealed class HelpOverlay(ITuiRuntime runtime, ITextMeasurer textMeasurer) :
}
}
file sealed class TipOverlay(ITextMeasurer textMeasurer) : IComponent
{
private readonly Box _box = new(
new Text(
"这是 non-capturing overlay\n" +
"打开后输入框仍然接收键盘输入\n" +
"再次输入 /tip 会替换当前提示"),
textMeasurer)
{
Title = "Tip",
};
public IReadOnlyList<string> Render(int width) => _box.Render(width);
}
file sealed class SelectOverlay : IInputComponent
{
private readonly ITuiRuntime _runtime;