diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IElementPreviewViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IElementPreviewViewModel.cs index 7089203..43864bc 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IElementPreviewViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IElementPreviewViewModel.cs @@ -5,4 +5,6 @@ namespace FileTime.App.Core.ViewModels.ItemPreview; public interface IElementPreviewViewModel : IItemPreviewViewModel { ItemPreviewMode Mode { get; } + string TextContent { get; } + string TextEncoding { get; } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs b/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs index 6bf7eb3..a746f8a 100644 --- a/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs @@ -6,20 +6,17 @@ namespace FileTime.App.Core.Services; public class ItemPreviewService : IItemPreviewService { - private readonly IServiceProvider _serviceProvider; private readonly IEnumerable _itemPreviewProviders; public IDeclarativeProperty ItemPreview { get; } public ItemPreviewService( IAppState appState, - IServiceProvider serviceProvider, IEnumerable itemPreviewProviders) { - _serviceProvider = serviceProvider; _itemPreviewProviders = itemPreviewProviders; ItemPreview = appState .SelectedTab - .Map(t => t.CurrentSelectedItem) + .Map(t => t?.CurrentSelectedItem) .Switch() .Debounce(TimeSpan.FromMilliseconds(250)) .Map(async (item, _) => diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/IRootViewModel.cs b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/IRootViewModel.cs index 4c19952..ecaca3a 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/IRootViewModel.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App.Abstractions/IRootViewModel.cs @@ -1,5 +1,6 @@ using DeclarativeProperty; using FileTime.App.CommandPalette.ViewModels; +using FileTime.App.Core.Services; using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels.Timeline; using FileTime.App.FrequencyNavigation.ViewModels; @@ -20,5 +21,6 @@ public interface IRootViewModel ITimelineViewModel TimelineViewModel { get; } IDeclarativeProperty VolumeSizeInfo { get; } IFrequencyNavigationViewModel FrequencyNavigation { get; } + IItemPreviewService ItemPreviewService { get; } event Action? FocusReadInputElement; } \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs index c6b2d91..9a24ff7 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/App.cs @@ -117,6 +117,8 @@ public class App : IApplication Thread.Sleep(10); } + + Task.Run(async () => await _lifecycleService.ExitAsync()).Wait(); } private void Render() => _applicationContext.RenderEngine.Run(); diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/ItemPreviews.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/ItemPreviews.cs new file mode 100644 index 0000000..0ca33d4 --- /dev/null +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/ItemPreviews.cs @@ -0,0 +1,110 @@ +using FileTime.App.Core.Models; +using FileTime.App.Core.Services; +using FileTime.App.Core.ViewModels.ItemPreview; +using FileTime.ConsoleUI.App.Styling; +using TerminalUI.Controls; +using TerminalUI.Extensions; +using TerminalUI.Models; +using TerminalUI.ViewExtensions; + +namespace FileTime.ConsoleUI.App.Controls; + +public class ItemPreviews +{ + private readonly ITheme _theme; + + public ItemPreviews(ITheme theme) + { + _theme = theme; + } + + public IView View() + { + var view = new Grid() + { + ChildInitializer = + { + new TextBlock + { + TextAlignment = TextAlignment.Center, + Text = "Empty", + Foreground = _theme.ErrorForegroundColor, + }.Setup(t => t.Bind( + t, + dc => dc.AppState.SelectedTab.Value.SelectedsChildren.Value.Count == 0, + t => t.IsVisible, + fallbackValue: false)), + ElementPreviews() + .WithDataContextBinding( + dc => (IElementPreviewViewModel)dc.ItemPreviewService.ItemPreview.Value + ) + } + }; + + return view; + } + + private IView ElementPreviews() + { + var view = new Grid + { + ChildInitializer = + { + new TextBlock + { + TextAlignment = TextAlignment.Center, + Text = "Don't know how to preview this item.", + }.Setup(t => t.Bind( + t, + dc => dc.Mode == ItemPreviewMode.Unknown, + t => t.IsVisible, + v => v, + fallbackValue: false)), + new TextBlock + { + TextAlignment = TextAlignment.Center, + Text = "Empty", + }.Setup(t => t.Bind( + t, + dc => dc.Mode == ItemPreviewMode.Empty, + t => t.IsVisible, + v => v, + fallbackValue: false)), + new Grid + { + RowDefinitionsObject = "* Auto", + ChildInitializer = + { + new TextBlock() + .Setup(t => t.Bind( + t, + dc => dc.TextContent, + t => t.Text)), + new TextBlock + { + Extensions = {new GridPositionExtension(0, 1)} + }.Setup(t => t.Bind( + t, + dc => dc.TextEncoding, + t => t.Text, + v => $"Encoding: {v}")) + } + }.Setup(t => t.Bind( + t, + dc => dc.Mode == ItemPreviewMode.Text, + t => t.IsVisible, + v => v, + fallbackValue: false)), + } + }; + + view.Bind( + view, + dc => dc.Name == ElementPreviewViewModel.PreviewName, + v => v.IsVisible, + v => v + ); + + return view; + } +} \ No newline at end of file diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs index 0443e2b..4db8e1b 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs @@ -34,6 +34,7 @@ public class MainWindow private readonly FrequencyNavigation _frequencyNavigation; private readonly Dialogs _dialogs; private readonly Timeline _timeline; + private readonly ItemPreviews _itemPreviews; private readonly Lazy _root; @@ -44,7 +45,8 @@ public class MainWindow CommandPalette commandPalette, FrequencyNavigation frequencyNavigation, Dialogs dialogs, - Timeline timeline) + Timeline timeline, + ItemPreviews itemPreviews) { _rootViewModel = rootViewModel; _applicationContext = applicationContext; @@ -53,6 +55,7 @@ public class MainWindow _frequencyNavigation = frequencyNavigation; _dialogs = dialogs; _timeline = timeline; + _itemPreviews = itemPreviews; _root = new Lazy(Initialize); } @@ -141,7 +144,18 @@ public class MainWindow { ParentsItemsView().WithExtension(new GridPositionExtension(0, 0)), SelectedItemsView().WithExtension(new GridPositionExtension(1, 0)), - SelectedsItemsView().WithExtension(new GridPositionExtension(2, 0)), + new Grid + { + Extensions = + { + new GridPositionExtension(2, 0) + }, + ChildInitializer = + { + SelectedsItemsView(), + _itemPreviews.View() + } + } } }, new ItemsControl @@ -347,15 +361,21 @@ public class MainWindow { var list = new ListView { - ListPadding = 8 + ListPadding = 8, + ItemTemplate = item => ItemItemTemplate(item, new ItemViewRenderOptions()) }; - list.ItemTemplate = item => ItemItemTemplate(item, new ItemViewRenderOptions()); - list.Bind( list, - root => root.AppState.SelectedTab.Value.SelectedsChildren.Value, - v => v.ItemsSource); + dc => dc.AppState.SelectedTab.Value.SelectedsChildren.Value.Count > 0, + l => l.IsVisible, + fallbackValue: false); + + list.Bind( + list, + dc => dc.AppState.SelectedTab.Value.SelectedsChildren.Value, + v => v.ItemsSource, + fallbackValue: null); return list; } @@ -364,14 +384,13 @@ public class MainWindow { var list = new ListView { - ListPadding = 8 + ListPadding = 8, + ItemTemplate = item => ItemItemTemplate(item, new ItemViewRenderOptions()) }; - list.ItemTemplate = item => ItemItemTemplate(item, new ItemViewRenderOptions()); - list.Bind( list, - root => root.AppState.SelectedTab.Value.ParentsChildren.Value, + dc => dc.AppState.SelectedTab.Value.ParentsChildren.Value, v => v.ItemsSource); return list; diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs index f8d75ab..15e99c5 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/RootViewModel.cs @@ -1,5 +1,6 @@ using DeclarativeProperty; using FileTime.App.CommandPalette.ViewModels; +using FileTime.App.Core.Services; using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels.Timeline; using FileTime.App.FrequencyNavigation.ViewModels; @@ -17,6 +18,7 @@ public class RootViewModel : IRootViewModel public IConsoleAppState AppState { get; } public ICommandPaletteViewModel CommandPalette { get; } public IFrequencyNavigationViewModel FrequencyNavigation { get; } + public IItemPreviewService ItemPreviewService { get; } public IDialogService DialogService { get; } public ITimelineViewModel TimelineViewModel { get; } public IDeclarativeProperty VolumeSizeInfo { get;} @@ -29,7 +31,8 @@ public class RootViewModel : IRootViewModel ICommandPaletteViewModel commandPalette, IDialogService dialogService, ITimelineViewModel timelineViewModel, - IFrequencyNavigationViewModel frequencyNavigation) + IFrequencyNavigationViewModel frequencyNavigation, + IItemPreviewService itemPreviewService) { AppState = appState; PossibleCommands = possibleCommands; @@ -37,6 +40,7 @@ public class RootViewModel : IRootViewModel DialogService = dialogService; TimelineViewModel = timelineViewModel; FrequencyNavigation = frequencyNavigation; + ItemPreviewService = itemPreviewService; DialogService.ReadInput.PropertyChanged += (o, e) => { diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Startup.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Startup.cs index 8c94625..750006e 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/Startup.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Startup.cs @@ -45,6 +45,7 @@ public static class Startup services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); return services; } } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Services/Tab.cs b/src/Core/FileTime.Core.Services/Tab.cs index 59ea1fa..477bec3 100644 --- a/src/Core/FileTime.Core.Services/Tab.cs +++ b/src/Core/FileTime.Core.Services/Tab.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using System.ComponentModel; +using System.Linq.Expressions; using CircularBuffer; using DeclarativeProperty; using DynamicData; @@ -14,6 +15,8 @@ namespace FileTime.Core.Services; public class Tab : ITab { + private record LastItemSelectingContext(IContainer? CurrentLocationValue); + private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITabEvents _tabEvents; private readonly DeclarativeProperty _currentLocation = new(); @@ -22,9 +25,11 @@ public class Tab : ITab private readonly ObservableCollection _itemFilters = new(); private readonly CircularBuffer _history = new(20); private readonly CircularBuffer _future = new(20); + private readonly List _selectedItemCandidates = new(); private AbsolutePath? _currentSelectedItemCached; private PointInTime _currentPointInTime; private CancellationTokenSource? _setCurrentLocationCancellationTokenSource; + private LastItemSelectingContext? _lastItemSelectingContext; public IDeclarativeProperty CurrentLocation { get; } public IDeclarativeProperty?> CurrentItems { get; } @@ -81,47 +86,32 @@ public class Tab : ITab return Task.FromResult(items); } ).CombineLatest( - Ordering, + Ordering.Map(ordering => + { + var (itemComparer, order) = ordering switch + { + ItemOrdering.Name => ((Expression>) (i => i.DisplayName), ListSortDirection.Ascending), + ItemOrdering.NameDesc => (i => i.DisplayName, ListSortDirection.Descending), + ItemOrdering.CreationDate => (i => i.CreatedAt ?? DateTime.MinValue, ListSortDirection.Ascending), + ItemOrdering.CreationDateDesc => (i => i.CreatedAt ?? DateTime.MinValue, ListSortDirection.Descending), + ItemOrdering.LastModifyDate => (i => i.ModifiedAt ?? DateTime.MinValue, ListSortDirection.Ascending), + ItemOrdering.LastModifyDateDesc => (i => i.ModifiedAt ?? DateTime.MinValue, ListSortDirection.Descending), + ItemOrdering.Size => (i => GetSize(i), ListSortDirection.Ascending), + ItemOrdering.SizeDesc => (i => GetSize(i), ListSortDirection.Descending), + _ => throw new NotImplementedException() + }; + + return (itemComparer, order); + }), (items, ordering) => { if (items is null) return Task.FromResult?>(null); - ObservableCollection? orderedItems = ordering switch - { - ItemOrdering.Name => - items - .Ordering(i => i.Type) - .ThenOrdering(i => i.DisplayName), - ItemOrdering.NameDesc => - items - .Ordering(i => i.Type) - .ThenOrdering(i => i.DisplayName, ListSortDirection.Descending), - ItemOrdering.CreationDate => - items - .Ordering(i => i.Type) - .ThenOrdering(i => i.CreatedAt), - ItemOrdering.CreationDateDesc => - items - .Ordering(i => i.Type) - .ThenOrdering(i => i.CreatedAt, ListSortDirection.Descending), - ItemOrdering.LastModifyDate => - items - .Ordering(i => i.Type) - .ThenOrdering(i => i.ModifiedAt), - ItemOrdering.LastModifyDateDesc => - items - .Ordering(i => i.Type) - .ThenOrdering(i => i.ModifiedAt, ListSortDirection.Descending), - ItemOrdering.Size => - items - .Ordering(i => i.Type) - .ThenOrdering(i => GetSize(i)), - ItemOrdering.SizeDesc => - items - .Ordering(i => i.Type) - .ThenOrdering(i => GetSize(i), ListSortDirection.Descending), - _ => throw new NotImplementedException() - }; + var (itemComparer, order) = ordering; + + ObservableCollection? orderedItems = items + .Ordering(i => i.Type) + .ThenOrdering(itemComparer, order); return Task.FromResult(orderedItems); } @@ -132,8 +122,25 @@ public class Tab : ITab _currentRequestItem.DistinctUntilChanged(), (items, selected) => { - if (selected != null && (items?.Any(i => i.FullName == selected.Path) ?? true)) return Task.FromResult(selected); + var itemSelectingContext = new LastItemSelectingContext(CurrentLocation.Value); + var lastItemSelectingContext = _lastItemSelectingContext; + _lastItemSelectingContext = itemSelectingContext; if (items == null || items.Count == 0) return Task.FromResult(null); + if (selected != null) + { + if (items.Any(i => i.FullName == selected.Path)) + return Task.FromResult(selected); + } + + if (lastItemSelectingContext != null + && itemSelectingContext == lastItemSelectingContext) + { + var candidate = _selectedItemCandidates.FirstOrDefault(c => items.Any(i => i.FullName?.Path == c.Path.Path)); + if (candidate != null) + { + return Task.FromResult(candidate); + } + } return Task.FromResult(GetSelectedItemByItems(items)); }).DistinctUntilChanged(); @@ -141,8 +148,31 @@ public class Tab : ITab CurrentSelectedItem.Subscribe(async (s, _) => { _currentSelectedItemCached = s; + await _currentRequestItem.SetValue(s); }); + + DeclarativePropertyHelpers.CombineLatest( + CurrentItems, + CurrentSelectedItem, + (items, selected) => + { + if(items is null || selected is null) return Task.FromResult?>(null); + var primaryCandidates = items.SkipWhile(i => i.FullName is {Path: var p} && p != selected.Path.Path).Skip(1); + var secondaryCandidates = items.TakeWhile(i => i.FullName is {Path: var p} && p != selected.Path.Path).Reverse(); + var candidates = primaryCandidates + .Concat(secondaryCandidates) + .Select(c => new AbsolutePath(_timelessContentProvider, c)); + + return Task.FromResult(candidates); + }) + .Subscribe(candidates => + { + if(candidates is null) return; + + _selectedItemCandidates.Clear(); + _selectedItemCandidates.AddRange(candidates); + }); } private static long GetSize(IItem item) diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp.App/Views/MainWindow.axaml index 80238cb..349196f 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/Views/MainWindow.axaml @@ -27,6 +27,7 @@ xmlns:ia="clr-namespace:Avalonia.Xaml.Interactions.Core;assembly=Avalonia.Xaml.Interactions" xmlns:interactions="using:FileTime.Core.Interactions" xmlns:itemPreview="clr-namespace:FileTime.App.Core.ViewModels.ItemPreview;assembly=FileTime.App.Core" + xmlns:itemPreviewInterface="clr-namespace:FileTime.App.Core.ViewModels.ItemPreview;assembly=FileTime.App.Core.Abstraction" xmlns:local="using:FileTime.GuiApp.App.Views" xmlns:local1="clr-namespace:FileTime.Providers.Local;assembly=FileTime.Providers.Local.Abstractions" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" @@ -557,7 +558,7 @@ - + - + }; } + public override void AddChild(IView child) + { + base.AddChild(child); + _children.Add(child); + } + public override TChild AddChild(TChild child) { child = base.AddChild(child); diff --git a/src/Library/TerminalUI/Controls/ChildInitializer.cs b/src/Library/TerminalUI/Controls/ChildInitializer.cs index 090310b..f83b72e 100644 --- a/src/Library/TerminalUI/Controls/ChildInitializer.cs +++ b/src/Library/TerminalUI/Controls/ChildInitializer.cs @@ -1,8 +1,11 @@ using System.Collections; +using System.Linq.Expressions; +using TerminalUI.Extensions; namespace TerminalUI.Controls; public record ChildWithDataContextMapper(IView Child, Func DataContextMapper); +public record ChildWithDataContextBinding(IView Child, Expression> DataContextExpression); public class ChildInitializer : IEnumerable { @@ -17,6 +20,16 @@ public class ChildInitializer : IEnumerable public void Add(ChildWithDataContextMapper item) => _childContainer.AddChild(item.Child, item.DataContextMapper); + + public void Add(ChildWithDataContextBinding item) + { + item.Child.Bind( + _childContainer, + item.DataContextExpression, + c => c.DataContext + ); + _childContainer.AddChild(item.Child); + } public IEnumerator GetEnumerator() => _childContainer.Children.GetEnumerator(); diff --git a/src/Library/TerminalUI/Controls/IView.cs b/src/Library/TerminalUI/Controls/IView.cs index 98dff52..46fdae2 100644 --- a/src/Library/TerminalUI/Controls/IView.cs +++ b/src/Library/TerminalUI/Controls/IView.cs @@ -55,6 +55,7 @@ public interface IView : IView TChild CreateChild(Func dataContextMapper) where TChild : IView, new(); + void AddChild(IView child); TChild AddChild(TChild child) where TChild : IView; TChild AddChild(TChild child, Func dataContextMapper) diff --git a/src/Library/TerminalUI/Controls/TextBlock.cs b/src/Library/TerminalUI/Controls/TextBlock.cs index 1e69837..99527ca 100644 --- a/src/Library/TerminalUI/Controls/TextBlock.cs +++ b/src/Library/TerminalUI/Controls/TextBlock.cs @@ -26,12 +26,14 @@ public sealed partial class TextBlock : View, T>, IDisplayView [Notify] private string? _text = string.Empty; [Notify] private TextAlignment _textAlignment = TextAlignment.Left; [Notify] private ITextFormat? _textFormat; + [Notify] private int _textStartIndex; public TextBlock() { RerenderProperties.Add(nameof(Text)); RerenderProperties.Add(nameof(TextAlignment)); RerenderProperties.Add(nameof(TextFormat)); + RerenderProperties.Add(nameof(TextStartIndex)); ((INotifyPropertyChanged) this).PropertyChanged += (o, e) => { @@ -78,7 +80,17 @@ public sealed partial class TextBlock : View, T>, IDisplayView SetStyleColor(renderContext, foreground, background, _textFormat); - RenderText(_textLines, renderContext, position, size, skipRender, TransformText); + var textLines = _textLines; + if (_textStartIndex < _textLines.Length) + { + textLines = _textLines[_textStartIndex..]; + } + else + { + _textStartIndex = _textLines.Length - size.Height; + } + + RenderText(textLines, renderContext, position, size, skipRender, TransformText); return !skipRender; } diff --git a/src/Library/TerminalUI/Controls/View.cs b/src/Library/TerminalUI/Controls/View.cs index 592db80..4a637dd 100644 --- a/src/Library/TerminalUI/Controls/View.cs +++ b/src/Library/TerminalUI/Controls/View.cs @@ -376,6 +376,12 @@ public abstract partial class View : IView where TConcrete : Vi return child; } + public virtual void AddChild(IView child) + { + Debug.Assert(child != null); + SetupNewChild(child, null); + } + public virtual TChild AddChild(TChild child, Func dataContextMapper) where TChild : IView { @@ -387,15 +393,18 @@ public abstract partial class View : IView where TConcrete : Vi return child; } - private void SetupNewChild(IView child, IDisposable dataContextmapper) + private void SetupNewChild(IView child, IDisposable? dataContextMapper) { child.ApplicationContext = ApplicationContext; child.Attached = Attached; child.VisualParent = this; VisualChildren.Add(child); - AddDisposable(dataContextmapper); - child.AddDisposable(dataContextmapper); + if (dataContextMapper is not null) + { + AddDisposable(dataContextMapper); + child.AddDisposable(dataContextMapper); + } } public virtual void RemoveChild(IView child) diff --git a/src/Library/TerminalUI/ExpressionTrackers/BinaryTracker.cs b/src/Library/TerminalUI/ExpressionTrackers/BinaryTracker.cs index 20c22d6..4dbc623 100644 --- a/src/Library/TerminalUI/ExpressionTrackers/BinaryTracker.cs +++ b/src/Library/TerminalUI/ExpressionTrackers/BinaryTracker.cs @@ -1,4 +1,5 @@ -using System.Linq.Expressions; +using System.Collections; +using System.Linq.Expressions; namespace TerminalUI.ExpressionTrackers; @@ -26,6 +27,10 @@ public class BinaryTracker : ExpressionTrackerBase { ExpressionType.Equal => (v1, v2) => Equals(v1, v2), ExpressionType.NotEqual => (v1, v2) => !Equals(v1, v2), + ExpressionType.GreaterThan => (v1, v2) => Comparer.Default.Compare(v1, v2) > 0, + ExpressionType.GreaterThanOrEqual => (v1, v2) => Comparer.Default.Compare(v1, v2) >= 0, + ExpressionType.LessThan => (v1, v2) => Comparer.Default.Compare(v1, v2) < 0, + ExpressionType.LessThanOrEqual => (v1, v2) => Comparer.Default.Compare(v1, v2) <= 0, _ => throw new NotImplementedException() }; diff --git a/src/Library/TerminalUI/Extensions/ViewExtensions.cs b/src/Library/TerminalUI/Extensions/ViewExtensions.cs index 118fedf..c741866 100644 --- a/src/Library/TerminalUI/Extensions/ViewExtensions.cs +++ b/src/Library/TerminalUI/Extensions/ViewExtensions.cs @@ -19,6 +19,11 @@ public static class ViewExtensions Func dataContextMapper) => new(view, dataContextMapper); + public static ChildWithDataContextBinding WithDataContextBinding( + this IView view, + Expression> dataContextMapper) + => new(view, dataContextMapper); + public static TView Setup(this TView view, Action action) { action(view);