Console Binary file preview

This commit is contained in:
2023-08-20 07:37:28 +02:00
parent 570ecd3f83
commit 845a37050f
23 changed files with 391 additions and 23 deletions

View File

@@ -22,7 +22,6 @@ public class CommandPaletteViewModel : FuzzyPanelViewModel<ICommandPaletteEntryV
IUserCommandHandlerService userCommandHandlerService, IUserCommandHandlerService userCommandHandlerService,
ICommandKeysHelperService commandKeysHelperService, ICommandKeysHelperService commandKeysHelperService,
ILogger<CommandPaletteViewModel> logger) ILogger<CommandPaletteViewModel> logger)
: base((a, b) => a.Identifier == b.Identifier)
{ {
_commandPaletteService = commandPaletteService; _commandPaletteService = commandPaletteService;
_identifiableUserCommandService = identifiableUserCommandService; _identifiableUserCommandService = identifiableUserCommandService;

View File

@@ -6,5 +6,6 @@ public interface IElementPreviewViewModel : IItemPreviewViewModel
{ {
ItemPreviewMode Mode { get; } ItemPreviewMode Mode { get; }
string TextContent { get; } string TextContent { get; }
byte[] BinaryContent { get; }
string TextEncoding { get; } string TextEncoding { get; }
} }

View File

@@ -26,6 +26,7 @@ public partial class ElementPreviewViewModel : IElementPreviewViewModel, IAsyncI
public ItemPreviewMode Mode { get; private set; } public ItemPreviewMode Mode { get; private set; }
[Property] private string? _textContent; [Property] private string? _textContent;
[Property] private byte[]? _binaryContent;
[Property] private string? _textEncoding; [Property] private string? _textEncoding;
public string Name => PreviewName; public string Name => PreviewName;
@@ -35,6 +36,7 @@ public partial class ElementPreviewViewModel : IElementPreviewViewModel, IAsyncI
try try
{ {
var content = await element.Provider.GetContentAsync(element, MaxTextPreviewSize); var content = await element.Provider.GetContentAsync(element, MaxTextPreviewSize);
BinaryContent = content;
if (content is null) if (content is null)
{ {

View File

@@ -29,13 +29,20 @@ public abstract partial class FuzzyPanelViewModel<TItem> : IFuzzyPanelViewModel<
_searchText = value; _searchText = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(SearchText))); OnPropertyChanged(new PropertyChangedEventArgs(nameof(SearchText)));
UpdateFilteredMatchesInternal(); UpdateFilteredMatches();
if (string.IsNullOrWhiteSpace(value))
{
SelectedItem = null;
}
else
{
UpdateSelectedItem();
}
} }
} }
private void UpdateFilteredMatchesInternal() private void UpdateSelectedItem()
{ {
UpdateFilteredMatches();
if (SelectedItem != null && FilteredMatches.Contains(SelectedItem)) return; if (SelectedItem != null && FilteredMatches.Contains(SelectedItem)) return;
SelectedItem = FilteredMatches.Count > 0 SelectedItem = FilteredMatches.Count > 0

View File

@@ -4,4 +4,6 @@ public class ConsoleApplicationConfiguration
{ {
public string? ConsoleDriver { get; set; } public string? ConsoleDriver { get; set; }
public bool DisableUtf8 { get; set; } public bool DisableUtf8 { get; set; }
public string? ClipboardSingleIcon { get; set; }
public string? ClipboardMultipleIcon { get; set; }
} }

View File

@@ -1,5 +1,6 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.ConsoleUI.App.Preview;
namespace FileTime.ConsoleUI.App; namespace FileTime.ConsoleUI.App;
@@ -7,4 +8,5 @@ public interface IConsoleAppState : IAppState
{ {
string ErrorText { get; set; } string ErrorText { get; set; }
ObservableCollection<string> PopupTexts { get; } ObservableCollection<string> PopupTexts { get; }
ItemPreviewType? PreviewType { get; set; }
} }

View File

@@ -22,5 +22,6 @@ public interface IRootViewModel
IDeclarativeProperty<VolumeSizeInfo?> VolumeSizeInfo { get; } IDeclarativeProperty<VolumeSizeInfo?> VolumeSizeInfo { get; }
IFrequencyNavigationViewModel FrequencyNavigation { get; } IFrequencyNavigationViewModel FrequencyNavigation { get; }
IItemPreviewService ItemPreviewService { get; } IItemPreviewService ItemPreviewService { get; }
IClipboardService ClipboardService { get; }
event Action<IInputElement>? FocusReadInputElement; event Action<IInputElement>? FocusReadInputElement;
} }

