New controls, main view
This commit is contained in:
@@ -12,7 +12,7 @@ public interface IAppState
|
|||||||
IDeclarativeProperty<ITabViewModel?> SelectedTab { get; }
|
IDeclarativeProperty<ITabViewModel?> SelectedTab { get; }
|
||||||
IObservable<string?> SearchText { get; }
|
IObservable<string?> SearchText { get; }
|
||||||
IDeclarativeProperty<ViewMode> ViewMode { get; }
|
IDeclarativeProperty<ViewMode> ViewMode { get; }
|
||||||
DeclarativeProperty<string?> RapidTravelText { get; }
|
IDeclarativeProperty<string?> RapidTravelText { get; }
|
||||||
IDeclarativeProperty<string?> RapidTravelTextDebounced { get; }
|
IDeclarativeProperty<string?> RapidTravelTextDebounced { get; }
|
||||||
IDeclarativeProperty<string?> ContainerStatus { get; }
|
IDeclarativeProperty<string?> ContainerStatus { get; }
|
||||||
List<KeyConfig> PreviousKeys { get; }
|
List<KeyConfig> PreviousKeys { get; }
|
||||||
@@ -24,4 +24,5 @@ public interface IAppState
|
|||||||
void SetSearchText(string? searchText);
|
void SetSearchText(string? searchText);
|
||||||
Task SwitchViewModeAsync(ViewMode newViewMode);
|
Task SwitchViewModeAsync(ViewMode newViewMode);
|
||||||
Task SetSelectedTabAsync(ITabViewModel tabToSelect);
|
Task SetSelectedTabAsync(ITabViewModel tabToSelect);
|
||||||
|
Task SetRapidTravelTextAsync(string? text);
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
|||||||
if (_appState.RapidTravelText.Value!.Length > 0)
|
if (_appState.RapidTravelText.Value!.Length > 0)
|
||||||
{
|
{
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
await _appState.RapidTravelText.SetValue(
|
await _appState.SetRapidTravelTextAsync(
|
||||||
_appState.RapidTravelText.Value![..^1]
|
_appState.RapidTravelText.Value![..^1]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
|||||||
else if (keyString.Length == 1)
|
else if (keyString.Length == 1)
|
||||||
{
|
{
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
await _appState.RapidTravelText.SetValue(
|
await _appState.SetRapidTravelTextAsync(
|
||||||
_appState.RapidTravelText.Value + keyString.ToLower()
|
_appState.RapidTravelText.Value + keyString.ToLower()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
if (_currentSelectedItem?.Value is not IContainerViewModel containerViewModel || containerViewModel.Container is null)
|
if (_currentSelectedItem?.Value is not IContainerViewModel containerViewModel || containerViewModel.Container is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await _appState.RapidTravelText.SetValue("");
|
await _appState.SetRapidTravelTextAsync("");
|
||||||
if (_selectedTab?.Tab is { } tab)
|
if (_selectedTab?.Tab is { } tab)
|
||||||
{
|
{
|
||||||
await tab.SetCurrentLocation(containerViewModel.Container);
|
await tab.SetCurrentLocation(containerViewModel.Container);
|
||||||
@@ -260,13 +260,13 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
|
|
||||||
private async Task GoUp()
|
private async Task GoUp()
|
||||||
{
|
{
|
||||||
if (_currentLocation?.Value?.Parent is not AbsolutePath parentPath ||
|
if (_currentLocation?.Value?.Parent is not { } parentPath ||
|
||||||
await parentPath.ResolveAsyncSafe() is not IContainer newContainer)
|
await parentPath.ResolveAsyncSafe() is not IContainer newContainer)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _appState.RapidTravelText.SetValue("");
|
await _appState.SetRapidTravelTextAsync("");
|
||||||
if (_selectedTab?.Tab is { } tab)
|
if (_selectedTab?.Tab is { } tab)
|
||||||
{
|
{
|
||||||
await tab.SetCurrentLocation(newContainer);
|
await tab.SetCurrentLocation(newContainer);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public abstract partial class AppStateBase : IAppState
|
|||||||
private readonly DeclarativeProperty<ITabViewModel?> _selectedTab = new();
|
private readonly DeclarativeProperty<ITabViewModel?> _selectedTab = new();
|
||||||
private readonly DeclarativeProperty<ViewMode> _viewMode = new(Models.Enums.ViewMode.Default);
|
private readonly DeclarativeProperty<ViewMode> _viewMode = new(Models.Enums.ViewMode.Default);
|
||||||
private readonly ObservableCollection<ITabViewModel> _tabs = new();
|
private readonly ObservableCollection<ITabViewModel> _tabs = new();
|
||||||
|
private readonly DeclarativeProperty<string?> _rapidTravelText;
|
||||||
|
|
||||||
public IDeclarativeProperty<ViewMode> ViewMode { get; }
|
public IDeclarativeProperty<ViewMode> ViewMode { get; }
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ public abstract partial class AppStateBase : IAppState
|
|||||||
public IObservable<string?> SearchText { get; }
|
public IObservable<string?> SearchText { get; }
|
||||||
|
|
||||||
public IDeclarativeProperty<ITabViewModel?> SelectedTab { get; }
|
public IDeclarativeProperty<ITabViewModel?> SelectedTab { get; }
|
||||||
public DeclarativeProperty<string?> RapidTravelText { get; }
|
public IDeclarativeProperty<string?> RapidTravelText { get; }
|
||||||
public IDeclarativeProperty<string?> RapidTravelTextDebounced { get; }
|
public IDeclarativeProperty<string?> RapidTravelTextDebounced { get; }
|
||||||
|
|
||||||
public IDeclarativeProperty<string?> ContainerStatus { get; }
|
public IDeclarativeProperty<string?> ContainerStatus { get; }
|
||||||
@@ -35,7 +36,8 @@ public abstract partial class AppStateBase : IAppState
|
|||||||
|
|
||||||
protected AppStateBase()
|
protected AppStateBase()
|
||||||
{
|
{
|
||||||
RapidTravelText = new("");
|
_rapidTravelText = new ("");
|
||||||
|
RapidTravelText = _rapidTravelText.DistinctUntilChanged();
|
||||||
RapidTravelTextDebounced = RapidTravelText
|
RapidTravelTextDebounced = RapidTravelText
|
||||||
.Debounce(v =>
|
.Debounce(v =>
|
||||||
string.IsNullOrEmpty(v)
|
string.IsNullOrEmpty(v)
|
||||||
@@ -83,6 +85,7 @@ public abstract partial class AppStateBase : IAppState
|
|||||||
public async Task SwitchViewModeAsync(ViewMode newViewMode) => await _viewMode.SetValue(newViewMode);
|
public async Task SwitchViewModeAsync(ViewMode newViewMode) => await _viewMode.SetValue(newViewMode);
|
||||||
|
|
||||||
public async Task SetSelectedTabAsync(ITabViewModel tabToSelect) => await _selectedTab.SetValue(tabToSelect);
|
public async Task SetSelectedTabAsync(ITabViewModel tabToSelect) => await _selectedTab.SetValue(tabToSelect);
|
||||||
|
public async Task SetRapidTravelTextAsync(string? text) => await _rapidTravelText.SetValue(text);
|
||||||
|
|
||||||
private ITabViewModel? GetSelectedTab(IEnumerable<ITabViewModel> tabs, ITabViewModel? expectedSelectedTab)
|
private ITabViewModel? GetSelectedTab(IEnumerable<ITabViewModel> tabs, ITabViewModel? expectedSelectedTab)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
using FileTime.App.Core.Models.Enums;
|
using FileTime.App.Core.Models.Enums;
|
||||||
using FileTime.App.Core.Services;
|
using FileTime.App.Core.Services;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using MvvmGen;
|
|
||||||
|
|
||||||
namespace FileTime.App.Core.ViewModels;
|
namespace FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
[ViewModel(GenerateConstructor = false)]
|
|
||||||
public partial class ContainerViewModel : ItemViewModel, IContainerViewModel
|
public partial class ContainerViewModel : ItemViewModel, IContainerViewModel
|
||||||
{
|
{
|
||||||
public IContainer? Container => BaseItem as IContainer;
|
public IContainer? Container => BaseItem as IContainer;
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ using DeclarativeProperty;
|
|||||||
using FileTime.App.Core.Models.Enums;
|
using FileTime.App.Core.Models.Enums;
|
||||||
using FileTime.App.Core.Services;
|
using FileTime.App.Core.Services;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using MvvmGen;
|
|
||||||
|
|
||||||
namespace FileTime.App.Core.ViewModels;
|
namespace FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
[ViewModel(GenerateConstructor = false)]
|
|
||||||
public partial class ElementViewModel : ItemViewModel, IElementViewModel
|
public partial class ElementViewModel : ItemViewModel, IElementViewModel
|
||||||
{
|
{
|
||||||
public IElement? Element => BaseItem as Element;
|
public IElement? Element => BaseItem as Element;
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ using FileTime.App.Core.Models.Enums;
|
|||||||
using FileTime.App.Core.Services;
|
using FileTime.App.Core.Services;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.Models.Extensions;
|
using FileTime.Core.Models.Extensions;
|
||||||
using MvvmGen;
|
|
||||||
|
|
||||||
namespace FileTime.App.Core.ViewModels;
|
namespace FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
[ViewModel(GenerateConstructor = false)]
|
|
||||||
public partial class FileViewModel : ElementViewModel, IFileViewModel
|
public partial class FileViewModel : ElementViewModel, IFileViewModel
|
||||||
{
|
{
|
||||||
public FileViewModel(IItemNameConverterService itemNameConverterService, IAppState appState) : base(itemNameConverterService, appState)
|
public FileViewModel(IItemNameConverterService itemNameConverterService, IAppState appState) : base(itemNameConverterService, appState)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public abstract partial class ItemViewModel : IItemViewModel
|
|||||||
? parentTab.CurrentSelectedItem
|
? parentTab.CurrentSelectedItem
|
||||||
.Map(EqualsTo)
|
.Map(EqualsTo)
|
||||||
.DistinctUntilChanged()
|
.DistinctUntilChanged()
|
||||||
.Debounce(TimeSpan.FromMilliseconds(10))
|
.Debounce(TimeSpan.FromMilliseconds(1))
|
||||||
: new DeclarativeProperty<bool>(IsInDeepestPath());
|
: new DeclarativeProperty<bool>(IsInDeepestPath());
|
||||||
|
|
||||||
IsAlternative = sourceCollection
|
IsAlternative = sourceCollection
|
||||||
@@ -86,7 +86,7 @@ public abstract partial class ItemViewModel : IItemViewModel
|
|||||||
ViewMode = DeclarativePropertyHelpers
|
ViewMode = DeclarativePropertyHelpers
|
||||||
.CombineLatest(IsMarked, IsSelected, IsAlternative, GenerateViewMode)
|
.CombineLatest(IsMarked, IsSelected, IsAlternative, GenerateViewMode)
|
||||||
.DistinctUntilChanged()
|
.DistinctUntilChanged()
|
||||||
.Debounce(TimeSpan.FromMilliseconds(100));
|
.Debounce(TimeSpan.FromMilliseconds(1));
|
||||||
Attributes = item.Attributes;
|
Attributes = item.Attributes;
|
||||||
CreatedAt = item.CreatedAt;
|
CreatedAt = item.CreatedAt;
|
||||||
ModifiedAt = item.ModifiedAt;
|
ModifiedAt = item.ModifiedAt;
|
||||||
|
|||||||
@@ -5,17 +5,9 @@ namespace FileTime.ConsoleUI.App;
|
|||||||
|
|
||||||
public interface ITheme
|
public interface ITheme
|
||||||
{
|
{
|
||||||
IColor? ItemBackgroundColor { get; }
|
|
||||||
IColor? AlternativeItemBackgroundColor { get; }
|
|
||||||
IColor? SelectedItemBackgroundColor { get; }
|
|
||||||
IColor? MarkedItemBackgroundColor { get; }
|
|
||||||
IColor? MarkedAlternativeItemBackgroundColor { get; }
|
|
||||||
IColor? MarkedSelectedItemBackgroundColor { get; }
|
|
||||||
IColor? DefaultForegroundColor { get; }
|
IColor? DefaultForegroundColor { get; }
|
||||||
IColor? DefaultBackgroundColor { get; }
|
IColor? DefaultBackgroundColor { get; }
|
||||||
IColor? AlternativeItemForegroundColor { get; }
|
IColor? ElementColor { get; }
|
||||||
IColor? SelectedItemForegroundColor { get; }
|
IColor? ContainerColor { get; }
|
||||||
IColor? MarkedItemForegroundColor { get; }
|
IColor? MarkedItemColor { get; }
|
||||||
IColor? MarkedAlternativeItemForegroundColor { get; }
|
|
||||||
IColor? MarkedSelectedItemForegroundColor { get; }
|
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
using System.Collections.ObjectModel;
|
using DeclarativeProperty;
|
||||||
using System.Linq.Expressions;
|
|
||||||
using DeclarativeProperty;
|
|
||||||
using FileTime.App.Core.Models.Enums;
|
using FileTime.App.Core.Models.Enums;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.Core.Enums;
|
||||||
using TerminalUI;
|
using TerminalUI;
|
||||||
using TerminalUI.Color;
|
using TerminalUI.Color;
|
||||||
using TerminalUI.Controls;
|
using TerminalUI.Controls;
|
||||||
using TerminalUI.Extensions;
|
using TerminalUI.Extensions;
|
||||||
using TerminalUI.Models;
|
|
||||||
using TerminalUI.ViewExtensions;
|
using TerminalUI.ViewExtensions;
|
||||||
using ConsoleColor = TerminalUI.Color.ConsoleColor;
|
using ConsoleColor = TerminalUI.Color.ConsoleColor;
|
||||||
|
|
||||||
@@ -18,9 +16,8 @@ public class MainWindow
|
|||||||
private readonly IConsoleAppState _consoleAppState;
|
private readonly IConsoleAppState _consoleAppState;
|
||||||
private readonly IApplicationContext _applicationContext;
|
private readonly IApplicationContext _applicationContext;
|
||||||
private readonly ITheme _theme;
|
private readonly ITheme _theme;
|
||||||
private ListView<IAppState, IItemViewModel> _selectedItemsView;
|
|
||||||
|
|
||||||
private Grid<object> _grid;
|
private IView _root;
|
||||||
|
|
||||||
public MainWindow(
|
public MainWindow(
|
||||||
IConsoleAppState consoleAppState,
|
IConsoleAppState consoleAppState,
|
||||||
@@ -34,13 +31,55 @@ public class MainWindow
|
|||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
_selectedItemsView = new()
|
var root = new Grid<IAppState>
|
||||||
{
|
{
|
||||||
DataContext = _consoleAppState,
|
DataContext = _consoleAppState,
|
||||||
ApplicationContext = _applicationContext
|
ApplicationContext = _applicationContext,
|
||||||
|
RowDefinitionsObject = "Auto *",
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
new TextBlock<IAppState>()
|
||||||
|
.Setup(t =>
|
||||||
|
t.Bind(
|
||||||
|
t,
|
||||||
|
appState => appState.SelectedTab.Value.CurrentLocation.Value.FullName.Path,
|
||||||
|
tb => tb.Text,
|
||||||
|
value => value
|
||||||
|
)
|
||||||
|
),
|
||||||
|
new Grid<IAppState>
|
||||||
|
{
|
||||||
|
ColumnDefinitionsObject = "* 4* 4*",
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
ParentsItemsView(),
|
||||||
|
SelectedItemsView(),
|
||||||
|
SelectedsItemsView(),
|
||||||
|
},
|
||||||
|
Extensions =
|
||||||
|
{
|
||||||
|
new GridPositionExtension(0, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_root = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListView<IAppState, IItemViewModel> SelectedItemsView()
|
||||||
|
{
|
||||||
|
var list = new ListView<IAppState, IItemViewModel>
|
||||||
|
{
|
||||||
|
DataContext = _consoleAppState,
|
||||||
|
ApplicationContext = _applicationContext,
|
||||||
|
ListPadding = 8,
|
||||||
|
Extensions =
|
||||||
|
{
|
||||||
|
new GridPositionExtension(1, 0)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_selectedItemsView.ItemTemplate = item =>
|
list.ItemTemplate = item =>
|
||||||
{
|
{
|
||||||
var textBlock = item.CreateChild<TextBlock<IItemViewModel>>();
|
var textBlock = item.CreateChild<TextBlock<IItemViewModel>>();
|
||||||
textBlock.Bind(
|
textBlock.Bind(
|
||||||
@@ -50,19 +89,120 @@ public class MainWindow
|
|||||||
);
|
);
|
||||||
textBlock.Bind(
|
textBlock.Bind(
|
||||||
textBlock,
|
textBlock,
|
||||||
dc => dc == null ? _theme.DefaultForegroundColor : ToForegroundColor(dc.ViewMode.Value),
|
dc => dc == null ? _theme.DefaultForegroundColor : ToForegroundColor(dc.ViewMode.Value, dc.BaseItem.Type),
|
||||||
tb => tb.Foreground
|
tb => tb.Foreground
|
||||||
);
|
);
|
||||||
|
textBlock.Bind(
|
||||||
|
textBlock,
|
||||||
|
dc => dc == null ? _theme.DefaultBackgroundColor : ToBackgroundColor(dc.ViewMode.Value, dc.BaseItem.Type),
|
||||||
|
tb => tb.Background
|
||||||
|
);
|
||||||
|
|
||||||
return textBlock;
|
return textBlock;
|
||||||
};
|
};
|
||||||
|
|
||||||
_selectedItemsView.Bind(
|
list.Bind(
|
||||||
_selectedItemsView,
|
list,
|
||||||
appState => appState == null ? null : appState.SelectedTab.Map(t => t == null ? null : t.CurrentItems).Switch(),
|
appState => appState == null ? null : appState.SelectedTab.Map(t => t == null ? null : t.CurrentItems).Switch(),
|
||||||
v => v.ItemsSource);
|
v => v.ItemsSource);
|
||||||
|
|
||||||
TestGrid();
|
list.Bind(
|
||||||
|
list,
|
||||||
|
appState =>
|
||||||
|
appState == null
|
||||||
|
? null
|
||||||
|
: appState.SelectedTab.Value == null
|
||||||
|
? null
|
||||||
|
: appState.SelectedTab.Value.CurrentSelectedItem.Value,
|
||||||
|
v => v.SelectedItem);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListView<IAppState, IItemViewModel> SelectedsItemsView()
|
||||||
|
{
|
||||||
|
var list = new ListView<IAppState, IItemViewModel>
|
||||||
|
{
|
||||||
|
DataContext = _consoleAppState,
|
||||||
|
ApplicationContext = _applicationContext,
|
||||||
|
ListPadding = 8,
|
||||||
|
Extensions =
|
||||||
|
{
|
||||||
|
new GridPositionExtension(2, 0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
list.ItemTemplate = item =>
|
||||||
|
{
|
||||||
|
var textBlock = item.CreateChild<TextBlock<IItemViewModel>>();
|
||||||
|
textBlock.Bind(
|
||||||
|
textBlock,
|
||||||
|
dc => dc == null ? string.Empty : dc.DisplayNameText,
|
||||||
|
tb => tb.Text
|
||||||
|
);
|
||||||
|
textBlock.Bind(
|
||||||
|
textBlock,
|
||||||
|
dc => dc == null ? _theme.DefaultForegroundColor : ToForegroundColor(dc.ViewMode.Value, dc.BaseItem.Type),
|
||||||
|
tb => tb.Foreground
|
||||||
|
);
|
||||||
|
textBlock.Bind(
|
||||||
|
textBlock,
|
||||||
|
dc => dc == null ? _theme.DefaultBackgroundColor : ToBackgroundColor(dc.ViewMode.Value, dc.BaseItem.Type),
|
||||||
|
tb => tb.Background
|
||||||
|
);
|
||||||
|
|
||||||
|
return textBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
list.Bind(
|
||||||
|
list,
|
||||||
|
appState => appState == null ? null : appState.SelectedTab.Map(t => t == null ? null : t.SelectedsChildren).Switch(),
|
||||||
|
v => v.ItemsSource);
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListView<IAppState, IItemViewModel> ParentsItemsView()
|
||||||
|
{
|
||||||
|
var list = new ListView<IAppState, IItemViewModel>
|
||||||
|
{
|
||||||
|
DataContext = _consoleAppState,
|
||||||
|
ApplicationContext = _applicationContext,
|
||||||
|
ListPadding = 8,
|
||||||
|
Extensions =
|
||||||
|
{
|
||||||
|
new GridPositionExtension(0, 0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
list.ItemTemplate = item =>
|
||||||
|
{
|
||||||
|
var textBlock = item.CreateChild<TextBlock<IItemViewModel>>();
|
||||||
|
textBlock.Bind(
|
||||||
|
textBlock,
|
||||||
|
dc => dc == null ? string.Empty : dc.DisplayNameText,
|
||||||
|
tb => tb.Text
|
||||||
|
);
|
||||||
|
textBlock.Bind(
|
||||||
|
textBlock,
|
||||||
|
dc => dc == null ? _theme.DefaultForegroundColor : ToForegroundColor(dc.ViewMode.Value, dc.BaseItem.Type),
|
||||||
|
tb => tb.Foreground
|
||||||
|
);
|
||||||
|
textBlock.Bind(
|
||||||
|
textBlock,
|
||||||
|
dc => dc == null ? _theme.DefaultBackgroundColor : ToBackgroundColor(dc.ViewMode.Value, dc.BaseItem.Type),
|
||||||
|
tb => tb.Background
|
||||||
|
);
|
||||||
|
|
||||||
|
return textBlock;
|
||||||
|
};
|
||||||
|
|
||||||
|
list.Bind(
|
||||||
|
list,
|
||||||
|
appState => appState == null ? null : appState.SelectedTab.Map(t => t == null ? null : t.ParentsChildren).Switch(),
|
||||||
|
v => v.ItemsSource);
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TestGrid()
|
private void TestGrid()
|
||||||
@@ -117,35 +257,37 @@ public class MainWindow
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_grid = grid;
|
//_grid = grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IView> RootViews() => new IView[]
|
public IEnumerable<IView> RootViews() => new IView[]
|
||||||
{
|
{
|
||||||
_grid, _selectedItemsView
|
_root
|
||||||
};
|
};
|
||||||
|
|
||||||
private IColor? ToForegroundColor(ItemViewMode viewMode)
|
private IColor? ToForegroundColor(ItemViewMode viewMode, AbsolutePathType absolutePathType) =>
|
||||||
=> viewMode switch
|
(viewMode, absolutePathType) switch
|
||||||
{
|
{
|
||||||
ItemViewMode.Default => _theme.DefaultForegroundColor,
|
(ItemViewMode.Default, AbsolutePathType.Container) => _theme.ContainerColor,
|
||||||
ItemViewMode.Alternative => _theme.AlternativeItemForegroundColor,
|
(ItemViewMode.Alternative, AbsolutePathType.Container) => _theme.ContainerColor,
|
||||||
ItemViewMode.Selected => _theme.SelectedItemForegroundColor,
|
(ItemViewMode.Default, _) => _theme.ElementColor,
|
||||||
ItemViewMode.Marked => _theme.MarkedItemForegroundColor,
|
(ItemViewMode.Alternative, _) => _theme.ElementColor,
|
||||||
ItemViewMode.MarkedSelected => _theme.MarkedSelectedItemForegroundColor,
|
(ItemViewMode.Selected, _) => ToBackgroundColor(ItemViewMode.Default, absolutePathType)?.AsForeground(),
|
||||||
ItemViewMode.MarkedAlternative => _theme.MarkedAlternativeItemForegroundColor,
|
(ItemViewMode.Marked, _) => _theme.MarkedItemColor,
|
||||||
|
(ItemViewMode.MarkedSelected, _) => ToBackgroundColor(ItemViewMode.Marked, absolutePathType)?.AsForeground(),
|
||||||
|
(ItemViewMode.MarkedAlternative, _) => _theme.MarkedItemColor,
|
||||||
_ => throw new NotImplementedException()
|
_ => throw new NotImplementedException()
|
||||||
};
|
};
|
||||||
|
|
||||||
private IColor? ToBackgroundColor(ItemViewMode viewMode)
|
private IColor? ToBackgroundColor(ItemViewMode viewMode, AbsolutePathType absolutePathType)
|
||||||
=> viewMode switch
|
=> (viewMode, absolutePathType) switch
|
||||||
{
|
{
|
||||||
ItemViewMode.Default => _theme.DefaultBackgroundColor,
|
(ItemViewMode.Default, _) => _theme.DefaultBackgroundColor,
|
||||||
ItemViewMode.Alternative => _theme.AlternativeItemBackgroundColor,
|
(ItemViewMode.Alternative, _) => _theme.DefaultBackgroundColor,
|
||||||
ItemViewMode.Selected => _theme.SelectedItemBackgroundColor,
|
(ItemViewMode.Selected, _) => ToForegroundColor(ItemViewMode.Default, absolutePathType)?.AsBackground(),
|
||||||
ItemViewMode.Marked => _theme.MarkedItemBackgroundColor,
|
(ItemViewMode.Marked, _) => _theme.MarkedItemColor,
|
||||||
ItemViewMode.MarkedSelected => _theme.MarkedSelectedItemBackgroundColor,
|
(ItemViewMode.MarkedSelected, _) => ToForegroundColor(ItemViewMode.Marked, absolutePathType)?.AsBackground(),
|
||||||
ItemViewMode.MarkedAlternative => _theme.MarkedAlternativeItemBackgroundColor,
|
(ItemViewMode.MarkedAlternative, _) => _theme.MarkedItemColor,
|
||||||
_ => throw new NotImplementedException()
|
_ => throw new NotImplementedException()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ using FileTime.Core.Interactions;
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using TerminalUI;
|
using TerminalUI;
|
||||||
using TerminalUI.ConsoleDrivers;
|
using TerminalUI.ConsoleDrivers;
|
||||||
|
|
||||||
@@ -33,7 +34,8 @@ public static class Startup
|
|||||||
services.TryAddSingleton<IApplicationContext>(sp
|
services.TryAddSingleton<IApplicationContext>(sp
|
||||||
=> new ApplicationContext
|
=> new ApplicationContext
|
||||||
{
|
{
|
||||||
ConsoleDriver = sp.GetRequiredService<IConsoleDriver>()
|
ConsoleDriver = sp.GetRequiredService<IConsoleDriver>(),
|
||||||
|
LoggerFactory = sp.GetRequiredService<ILoggerFactory>()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return services;
|
return services;
|
||||||
|
|||||||
@@ -1,54 +1,29 @@
|
|||||||
using FileTime.ConsoleUI.App;
|
using FileTime.ConsoleUI.App;
|
||||||
using TerminalUI.Color;
|
using TerminalUI.Color;
|
||||||
using TerminalUI.Models;
|
|
||||||
using ConsoleColor = TerminalUI.Color.ConsoleColor;
|
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI.Styles;
|
namespace FileTime.ConsoleUI.Styles;
|
||||||
|
|
||||||
public record Theme(
|
public record Theme(
|
||||||
IColor? ItemBackgroundColor,
|
|
||||||
IColor? AlternativeItemBackgroundColor,
|
|
||||||
IColor? SelectedItemBackgroundColor,
|
|
||||||
IColor? MarkedItemBackgroundColor,
|
|
||||||
IColor? MarkedAlternativeItemBackgroundColor,
|
|
||||||
IColor? MarkedSelectedItemBackgroundColor,
|
|
||||||
IColor? DefaultForegroundColor,
|
IColor? DefaultForegroundColor,
|
||||||
IColor? DefaultBackgroundColor,
|
IColor? DefaultBackgroundColor,
|
||||||
IColor? AlternativeItemForegroundColor,
|
IColor? ElementColor,
|
||||||
IColor? SelectedItemForegroundColor,
|
IColor? ContainerColor,
|
||||||
IColor? MarkedItemForegroundColor,
|
IColor? MarkedItemColor) : ITheme;
|
||||||
IColor? MarkedAlternativeItemForegroundColor,
|
|
||||||
IColor? MarkedSelectedItemForegroundColor) : ITheme;
|
|
||||||
|
|
||||||
public static class DefaultThemes
|
public static class DefaultThemes
|
||||||
{
|
{
|
||||||
public static Theme Color256Theme => new(
|
public static Theme Color256Theme => new(
|
||||||
ItemBackgroundColor: Color256Colors.Backgrounds.Black,
|
DefaultForegroundColor: Color256Colors.Foregrounds.Gray,
|
||||||
AlternativeItemBackgroundColor: Color256Colors.Backgrounds.Black,
|
DefaultBackgroundColor: Color256Colors.Foregrounds.Black,
|
||||||
SelectedItemBackgroundColor: Color256Colors.Backgrounds.Black,
|
ElementColor: Color256Colors.Foregrounds.Gray,
|
||||||
MarkedItemBackgroundColor: Color256Colors.Backgrounds.Black,
|
ContainerColor: Color256Colors.Foregrounds.Blue,
|
||||||
MarkedAlternativeItemBackgroundColor: Color256Colors.Backgrounds.Black,
|
MarkedItemColor: Color256Colors.Foregrounds.Black
|
||||||
MarkedSelectedItemBackgroundColor: Color256Colors.Backgrounds.Black,
|
);
|
||||||
DefaultForegroundColor: null,
|
|
||||||
DefaultBackgroundColor: null,
|
|
||||||
AlternativeItemForegroundColor: null,
|
|
||||||
SelectedItemForegroundColor: Color256Colors.Foregrounds.Black,
|
|
||||||
MarkedItemForegroundColor: Color256Colors.Foregrounds.White,
|
|
||||||
MarkedAlternativeItemForegroundColor: Color256Colors.Foregrounds.White,
|
|
||||||
MarkedSelectedItemForegroundColor: Color256Colors.Foregrounds.Cyan);
|
|
||||||
|
|
||||||
public static Theme ConsoleColorTheme => new(
|
public static Theme ConsoleColorTheme => new(
|
||||||
ItemBackgroundColor: ConsoleColors.Foregrounds.Black,
|
DefaultForegroundColor: ConsoleColors.Foregrounds.Gray,
|
||||||
AlternativeItemBackgroundColor: ConsoleColors.Foregrounds.Black,
|
DefaultBackgroundColor: ConsoleColors.Foregrounds.Black,
|
||||||
SelectedItemBackgroundColor: ConsoleColors.Foregrounds.Black,
|
ElementColor: ConsoleColors.Foregrounds.Gray,
|
||||||
MarkedItemBackgroundColor: ConsoleColors.Foregrounds.Black,
|
ContainerColor: ConsoleColors.Foregrounds.Blue,
|
||||||
MarkedAlternativeItemBackgroundColor: ConsoleColors.Foregrounds.Black,
|
MarkedItemColor: ConsoleColors.Foregrounds.Black);
|
||||||
MarkedSelectedItemBackgroundColor: ConsoleColors.Foregrounds.Black,
|
|
||||||
DefaultForegroundColor: null,
|
|
||||||
DefaultBackgroundColor: null,
|
|
||||||
AlternativeItemForegroundColor: null,
|
|
||||||
SelectedItemForegroundColor: ConsoleColors.Foregrounds.Black,
|
|
||||||
MarkedItemForegroundColor: ConsoleColors.Foregrounds.White,
|
|
||||||
MarkedAlternativeItemForegroundColor: ConsoleColors.Foregrounds.White,
|
|
||||||
MarkedSelectedItemForegroundColor: ConsoleColors.Foregrounds.Cyan);
|
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,6 @@ public class Tab : ITab
|
|||||||
private AbsolutePath? _currentSelectedItemCached;
|
private AbsolutePath? _currentSelectedItemCached;
|
||||||
private PointInTime _currentPointInTime;
|
private PointInTime _currentPointInTime;
|
||||||
private CancellationTokenSource? _setCurrentLocationCancellationTokenSource;
|
private CancellationTokenSource? _setCurrentLocationCancellationTokenSource;
|
||||||
private CancellationTokenSource? _setCurrentItemCancellationTokenSource;
|
|
||||||
|
|
||||||
public IDeclarativeProperty<IContainer?> CurrentLocation { get; }
|
public IDeclarativeProperty<IContainer?> CurrentLocation { get; }
|
||||||
public IDeclarativeProperty<ObservableCollection<IItem>?> CurrentItems { get; }
|
public IDeclarativeProperty<ObservableCollection<IItem>?> CurrentItems { get; }
|
||||||
@@ -214,9 +213,8 @@ public class Tab : ITab
|
|||||||
|
|
||||||
public async Task SetSelectedItem(AbsolutePath newSelectedItem)
|
public async Task SetSelectedItem(AbsolutePath newSelectedItem)
|
||||||
{
|
{
|
||||||
_setCurrentItemCancellationTokenSource?.Cancel();
|
if (_currentRequestItem.Value is {} v && v.Path == newSelectedItem.Path) return;
|
||||||
_setCurrentItemCancellationTokenSource = new CancellationTokenSource();
|
await _currentRequestItem.SetValue(newSelectedItem);
|
||||||
await _currentRequestItem.SetValue(newSelectedItem, _setCurrentItemCancellationTokenSource.Token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddItemFilter(ItemFilter filter) => _itemFilters.Add(filter);
|
public void AddItemFilter(ItemFilter filter) => _itemFilters.Add(filter);
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using TerminalUI.ConsoleDrivers;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using TerminalUI.ConsoleDrivers;
|
||||||
|
|
||||||
namespace TerminalUI;
|
namespace TerminalUI;
|
||||||
|
|
||||||
public class ApplicationContext : IApplicationContext
|
public class ApplicationContext : IApplicationContext
|
||||||
{
|
{
|
||||||
public required IConsoleDriver ConsoleDriver { get; init; }
|
public required IConsoleDriver ConsoleDriver { get; init; }
|
||||||
|
public ILoggerFactory? LoggerFactory { get; init; }
|
||||||
public IEventLoop EventLoop { get; init; }
|
public IEventLoop EventLoop { get; init; }
|
||||||
public bool IsRunning { get; set; }
|
public bool IsRunning { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -6,30 +6,38 @@ using TerminalUI.Traits;
|
|||||||
|
|
||||||
namespace TerminalUI;
|
namespace TerminalUI;
|
||||||
|
|
||||||
public class Binding<TDataContext, TResult> : IDisposable
|
public class Binding<TDataContext, TExpressionResult, TResult> : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Func<TDataContext, TResult> _dataContextMapper;
|
private readonly Func<TDataContext, TExpressionResult> _dataContextMapper;
|
||||||
private IView<TDataContext> _dataSourceView;
|
private IView<TDataContext> _dataSourceView;
|
||||||
private object? _propertySource;
|
private object? _propertySource;
|
||||||
private PropertyInfo _targetProperty;
|
private PropertyInfo _targetProperty;
|
||||||
|
private readonly Func<TExpressionResult, TResult> _converter;
|
||||||
|
private readonly TResult? _fallbackValue;
|
||||||
private IDisposableCollection? _propertySourceDisposableCollection;
|
private IDisposableCollection? _propertySourceDisposableCollection;
|
||||||
private PropertyTrackTreeItem? _propertyTrackTreeItem;
|
private PropertyTrackTreeItem? _propertyTrackTreeItem;
|
||||||
private IPropertyChangeTracker? _propertyChangeTracker;
|
private IPropertyChangeTracker? _propertyChangeTracker;
|
||||||
|
|
||||||
public Binding(
|
public Binding(
|
||||||
IView<TDataContext> dataSourceView,
|
IView<TDataContext> dataSourceView,
|
||||||
Expression<Func<TDataContext?, TResult>> dataContextExpression,
|
Expression<Func<TDataContext?, TExpressionResult>> dataContextExpression,
|
||||||
object? propertySource,
|
object? propertySource,
|
||||||
PropertyInfo targetProperty
|
PropertyInfo targetProperty,
|
||||||
|
Func<TExpressionResult, TResult> converter,
|
||||||
|
TResult? fallbackValue = default
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(dataSourceView);
|
ArgumentNullException.ThrowIfNull(dataSourceView);
|
||||||
ArgumentNullException.ThrowIfNull(dataContextExpression);
|
ArgumentNullException.ThrowIfNull(dataContextExpression);
|
||||||
ArgumentNullException.ThrowIfNull(targetProperty);
|
ArgumentNullException.ThrowIfNull(targetProperty);
|
||||||
|
ArgumentNullException.ThrowIfNull(converter);
|
||||||
|
|
||||||
_dataSourceView = dataSourceView;
|
_dataSourceView = dataSourceView;
|
||||||
_dataContextMapper = dataContextExpression.Compile();
|
_dataContextMapper = dataContextExpression.Compile();
|
||||||
_propertySource = propertySource;
|
_propertySource = propertySource;
|
||||||
_targetProperty = targetProperty;
|
_targetProperty = targetProperty;
|
||||||
|
_converter = converter;
|
||||||
|
_fallbackValue = fallbackValue;
|
||||||
|
|
||||||
InitTrackingTree(dataContextExpression);
|
InitTrackingTree(dataContextExpression);
|
||||||
|
|
||||||
@@ -52,7 +60,7 @@ public class Binding<TDataContext, TResult> : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitTrackingTree(Expression<Func<TDataContext?, TResult>> dataContextExpression)
|
private void InitTrackingTree(Expression<Func<TDataContext?, TExpressionResult>> dataContextExpression)
|
||||||
{
|
{
|
||||||
var properties = new List<string>();
|
var properties = new List<string>();
|
||||||
FindReactiveProperties(dataContextExpression, properties);
|
FindReactiveProperties(dataContextExpression, properties);
|
||||||
@@ -175,7 +183,19 @@ public class Binding<TDataContext, TResult> : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateTargetProperty()
|
private void UpdateTargetProperty()
|
||||||
=> _targetProperty.SetValue(_propertySource, _dataContextMapper(_dataSourceView.DataContext));
|
{
|
||||||
|
TResult value;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value = _converter(_dataContextMapper(_dataSourceView.DataContext));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
value = _fallbackValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_targetProperty.SetValue(_propertySource, value);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,4 +11,8 @@ public record struct Color256(byte Color, ColorType Type) : IColor
|
|||||||
ColorType.Background => $"\x1b[48;5;{Color}m",
|
ColorType.Background => $"\x1b[48;5;{Color}m",
|
||||||
_ => throw new InvalidEnumArgumentException(nameof(Type), (int) Type, typeof(ColorType))
|
_ => throw new InvalidEnumArgumentException(nameof(Type), (int) Type, typeof(ColorType))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public IColor AsForeground() => this with {Type = ColorType.Foreground};
|
||||||
|
|
||||||
|
public IColor AsBackground() => this with {Type = ColorType.Background};
|
||||||
}
|
}
|
||||||
@@ -11,4 +11,7 @@ public record struct ColorRgb(byte R, byte G, byte B, ColorType Type) : IColor
|
|||||||
ColorType.Background => $"\x1b[48;2;{R};{G};{B};m",
|
ColorType.Background => $"\x1b[48;2;{R};{G};{B};m",
|
||||||
_ => throw new InvalidEnumArgumentException(nameof(Type), (int) Type, typeof(ColorType))
|
_ => throw new InvalidEnumArgumentException(nameof(Type), (int) Type, typeof(ColorType))
|
||||||
};
|
};
|
||||||
|
public IColor AsForeground() => this with {Type = ColorType.Foreground};
|
||||||
|
|
||||||
|
public IColor AsBackground() => this with {Type = ColorType.Background};
|
||||||
}
|
}
|
||||||
@@ -3,4 +3,7 @@
|
|||||||
public record ConsoleColor(System.ConsoleColor Color, ColorType Type) : IColor
|
public record ConsoleColor(System.ConsoleColor Color, ColorType Type) : IColor
|
||||||
{
|
{
|
||||||
public string ToConsoleColor() => throw new NotImplementedException();
|
public string ToConsoleColor() => throw new NotImplementedException();
|
||||||
|
public IColor AsForeground() => this with {Type = ColorType.Foreground};
|
||||||
|
|
||||||
|
public IColor AsBackground() => this with {Type = ColorType.Background};
|
||||||
}
|
}
|
||||||
@@ -4,4 +4,6 @@ public interface IColor
|
|||||||
{
|
{
|
||||||
ColorType Type { get; }
|
ColorType Type { get; }
|
||||||
string ToConsoleColor();
|
string ToConsoleColor();
|
||||||
|
IColor AsForeground();
|
||||||
|
IColor AsBackground();
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,6 @@ public class DotnetDriver : IConsoleDriver
|
|||||||
Console.BackgroundColor = consoleColor.Color;
|
Console.BackgroundColor = consoleColor.Color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Size GetBufferSize() => new(Console.BufferWidth, Console.BufferHeight);
|
public Size GetWindowSize() => new(Console.WindowWidth, Console.WindowHeight);
|
||||||
public void Clear() => Console.Clear();
|
public void Clear() => Console.Clear();
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,6 @@ public interface IConsoleDriver
|
|||||||
void SetCursorVisible(bool cursorVisible);
|
void SetCursorVisible(bool cursorVisible);
|
||||||
void SetForegroundColor(IColor foreground);
|
void SetForegroundColor(IColor foreground);
|
||||||
void SetBackgroundColor(IColor background);
|
void SetBackgroundColor(IColor background);
|
||||||
Size GetBufferSize();
|
Size GetWindowSize();
|
||||||
void Clear();
|
void Clear();
|
||||||
}
|
}
|
||||||
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;
|
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 const int ToBeCalculated = -1;
|
||||||
private readonly ObservableCollection<IView> _children = new();
|
public ObservableCollection<RowDefinition> RowDefinitions { get; } = new() {RowDefinition.Star(1)};
|
||||||
public ReadOnlyObservableCollection<IView> Children { get; }
|
public ObservableCollection<ColumnDefinition> ColumnDefinitions { get; } = new() {ColumnDefinition.Star(1)};
|
||||||
public GridChildInitializer<T> ChildInitializer { get; }
|
|
||||||
public ObservableCollection<RowDefinition> RowDefinitions { get; } = new();
|
|
||||||
public ObservableCollection<ColumnDefinition> ColumnDefinitions { get; } = new();
|
|
||||||
|
|
||||||
public object? ColumnDefinitionsObject
|
public object? ColumnDefinitionsObject
|
||||||
{
|
{
|
||||||
@@ -62,97 +63,30 @@ public class Grid<T> : View<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Grid()
|
public override Size GetRequestedSize()
|
||||||
|
=> WithCalculatedSize((columnWidths, rowHeights) =>
|
||||||
{
|
{
|
||||||
ChildInitializer = new GridChildInitializer<T>(this);
|
var width = 0;
|
||||||
Children = new ReadOnlyObservableCollection<IView>(_children);
|
var height = 0;
|
||||||
_children.CollectionChanged += (o, e) =>
|
|
||||||
{
|
|
||||||
if (Attached)
|
|
||||||
{
|
|
||||||
if (e.NewItems?.OfType<IView>() is { } newItems)
|
|
||||||
{
|
|
||||||
foreach (var newItem in newItems)
|
|
||||||
{
|
|
||||||
newItem.Attached = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplicationContext?.EventLoop.RequestRerender();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Size GetRequestedSize() => throw new NotImplementedException();
|
|
||||||
|
|
||||||
protected override void DefaultRenderer(Position position, 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];
|
|
||||||
|
|
||||||
foreach (var child in Children)
|
|
||||||
{
|
|
||||||
var childSize = child.GetRequestedSize();
|
|
||||||
var positionExtension = child.GetExtension<GridPositionExtension>();
|
|
||||||
var x = positionExtension?.Column ?? 0;
|
|
||||||
var y = positionExtension?.Row ?? 0;
|
|
||||||
|
|
||||||
allWidth.SetToMatrix(childSize.Width, x, y, columns);
|
|
||||||
allHeight.SetToMatrix(childSize.Height, x, y, columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
Span<int> columnWidths = stackalloc int[columns];
|
|
||||||
Span<int> rowHeights = stackalloc int[RowDefinitions.Count];
|
|
||||||
|
|
||||||
for (var i = 0; i < columnWidths.Length; i++)
|
for (var i = 0; i < columnWidths.Length; i++)
|
||||||
{
|
{
|
||||||
if (ColumnDefinitions[i].Type == GridUnitType.Pixel)
|
width += columnWidths[i];
|
||||||
{
|
|
||||||
columnWidths[i] = ColumnDefinitions[i].Value;
|
|
||||||
}
|
|
||||||
else if (ColumnDefinitions[i].Type == GridUnitType.Star)
|
|
||||||
{
|
|
||||||
columnWidths[i] = ToBeCalculated;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var max = 0;
|
|
||||||
for (var j = 0; j < RowDefinitions.Count; j++)
|
|
||||||
{
|
|
||||||
max = Math.Max(max, allWidth.GetFromMatrix(i, j, columns));
|
|
||||||
}
|
|
||||||
|
|
||||||
columnWidths[i] = max;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < rowHeights.Length; i++)
|
for (var i = 0; i < rowHeights.Length; i++)
|
||||||
{
|
{
|
||||||
if (RowDefinitions[i].Type == GridUnitType.Pixel)
|
height += rowHeights[i];
|
||||||
{
|
|
||||||
rowHeights[i] = RowDefinitions[i].Value;
|
|
||||||
}
|
|
||||||
else if (RowDefinitions[i].Type == GridUnitType.Star)
|
|
||||||
{
|
|
||||||
rowHeights[i] = ToBeCalculated;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var max = 0;
|
|
||||||
for (var j = 0; j < columns; j++)
|
|
||||||
{
|
|
||||||
max = Math.Max(max, allHeight.GetFromMatrix(j, i, columns));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rowHeights[i] = max;
|
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)
|
foreach (var child in Children)
|
||||||
{
|
{
|
||||||
var childSize = child.GetRequestedSize();
|
|
||||||
var positionExtension = child.GetExtension<GridPositionExtension>();
|
var positionExtension = child.GetExtension<GridPositionExtension>();
|
||||||
var x = positionExtension?.Column ?? 0;
|
var x = positionExtension?.Column ?? 0;
|
||||||
var y = positionExtension?.Row ?? 0;
|
var y = positionExtension?.Row ?? 0;
|
||||||
@@ -173,8 +107,131 @@ public class Grid<T> : View<T>
|
|||||||
top += rowHeights[i];
|
top += rowHeights[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
child.Render(new Position(left, top), new Size(width, height));
|
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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var childSize = child.GetRequestedSize();
|
||||||
|
var positionExtension = child.GetExtension<GridPositionExtension>();
|
||||||
|
var x = positionExtension?.Column ?? 0;
|
||||||
|
var y = positionExtension?.Row ?? 0;
|
||||||
|
|
||||||
|
allWidth.SetToMatrix(childSize.Width, x, y, columns);
|
||||||
|
allHeight.SetToMatrix(childSize.Height, x, y, columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<int> columnWidths = stackalloc int[columns];
|
||||||
|
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 (size.IsSome && ColumnDefinitions[i].Type == GridUnitType.Star)
|
||||||
|
{
|
||||||
|
widthStars += ColumnDefinitions[i].Value;
|
||||||
|
columnWidths[i] = ToBeCalculated;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var max = 0;
|
||||||
|
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 (size.IsSome && RowDefinitions[i].Type == GridUnitType.Star)
|
||||||
|
{
|
||||||
|
heightStars += RowDefinitions[i].Value;
|
||||||
|
rowHeights[i] = ToBeCalculated;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var max = 0;
|
||||||
|
for (var j = 0; j < columns; j++)
|
||||||
|
{
|
||||||
|
max = Math.Max(max, allHeight.GetFromMatrix(j, i, columns));
|
||||||
|
}
|
||||||
|
|
||||||
|
rowHeights[i] = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowHeights[i] != ToBeCalculated)
|
||||||
|
usedHeight += rowHeights[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size.IsSome)
|
||||||
|
{
|
||||||
|
var widthLeft = size.Value.Width - usedWidth;
|
||||||
|
var heightLeft = size.Value.Height - usedHeight;
|
||||||
|
|
||||||
|
var widthPerStart = (int) Math.Floor((double) widthLeft / widthStars);
|
||||||
|
var heightPerStart = (int) Math.Floor((double) heightLeft / heightStars);
|
||||||
|
|
||||||
|
for (var i = 0; i < columnWidths.Length; i++)
|
||||||
|
{
|
||||||
|
var column = ColumnDefinitions[i];
|
||||||
|
if (column.Type == GridUnitType.Star)
|
||||||
|
{
|
||||||
|
columnWidths[i] = widthPerStart * column.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < rowHeights.Length; i++)
|
||||||
|
{
|
||||||
|
var row = RowDefinitions[i];
|
||||||
|
if (row.Type == GridUnitType.Star)
|
||||||
|
{
|
||||||
|
rowHeights[i] = heightPerStart * row.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return actionWithSizes(columnWidths, rowHeights);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRowDefinitions(string value)
|
public void SetRowDefinitions(string value)
|
||||||
@@ -190,7 +247,7 @@ public class Grid<T> : View<T>
|
|||||||
}
|
}
|
||||||
else if (v.EndsWith("*"))
|
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));
|
RowDefinitions.Add(RowDefinition.Star(starValue));
|
||||||
}
|
}
|
||||||
else if (int.TryParse(v, out var pixelValue))
|
else if (int.TryParse(v, out var pixelValue))
|
||||||
@@ -217,7 +274,7 @@ public class Grid<T> : View<T>
|
|||||||
}
|
}
|
||||||
else if (v.EndsWith("*"))
|
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));
|
ColumnDefinitions.Add(ColumnDefinition.Star(starValue));
|
||||||
}
|
}
|
||||||
else if (int.TryParse(v, out var pixelValue))
|
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)
|
TChild CreateChild<TChild, TDataContext>(Func<T?, TDataContext?> dataContextMapper)
|
||||||
where TChild : IView<TDataContext>, new();
|
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>;
|
where TChild : IView<TDataContext>;
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using DeclarativeProperty;
|
using DeclarativeProperty;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
using TerminalUI.Models;
|
using TerminalUI.Models;
|
||||||
|
|
||||||
namespace TerminalUI.Controls;
|
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;
|
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 _selectedIndex = 0;
|
||||||
private int _renderStartIndex = 0;
|
private int _renderStartIndex = 0;
|
||||||
private Size _requestedItemSize = new(0, 0);
|
private Size _requestedItemSize = new(0, 0);
|
||||||
|
[Notify] private int _listPadding = 0;
|
||||||
|
[Notify] private Orientation _orientation = Orientation.Vertical;
|
||||||
|
|
||||||
public int SelectedIndex
|
public int SelectedIndex
|
||||||
{
|
{
|
||||||
@@ -28,7 +30,34 @@ public class ListView<TDataContext, TItem> : View<TDataContext>
|
|||||||
{
|
{
|
||||||
_selectedIndex = value;
|
_selectedIndex = value;
|
||||||
OnPropertyChanged();
|
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;
|
_listViewItems = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_renderStartIndex = 0;
|
||||||
|
SelectedIndex = 0;
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<ListViewItem<TItem>, IView?> ItemTemplate { get; set; } = DefaultItemTemplate;
|
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()
|
public override Size GetRequestedSize()
|
||||||
{
|
{
|
||||||
if (_listViewItems is null || _listViewItems.Length == 0)
|
InstantiateItemViews();
|
||||||
|
if (_listViewItems is null || _listViewItemLength == 0)
|
||||||
return new Size(0, 0);
|
return new Size(0, 0);
|
||||||
|
|
||||||
|
|
||||||
var itemSize = _listViewItems[0].GetRequestedSize();
|
var itemSize = _listViewItems[0].GetRequestedSize();
|
||||||
_requestedItemSize = itemSize;
|
_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)
|
protected override void DefaultRenderer(Position position, Size size)
|
||||||
{
|
{
|
||||||
|
var requestedItemSize = _requestedItemSize;
|
||||||
|
if (requestedItemSize.Height == 0 || requestedItemSize.Width == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
var listViewItems = InstantiateItemViews();
|
var listViewItems = InstantiateItemViews();
|
||||||
if (listViewItems.Length == 0) return;
|
if (listViewItems.Length == 0) return;
|
||||||
|
|
||||||
var requestedItemSize = _requestedItemSize;
|
|
||||||
|
|
||||||
var itemsToRender = listViewItems.Length;
|
var itemsToRender = listViewItems.Length;
|
||||||
var heightNeeded = requestedItemSize.Height * listViewItems.Length;
|
var heightNeeded = requestedItemSize.Height * listViewItems.Length;
|
||||||
var renderStartIndex = _renderStartIndex;
|
var renderStartIndex = _renderStartIndex;
|
||||||
if (heightNeeded < size.Height)
|
if (heightNeeded > size.Height)
|
||||||
{
|
{
|
||||||
var maxItemsToRender = (int) Math.Floor((double) size.Height / requestedItemSize.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 + itemsToRender > listViewItems.Length)
|
||||||
|
renderStartIndex = listViewItems.Length - itemsToRender;
|
||||||
|
|
||||||
if (renderStartIndex < 0)
|
if (renderStartIndex < 0)
|
||||||
renderStartIndex = 0;
|
renderStartIndex = 0;
|
||||||
else if (renderStartIndex + maxItemsToRender > listViewItems.Length)
|
|
||||||
renderStartIndex = listViewItems.Length - maxItemsToRender;
|
|
||||||
|
|
||||||
_renderStartIndex = renderStartIndex;
|
_renderStartIndex = renderStartIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
var deltaY = 0;
|
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];
|
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;
|
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()
|
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 string? _text = string.Empty;
|
||||||
[Notify] private IColor? _foreground;
|
[Notify] private IColor? _foreground;
|
||||||
[Notify] private IColor? _background;
|
[Notify] private IColor? _background;
|
||||||
|
[Notify] private TextAlignment _textAlignment = TextAlignment.Left;
|
||||||
|
|
||||||
public TextBlock()
|
public TextBlock()
|
||||||
{
|
{
|
||||||
@@ -26,12 +27,15 @@ public partial class TextBlock<T> : View<T>
|
|||||||
RerenderProperties.Add(nameof(Text));
|
RerenderProperties.Add(nameof(Text));
|
||||||
RerenderProperties.Add(nameof(Foreground));
|
RerenderProperties.Add(nameof(Foreground));
|
||||||
RerenderProperties.Add(nameof(Background));
|
RerenderProperties.Add(nameof(Background));
|
||||||
|
RerenderProperties.Add(nameof(TextAlignment));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Size GetRequestedSize() => new(Text?.Length ?? 0, 1);
|
public override Size GetRequestedSize() => new(Text?.Length ?? 0, 1);
|
||||||
|
|
||||||
protected override void DefaultRenderer(Position position, Size size)
|
protected override void DefaultRenderer(Position position, Size size)
|
||||||
{
|
{
|
||||||
|
if (size.Width == 0 || size.Height == 0) return;
|
||||||
|
|
||||||
var driver = ApplicationContext!.ConsoleDriver;
|
var driver = ApplicationContext!.ConsoleDriver;
|
||||||
var renderContext = new RenderContext(position, Text, _foreground, _background);
|
var renderContext = new RenderContext(position, Text, _foreground, _background);
|
||||||
if (!NeedsRerender(renderContext)) return;
|
if (!NeedsRerender(renderContext)) return;
|
||||||
@@ -52,7 +56,17 @@ public partial class TextBlock<T> : View<T>
|
|||||||
driver.SetBackgroundColor(background);
|
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)
|
private bool NeedsRerender(RenderContext renderContext)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public abstract partial class View<T> : IView<T>
|
|||||||
[Notify] private int? _minHeight;
|
[Notify] private int? _minHeight;
|
||||||
[Notify] private int? _maxHeight;
|
[Notify] private int? _maxHeight;
|
||||||
[Notify] private int? _height;
|
[Notify] private int? _height;
|
||||||
|
[Notify] private IApplicationContext? _applicationContext;
|
||||||
private bool _attached;
|
private bool _attached;
|
||||||
|
|
||||||
public bool Attached
|
public bool Attached
|
||||||
@@ -33,7 +34,6 @@ public abstract partial class View<T> : IView<T>
|
|||||||
}
|
}
|
||||||
public List<object> Extensions { get; } = new();
|
public List<object> Extensions { get; } = new();
|
||||||
public Action<Position, Size> RenderMethod { get; set; }
|
public Action<Position, Size> RenderMethod { get; set; }
|
||||||
public IApplicationContext? ApplicationContext { get; set; }
|
|
||||||
public event Action<IView>? Disposed;
|
public event Action<IView>? Disposed;
|
||||||
protected List<string> RerenderProperties { get; } = new();
|
protected List<string> RerenderProperties { get; } = new();
|
||||||
|
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ public class EventLoop : IEventLoop
|
|||||||
{
|
{
|
||||||
if (!_rerenderRequested) return;
|
if (!_rerenderRequested) return;
|
||||||
_rerenderRequested = false;
|
_rerenderRequested = false;
|
||||||
|
|
||||||
viewsToRender = _viewsToRender.ToList();
|
viewsToRender = _viewsToRender.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
var size =_applicationContext.ConsoleDriver.GetBufferSize();
|
var size = _applicationContext.ConsoleDriver.GetWindowSize();
|
||||||
foreach (var view in viewsToRender)
|
foreach (var view in viewsToRender)
|
||||||
{
|
{
|
||||||
view.Attached = true;
|
view.Attached = true;
|
||||||
|
view.GetRequestedSize();
|
||||||
view.Render(new Position(0, 0), size);
|
view.Render(new Position(0, 0), size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,20 +6,44 @@ namespace TerminalUI.Extensions;
|
|||||||
|
|
||||||
public static class Binding
|
public static class Binding
|
||||||
{
|
{
|
||||||
public static Binding<TDataContext, TResult> Bind<TView, TDataContext, TResult>(
|
public static Binding<TDataContext, TResult, TResult> Bind<TView, TDataContext, TResult>(
|
||||||
this TView targetView,
|
this TView targetView,
|
||||||
IView<TDataContext> dataSourceView,
|
IView<TDataContext> dataSourceView,
|
||||||
Expression<Func<TDataContext?, TResult>> dataContextExpression,
|
Expression<Func<TDataContext?, TResult>> dataContextExpression,
|
||||||
Expression<Func<TView, TResult>> propertyExpression)
|
Expression<Func<TView, TResult>> propertyExpression,
|
||||||
|
TResult? fallbackValue = default)
|
||||||
{
|
{
|
||||||
if (propertyExpression.Body is not MemberExpression {Member: PropertyInfo propertyInfo})
|
if (propertyExpression.Body is not MemberExpression {Member: PropertyInfo propertyInfo})
|
||||||
throw new AggregateException(nameof(propertyExpression) + " must be a property expression");
|
throw new AggregateException(nameof(propertyExpression) + " must be a property expression");
|
||||||
|
|
||||||
return new Binding<TDataContext, TResult>(
|
return new Binding<TDataContext, TResult, TResult>(
|
||||||
dataSourceView,
|
dataSourceView,
|
||||||
dataContextExpression,
|
dataContextExpression,
|
||||||
targetView,
|
targetView,
|
||||||
propertyInfo
|
propertyInfo,
|
||||||
|
value => value,
|
||||||
|
fallbackValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Binding<TDataContext, TExpressionResult, TResult> Bind<TView, TDataContext, TExpressionResult, TResult>(
|
||||||
|
this TView targetView,
|
||||||
|
IView<TDataContext> dataSourceView,
|
||||||
|
Expression<Func<TDataContext?, TExpressionResult>> dataContextExpression,
|
||||||
|
Expression<Func<TView, TResult>> propertyExpression,
|
||||||
|
Func<TExpressionResult, TResult> converter,
|
||||||
|
TResult? fallbackValue = default)
|
||||||
|
{
|
||||||
|
if (propertyExpression.Body is not MemberExpression {Member: PropertyInfo propertyInfo})
|
||||||
|
throw new AggregateException(nameof(propertyExpression) + " must be a property expression");
|
||||||
|
|
||||||
|
return new Binding<TDataContext, TExpressionResult, TResult>(
|
||||||
|
dataSourceView,
|
||||||
|
dataContextExpression,
|
||||||
|
targetView,
|
||||||
|
propertyInfo,
|
||||||
|
converter,
|
||||||
|
fallbackValue
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,4 +11,10 @@ public static class ViewExtensions
|
|||||||
this IView<TTargetDataContext> view,
|
this IView<TTargetDataContext> view,
|
||||||
Func<TSourceDataContext?, TTargetDataContext?> dataContextMapper)
|
Func<TSourceDataContext?, TTargetDataContext?> dataContextMapper)
|
||||||
=> new(view, dataContextMapper);
|
=> new(view, dataContextMapper);
|
||||||
|
|
||||||
|
public static TView Setup<TView>(this TView view, Action<TView> action)
|
||||||
|
{
|
||||||
|
action(view);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using TerminalUI.ConsoleDrivers;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using TerminalUI.ConsoleDrivers;
|
||||||
|
|
||||||
namespace TerminalUI;
|
namespace TerminalUI;
|
||||||
|
|
||||||
@@ -7,4 +8,5 @@ public interface IApplicationContext
|
|||||||
IEventLoop EventLoop { get; init; }
|
IEventLoop EventLoop { get; init; }
|
||||||
bool IsRunning { get; set; }
|
bool IsRunning { get; set; }
|
||||||
IConsoleDriver ConsoleDriver { get; init; }
|
IConsoleDriver ConsoleDriver { get; init; }
|
||||||
|
ILoggerFactory? LoggerFactory { get; init; }
|
||||||
}
|
}
|
||||||
13
src/Library/TerminalUI/Models/Option.cs
Normal file
13
src/Library/TerminalUI/Models/Option.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace TerminalUI.Models;
|
||||||
|
|
||||||
|
public readonly ref struct Option<T>
|
||||||
|
{
|
||||||
|
public readonly T Value;
|
||||||
|
public readonly bool IsSome;
|
||||||
|
|
||||||
|
public Option(T value, bool isSome)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
IsSome = isSome;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/Library/TerminalUI/Models/Orientation.cs
Normal file
7
src/Library/TerminalUI/Models/Orientation.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace TerminalUI.Models;
|
||||||
|
|
||||||
|
public enum Orientation
|
||||||
|
{
|
||||||
|
Horizontal,
|
||||||
|
Vertical
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace TerminalUI.Models;
|
namespace TerminalUI.Models;
|
||||||
|
|
||||||
public record Size(int Width, int Height);
|
public record struct Size(int Width, int Height);
|
||||||
8
src/Library/TerminalUI/Models/TextAlignment.cs
Normal file
8
src/Library/TerminalUI/Models/TextAlignment.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace TerminalUI.Models;
|
||||||
|
|
||||||
|
public enum TextAlignment
|
||||||
|
{
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
|
||||||
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.0.8">
|
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.0.8">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
namespace TerminalUI.ViewExtensions;
|
namespace TerminalUI.ViewExtensions;
|
||||||
|
|
||||||
public record GridPositionExtension(int Row, int Column);
|
public record GridPositionExtension(int Column, int Row);
|
||||||
Reference in New Issue
Block a user