TextBox, PropertyChangeHandler
This commit is contained in:
157
src/Library/TerminalUI/Controls/Border.cs
Normal file
157
src/Library/TerminalUI/Controls/Border.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using TerminalUI.Models;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public partial class Border<T> : ContentView<T>
|
||||
{
|
||||
[Notify] private Thickness _borderThickness = 1;
|
||||
[Notify] private Thickness _padding = 0;
|
||||
[Notify] private char _topChar = '─';
|
||||
[Notify] private char _leftChar = '│';
|
||||
[Notify] private char _rightChar = '│';
|
||||
[Notify] private char _bottomChar = '─';
|
||||
[Notify] private char _topLeftChar = '┌';
|
||||
[Notify] private char _topRightChar = '┐';
|
||||
[Notify] private char _bottomLeftChar = '└';
|
||||
[Notify] private char _bottomRightChar = '┘';
|
||||
|
||||
public Border()
|
||||
{
|
||||
RerenderProperties.Add(nameof(BorderThickness));
|
||||
RerenderProperties.Add(nameof(Padding));
|
||||
RerenderProperties.Add(nameof(TopChar));
|
||||
RerenderProperties.Add(nameof(LeftChar));
|
||||
RerenderProperties.Add(nameof(RightChar));
|
||||
RerenderProperties.Add(nameof(BottomChar));
|
||||
}
|
||||
|
||||
protected override Size CalculateSize()
|
||||
{
|
||||
var size = new Size(
|
||||
_borderThickness.Left + _borderThickness.Right + _padding.Left + _padding.Right,
|
||||
_borderThickness.Top + _borderThickness.Bottom + _padding.Top + _padding.Bottom
|
||||
);
|
||||
if (Content is null || !Content.IsVisible) return size;
|
||||
|
||||
var contentSize = Content.GetRequestedSize();
|
||||
return new Size(contentSize.Width + size.Width, contentSize.Height + size.Height);
|
||||
}
|
||||
|
||||
protected override bool DefaultRenderer(RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
if (ContentRendererMethod is null)
|
||||
{
|
||||
throw new NullReferenceException(
|
||||
nameof(ContentRendererMethod)
|
||||
+ " is null, cannot render content of "
|
||||
+ Content?.GetType().Name
|
||||
+ " with DataContext of "
|
||||
+ DataContext?.GetType().Name);
|
||||
}
|
||||
|
||||
var childPosition = new Position(X: position.X + _borderThickness.Left, Y: position.Y + _borderThickness.Top);
|
||||
var childSize = new Size(
|
||||
Width: size.Width - _borderThickness.Left - _borderThickness.Right,
|
||||
Height: size.Height - _borderThickness.Top - _borderThickness.Bottom
|
||||
);
|
||||
|
||||
if (_padding.Left > 0 || _padding.Top > 0 || _padding.Right > 0 || _padding.Bottom > 0)
|
||||
{
|
||||
childPosition = new Position(X: childPosition.X + _padding.Left, Y: childPosition.Y + _padding.Top);
|
||||
childSize = new Size(
|
||||
Width: childSize.Width - _padding.Left - _padding.Right,
|
||||
Height: childSize.Height - _padding.Top - _padding.Bottom
|
||||
);
|
||||
}
|
||||
|
||||
var contentRendered = ContentRendererMethod(renderContext, childPosition, childSize);
|
||||
|
||||
if (contentRendered)
|
||||
{
|
||||
var driver = renderContext.ConsoleDriver;
|
||||
driver.ResetColor();
|
||||
SetColorsForDriver(renderContext);
|
||||
|
||||
RenderTopBorder(renderContext, position, size);
|
||||
RenderBottomBorder(renderContext, position, size);
|
||||
RenderLeftBorder(renderContext, position, size);
|
||||
RenderRightBorder(renderContext, position, size);
|
||||
|
||||
RenderTopLeftCorner(renderContext, position);
|
||||
RenderTopRightCorner(renderContext, position, size);
|
||||
RenderBottomLeftCorner(renderContext, position, size);
|
||||
RenderBottomRightCorner(renderContext, position, size);
|
||||
|
||||
//TODO render padding
|
||||
}
|
||||
|
||||
return contentRendered;
|
||||
}
|
||||
|
||||
private void RenderTopBorder(RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
position = position with {X = position.X + _borderThickness.Left};
|
||||
size = new Size(Width: size.Width - _borderThickness.Left - _borderThickness.Right, Height: _borderThickness.Top);
|
||||
RenderText(_topChar, renderContext.ConsoleDriver, position, size);
|
||||
}
|
||||
|
||||
private void RenderBottomBorder(RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
position = new Position(X: position.X + _borderThickness.Left, Y: position.Y + size.Height - _borderThickness.Bottom);
|
||||
size = new Size(Width: size.Width - _borderThickness.Left - _borderThickness.Right, Height: _borderThickness.Bottom);
|
||||
RenderText(_bottomChar, renderContext.ConsoleDriver, position, size);
|
||||
}
|
||||
|
||||
private void RenderLeftBorder(RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
position = position with {Y = position.Y + _borderThickness.Top};
|
||||
size = new Size(Width: _borderThickness.Left, Height: size.Height - _borderThickness.Top - _borderThickness.Bottom);
|
||||
RenderText(_leftChar, renderContext.ConsoleDriver, position, size);
|
||||
}
|
||||
|
||||
private void RenderRightBorder(RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
position = new Position(X: position.X + size.Width - _borderThickness.Right, Y: position.Y + _borderThickness.Top);
|
||||
size = new Size(Width: _borderThickness.Right, Height: size.Height - _borderThickness.Top - _borderThickness.Bottom);
|
||||
RenderText(_rightChar, renderContext.ConsoleDriver, position, size);
|
||||
}
|
||||
|
||||
private void RenderTopLeftCorner(RenderContext renderContext, Position position)
|
||||
{
|
||||
if (_borderThickness.Left == 0 || _borderThickness.Top == 0) return;
|
||||
|
||||
var size = new Size(Width: _borderThickness.Left, Height: _borderThickness.Top);
|
||||
RenderText(_topLeftChar, renderContext.ConsoleDriver, position, size);
|
||||
}
|
||||
|
||||
private void RenderTopRightCorner(RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
if (_borderThickness.Right == 0 || _borderThickness.Top == 0) return;
|
||||
|
||||
position = position with {X = position.X + size.Width - _borderThickness.Right};
|
||||
size = new Size(Width: _borderThickness.Right, Height: _borderThickness.Top);
|
||||
RenderText(_topRightChar, renderContext.ConsoleDriver, position, size);
|
||||
}
|
||||
|
||||
private void RenderBottomLeftCorner(RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
if (_borderThickness.Left == 0 || _borderThickness.Bottom == 0) return;
|
||||
|
||||
position = position with {Y = position.Y + size.Height - _borderThickness.Bottom};
|
||||
size = new Size(Width: _borderThickness.Left, Height: _borderThickness.Bottom);
|
||||
RenderText(_bottomLeftChar, renderContext.ConsoleDriver, position, size);
|
||||
}
|
||||
|
||||
private void RenderBottomRightCorner(RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
if (_borderThickness.Right == 0 || _borderThickness.Bottom == 0) return;
|
||||
|
||||
position = new Position(
|
||||
X: position.X + size.Width - _borderThickness.Right,
|
||||
Y: position.Y + size.Height - _borderThickness.Bottom
|
||||
);
|
||||
size = new Size(Width: _borderThickness.Right, Height: _borderThickness.Bottom);
|
||||
RenderText(_bottomRightChar, renderContext.ConsoleDriver, position, size);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public abstract class ChildContainerView<T> : View<T>, IChildContainer<T>
|
||||
{
|
||||
private readonly ObservableCollection<IView> _children = new();
|
||||
private readonly Dictionary<IView, bool> _visibilities = new();
|
||||
public ReadOnlyObservableCollection<IView> Children { get; }
|
||||
public ChildInitializer<T> ChildInitializer { get; }
|
||||
|
||||
@@ -28,8 +30,8 @@ public abstract class ChildContainerView<T> : View<T>, IChildContainer<T>
|
||||
ApplicationContext?.EventLoop.RequestRerender();
|
||||
}
|
||||
};
|
||||
|
||||
((INotifyPropertyChanged)this).PropertyChanged += (o, args) =>
|
||||
|
||||
((INotifyPropertyChanged) this).PropertyChanged += (o, args) =>
|
||||
{
|
||||
if (args.PropertyName == nameof(ApplicationContext))
|
||||
{
|
||||
@@ -41,14 +43,18 @@ public abstract class ChildContainerView<T> : View<T>, IChildContainer<T>
|
||||
};
|
||||
}
|
||||
|
||||
protected override void AttachChildren()
|
||||
protected void SaveVisibilities()
|
||||
{
|
||||
foreach (var child in Children)
|
||||
_visibilities.Clear();
|
||||
foreach (var child in _children)
|
||||
{
|
||||
child.Attached = true;
|
||||
_visibilities[child] = child.IsVisible;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool? GetLastVisibility(IView view)
|
||||
=> _visibilities.TryGetValue(view, out var visibility) ? visibility : null;
|
||||
|
||||
public override TChild AddChild<TChild>(TChild child)
|
||||
{
|
||||
child = base.AddChild(child);
|
||||
|
||||
@@ -40,18 +40,9 @@ public abstract partial class ContentView<T> : View<T>, IContentRenderer<T>
|
||||
RerenderProperties.Add(nameof(ContentRendererMethod));
|
||||
}
|
||||
|
||||
protected override void AttachChildren()
|
||||
{
|
||||
base.AttachChildren();
|
||||
if (Content is not null)
|
||||
{
|
||||
Content.Attached = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool DefaultContentRender(RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
if (Content is null)
|
||||
if (Content is null || !Content.IsVisible)
|
||||
{
|
||||
if (_placeholderRenderDone) return false;
|
||||
_placeholderRenderDone = true;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TerminalUI.Extensions;
|
||||
using TerminalUI.Models;
|
||||
@@ -13,9 +12,9 @@ public class Grid<T> : ChildContainerView<T>
|
||||
private List<ColumnDefinition> _columnDefinitions = new() {ColumnDefinition.Star(1)};
|
||||
private ILogger<Grid<T>>? Logger => ApplicationContext?.LoggerFactory?.CreateLogger<Grid<T>>();
|
||||
|
||||
private delegate void WithSizes(RenderContext renderContext, Span<int> widths, Span<int> heights);
|
||||
private delegate void WithSizes(RenderContext renderContext, ReadOnlySpan<int> widths, ReadOnlySpan<int> heights);
|
||||
|
||||
private delegate TResult WithSizes<TResult>(RenderContext renderContext, Span<int> widths, Span<int> heights);
|
||||
private delegate TResult WithSizes<TResult>(RenderContext renderContext, ReadOnlySpan<int> widths, ReadOnlySpan<int> heights);
|
||||
|
||||
private const int ToBeCalculated = -1;
|
||||
|
||||
@@ -132,14 +131,14 @@ public class Grid<T> : ChildContainerView<T>
|
||||
var width = 0;
|
||||
var height = 0;
|
||||
|
||||
for (var i = 0; i < columnWidths.Length; i++)
|
||||
foreach (var t in columnWidths)
|
||||
{
|
||||
width += columnWidths[i];
|
||||
width += t;
|
||||
}
|
||||
|
||||
for (var i = 0; i < rowHeights.Length; i++)
|
||||
foreach (var t in rowHeights)
|
||||
{
|
||||
height += rowHeights[i];
|
||||
height += t;
|
||||
}
|
||||
|
||||
return new Size(width, height);
|
||||
@@ -151,49 +150,122 @@ public class Grid<T> : ChildContainerView<T>
|
||||
new Option<Size>(size, true),
|
||||
(context, columnWidths, rowHeights) =>
|
||||
{
|
||||
foreach (var child in Children)
|
||||
context = new RenderContext(
|
||||
context.ConsoleDriver,
|
||||
context.ForceRerender,
|
||||
Foreground ?? context.Foreground,
|
||||
Background ?? context.Background
|
||||
);
|
||||
var viewsByPosition = GroupViewsByPosition(columnWidths.Length, rowHeights.Length);
|
||||
|
||||
for (var column = 0; column < columnWidths.Length; column++)
|
||||
{
|
||||
var (x, y) = GetViewColumnAndRow(child, columnWidths.Length, rowHeights.Length);
|
||||
|
||||
var width = columnWidths[x];
|
||||
var height = rowHeights[y];
|
||||
|
||||
var left = position.X;
|
||||
var top = position.Y;
|
||||
|
||||
for (var i = 0; i < x; i++)
|
||||
for (var row = 0; row < rowHeights.Length; row++)
|
||||
{
|
||||
left += columnWidths[i];
|
||||
RenderViewsByPosition(
|
||||
context,
|
||||
position,
|
||||
columnWidths,
|
||||
rowHeights,
|
||||
viewsByPosition,
|
||||
column,
|
||||
row
|
||||
);
|
||||
}
|
||||
|
||||
for (var i = 0; i < y; i++)
|
||||
{
|
||||
top += rowHeights[i];
|
||||
}
|
||||
|
||||
child.Render(context, new Position(left, top), new Size(width, height));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
/*var viewsByPosition = GroupViewsByPosition(columnWidths, rowHeights);
|
||||
CleanUnusedArea(viewsByPosition, columnWidths, rowHeights);*/
|
||||
});
|
||||
|
||||
/*private void CleanUnusedArea(Dictionary<(int, int),List<IView>> viewsByPosition, Span<int> columnWidths, Span<int> rowHeights)
|
||||
private void RenderViewsByPosition(
|
||||
RenderContext context,
|
||||
Position gridPosition,
|
||||
ReadOnlySpan<int> columnWidths,
|
||||
ReadOnlySpan<int> rowHeights,
|
||||
IReadOnlyDictionary<(int, int), List<IView>> viewsByPosition,
|
||||
int column,
|
||||
int row)
|
||||
{
|
||||
for (var x = 0; x < columnWidths.Length; x++)
|
||||
if (!viewsByPosition.TryGetValue((column, row), out var children)) return;
|
||||
|
||||
var anyChangedVisibility = false;
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
for (var y = 0; y < rowHeights.Length; y++)
|
||||
var lastVisibility = GetLastVisibility(child);
|
||||
if (lastVisibility is { } b && b != child.IsVisible)
|
||||
{
|
||||
if (!viewsByPosition.TryGetValue((x, y), out var list)) continue;
|
||||
|
||||
|
||||
anyChangedVisibility = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
/*private Dictionary<(int, int), List<IView>> GroupViewsByPosition(int columns, int rows)
|
||||
var width = columnWidths[column];
|
||||
var height = rowHeights[row];
|
||||
var renderSize = new Size(width, height);
|
||||
|
||||
var renderPosition = GetRenderPosition(
|
||||
gridPosition,
|
||||
columnWidths,
|
||||
rowHeights,
|
||||
column,
|
||||
row
|
||||
);
|
||||
|
||||
var needsRerender = anyChangedVisibility;
|
||||
if (needsRerender)
|
||||
{
|
||||
context = new RenderContext(
|
||||
context.ConsoleDriver,
|
||||
true,
|
||||
context.Foreground,
|
||||
context.Background
|
||||
);
|
||||
RenderEmpty(context, renderPosition, renderSize);
|
||||
}
|
||||
|
||||
//This implies that children further back in the list will be rendered on top of children placed before in the list.
|
||||
foreach (var child in children.Where(child => child.IsVisible))
|
||||
{
|
||||
var rendered = child.Render(context, renderPosition, renderSize);
|
||||
if (rendered && !needsRerender)
|
||||
{
|
||||
needsRerender = true;
|
||||
context = new RenderContext(
|
||||
context.ConsoleDriver,
|
||||
true,
|
||||
context.Foreground,
|
||||
context.Background
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static Position GetRenderPosition(
|
||||
Position gridPosition,
|
||||
ReadOnlySpan<int> columnWidths,
|
||||
ReadOnlySpan<int> rowHeights,
|
||||
int column,
|
||||
int row
|
||||
)
|
||||
{
|
||||
var left = gridPosition.X;
|
||||
var top = gridPosition.Y;
|
||||
|
||||
for (var i = 0; i < column; i++)
|
||||
{
|
||||
left += columnWidths[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < row; i++)
|
||||
{
|
||||
top += rowHeights[i];
|
||||
}
|
||||
|
||||
return new Position(left, top);
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<(int, int), List<IView>> GroupViewsByPosition(int columns, int rows)
|
||||
{
|
||||
Dictionary<ValueTuple<int, int>, List<IView>> viewsByPosition = new();
|
||||
foreach (var child in Children)
|
||||
@@ -210,7 +282,7 @@ public class Grid<T> : ChildContainerView<T>
|
||||
}
|
||||
|
||||
return viewsByPosition;
|
||||
}*/
|
||||
}
|
||||
|
||||
private ValueTuple<int, int> GetViewColumnAndRow(IView view, int columns, int rows)
|
||||
{
|
||||
@@ -237,7 +309,7 @@ public class Grid<T> : ChildContainerView<T>
|
||||
{
|
||||
WithCalculatedSize(renderContext, size, Helper);
|
||||
|
||||
object? Helper(RenderContext renderContext1, Span<int> widths, Span<int> heights)
|
||||
object? Helper(RenderContext renderContext1, ReadOnlySpan<int> widths, ReadOnlySpan<int> heights)
|
||||
{
|
||||
actionWithSizes(renderContext1, widths, heights);
|
||||
return null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel;
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.Models;
|
||||
using TerminalUI.Traits;
|
||||
|
||||
@@ -17,9 +18,12 @@ public interface IView : INotifyPropertyChanged, IDisposableCollection
|
||||
int? MaxHeight { get; set; }
|
||||
int? Height { get; set; }
|
||||
int ActualHeight { get; }
|
||||
Margin Margin { get; set; }
|
||||
Thickness Margin { get; set; }
|
||||
bool IsVisible { get; set; }
|
||||
bool Attached { get; set; }
|
||||
string? Name { get; set; }
|
||||
IColor? Foreground { get; set; }
|
||||
IColor? Background { get; set; }
|
||||
IApplicationContext? ApplicationContext { get; set; }
|
||||
List<object> Extensions { get; }
|
||||
RenderMethod RenderMethod { get; set; }
|
||||
|
||||
@@ -9,12 +9,12 @@ namespace TerminalUI.Controls;
|
||||
|
||||
public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
{
|
||||
private static readonly ArrayPool<ListViewItem<TItem>> ListViewItemPool = ArrayPool<ListViewItem<TItem>>.Shared;
|
||||
private static readonly ArrayPool<ListViewItem<TItem, TDataContext>> ListViewItemPool = ArrayPool<ListViewItem<TItem, TDataContext>>.Shared;
|
||||
|
||||
private readonly List<IDisposable> _itemsDisposables = new();
|
||||
private Func<IEnumerable<TItem>?>? _getItems;
|
||||
private object? _itemsSource;
|
||||
private ListViewItem<TItem>[]? _listViewItems;
|
||||
private ListViewItem<TItem, TDataContext>[]? _listViewItems;
|
||||
private int _listViewItemLength;
|
||||
private int _selectedIndex = 0;
|
||||
private int _renderStartIndex = 0;
|
||||
@@ -30,6 +30,14 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
if (_selectedIndex != value)
|
||||
{
|
||||
_selectedIndex = value;
|
||||
if (_listViewItems is not null)
|
||||
{
|
||||
for (var i = 0; i < _listViewItemLength; i++)
|
||||
{
|
||||
_listViewItems[i].IsSelected = i == value;
|
||||
}
|
||||
}
|
||||
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(SelectedItem));
|
||||
}
|
||||
@@ -124,7 +132,7 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
}
|
||||
}
|
||||
|
||||
public Func<ListViewItem<TItem>, IView<TItem>?> ItemTemplate { get; set; } = DefaultItemTemplate;
|
||||
public Func<ListViewItem<TItem, TDataContext>, IView<TItem>?> ItemTemplate { get; set; } = DefaultItemTemplate;
|
||||
|
||||
public ListView()
|
||||
{
|
||||
@@ -292,7 +300,7 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
return true;
|
||||
}
|
||||
|
||||
private Span<ListViewItem<TItem>> InstantiateItemViews()
|
||||
private ReadOnlySpan<ListViewItem<TItem, TDataContext>> InstantiateItemViews()
|
||||
{
|
||||
var items = _getItems?.Invoke()?.ToList();
|
||||
if (items is null)
|
||||
@@ -305,7 +313,7 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
return _listViewItems;
|
||||
}
|
||||
|
||||
Span<ListViewItem<TItem>> listViewItems;
|
||||
ReadOnlySpan<ListViewItem<TItem, TDataContext>> listViewItems;
|
||||
|
||||
if (_listViewItems is null || _listViewItemLength != items.Count)
|
||||
{
|
||||
@@ -313,7 +321,8 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
var dataContext = items[i];
|
||||
var child = CreateChild<ListViewItem<TItem>, TItem>(_ => dataContext);
|
||||
var child = new ListViewItem<TItem, TDataContext>(this);
|
||||
AddChild(child, _ => dataContext);
|
||||
var newContent = ItemTemplate(child);
|
||||
child.Content = newContent;
|
||||
newListViewItems[i] = child;
|
||||
@@ -336,12 +345,12 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
return listViewItems;
|
||||
}
|
||||
|
||||
private Span<ListViewItem<TItem>> InstantiateEmptyItemViews()
|
||||
private ReadOnlySpan<ListViewItem<TItem, TDataContext>> InstantiateEmptyItemViews()
|
||||
{
|
||||
_listViewItems = ListViewItemPool.Rent(0);
|
||||
_listViewItemLength = 0;
|
||||
return _listViewItems;
|
||||
}
|
||||
|
||||
private static IView<TItem>? DefaultItemTemplate(ListViewItem<TItem> listViewItem) => null;
|
||||
private static IView<TItem>? DefaultItemTemplate(ListViewItem<TItem, TDataContext> listViewItem) => null;
|
||||
}
|
||||
@@ -1,12 +1,23 @@
|
||||
using TerminalUI.Models;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using TerminalUI.Models;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public class ListViewItem<T> : ContentView<T>
|
||||
public partial class ListViewItem<T, TParentDataContext> : ContentView<T>
|
||||
{
|
||||
public ListView<TParentDataContext, T> Parent { get; }
|
||||
[Notify] private bool _isSelected;
|
||||
|
||||
public ListViewItem(ListView<TParentDataContext, T> parent)
|
||||
{
|
||||
Parent = parent;
|
||||
|
||||
RerenderProperties.Add(nameof(IsSelected));
|
||||
}
|
||||
|
||||
protected override Size CalculateSize()
|
||||
{
|
||||
if (Content is null) return new Size(0, 0);
|
||||
if (Content is null || !Content.IsVisible) return new Size(0, 0);
|
||||
return Content.GetRequestedSize();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ public partial class Rectangle<T> : View<T>
|
||||
protected override bool DefaultRenderer(RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
var renderState = new RenderState(position, size, Fill);
|
||||
if (!NeedsRerender(renderState) || Fill is null) return false;
|
||||
if ((!renderContext.ForceRerender && !NeedsRerender(renderState)) || Fill is null) return false;
|
||||
_lastRenderState = renderState;
|
||||
|
||||
var driver = renderContext.ConsoleDriver;
|
||||
@@ -34,7 +34,7 @@ public partial class Rectangle<T> : View<T>
|
||||
driver.SetCursorPosition(position with {Y = position.Y + i});
|
||||
driver.Write(s);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ public partial class StackPanel<T> : ChildContainerView<T>
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (!child.IsVisible) continue;
|
||||
|
||||
var childSize = child.GetRequestedSize();
|
||||
_requestedSizes.Add(child, childSize);
|
||||
|
||||
@@ -41,6 +43,8 @@ public partial class StackPanel<T> : ChildContainerView<T>
|
||||
var neededRerender = false;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (!child.IsVisible) continue;
|
||||
|
||||
if (!_requestedSizes.TryGetValue(child, out var childSize)) throw new Exception("Child size not found");
|
||||
|
||||
var childPosition = Orientation == Orientation.Vertical
|
||||
@@ -49,12 +53,13 @@ public partial class StackPanel<T> : ChildContainerView<T>
|
||||
|
||||
var endX = position.X + size.Width;
|
||||
var endY = position.Y + size.Height;
|
||||
|
||||
|
||||
if (childPosition.X > endX || childPosition.Y > endY) break;
|
||||
if (childPosition.X + childSize.Width > endX)
|
||||
{
|
||||
childSize = childSize with {Width = endX - childPosition.X};
|
||||
}
|
||||
|
||||
if (childPosition.Y + childSize.Height > endY)
|
||||
{
|
||||
childSize = childSize with {Height = endY - childPosition.Y};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.ConsoleDrivers;
|
||||
using TerminalUI.Extensions;
|
||||
using TerminalUI.Models;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
[DebuggerDisplay("Text = {Text}")]
|
||||
public partial class TextBlock<T> : View<T>
|
||||
{
|
||||
private record RenderState(
|
||||
@@ -21,8 +22,6 @@ public partial class TextBlock<T> : View<T>
|
||||
private bool _placeholderRenderDone;
|
||||
|
||||
[Notify] private string? _text = string.Empty;
|
||||
[Notify] private IColor? _foreground;
|
||||
[Notify] private IColor? _background;
|
||||
[Notify] private TextAlignment _textAlignment = TextAlignment.Left;
|
||||
|
||||
public TextBlock()
|
||||
@@ -34,8 +33,6 @@ public partial class TextBlock<T> : View<T>
|
||||
);
|
||||
|
||||
RerenderProperties.Add(nameof(Text));
|
||||
RerenderProperties.Add(nameof(Foreground));
|
||||
RerenderProperties.Add(nameof(Background));
|
||||
RerenderProperties.Add(nameof(TextAlignment));
|
||||
|
||||
((INotifyPropertyChanged) this).PropertyChanged += (o, e) =>
|
||||
@@ -53,9 +50,16 @@ public partial class TextBlock<T> : View<T>
|
||||
{
|
||||
if (size.Width == 0 || size.Height == 0) return false;
|
||||
|
||||
var driver = renderContext.ConsoleDriver;
|
||||
var renderState = new RenderState(position, size, Text, _foreground, _background);
|
||||
if (!NeedsRerender(renderState)) return false;
|
||||
var foreground = Foreground ?? renderContext.Foreground;
|
||||
var background = Background ?? renderContext.Background;
|
||||
var renderState = new RenderState(
|
||||
position,
|
||||
size,
|
||||
Text,
|
||||
foreground,
|
||||
background);
|
||||
|
||||
if (!renderContext.ForceRerender && !NeedsRerender(renderState)) return false;
|
||||
|
||||
_lastRenderState = renderState;
|
||||
|
||||
@@ -72,41 +76,29 @@ public partial class TextBlock<T> : View<T>
|
||||
|
||||
_placeholderRenderDone = false;
|
||||
|
||||
var driver = renderContext.ConsoleDriver;
|
||||
driver.ResetColor();
|
||||
if (Foreground is { } foreground)
|
||||
if (foreground is not null)
|
||||
{
|
||||
driver.SetForegroundColor(foreground);
|
||||
}
|
||||
|
||||
if (Background is { } background)
|
||||
if (background is not null)
|
||||
{
|
||||
driver.SetBackgroundColor(background);
|
||||
}
|
||||
|
||||
RenderText(_textLines, driver, position, size);
|
||||
RenderText(_textLines, driver, position, size, TransformText);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RenderText(string[] textLines, IConsoleDriver driver, Position position, Size size)
|
||||
{
|
||||
for (var i = 0; i < textLines.Length; i++)
|
||||
private string TransformText(string text, Position position, Size size)
|
||||
=> TextAlignment switch
|
||||
{
|
||||
var text = textLines[i];
|
||||
text = TextAlignment switch
|
||||
{
|
||||
TextAlignment.Right => string.Format($"{{0,{size.Width}}}", text),
|
||||
_ => string.Format($"{{0,{-size.Width}}}", text)
|
||||
};
|
||||
if (text.Length > size.Width)
|
||||
{
|
||||
text = text[..size.Width];
|
||||
}
|
||||
|
||||
driver.SetCursorPosition(position with {Y = position.Y + i});
|
||||
driver.Write(text);
|
||||
}
|
||||
}
|
||||
TextAlignment.Right => string.Format($"{{0,{size.Width}}}", text),
|
||||
_ => string.Format($"{{0,{-size.Width}}}", text)
|
||||
};
|
||||
|
||||
private bool NeedsRerender(RenderState renderState)
|
||||
=> _lastRenderState is null || _lastRenderState != renderState;
|
||||
|
||||
296
src/Library/TerminalUI/Controls/TextBox.cs
Normal file
296
src/Library/TerminalUI/Controls/TextBox.cs
Normal file
@@ -0,0 +1,296 @@
|
||||
using GeneralInputKey;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.ConsoleDrivers;
|
||||
using TerminalUI.Models;
|
||||
using TerminalUI.Traits;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public partial class TextBox<T> : View<T>, IFocusable
|
||||
{
|
||||
private record RenderState(
|
||||
string? Text,
|
||||
Position Position,
|
||||
Size Size,
|
||||
IColor? ForegroundColor,
|
||||
IColor? BackgroundColor
|
||||
);
|
||||
|
||||
private readonly List<Action<GeneralKeyEventArgs>> _keyHandlers = new();
|
||||
|
||||
private RenderState? _lastRenderState;
|
||||
private string _text = string.Empty;
|
||||
private List<string> _textLines;
|
||||
|
||||
private Position? _cursorPosition;
|
||||
private Position _relativeCursorPosition = new(0, 0);
|
||||
|
||||
[Notify] private bool _multiLine;
|
||||
public bool SetKeyHandledIfKnown { get; set; }
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => _text;
|
||||
set
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
if (value == _text) return;
|
||||
_text = MultiLine ? value : value.Split(Environment.NewLine)[0];
|
||||
|
||||
_textLines = _text.Split(Environment.NewLine).ToList();
|
||||
UpdateRelativeCursorPosition();
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public TextBox()
|
||||
{
|
||||
_textLines = _text.Split(Environment.NewLine).ToList();
|
||||
RerenderProperties.Add(nameof(Text));
|
||||
RerenderProperties.Add(nameof(MultiLine));
|
||||
}
|
||||
|
||||
private void UpdateTextField()
|
||||
{
|
||||
_text = string.Join(Environment.NewLine, _textLines);
|
||||
UpdateRelativeCursorPosition();
|
||||
OnPropertyChanged(nameof(Text));
|
||||
}
|
||||
|
||||
private void UpdateRelativeCursorPosition()
|
||||
{
|
||||
if (_relativeCursorPosition.Y > _textLines.Count - 1)
|
||||
_relativeCursorPosition = _relativeCursorPosition with {Y = _textLines.Count - 1};
|
||||
|
||||
if (_relativeCursorPosition.X > _textLines[_relativeCursorPosition.Y].Length)
|
||||
_relativeCursorPosition = _relativeCursorPosition with {X = _textLines[_relativeCursorPosition.Y].Length};
|
||||
}
|
||||
|
||||
protected override Size CalculateSize() => new(Width ?? 10, Height ?? 1);
|
||||
|
||||
protected override bool DefaultRenderer(RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
var foreground = Foreground ?? renderContext.Foreground;
|
||||
var background = Background ?? renderContext.Background;
|
||||
var renderStatus = new RenderState(
|
||||
Text,
|
||||
position,
|
||||
size,
|
||||
foreground,
|
||||
background);
|
||||
|
||||
if (!renderContext.ForceRerender && !NeedsRerender(renderStatus)) return false;
|
||||
_lastRenderState = renderStatus;
|
||||
|
||||
var driver = renderContext.ConsoleDriver;
|
||||
driver.ResetColor();
|
||||
if (foreground is not null)
|
||||
{
|
||||
driver.SetForegroundColor(foreground);
|
||||
}
|
||||
|
||||
if (background is not null)
|
||||
{
|
||||
driver.SetBackgroundColor(background);
|
||||
}
|
||||
|
||||
RenderEmpty(renderContext, position, size);
|
||||
RenderText(_textLines, driver, position, size);
|
||||
_cursorPosition = position + _relativeCursorPosition;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool NeedsRerender(RenderState renderState)
|
||||
=> _lastRenderState is null || _lastRenderState != renderState;
|
||||
|
||||
public void Focus()
|
||||
=> ApplicationContext?.FocusManager.SetFocus(this);
|
||||
|
||||
public void UnFocus()
|
||||
=> ApplicationContext?.FocusManager.UnFocus(this);
|
||||
|
||||
public void SetCursorPosition(IConsoleDriver consoleDriver)
|
||||
{
|
||||
if (_cursorPosition is null) return;
|
||||
consoleDriver.SetCursorPosition(_cursorPosition.Value);
|
||||
}
|
||||
|
||||
public void HandleKeyInput(GeneralKeyEventArgs keyEventArgs)
|
||||
{
|
||||
HandleKeyInputInternal(keyEventArgs);
|
||||
if (keyEventArgs.Handled)
|
||||
{
|
||||
ApplicationContext?.EventLoop.RequestRerender();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleKeyInputInternal(GeneralKeyEventArgs keyEventArgs)
|
||||
{
|
||||
if (keyEventArgs.Handled) return;
|
||||
|
||||
if (HandleBackspace(keyEventArgs, out var known))
|
||||
return;
|
||||
|
||||
if (!known && HandleDelete(keyEventArgs, out known))
|
||||
return;
|
||||
|
||||
if (!known && HandleNavigation(keyEventArgs, out known))
|
||||
return;
|
||||
|
||||
if (!known && ProcessKeyHandlers(keyEventArgs))
|
||||
return;
|
||||
|
||||
if (!known
|
||||
&& keyEventArgs.KeyChar != '\0'
|
||||
&& keyEventArgs.KeyChar.ToString() is {Length: 1} keyString)
|
||||
{
|
||||
var y = _relativeCursorPosition.Y;
|
||||
var x = _relativeCursorPosition.X;
|
||||
_textLines[y] = _textLines[y][..x] + keyString + _textLines[y][x..];
|
||||
_relativeCursorPosition = _relativeCursorPosition with {X = x + 1};
|
||||
|
||||
keyEventArgs.Handled = true;
|
||||
UpdateTextField();
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandleBackspace(GeneralKeyEventArgs keyEventArgs, out bool known)
|
||||
{
|
||||
if (keyEventArgs.Key != Keys.Backspace)
|
||||
{
|
||||
known = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
known = true;
|
||||
|
||||
if (_relativeCursorPosition is {X: 0, Y: 0})
|
||||
{
|
||||
return keyEventArgs.Handled = SetKeyHandledIfKnown;
|
||||
}
|
||||
|
||||
if (_relativeCursorPosition.X == 0)
|
||||
{
|
||||
var y = _relativeCursorPosition.Y;
|
||||
_textLines[y - 1] += _textLines[y];
|
||||
_textLines.RemoveAt(y);
|
||||
_relativeCursorPosition = new Position(Y: y - 1, X: _textLines[y - 1].Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
var y = _relativeCursorPosition.Y;
|
||||
var x = _relativeCursorPosition.X;
|
||||
_textLines[y] = _textLines[y].Remove(x - 1, 1);
|
||||
_relativeCursorPosition = _relativeCursorPosition with {X = x - 1};
|
||||
}
|
||||
|
||||
UpdateTextField();
|
||||
return keyEventArgs.Handled = true;
|
||||
}
|
||||
|
||||
private bool HandleDelete(GeneralKeyEventArgs keyEventArgs, out bool known)
|
||||
{
|
||||
if (keyEventArgs.Key != Keys.Delete)
|
||||
{
|
||||
known = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
known = true;
|
||||
|
||||
if (_relativeCursorPosition.Y == _textLines.Count - 1
|
||||
&& _relativeCursorPosition.X == _textLines[_relativeCursorPosition.Y].Length)
|
||||
{
|
||||
return keyEventArgs.Handled = SetKeyHandledIfKnown;
|
||||
}
|
||||
|
||||
if (_relativeCursorPosition.X == _textLines[_relativeCursorPosition.Y].Length)
|
||||
{
|
||||
var y = _relativeCursorPosition.Y;
|
||||
_textLines[y] += _textLines[y + 1];
|
||||
_textLines.RemoveAt(y + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
var y = _relativeCursorPosition.Y;
|
||||
var x = _relativeCursorPosition.X;
|
||||
_textLines[y] = _textLines[y].Remove(x, 1);
|
||||
}
|
||||
|
||||
UpdateTextField();
|
||||
return keyEventArgs.Handled = true;
|
||||
}
|
||||
|
||||
private bool HandleNavigation(GeneralKeyEventArgs keyEventArgs, out bool known)
|
||||
{
|
||||
if (keyEventArgs.Key == Keys.Left)
|
||||
{
|
||||
known = true;
|
||||
keyEventArgs.Handled = SetKeyHandledIfKnown;
|
||||
|
||||
if (_relativeCursorPosition is {X: 0, Y: 0})
|
||||
{
|
||||
return keyEventArgs.Handled;
|
||||
}
|
||||
|
||||
if (_relativeCursorPosition.X == 0)
|
||||
{
|
||||
var y = _relativeCursorPosition.Y - 1;
|
||||
_relativeCursorPosition = new Position(_textLines[y].Length, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
_relativeCursorPosition = _relativeCursorPosition with {X = _relativeCursorPosition.X - 1};
|
||||
}
|
||||
|
||||
return keyEventArgs.Handled = true;
|
||||
}
|
||||
else if (keyEventArgs.Key == Keys.Right)
|
||||
{
|
||||
known = true;
|
||||
keyEventArgs.Handled = SetKeyHandledIfKnown;
|
||||
|
||||
if (_relativeCursorPosition.Y == _textLines.Count - 1
|
||||
&& _relativeCursorPosition.X == _textLines[_relativeCursorPosition.Y].Length)
|
||||
{
|
||||
return keyEventArgs.Handled;
|
||||
}
|
||||
|
||||
if (_relativeCursorPosition.X == _textLines[_relativeCursorPosition.Y].Length)
|
||||
{
|
||||
_relativeCursorPosition = new Position(0, _relativeCursorPosition.Y + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_relativeCursorPosition = _relativeCursorPosition with {X = _relativeCursorPosition.X + 1};
|
||||
}
|
||||
|
||||
return keyEventArgs.Handled = true;
|
||||
}
|
||||
|
||||
|
||||
known = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ProcessKeyHandlers(GeneralKeyEventArgs keyEventArgs)
|
||||
{
|
||||
foreach (var keyHandler in _keyHandlers)
|
||||
{
|
||||
keyHandler(keyEventArgs);
|
||||
if (keyEventArgs.Handled) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public TextBox<T> WithKeyHandler(Action<GeneralKeyEventArgs> keyHandler)
|
||||
{
|
||||
_keyHandlers.Add(keyHandler);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
using System.Buffers;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.ConsoleDrivers;
|
||||
using TerminalUI.Models;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public delegate string TextTransformer(string text, Position position, Size size);
|
||||
|
||||
public abstract partial class View<T> : IView<T>
|
||||
{
|
||||
private readonly List<IDisposable> _disposables = new();
|
||||
@@ -18,24 +23,15 @@ public abstract partial class View<T> : IView<T>
|
||||
[Notify] private int? _maxHeight;
|
||||
[Notify] private int? _height;
|
||||
[Notify] private int _actualHeight;
|
||||
[Notify] private Margin _margin = new Margin(0, 0, 0, 0);
|
||||
[Notify] private bool _isVisible = true;
|
||||
[Notify] private Thickness _margin = 0;
|
||||
[Notify] private IColor? _foreground;
|
||||
[Notify] private IColor? _background;
|
||||
[Notify] private string? _name;
|
||||
[Notify] private IApplicationContext? _applicationContext;
|
||||
private bool _attached;
|
||||
|
||||
public bool Attached
|
||||
{
|
||||
get => _attached;
|
||||
set
|
||||
{
|
||||
if (_attached == value) return;
|
||||
_attached = value;
|
||||
if (value)
|
||||
{
|
||||
AttachChildren();
|
||||
}
|
||||
}
|
||||
}
|
||||
[Notify] private bool _attached;
|
||||
|
||||
protected ObservableCollection<IView> VisualChildren { get; } = new();
|
||||
|
||||
public List<object> Extensions { get; } = new();
|
||||
public RenderMethod RenderMethod { get; set; }
|
||||
@@ -46,11 +42,16 @@ public abstract partial class View<T> : IView<T>
|
||||
{
|
||||
RenderMethod = DefaultRenderer;
|
||||
|
||||
RerenderProperties.Add(nameof(Width));
|
||||
RerenderProperties.Add(nameof(MinWidth));
|
||||
RerenderProperties.Add(nameof(MaxWidth));
|
||||
RerenderProperties.Add(nameof(Height));
|
||||
RerenderProperties.Add(nameof(MinHeight));
|
||||
RerenderProperties.Add(nameof(MaxHeight));
|
||||
RerenderProperties.Add(nameof(IsVisible));
|
||||
RerenderProperties.Add(nameof(Margin));
|
||||
RerenderProperties.Add(nameof(Foreground));
|
||||
RerenderProperties.Add(nameof(Background));
|
||||
|
||||
((INotifyPropertyChanged) this).PropertyChanged += Handle_PropertyChanged;
|
||||
}
|
||||
@@ -80,11 +81,6 @@ public abstract partial class View<T> : IView<T>
|
||||
|
||||
protected abstract Size CalculateSize();
|
||||
|
||||
|
||||
protected virtual void AttachChildren()
|
||||
{
|
||||
}
|
||||
|
||||
private void Handle_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (Attached
|
||||
@@ -96,6 +92,21 @@ public abstract partial class View<T> : IView<T>
|
||||
{
|
||||
ApplicationContext?.EventLoop.RequestRerender();
|
||||
}
|
||||
|
||||
if (e.PropertyName == nameof(Attached))
|
||||
{
|
||||
foreach (var visualChild in VisualChildren)
|
||||
{
|
||||
visualChild.Attached = Attached;
|
||||
}
|
||||
}
|
||||
else if(e.PropertyName == nameof(ApplicationContext))
|
||||
{
|
||||
foreach (var visualChild in VisualChildren)
|
||||
{
|
||||
visualChild.ApplicationContext = ApplicationContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract bool DefaultRenderer(RenderContext renderContext, Position position, Size size);
|
||||
@@ -105,6 +116,8 @@ public abstract partial class View<T> : IView<T>
|
||||
if (!Attached)
|
||||
throw new InvalidOperationException("Cannot render unattached view");
|
||||
|
||||
if (!IsVisible) return false;
|
||||
|
||||
ActualWidth = size.Width;
|
||||
ActualHeight = size.Height;
|
||||
|
||||
@@ -137,6 +150,8 @@ public abstract partial class View<T> : IView<T>
|
||||
protected void RenderEmpty(RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
var driver = renderContext.ConsoleDriver;
|
||||
driver.ResetColor();
|
||||
|
||||
var placeHolder = new string(ApplicationContext!.EmptyCharacter, size.Width);
|
||||
for (var i = 0; i < size.Height; i++)
|
||||
{
|
||||
@@ -145,6 +160,94 @@ public abstract partial class View<T> : IView<T>
|
||||
}
|
||||
}
|
||||
|
||||
protected void RenderText(
|
||||
IList<string> textLines,
|
||||
IConsoleDriver driver,
|
||||
Position position,
|
||||
Size size,
|
||||
TextTransformer? textTransformer = null)
|
||||
{
|
||||
for (var i = 0; i < textLines.Count; i++)
|
||||
{
|
||||
var currentPosition = position with {Y = position.Y + i};
|
||||
var text = textLines[i];
|
||||
|
||||
if (textTransformer is not null)
|
||||
{
|
||||
text = textTransformer(text, currentPosition, size);
|
||||
}
|
||||
|
||||
if (text.Length > size.Width)
|
||||
{
|
||||
text = text[..size.Width];
|
||||
}
|
||||
|
||||
driver.SetCursorPosition(currentPosition);
|
||||
driver.Write(text);
|
||||
}
|
||||
}
|
||||
|
||||
protected void RenderText(
|
||||
string text,
|
||||
IConsoleDriver driver,
|
||||
Position position,
|
||||
Size size,
|
||||
TextTransformer? textTransformer = null)
|
||||
{
|
||||
for (var i = 0; i < size.Height; i++)
|
||||
{
|
||||
var currentPosition = position with {Y = position.Y + i};
|
||||
var finalText = text;
|
||||
|
||||
if (textTransformer is not null)
|
||||
{
|
||||
finalText = textTransformer(finalText, currentPosition, size);
|
||||
}
|
||||
|
||||
if (finalText.Length > size.Width)
|
||||
{
|
||||
finalText = finalText[..size.Width];
|
||||
}
|
||||
|
||||
driver.SetCursorPosition(currentPosition);
|
||||
driver.Write(finalText);
|
||||
}
|
||||
}
|
||||
|
||||
protected void RenderText(
|
||||
char content,
|
||||
IConsoleDriver driver,
|
||||
Position position,
|
||||
Size size)
|
||||
{
|
||||
var contentString = new string(content, size.Width);
|
||||
|
||||
for (var i = 0; i < size.Height; i++)
|
||||
{
|
||||
var currentPosition = position with {Y = position.Y + i};
|
||||
|
||||
driver.SetCursorPosition(currentPosition);
|
||||
driver.Write(contentString);
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetColorsForDriver(RenderContext renderContext)
|
||||
{
|
||||
var driver = renderContext.ConsoleDriver;
|
||||
|
||||
var foreground = Foreground ?? renderContext.Foreground;
|
||||
var background = Background ?? renderContext.Background;
|
||||
if (foreground is not null)
|
||||
{
|
||||
driver.SetForegroundColor(foreground);
|
||||
}
|
||||
|
||||
if (background is not null)
|
||||
{
|
||||
driver.SetBackgroundColor(background);
|
||||
}
|
||||
}
|
||||
|
||||
public TChild CreateChild<TChild>() where TChild : IView<T>, new()
|
||||
{
|
||||
var child = new TChild();
|
||||
@@ -162,6 +265,7 @@ public abstract partial class View<T> : IView<T>
|
||||
{
|
||||
child.DataContext = DataContext;
|
||||
CopyCommonPropertiesToNewChild(child);
|
||||
VisualChildren.Add(child);
|
||||
|
||||
var mapper = new DataContextMapper<T, T>(this, child, d => d);
|
||||
AddDisposable(mapper);
|
||||
@@ -175,6 +279,7 @@ public abstract partial class View<T> : IView<T>
|
||||
{
|
||||
child.DataContext = dataContextMapper(DataContext);
|
||||
CopyCommonPropertiesToNewChild(child);
|
||||
VisualChildren.Add(child);
|
||||
|
||||
var mapper = new DataContextMapper<T, TDataContext>(this, child, dataContextMapper);
|
||||
AddDisposable(mapper);
|
||||
|
||||
Reference in New Issue
Block a user