View File

@@ -0,0 +1,7 @@
namespace FileTime.ConsoleUI.App.Preview;
public enum ItemPreviewType
{
Text,
Binary
}

View File

@@ -0,0 +1,17 @@
using FileTime.App.Core.UserCommand;
namespace FileTime.ConsoleUI.App.UserCommand;
public class NextPreviewUserCommand : IIdentifiableUserCommand
{
public const string CommandId = "console_next_preview";
public static NextPreviewUserCommand Instance = new();
private NextPreviewUserCommand()
{
}
public string UserCommandID => CommandId;
public string Title => "Next preview";
}

View File

@@ -0,0 +1,17 @@
using FileTime.App.Core.UserCommand;
namespace FileTime.ConsoleUI.App.UserCommand;
public class PreviousPreviewUserCommand : IIdentifiableUserCommand
{
public const string CommandId = "console_previous_preview";
public static PreviousPreviewUserCommand Instance = new();
private PreviousPreviewUserCommand()
{
}
public string UserCommandID => CommandId;
public string Title => "Previous preview";
}

View File

@@ -1,5 +1,6 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.ConsoleUI.App.Preview;
using PropertyChanged.SourceGenerator; using PropertyChanged.SourceGenerator;
namespace FileTime.ConsoleUI.App; namespace FileTime.ConsoleUI.App;
@@ -9,4 +10,6 @@ public partial class ConsoleAppState : AppStateBase, IConsoleAppState
[Notify] private string? _errorText; [Notify] private string? _errorText;
//TODO: make it thread safe //TODO: make it thread safe
public ObservableCollection<string> PopupTexts { get; } = new(); public ObservableCollection<string> PopupTexts { get; } = new();
[Notify] private ItemPreviewType? _previewType = ItemPreviewType.Binary;
} }

View File

@@ -105,13 +105,13 @@ public class CommandPalette
item.Bind( item.Bind(
item.Parent, item.Parent,
d => d.CommandPalette.SelectedItem == item.DataContext ? _theme.ListViewItemTheme.SelectedBackgroundColor : null, d => d.CommandPalette.SelectedItem.Identifier == item.DataContext.Identifier ? _theme.ListViewItemTheme.SelectedBackgroundColor : null,
t => t.Background t => t.Background
); );
item.Bind( item.Bind(
item.Parent, item.Parent,
d => d.CommandPalette.SelectedItem == item.DataContext ? _theme.ListViewItemTheme.SelectedForegroundColor : null, d => d.CommandPalette.SelectedItem.Identifier == item.DataContext.Identifier ? _theme.ListViewItemTheme.SelectedForegroundColor : null,
t => t.Foreground t => t.Foreground
); );

View File

