New controls, main view
This commit is contained in:
58
src/Library/TerminalUI/Controls/ChildContainerView.cs
Normal file
58
src/Library/TerminalUI/Controls/ChildContainerView.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public abstract class ChildContainerView<T> : View<T>, IChildContainer<T>
|
||||
{
|
||||
private readonly ObservableCollection<IView> _children = new();
|
||||
public ReadOnlyObservableCollection<IView> Children { get; }
|
||||
public ChildInitializer<T> ChildInitializer { get; }
|
||||
|
||||
protected ChildContainerView()
|
||||
{
|
||||
ChildInitializer = new ChildInitializer<T>(this);
|
||||
Children = new ReadOnlyObservableCollection<IView>(_children);
|
||||
_children.CollectionChanged += (o, args) =>
|
||||
{
|
||||
if (Attached)
|
||||
{
|
||||
if (args.NewItems?.OfType<IView>() is { } newItems)
|
||||
{
|
||||
foreach (var newItem in newItems)
|
||||
{
|
||||
newItem.Attached = true;
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationContext?.EventLoop.RequestRerender();
|
||||
}
|
||||
};
|
||||
|
||||
((INotifyPropertyChanged)this).PropertyChanged += (o, args) =>
|
||||
{
|
||||
if (args.PropertyName == nameof(ApplicationContext))
|
||||
{
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.ApplicationContext = ApplicationContext;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override TChild AddChild<TChild>(TChild child)
|
||||
{
|
||||
child = base.AddChild(child);
|
||||
_children.Add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
public override TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
||||
where TDataContext : default
|
||||
{
|
||||
child = base.AddChild(child, dataContextMapper);
|
||||
_children.Add(child);
|
||||
return child;
|
||||
}
|
||||
}
|
||||
24
src/Library/TerminalUI/Controls/ChildInitializer.cs
Normal file
24
src/Library/TerminalUI/Controls/ChildInitializer.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public record ChildWithDataContextMapper<TSourceDataContext, TTargetDataContext>(IView<TTargetDataContext> Child, Func<TSourceDataContext?, TTargetDataContext?> DataContextMapper);
|
||||
|
||||
public class ChildInitializer<T> : IEnumerable<IView>
|
||||
{
|
||||
private readonly IChildContainer<T> _childContainer;
|
||||
|
||||
public ChildInitializer(IChildContainer<T> childContainer)
|
||||
{
|
||||
_childContainer = childContainer;
|
||||
}
|
||||
|
||||
public void Add(IView<T> item) => _childContainer.AddChild(item);
|
||||
|
||||
public void Add<TDataContext>(ChildWithDataContextMapper<T, TDataContext> item)
|
||||
=> _childContainer.AddChild(item.Child, item.DataContextMapper);
|
||||
|
||||
public IEnumerator<IView> GetEnumerator() => _childContainer.Children.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
@@ -5,14 +5,15 @@ using TerminalUI.ViewExtensions;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public class Grid<T> : View<T>
|
||||
public class Grid<T> : ChildContainerView<T>
|
||||
{
|
||||
private delegate void WithSizes(Span<int> widths, Span<int> heights);
|
||||
|
||||
private delegate TResult WithSizes<TResult>(Span<int> widths, Span<int> heights);
|
||||
|
||||
private const int ToBeCalculated = -1;
|
||||
private readonly ObservableCollection<IView> _children = new();
|
||||
public ReadOnlyObservableCollection<IView> Children { get; }
|
||||
public GridChildInitializer<T> ChildInitializer { get; }
|
||||
public ObservableCollection<RowDefinition> RowDefinitions { get; } = new();
|
||||
public ObservableCollection<ColumnDefinition> ColumnDefinitions { get; } = new();
|
||||
public ObservableCollection<RowDefinition> RowDefinitions { get; } = new() {RowDefinition.Star(1)};
|
||||
public ObservableCollection<ColumnDefinition> ColumnDefinitions { get; } = new() {ColumnDefinition.Star(1)};
|
||||
|
||||
public object? ColumnDefinitionsObject
|
||||
{
|
||||
@@ -62,35 +63,76 @@ public class Grid<T> : View<T>
|
||||
}
|
||||
}
|
||||
|
||||
public Grid()
|
||||
{
|
||||
ChildInitializer = new GridChildInitializer<T>(this);
|
||||
Children = new ReadOnlyObservableCollection<IView>(_children);
|
||||
_children.CollectionChanged += (o, e) =>
|
||||
public override Size GetRequestedSize()
|
||||
=> WithCalculatedSize((columnWidths, rowHeights) =>
|
||||
{
|
||||
if (Attached)
|
||||
var width = 0;
|
||||
var height = 0;
|
||||
|
||||
for (var i = 0; i < columnWidths.Length; i++)
|
||||
{
|
||||
if (e.NewItems?.OfType<IView>() is { } newItems)
|
||||
{
|
||||
foreach (var newItem in newItems)
|
||||
{
|
||||
newItem.Attached = true;
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationContext?.EventLoop.RequestRerender();
|
||||
width += columnWidths[i];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override Size GetRequestedSize() => throw new NotImplementedException();
|
||||
for (var i = 0; i < rowHeights.Length; i++)
|
||||
{
|
||||
height += rowHeights[i];
|
||||
}
|
||||
|
||||
return new Size(width, height);
|
||||
}, new Option<Size>(new Size(0, 0), false));
|
||||
|
||||
protected override void DefaultRenderer(Position position, Size size)
|
||||
=> WithCalculatedSize((columnWidths, rowHeights) =>
|
||||
{
|
||||
foreach (var child in Children)
|
||||
{
|
||||
var positionExtension = child.GetExtension<GridPositionExtension>();
|
||||
var x = positionExtension?.Column ?? 0;
|
||||
var y = positionExtension?.Row ?? 0;
|
||||
|
||||
var width = columnWidths[x];
|
||||
var height = rowHeights[y];
|
||||
|
||||
var left = 0;
|
||||
var top = 0;
|
||||
|
||||
for (var i = 0; i < x; i++)
|
||||
{
|
||||
left += columnWidths[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < y; i++)
|
||||
{
|
||||
top += rowHeights[i];
|
||||
}
|
||||
|
||||
child.Render(new Position(position.X + left, position.Y + top), new Size(width, height));
|
||||
}
|
||||
}, new Option<Size>(size, true));
|
||||
|
||||
private void WithCalculatedSize(WithSizes actionWithSizes, Option<Size> size)
|
||||
{
|
||||
//TODO: Optimize it, dont calculate all of these only if there is Auto value(s)
|
||||
WithCalculatedSize(Helper, size);
|
||||
|
||||
object? Helper(Span<int> widths, Span<int> heights)
|
||||
{
|
||||
actionWithSizes(widths, heights);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private TResult WithCalculatedSize<TResult>(WithSizes<TResult> actionWithSizes, Option<Size> size)
|
||||
{
|
||||
//TODO: Optimize it, dont calculate all of these, only if there is Auto value(s)
|
||||
var columns = ColumnDefinitions.Count;
|
||||
Span<int> allWidth = stackalloc int[columns * RowDefinitions.Count];
|
||||
Span<int> allHeight = stackalloc int[columns * RowDefinitions.Count];
|
||||
var rows = RowDefinitions.Count;
|
||||
|
||||
if (columns < 1) columns = 1;
|
||||
if (rows < 1) rows = 1;
|
||||
|
||||
Span<int> allWidth = stackalloc int[columns * rows];
|
||||
Span<int> allHeight = stackalloc int[columns * rows];
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
@@ -104,38 +146,47 @@ public class Grid<T> : View<T>
|
||||
}
|
||||
|
||||
Span<int> columnWidths = stackalloc int[columns];
|
||||
Span<int> rowHeights = stackalloc int[RowDefinitions.Count];
|
||||
Span<int> rowHeights = stackalloc int[rows];
|
||||
|
||||
var usedWidth = 0;
|
||||
var widthStars = 0;
|
||||
for (var i = 0; i < columnWidths.Length; i++)
|
||||
{
|
||||
if (ColumnDefinitions[i].Type == GridUnitType.Pixel)
|
||||
{
|
||||
columnWidths[i] = ColumnDefinitions[i].Value;
|
||||
}
|
||||
else if (ColumnDefinitions[i].Type == GridUnitType.Star)
|
||||
else if (size.IsSome && ColumnDefinitions[i].Type == GridUnitType.Star)
|
||||
{
|
||||
widthStars += ColumnDefinitions[i].Value;
|
||||
columnWidths[i] = ToBeCalculated;
|
||||
}
|
||||
else
|
||||
{
|
||||
var max = 0;
|
||||
for (var j = 0; j < RowDefinitions.Count; j++)
|
||||
for (var j = 0; j < rows; j++)
|
||||
{
|
||||
max = Math.Max(max, allWidth.GetFromMatrix(i, j, columns));
|
||||
}
|
||||
|
||||
columnWidths[i] = max;
|
||||
}
|
||||
|
||||
if (columnWidths[i] != ToBeCalculated)
|
||||
usedWidth += columnWidths[i];
|
||||
}
|
||||
|
||||
var usedHeight = 0;
|
||||
var heightStars = 0;
|
||||
for (var i = 0; i < rowHeights.Length; i++)
|
||||
{
|
||||
if (RowDefinitions[i].Type == GridUnitType.Pixel)
|
||||
{
|
||||
rowHeights[i] = RowDefinitions[i].Value;
|
||||
}
|
||||
else if (RowDefinitions[i].Type == GridUnitType.Star)
|
||||
else if (size.IsSome && RowDefinitions[i].Type == GridUnitType.Star)
|
||||
{
|
||||
heightStars += RowDefinitions[i].Value;
|
||||
rowHeights[i] = ToBeCalculated;
|
||||
}
|
||||
else
|
||||
@@ -148,33 +199,39 @@ public class Grid<T> : View<T>
|
||||
|
||||
rowHeights[i] = max;
|
||||
}
|
||||
|
||||
if (rowHeights[i] != ToBeCalculated)
|
||||
usedHeight += rowHeights[i];
|
||||
}
|
||||
|
||||
foreach (var child in Children)
|
||||
if (size.IsSome)
|
||||
{
|
||||
var childSize = child.GetRequestedSize();
|
||||
var positionExtension = child.GetExtension<GridPositionExtension>();
|
||||
var x = positionExtension?.Column ?? 0;
|
||||
var y = positionExtension?.Row ?? 0;
|
||||
var widthLeft = size.Value.Width - usedWidth;
|
||||
var heightLeft = size.Value.Height - usedHeight;
|
||||
|
||||
var width = columnWidths[x];
|
||||
var height = rowHeights[y];
|
||||
var widthPerStart = (int) Math.Floor((double) widthLeft / widthStars);
|
||||
var heightPerStart = (int) Math.Floor((double) heightLeft / heightStars);
|
||||
|
||||
var left = 0;
|
||||
var top = 0;
|
||||
|
||||
for (var i = 0; i < x; i++)
|
||||
for (var i = 0; i < columnWidths.Length; i++)
|
||||
{
|
||||
left += columnWidths[i];
|
||||
var column = ColumnDefinitions[i];
|
||||
if (column.Type == GridUnitType.Star)
|
||||
{
|
||||
columnWidths[i] = widthPerStart * column.Value;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < y; i++)
|
||||
for (var i = 0; i < rowHeights.Length; i++)
|
||||
{
|
||||
top += rowHeights[i];
|
||||
var row = RowDefinitions[i];
|
||||
if (row.Type == GridUnitType.Star)
|
||||
{
|
||||
rowHeights[i] = heightPerStart * row.Value;
|
||||
}
|
||||
}
|
||||
|
||||
child.Render(new Position(left, top), new Size(width, height));
|
||||
}
|
||||
|
||||
return actionWithSizes(columnWidths, rowHeights);
|
||||
}
|
||||
|
||||
public void SetRowDefinitions(string value)
|
||||
@@ -190,7 +247,7 @@ public class Grid<T> : View<T>
|
||||
}
|
||||
else if (v.EndsWith("*"))
|
||||
{
|
||||
var starValue = int.Parse(v[0..^1]);
|
||||
var starValue = v.Length == 1 ? 1 : int.Parse(v[..^1]);
|
||||
RowDefinitions.Add(RowDefinition.Star(starValue));
|
||||
}
|
||||
else if (int.TryParse(v, out var pixelValue))
|
||||
@@ -217,7 +274,7 @@ public class Grid<T> : View<T>
|
||||
}
|
||||
else if (v.EndsWith("*"))
|
||||
{
|
||||
var starValue = int.Parse(v[0..^1]);
|
||||
var starValue = v.Length == 1 ? 1 : int.Parse(v[..^1]);
|
||||
ColumnDefinitions.Add(ColumnDefinition.Star(starValue));
|
||||
}
|
||||
else if (int.TryParse(v, out var pixelValue))
|
||||
@@ -230,19 +287,4 @@ public class Grid<T> : View<T>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override TChild AddChild<TChild>(TChild child)
|
||||
{
|
||||
child = base.AddChild(child);
|
||||
_children.Add(child);
|
||||
return child;
|
||||
}
|
||||
|
||||
public override TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
||||
where TDataContext : default
|
||||
{
|
||||
child = base.AddChild(child, dataContextMapper);
|
||||
_children.Add(child);
|
||||
return child;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public record ChildWithDataContextMapper<TSourceDataContext, TTargetDataContext>(IView<TTargetDataContext> Child, Func<TSourceDataContext?, TTargetDataContext?> DataContextMapper);
|
||||
|
||||
public class GridChildInitializer<T> : IEnumerable<IView>
|
||||
{
|
||||
private readonly Grid<T> _grid;
|
||||
|
||||
public GridChildInitializer(Grid<T> grid)
|
||||
{
|
||||
_grid = grid;
|
||||
}
|
||||
|
||||
public void Add(IView<T> item) => _grid.AddChild(item);
|
||||
|
||||
public void Add<TDataContext>(ChildWithDataContextMapper<T, TDataContext> item)
|
||||
=> _grid.AddChild(item.Child, item.DataContextMapper);
|
||||
|
||||
public IEnumerator<IView> GetEnumerator() => _grid.Children.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
8
src/Library/TerminalUI/Controls/IChildContainer.cs
Normal file
8
src/Library/TerminalUI/Controls/IChildContainer.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public interface IChildContainer<T> : IView<T>
|
||||
{
|
||||
ReadOnlyObservableCollection<IView> Children { get; }
|
||||
}
|
||||
@@ -39,8 +39,8 @@ public interface IView<T> : IView
|
||||
TChild CreateChild<TChild, TDataContext>(Func<T?, TDataContext?> dataContextMapper)
|
||||
where TChild : IView<TDataContext>, new();
|
||||
|
||||
TChild AddChild<TChild>(TChild child) where TChild : IView<T>;
|
||||
public TChild AddChild<TChild>(TChild child) where TChild : IView<T>;
|
||||
|
||||
TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
||||
public TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
||||
where TChild : IView<TDataContext>;
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using System.Buffers;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using DeclarativeProperty;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using TerminalUI.Models;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
{
|
||||
private static readonly ArrayPool<ListViewItem<TItem>> ListViewItemPool = ArrayPool<ListViewItem<TItem>>.Shared;
|
||||
|
||||
@@ -18,6 +18,8 @@ public class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
private int _selectedIndex = 0;
|
||||
private int _renderStartIndex = 0;
|
||||
private Size _requestedItemSize = new(0, 0);
|
||||
[Notify] private int _listPadding = 0;
|
||||
[Notify] private Orientation _orientation = Orientation.Vertical;
|
||||
|
||||
public int SelectedIndex
|
||||
{
|
||||
@@ -28,7 +30,34 @@ public class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
{
|
||||
_selectedIndex = value;
|
||||
OnPropertyChanged();
|
||||
ApplicationContext?.EventLoop.RequestRerender();
|
||||
OnPropertyChanged(nameof(SelectedItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TItem? SelectedItem
|
||||
{
|
||||
get => _listViewItems is null ? default : _listViewItems[_selectedIndex].DataContext;
|
||||
set
|
||||
{
|
||||
if (_listViewItems is null || value is null) return;
|
||||
|
||||
var newSelectedIndex = -1;
|
||||
for (var i = 0; i < _listViewItemLength; i++)
|
||||
{
|
||||
var dataContext = _listViewItems[i].DataContext;
|
||||
if (dataContext is null) continue;
|
||||
|
||||
if (dataContext.Equals(value))
|
||||
{
|
||||
newSelectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newSelectedIndex != -1)
|
||||
{
|
||||
SelectedIndex = newSelectedIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,60 +105,87 @@ public class ListView<TDataContext, TItem> : View<TDataContext>
|
||||
_listViewItems = null;
|
||||
}
|
||||
|
||||
_renderStartIndex = 0;
|
||||
SelectedIndex = 0;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public Func<ListViewItem<TItem>, IView?> ItemTemplate { get; set; } = DefaultItemTemplate;
|
||||
|
||||
public ListView()
|
||||
{
|
||||
RerenderProperties.Add(nameof(ItemsSource));
|
||||
RerenderProperties.Add(nameof(SelectedIndex));
|
||||
RerenderProperties.Add(nameof(Orientation));
|
||||
}
|
||||
|
||||
public override Size GetRequestedSize()
|
||||
{
|
||||
if (_listViewItems is null || _listViewItems.Length == 0)
|
||||
InstantiateItemViews();
|
||||
if (_listViewItems is null || _listViewItemLength == 0)
|
||||
return new Size(0, 0);
|
||||
|
||||
|
||||
var itemSize = _listViewItems[0].GetRequestedSize();
|
||||
_requestedItemSize = itemSize;
|
||||
return itemSize with {Height = itemSize.Height * _listViewItems.Length};
|
||||
return itemSize with {Height = itemSize.Height * _listViewItemLength};
|
||||
}
|
||||
|
||||
protected override void DefaultRenderer(Position position, Size size)
|
||||
{
|
||||
var requestedItemSize = _requestedItemSize;
|
||||
if (requestedItemSize.Height == 0 || requestedItemSize.Width == 0)
|
||||
return;
|
||||
|
||||
var listViewItems = InstantiateItemViews();
|
||||
if (listViewItems.Length == 0) return;
|
||||
|
||||
var requestedItemSize = _requestedItemSize;
|
||||
|
||||
var itemsToRender = listViewItems.Length;
|
||||
var heightNeeded = requestedItemSize.Height * listViewItems.Length;
|
||||
var renderStartIndex = _renderStartIndex;
|
||||
if (heightNeeded < size.Height)
|
||||
if (heightNeeded > size.Height)
|
||||
{
|
||||
var maxItemsToRender = (int) Math.Floor((double) size.Height / requestedItemSize.Height);
|
||||
if (SelectedIndex < renderStartIndex)
|
||||
itemsToRender = maxItemsToRender;
|
||||
|
||||
if (SelectedIndex - ListPadding < renderStartIndex)
|
||||
{
|
||||
renderStartIndex = SelectedIndex - 1;
|
||||
renderStartIndex = SelectedIndex - ListPadding;
|
||||
}
|
||||
else if (SelectedIndex > renderStartIndex + maxItemsToRender)
|
||||
else if (SelectedIndex + ListPadding >= renderStartIndex + maxItemsToRender)
|
||||
{
|
||||
renderStartIndex = SelectedIndex - maxItemsToRender + 1;
|
||||
renderStartIndex = SelectedIndex + ListPadding - maxItemsToRender + 1;
|
||||
}
|
||||
|
||||
if(renderStartIndex < 0)
|
||||
|
||||
if (renderStartIndex + itemsToRender > listViewItems.Length)
|
||||
renderStartIndex = listViewItems.Length - itemsToRender;
|
||||
|
||||
if (renderStartIndex < 0)
|
||||
renderStartIndex = 0;
|
||||
else if (renderStartIndex + maxItemsToRender > listViewItems.Length)
|
||||
renderStartIndex = listViewItems.Length - maxItemsToRender;
|
||||
|
||||
_renderStartIndex = renderStartIndex;
|
||||
}
|
||||
|
||||
var deltaY = 0;
|
||||
for (var i = renderStartIndex; i < itemsToRender && i < listViewItems.Length; i++)
|
||||
var lastItemIndex = renderStartIndex + itemsToRender;
|
||||
if (lastItemIndex > listViewItems.Length)
|
||||
lastItemIndex = listViewItems.Length;
|
||||
|
||||
for (var i = renderStartIndex; i < lastItemIndex; i++)
|
||||
{
|
||||
var item = listViewItems[i];
|
||||
item.Render(position with {Y = position.Y + deltaY}, requestedItemSize);
|
||||
item.Render(position with {Y = position.Y + deltaY}, requestedItemSize with {Width = size.Width});
|
||||
deltaY += requestedItemSize.Height;
|
||||
}
|
||||
|
||||
var driver = ApplicationContext!.ConsoleDriver;
|
||||
var placeholder = new string(' ', size.Width);
|
||||
driver.ResetColor();
|
||||
for (var i = deltaY; i < size.Height; i++)
|
||||
{
|
||||
driver.SetCursorPosition(position with {Y = position.Y + i});
|
||||
driver.Write(placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
private Span<ListViewItem<TItem>> InstantiateItemViews()
|
||||
|
||||
54
src/Library/TerminalUI/Controls/StackPanel.cs
Normal file
54
src/Library/TerminalUI/Controls/StackPanel.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using TerminalUI.Models;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public partial class StackPanel<T> : ChildContainerView<T>
|
||||
{
|
||||
private readonly Dictionary<IView, Size> _requestedSizes = new();
|
||||
[Notify] private Orientation _orientation = Orientation.Vertical;
|
||||
|
||||
public override Size GetRequestedSize()
|
||||
{
|
||||
_requestedSizes.Clear();
|
||||
var width = 0;
|
||||
var height = 0;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
var childSize = child.GetRequestedSize();
|
||||
_requestedSizes.Add(child, childSize);
|
||||
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
width = Math.Max(width, childSize.Width);
|
||||
height += childSize.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
width += childSize.Width;
|
||||
height = Math.Max(height, childSize.Height);
|
||||
}
|
||||
}
|
||||
|
||||
return new Size(width, height);
|
||||
}
|
||||
|
||||
protected override void DefaultRenderer(Position position, Size size)
|
||||
{
|
||||
var delta = 0;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (!_requestedSizes.TryGetValue(child, out var childSize)) throw new Exception("Child size not found");
|
||||
var childPosition = Orientation == Orientation.Vertical
|
||||
? position with {Y = position.Y + delta}
|
||||
: position with {X = position.X + delta};
|
||||
child.Render(childPosition, childSize);
|
||||
|
||||
delta += Orientation == Orientation.Vertical
|
||||
? childSize.Height
|
||||
: childSize.Width;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ public partial class TextBlock<T> : View<T>
|
||||
[Notify] private string? _text = string.Empty;
|
||||
[Notify] private IColor? _foreground;
|
||||
[Notify] private IColor? _background;
|
||||
[Notify] private TextAlignment _textAlignment = TextAlignment.Left;
|
||||
|
||||
public TextBlock()
|
||||
{
|
||||
@@ -26,12 +27,15 @@ public partial class TextBlock<T> : View<T>
|
||||
RerenderProperties.Add(nameof(Text));
|
||||
RerenderProperties.Add(nameof(Foreground));
|
||||
RerenderProperties.Add(nameof(Background));
|
||||
RerenderProperties.Add(nameof(TextAlignment));
|
||||
}
|
||||
|
||||
public override Size GetRequestedSize() => new(Text?.Length ?? 0, 1);
|
||||
|
||||
protected override void DefaultRenderer(Position position, Size size)
|
||||
{
|
||||
if (size.Width == 0 || size.Height == 0) return;
|
||||
|
||||
var driver = ApplicationContext!.ConsoleDriver;
|
||||
var renderContext = new RenderContext(position, Text, _foreground, _background);
|
||||
if (!NeedsRerender(renderContext)) return;
|
||||
@@ -52,7 +56,17 @@ public partial class TextBlock<T> : View<T>
|
||||
driver.SetBackgroundColor(background);
|
||||
}
|
||||
|
||||
driver.Write(Text);
|
||||
var 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.Write(text);
|
||||
}
|
||||
|
||||
private bool NeedsRerender(RenderContext renderContext)
|
||||
|
||||
@@ -16,6 +16,7 @@ public abstract partial class View<T> : IView<T>
|
||||
[Notify] private int? _minHeight;
|
||||
[Notify] private int? _maxHeight;
|
||||
[Notify] private int? _height;
|
||||
[Notify] private IApplicationContext? _applicationContext;
|
||||
private bool _attached;
|
||||
|
||||
public bool Attached
|
||||
@@ -33,7 +34,6 @@ public abstract partial class View<T> : IView<T>
|
||||
}
|
||||
public List<object> Extensions { get; } = new();
|
||||
public Action<Position, Size> RenderMethod { get; set; }
|
||||
public IApplicationContext? ApplicationContext { get; set; }
|
||||
public event Action<IView>? Disposed;
|
||||
protected List<string> RerenderProperties { get; } = new();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user