diff --git a/src/Example/Program.cs b/src/Example/Program.cs index ac8e5f4..63465d3 100644 --- a/src/Example/Program.cs +++ b/src/Example/Program.cs @@ -11,10 +11,10 @@ using var terminalInput = new ConsoleTerminalInput(); var terminalOutput = new ConsoleTerminalOutput(); var parser = new DefaultInputParser(); var textMeasurer = new TerminalTextMeasurer(); -var renderer = new FullScreenRenderer(terminalOutput, textMeasurer); +var renderer = new DifferentialRenderer(terminalOutput, textMeasurer); using var runtime = new TuiRuntime(terminalInput, parser, renderer); -var component = new EchoComponent(runtime, textMeasurer); +var component = new EchoComponent(runtime, textMeasurer, renderer); runtime.Add(component); runtime.SetFocus(component); @@ -36,7 +36,7 @@ finally /// /// 用于手动验证 Runtime 的示例组件 /// -file sealed class EchoComponent(ITuiRuntime runtime, ITextMeasurer textMeasurer) : IInputComponent +file sealed class EchoComponent(ITuiRuntime runtime, ITextMeasurer textMeasurer, DifferentialRenderer renderer) : IInputComponent { private readonly ManualResetEventSlim _done = new(); private readonly List _events = []; @@ -49,6 +49,8 @@ file sealed class EchoComponent(ITuiRuntime runtime, ITextMeasurer textMeasurer) "TinyTUI Runtime 示例", "====================", $"当前渲染宽度: {width}", + $"全量重绘次数: {renderer.FullRedrawCount}", + $"差分重绘次数: {renderer.DifferentialRedrawCount}", string.Empty, "测试方式:", "- 输入普通字符 验证 Text 事件会进入焦点组件", diff --git a/src/TinyTUI/Rendering/DifferentialRenderer.cs b/src/TinyTUI/Rendering/DifferentialRenderer.cs new file mode 100644 index 0000000..3ab1c7e --- /dev/null +++ b/src/TinyTUI/Rendering/DifferentialRenderer.cs @@ -0,0 +1,103 @@ +using TinyTUI.Stdout; +using TinyTUI.Text; + +namespace TinyTUI.Rendering; + +/// +/// 只重绘变化行的基础差分渲染器 +/// +public sealed class DifferentialRenderer(ITerminalOutput output, ITextMeasurer? textMeasurer = null) : IRenderer +{ + private readonly ITextMeasurer _textMeasurer = textMeasurer ?? new TerminalTextMeasurer(); + private List _previousLines = []; + private TerminalSize? _previousSize; + + /// + /// 获取累计全量重绘次数 + /// + public int FullRedrawCount { get; private set; } + + /// + /// 获取累计差分重绘次数 + /// + public int DifferentialRedrawCount { get; private set; } + + /// + public void Render(IReadOnlyList lines, TerminalSize size) + { + var nextLines = NormalizeLines(lines, size.Columns); + + if (_previousSize != size) + { + RenderFull(nextLines, size); + return; + } + + RenderDiff(nextLines, size); + } + + /// + public void Reset() + { + _previousLines = []; + _previousSize = null; + output.ClearScreen(); + output.Flush(); + } + + private void RenderFull(List lines, TerminalSize size) + { + FullRedrawCount++; + output.ClearScreen(); + + foreach (var line in lines) + { + output.Write(line); + output.Write("\r\n"); + } + + output.Flush(); + _previousLines = lines; + _previousSize = size; + } + + private void RenderDiff(List lines, TerminalSize size) + { + var maxLineCount = Math.Max(lines.Count, _previousLines.Count); + var changed = false; + + for (var index = 0; index < maxLineCount; index++) + { + var previous = index < _previousLines.Count ? _previousLines[index] : string.Empty; + var next = index < lines.Count ? lines[index] : string.Empty; + + if (previous == next) + { + continue; + } + + changed = true; + output.Write($"\e[{index + 1};1H"); + output.Write("\e[2K"); + + if (next.Length > 0) + { + output.Write(next); + } + } + + if (changed) + { + DifferentialRedrawCount++; + output.Flush(); + } + + _previousLines = lines; + _previousSize = size; + } + + private List NormalizeLines(IReadOnlyList lines, int width) + { + return [.. lines.Select(line => _textMeasurer.Truncate(line, width))]; + } +}