@@ -1,5 +1,6 @@
using FileTime.App.Core.Models; using FileTime.App.Core.Models;
using FileTime.App.Core.ViewModels.ItemPreview; using FileTime.App.Core.ViewModels.ItemPreview;
using FileTime.ConsoleUI.App.Preview;
using FileTime.ConsoleUI.App.Styling; using FileTime.ConsoleUI.App.Styling;
using TerminalUI.Controls; using TerminalUI.Controls;
using TerminalUI.Extensions; using TerminalUI.Extensions;
@@ -11,10 +12,12 @@ namespace FileTime.ConsoleUI.App.Controls;
public class ItemPreviews public class ItemPreviews
{ {
private readonly ITheme _theme; private readonly ITheme _theme;
private readonly IConsoleAppState _appState;
public ItemPreviews(ITheme theme) public ItemPreviews(ITheme theme, IConsoleAppState appState)
{ {
_theme = theme; _theme = theme;
_appState = appState;
} }
public IView<IRootViewModel> View() public IView<IRootViewModel> View()
@@ -75,11 +78,34 @@ public class ItemPreviews
ChildInitializer = ChildInitializer =
{ {
new TextBlock<IElementPreviewViewModel>() new TextBlock<IElementPreviewViewModel>()
.Setup(t => t.Bind( .Setup(t =>
t, {
dc => dc.TextContent, t.Bind(
t => t.Text, t,
fallbackValue: string.Empty)), dc => dc.TextContent,
t => t.Text,
fallbackValue: string.Empty);
t.Bind(
t,
dc => _appState.PreviewType,
t => t.IsVisible,
v => v is null or ItemPreviewType.Text);
}),
new BinaryView<IElementPreviewViewModel>()
.Setup(b =>
{
b.Bind(
b,
dc => dc.BinaryContent,
b => b.Data);
b.Bind(
b,
dc => _appState.PreviewType,
t => t.IsVisible,
v => v == ItemPreviewType.Binary);
}),
new TextBlock<IElementPreviewViewModel> new TextBlock<IElementPreviewViewModel>
{ {
Margin = "0 1 0 0", Margin = "0 1 0 0",

View File

@@ -1,11 +1,13 @@
using System.Globalization; using System.Globalization;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.ConsoleUI.App.Configuration;
using FileTime.ConsoleUI.App.Controls; using FileTime.ConsoleUI.App.Controls;
using FileTime.ConsoleUI.App.Styling; using FileTime.ConsoleUI.App.Styling;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using Humanizer.Bytes; using Humanizer.Bytes;
using Microsoft.Extensions.Options;
using TerminalUI; using TerminalUI;
using TerminalUI.Color; using TerminalUI.Color;
using TerminalUI.Controls; using TerminalUI.Controls;
@@ -36,6 +38,7 @@ public class MainWindow
private readonly Dialogs _dialogs; private readonly Dialogs _dialogs;
private readonly Timeline _timeline; private readonly Timeline _timeline;
private readonly ItemPreviews _itemPreviews; private readonly ItemPreviews _itemPreviews;
private readonly IOptions<ConsoleApplicationConfiguration> _consoleApplicationConfiguration;
private readonly Lazy<IView> _root; private readonly Lazy<IView> _root;
@@ -47,7 +50,8 @@ public class MainWindow
FrequencyNavigation frequencyNavigation, FrequencyNavigation frequencyNavigation,
Dialogs dialogs, Dialogs dialogs,
Timeline timeline, Timeline timeline,
ItemPreviews itemPreviews) ItemPreviews itemPreviews,
IOptions<ConsoleApplicationConfiguration> consoleApplicationConfiguration)
{ {
_rootViewModel = rootViewModel; _rootViewModel = rootViewModel;
_applicationContext = applicationContext; _applicationContext = applicationContext;
@@ -57,6 +61,7 @@ public class MainWindow
_dialogs = dialogs; _dialogs = dialogs;
_timeline = timeline; _timeline = timeline;
_itemPreviews = itemPreviews; _itemPreviews = itemPreviews;
_consoleApplicationConfiguration = consoleApplicationConfiguration;
_root = new Lazy<IView>(Initialize); _root = new Lazy<IView>(Initialize);
} }
@@ -92,7 +97,7 @@ public class MainWindow
{ {
new Grid<IRootViewModel> new Grid<IRootViewModel>
{ {
ColumnDefinitionsObject = "Auto * Auto", ColumnDefinitionsObject = "Auto * Auto Auto",
ChildInitializer = ChildInitializer =
{ {
new StackPanel<IRootViewModel> new StackPanel<IRootViewModel>
@@ -130,8 +135,32 @@ public class MainWindow
root => root.AppState.SelectedTab.Value.CurrentLocation.Value.FullName.Path, root => root.AppState.SelectedTab.Value.CurrentLocation.Value.FullName.Path,
tb => tb.Text tb => tb.Text
)), )),
new StackPanel<IRootViewModel>
{
Margin = "2 0 0 0",
Extensions = {new GridPositionExtension(2, 0)},
ChildInitializer =
{
new TextBlock<IRootViewModel>
{
Text = _consoleApplicationConfiguration.Value.ClipboardSingleIcon ?? "C",
AsciiOnly = false
}.Setup(t => t.Bind(
t,
dc => dc.ClipboardService.Content.Count == 1,
t => t.IsVisible)),
new TextBlock<IRootViewModel>
{
Text = _consoleApplicationConfiguration.Value.ClipboardMultipleIcon ?? "CC",
AsciiOnly = false
}.Setup(t => t.Bind(
t,
dc => dc.ClipboardService.Content.Count > 1,
t => t.IsVisible))
}
},
TabControl() TabControl()
.WithExtension(new GridPositionExtension(2, 0)) .WithExtension(new GridPositionExtension(3, 0))
} }
}, },
new Grid<IRootViewModel> new Grid<IRootViewModel>
@@ -375,6 +404,7 @@ public class MainWindow
{ {
var tabList = new ListView<IRootViewModel, ITabViewModel> var tabList = new ListView<IRootViewModel, ITabViewModel>
{ {
Margin = "1 0 0 0",
Orientation = Orientation.Horizontal, Orientation = Orientation.Horizontal,
ItemTemplate = item => ItemTemplate = item =>
{ {

View File

@@ -4,13 +4,15 @@ using FileTime.App.Core.Services;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.App.Core.ViewModels.Timeline; using FileTime.App.Core.ViewModels.Timeline;
using FileTime.App.FrequencyNavigation.ViewModels; using FileTime.App.FrequencyNavigation.ViewModels;
using FileTime.ConsoleUI.App.Preview;
using FileTime.ConsoleUI.App.Services; using FileTime.ConsoleUI.App.Services;
using FileTime.Core.Interactions; using FileTime.Core.Interactions;
using FileTime.Core.Models; using FileTime.Core.Models;
using PropertyChanged.SourceGenerator;
namespace FileTime.ConsoleUI.App; namespace FileTime.ConsoleUI.App;
public class RootViewModel : IRootViewModel public partial class RootViewModel : IRootViewModel
{ {
public string UserName => Environment.UserName; public string UserName => Environment.UserName;
public string MachineName => Environment.MachineName; public string MachineName => Environment.MachineName;
@@ -19,6 +21,7 @@ public class RootViewModel : IRootViewModel
public ICommandPaletteViewModel CommandPalette { get; } public ICommandPaletteViewModel CommandPalette { get; }
public IFrequencyNavigationViewModel FrequencyNavigation { get; } public IFrequencyNavigationViewModel FrequencyNavigation { get; }
public IItemPreviewService ItemPreviewService { get; } public IItemPreviewService ItemPreviewService { get; }
public IClipboardService ClipboardService { get; }
public IDialogService DialogService { get; } public IDialogService DialogService { get; }
public ITimelineViewModel TimelineViewModel { get; } public ITimelineViewModel TimelineViewModel { get; }
public IDeclarativeProperty<VolumeSizeInfo?> VolumeSizeInfo { get;} public IDeclarativeProperty<VolumeSizeInfo?> VolumeSizeInfo { get;}
@@ -32,7 +35,8 @@ public class RootViewModel : IRootViewModel
IDialogService dialogService, IDialogService dialogService,
ITimelineViewModel timelineViewModel, ITimelineViewModel timelineViewModel,
IFrequencyNavigationViewModel frequencyNavigation, IFrequencyNavigationViewModel frequencyNavigation,
IItemPreviewService itemPreviewService) IItemPreviewService itemPreviewService,
IClipboardService clipboardService)
{ {
AppState = appState; AppState = appState;
PossibleCommands = possibleCommands; PossibleCommands = possibleCommands;
@@ -41,6 +45,7 @@ public class RootViewModel : IRootViewModel
TimelineViewModel = timelineViewModel; TimelineViewModel = timelineViewModel;
FrequencyNavigation = frequencyNavigation; FrequencyNavigation = frequencyNavigation;
ItemPreviewService = itemPreviewService; ItemPreviewService = itemPreviewService;
ClipboardService = clipboardService;
DialogService.ReadInput.PropertyChanged += (o, e) => DialogService.ReadInput.PropertyChanged += (o, e) =>
{ {
@@ -53,6 +58,14 @@ public class RootViewModel : IRootViewModel
} }
}; };
itemPreviewService.ItemPreview.PropertyChanged += (o, e) =>
{
if (e.PropertyName == nameof(itemPreviewService.ItemPreview.Value))
{
appState.PreviewType = null;
}
};
VolumeSizeInfo = appState.SelectedTab VolumeSizeInfo = appState.SelectedTab
.Map(t => t?.CurrentLocation) .Map(t => t?.CurrentLocation)
.Switch() .Switch()

View File

@@ -5,6 +5,7 @@ using FileTime.ConsoleUI.App.Configuration;
using FileTime.ConsoleUI.App.Controls; using FileTime.ConsoleUI.App.Controls;
using FileTime.ConsoleUI.App.KeyInputHandling; using FileTime.ConsoleUI.App.KeyInputHandling;
using FileTime.ConsoleUI.App.Services; using FileTime.ConsoleUI.App.Services;
using FileTime.ConsoleUI.App.UserCommand;
using FileTime.Core.Interactions; using FileTime.Core.Interactions;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -12,6 +13,16 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
namespace FileTime.ConsoleUI.App; namespace FileTime.ConsoleUI.App;
public class StartupHandler : IStartupHandler
{
public StartupHandler(IIdentifiableUserCommandService identifiableUserCommandService)
{
identifiableUserCommandService.AddIdentifiableUserCommand(NextPreviewUserCommand.Instance);
identifiableUserCommandService.AddIdentifiableUserCommand(PreviousPreviewUserCommand.Instance);
}
public Task InitAsync() => Task.CompletedTask;
}
public static class Startup public static class Startup
{ {
public static IServiceCollection AddConsoleServices(this IServiceCollection services, IConfigurationRoot configuration) public static IServiceCollection AddConsoleServices(this IServiceCollection services, IConfigurationRoot configuration)
@@ -27,6 +38,8 @@ public static class Startup
services.TryAddSingleton<IRootViewModel, RootViewModel>(); services.TryAddSingleton<IRootViewModel, RootViewModel>();
services.TryAddSingleton<IDialogService, DialogService>(); services.TryAddSingleton<IDialogService, DialogService>();
services.TryAddSingleton<IUserCommunicationService>(sp => sp.GetRequiredService<IDialogService>()); services.TryAddSingleton<IUserCommunicationService>(sp => sp.GetRequiredService<IDialogService>());
services.AddSingleton<IUserCommandHandler, ConsoleUserCommandHandler>();
services.AddSingleton<IStartupHandler, StartupHandler>();
services.Configure<ConsoleApplicationConfiguration>(configuration); services.Configure<ConsoleApplicationConfiguration>(configuration);
return services; return services;

View File

@@ -0,0 +1,80 @@
using FileTime.App.Core.Services;
using FileTime.App.Core.Services.UserCommandHandler;
using FileTime.App.Core.ViewModels.ItemPreview;
using FileTime.ConsoleUI.App.Preview;
namespace FileTime.ConsoleUI.App.UserCommand;
public class ConsoleUserCommandHandler : AggregatedUserCommandHandler
{
private static readonly ItemPreviewType[] ElementPreviewOrder = {ItemPreviewType.Text, ItemPreviewType.Binary};
private readonly IConsoleAppState _consoleAppState;
private readonly IItemPreviewService _itemPreviewService;
public ConsoleUserCommandHandler(
IConsoleAppState consoleAppState,
IItemPreviewService itemPreviewService
)
{
_consoleAppState = consoleAppState;
_itemPreviewService = itemPreviewService;
AddCommandHandler(new IUserCommandHandler[]
{
new TypeUserCommandHandler<NextPreviewUserCommand>(NextPreview),
new TypeUserCommandHandler<PreviousPreviewUserCommand>(PreviousPreview),
});
}
private Task NextPreview()
{
if (_itemPreviewService.ItemPreview.Value is not IElementPreviewViewModel) return Task.CompletedTask;
var previewOrder = ElementPreviewOrder;
if (previewOrder.Length < 2) return Task.CompletedTask;
if (_consoleAppState.PreviewType == null)
{
_consoleAppState.PreviewType = previewOrder.Length > 1 ? previewOrder[1] : null;
return Task.CompletedTask;
}
var currentPreviewType = _consoleAppState.PreviewType.Value;
int i;
for (i = 0; i < previewOrder.Length; i++)
{
if (previewOrder[i] == currentPreviewType) break;
}
i++;
_consoleAppState.PreviewType = i >= previewOrder.Length ? previewOrder[0] : previewOrder[i];
return Task.CompletedTask;
}
private Task PreviousPreview()
{
if (_itemPreviewService.ItemPreview.Value is not IElementPreviewViewModel) return Task.CompletedTask;
var previewOrder = ElementPreviewOrder;
if (previewOrder.Length < 2) return Task.CompletedTask;
if (_consoleAppState.PreviewType == null)
{
_consoleAppState.PreviewType = previewOrder.Length > 1 ? previewOrder[^1] : null;
return Task.CompletedTask;
}
var currentPreviewType = _consoleAppState.PreviewType.Value;
int i;
for (i = previewOrder.Length - 1; i > -1; i--)
{
if (previewOrder[i] == currentPreviewType) break;
}
i--;
_consoleAppState.PreviewType = i > -1 ? previewOrder[^1] : previewOrder[i];
return Task.CompletedTask;
}
}

View File

@@ -35,4 +35,8 @@
<PackageReference Include="Terminal.Gui" Version="1.13.5" /> <PackageReference Include="Terminal.Gui" Version="1.13.5" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,4 @@
{
"ClipboardSingleIcon": "\udb80\udd4c",
"ClipboardMultipleIcon": "\udb84\ude68"
}

View File

@@ -8,6 +8,10 @@ public class MockRenderEngine : IRenderEngine
{ {
} }
public void VisibilityChanged(IView view, bool newVisibility)
{
}
public void VisibilityChanged(IView view) public void VisibilityChanged(IView view)
{ {
} }

View File

@@ -0,0 +1,126 @@
using System.Reflection.Metadata;
using PropertyChanged.SourceGenerator;
using TerminalUI.Color;
using TerminalUI.Models;
using TerminalUI.Traits;
namespace TerminalUI.Controls;
public partial class BinaryView<T> : View<BinaryView<T>, T>, IDisplayView
{
private record RenderState(
byte[]? Data,
int BytesPerLine,
Position Position,
Size Size,
IColor? Foreground,
IColor? Background);
private RenderState? _lastRenderState;
[Notify] private byte[]? _data;
[Notify] private int _bytesPerLine = 16;
public BinaryView()
{
RerenderProperties.Add(nameof(Data));
RerenderProperties.Add(nameof(BytesPerLine));
}
protected override Size CalculateSize()
{
if (_data is null) return new(0, 0);
if (_data.Length < _bytesPerLine) return new Size(_data.Length, 1);
var completeLines = (int) Math.Floor((double) _data.Length / _bytesPerLine);
var remaining = completeLines * _bytesPerLine - completeLines;
var lines = remaining > 0 ? completeLines + 1 : completeLines;
return new Size(_bytesPerLine * 3 - 1, lines);
}
protected override bool DefaultRenderer(in RenderContext renderContext, Position position, Size size)
{
if (size.Width < 2 || size.Height == 0) return false;
var data = _data;
var bytesPerLine = _bytesPerLine;
if (size.Width < _bytesPerLine * 3 - 1)
{
bytesPerLine = (int) Math.Floor((double) (size.Width + 1) / 3);
}
var foreground = Foreground ?? renderContext.Foreground;
var background = Background ?? renderContext.Background;
var renderState = new RenderState(
data,
bytesPerLine,
position,
size,
foreground,
background);
var skipRender = !renderContext.ForceRerender && !NeedsRerender(renderState);
_lastRenderState = renderState;
if (data is null) return false;
var lineI = 0;
var textSize = size with {Height = 1};
for (var i = 0; i < data.Length; i += bytesPerLine, lineI++)
{
if (lineI > size.Height) break;
RenderLine(
renderContext,
data,
i,
i + bytesPerLine,
position with {Y = position.Y + lineI},
textSize,
skipRender);
}
return true;
}
private void RenderLine(
in RenderContext renderContext,
byte[] data,
int startIndex,
int maxEndIndex,
Position position,
Size size,
bool updateCellsOnly
)
{
Span<char> text = stackalloc char[(maxEndIndex - startIndex) * 3 - 1];
var textI = 0;
for (var i = startIndex; i < maxEndIndex && i < data.Length; i++, textI += 3)
{
var b = data[i];
var b1 = (byte) (b >> 4);
var b2 = (byte) (b & 0x0F);
var c1 = b1 < 10 ? (char) (b1 + '0') : (char) (b1 - 10 + 'A');
var c2 = b2 < 10 ? (char) (b2 + '0') : (char) (b2 - 10 + 'A');
text[textI] = c1;
text[textI + 1] = c2;
if (textI + 2 < text.Length)
text[textI + 2] = ' ';
}
RenderText(
text,
renderContext,
position,
size,
updateCellsOnly
);
}
private bool NeedsRerender(RenderState renderState)
=> _lastRenderState is null || _lastRenderState != renderState;
}

View File

@@ -17,7 +17,8 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
string? Text, string? Text,
IColor? Foreground, IColor? Foreground,
IColor? Background, IColor? Background,
ITextFormat? TextFormat); ITextFormat? TextFormat,
bool AsciiOnly);
private RenderState? _lastRenderState; private RenderState? _lastRenderState;
private string[]? _textLines; private string[]? _textLines;
@@ -26,6 +27,7 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
[Notify] private TextAlignment _textAlignment = TextAlignment.Left; [Notify] private TextAlignment _textAlignment = TextAlignment.Left;
[Notify] private ITextFormat? _textFormat; [Notify] private ITextFormat? _textFormat;
[Notify] private int _textStartIndex; [Notify] private int _textStartIndex;
[Notify] private bool _asciiOnly = true;
public TextBlock() public TextBlock()
{ {
@@ -33,6 +35,7 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
RerenderProperties.Add(nameof(TextAlignment)); RerenderProperties.Add(nameof(TextAlignment));
RerenderProperties.Add(nameof(TextFormat)); RerenderProperties.Add(nameof(TextFormat));
RerenderProperties.Add(nameof(TextStartIndex)); RerenderProperties.Add(nameof(TextStartIndex));
RerenderProperties.Add(nameof(AsciiOnly));
((INotifyPropertyChanged) this).PropertyChanged += (o, e) => ((INotifyPropertyChanged) this).PropertyChanged += (o, e) =>
{ {
@@ -51,19 +54,23 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
var foreground = Foreground ?? renderContext.Foreground; var foreground = Foreground ?? renderContext.Foreground;
var background = Background ?? renderContext.Background; var background = Background ?? renderContext.Background;
var text = Text;
var textLines = _textLines;
var asciiOnly = _asciiOnly;
var renderState = new RenderState( var renderState = new RenderState(
position, position,
size, size,
Text, text,
foreground, foreground,
background, background,
_textFormat); _textFormat,
asciiOnly);
var skipRender = !renderContext.ForceRerender && !NeedsRerender(renderState); var skipRender = !renderContext.ForceRerender && !NeedsRerender(renderState);
_lastRenderState = renderState; _lastRenderState = renderState;
var textLines = _textLines;
var textStartIndex = _textStartIndex; var textStartIndex = _textStartIndex;
if (textLines is null) if (textLines is null)
{ {
@@ -81,7 +88,7 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
_textStartIndex = textLines.Length - size.Height; _textStartIndex = textLines.Length - size.Height;
} }
RenderText(textLines, renderContext, position, size, skipRender, TransformText); RenderText(textLines, renderContext, position, size, skipRender, TransformText, asciiOnly);
return !skipRender; return !skipRender;
} }

View File

@@ -1,4 +1,4 @@
using System.Buffers; using System.Buffers;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
@@ -220,10 +220,13 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
private static void UpdateCells(bool[,] renderContextUpdatedCells, Position position, int sizeWidth, int sizeHeight) private static void UpdateCells(bool[,] renderContextUpdatedCells, Position position, int sizeWidth, int sizeHeight)
{ {
var xLen = renderContextUpdatedCells.GetLength(0);
var yLen = renderContextUpdatedCells.GetLength(1);
for (var x = 0; x < sizeWidth; x++) for (var x = 0; x < sizeWidth; x++)
{ {
for (var y = 0; y < sizeHeight; y++) for (var y = 0; y < sizeHeight; y++)
{ {
if (position.X + x >= xLen || position.Y + y >= yLen) continue;
renderContextUpdatedCells[position.X + x, position.Y + y] = true; renderContextUpdatedCells[position.X + x, position.Y + y] = true;
} }
} }