Console ItemPreview, select item after delete
This commit is contained in:
@@ -5,4 +5,6 @@ namespace FileTime.App.Core.ViewModels.ItemPreview;
|
||||
public interface IElementPreviewViewModel : IItemPreviewViewModel
|
||||
{
|
||||
ItemPreviewMode Mode { get; }
|
||||
string TextContent { get; }
|
||||
string TextEncoding { get; }
|
||||
}
|
||||
@@ -6,20 +6,17 @@ namespace FileTime.App.Core.Services;
|
||||
|
||||
public class ItemPreviewService : IItemPreviewService
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IEnumerable<IItemPreviewProvider> _itemPreviewProviders;
|
||||
public IDeclarativeProperty<IItemPreviewViewModel?> ItemPreview { get; }
|
||||
|
||||
public ItemPreviewService(
|
||||
IAppState appState,
|
||||
IServiceProvider serviceProvider,
|
||||
IEnumerable<IItemPreviewProvider> itemPreviewProviders)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_itemPreviewProviders = itemPreviewProviders;
|
||||
ItemPreview = appState
|
||||
.SelectedTab
|
||||
.Map(t => t.CurrentSelectedItem)
|
||||
.Map(t => t?.CurrentSelectedItem)
|
||||
.Switch()
|
||||
.Debounce(TimeSpan.FromMilliseconds(250))
|
||||
.Map(async (item, _) =>
|
||||
|
||||
@@ -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?> VolumeSizeInfo { get; }
|
||||
IFrequencyNavigationViewModel FrequencyNavigation { get; }
|
||||
IItemPreviewService ItemPreviewService { get; }
|
||||
event Action<IInputElement>? FocusReadInputElement;
|
||||
}
|
||||
@@ -117,6 +117,8 @@ public class App : IApplication
|
||||
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
|
||||
Task.Run(async () => await _lifecycleService.ExitAsync()).Wait();
|
||||
}
|
||||
|
||||
private void Render() => _applicationContext.RenderEngine.Run();
|
||||
|
||||
110
src/ConsoleApp/FileTime.ConsoleUI.App/Controls/ItemPreviews.cs
Normal file
110
src/ConsoleApp/FileTime.ConsoleUI.App/Controls/ItemPreviews.cs
Normal file
@@ -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<IRootViewModel> View()
|
||||
{
|
||||
var view = new Grid<IRootViewModel>()
|
||||
{
|
||||
ChildInitializer =
|
||||
{
|
||||
new TextBlock<IRootViewModel>
|
||||
{
|
||||
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<IRootViewModel, IElementPreviewViewModel>(
|
||||
dc => (IElementPreviewViewModel)dc.ItemPreviewService.ItemPreview.Value
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private IView<IElementPreviewViewModel> ElementPreviews()
|
||||
{
|
||||
var view = new Grid<IElementPreviewViewModel>
|
||||
{
|
||||
ChildInitializer =
|
||||
{
|
||||
new TextBlock<IElementPreviewViewModel>
|
||||
{
|
||||
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<IElementPreviewViewModel>
|
||||
{
|
||||
TextAlignment = TextAlignment.Center,
|
||||
Text = "Empty",
|
||||
}.Setup(t => t.Bind(
|
||||
t,
|
||||
dc => dc.Mode == ItemPreviewMode.Empty,
|
||||
t => t.IsVisible,
|
||||
v => v,
|
||||
fallbackValue: false)),
|
||||
new Grid<IElementPreviewViewModel>
|
||||
{
|
||||
RowDefinitionsObject = "* Auto",
|
||||
ChildInitializer =
|
||||
{
|
||||
new TextBlock<IElementPreviewViewModel>()
|
||||
.Setup(t => t.Bind(
|
||||
t,
|
||||
dc => dc.TextContent,
|
||||
t => t.Text)),
|
||||
new TextBlock<IElementPreviewViewModel>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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<IView> _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<IView>(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<IRootViewModel>
|
||||
{
|
||||
Extensions =
|
||||
{
|
||||
new GridPositionExtension(2, 0)
|
||||
},
|
||||
ChildInitializer =
|
||||
{
|
||||
SelectedsItemsView(),
|
||||
_itemPreviews.View()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new ItemsControl<IRootViewModel, string>
|
||||
@@ -347,15 +361,21 @@ public class MainWindow
|
||||
{
|
||||
var list = new ListView<IRootViewModel, IItemViewModel>
|
||||
{
|
||||
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<IRootViewModel, IItemViewModel>
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -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?> 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) =>
|
||||
{
|
||||
|
||||
@@ -45,6 +45,7 @@ public static class Startup
|
||||
services.TryAddSingleton<Dialogs>();
|
||||
services.TryAddSingleton<Timeline>();
|
||||
services.TryAddSingleton<FrequencyNavigation>();
|
||||
services.TryAddSingleton<ItemPreviews>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -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<IContainer?> _currentLocation = new();
|
||||
@@ -22,9 +25,11 @@ public class Tab : ITab
|
||||
private readonly ObservableCollection<ItemFilter> _itemFilters = new();
|
||||
private readonly CircularBuffer<FullName> _history = new(20);
|
||||
private readonly CircularBuffer<FullName> _future = new(20);
|
||||
private readonly List<AbsolutePath> _selectedItemCandidates = new();
|
||||
private AbsolutePath? _currentSelectedItemCached;
|
||||
private PointInTime _currentPointInTime;
|
||||
private CancellationTokenSource? _setCurrentLocationCancellationTokenSource;
|
||||
private LastItemSelectingContext? _lastItemSelectingContext;
|
||||
|
||||
public IDeclarativeProperty<IContainer?> CurrentLocation { get; }
|
||||
public IDeclarativeProperty<ObservableCollection<IItem>?> 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<Func<IItem, IComparable>>) (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<ObservableCollection<IItem>?>(null);
|
||||
|
||||
ObservableCollection<IItem>? 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<IItem>? 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<AbsolutePath?>(selected);
|
||||
var itemSelectingContext = new LastItemSelectingContext(CurrentLocation.Value);
|
||||
var lastItemSelectingContext = _lastItemSelectingContext;
|
||||
_lastItemSelectingContext = itemSelectingContext;
|
||||
if (items == null || items.Count == 0) return Task.FromResult<AbsolutePath?>(null);
|
||||
if (selected != null)
|
||||
{
|
||||
if (items.Any(i => i.FullName == selected.Path))
|
||||
return Task.FromResult<AbsolutePath?>(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<IEnumerable<AbsolutePath>?>(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)
|
||||
|
||||
@@ -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 @@
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
|
||||
<Grid IsVisible="{Binding AppState.SelectedTab.Value.SelectedsChildren.Value, Converter={x:Static ObjectConverters.IsNull}, ConverterParameter=0, FallbackValue=False}" RowDefinitions="Auto, Auto">
|
||||
<Grid IsVisible="{Binding AppState.SelectedTab.Value.CurrentSelectedItem.Value.BaseItem.Exceptions.Count, Converter={StaticResource GreaterThanConverter}, ConverterParameter=0, FallbackValue=False}" RowDefinitions="Auto, Auto">
|
||||
<TextBlock
|
||||
Foreground="{DynamicResource ErrorBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
@@ -579,7 +580,7 @@
|
||||
</Grid>
|
||||
<Grid DataContext="{Binding ItemPreviewService.ItemPreview.Value}" IsVisible="{Binding FallbackValue=false, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Grid IsVisible="{Binding Name, FallbackValue=false, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static itemPreview:ElementPreviewViewModel.PreviewName}}">
|
||||
<Grid x:DataType="itemPreview:ElementPreviewViewModel">
|
||||
<Grid x:DataType="itemPreviewInterface:IElementPreviewViewModel">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
IsVisible="{Binding Mode, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Unknown}, FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}"
|
||||
|
||||
@@ -44,6 +44,12 @@ public abstract class ChildCollectionView<TConcrete, T>
|
||||
};
|
||||
}
|
||||
|
||||
public override void AddChild(IView child)
|
||||
{
|
||||
base.AddChild(child);
|
||||
_children.Add(child);
|
||||
}
|
||||
|
||||
public override TChild AddChild<TChild>(TChild child)
|
||||
{
|
||||
child = base.AddChild(child);
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System.Collections;
|
||||
using System.Linq.Expressions;
|
||||
using TerminalUI.Extensions;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public record ChildWithDataContextMapper<TSourceDataContext, TTargetDataContext>(IView<TTargetDataContext> Child, Func<TSourceDataContext?, TTargetDataContext?> DataContextMapper);
|
||||
public record ChildWithDataContextBinding<TSourceDataContext, TTargetDataContext>(IView<TTargetDataContext> Child, Expression<Func<TSourceDataContext?, TTargetDataContext?>> DataContextExpression);
|
||||
|
||||
public class ChildInitializer<T> : IEnumerable<IView>
|
||||
{
|
||||
@@ -18,6 +21,16 @@ public class ChildInitializer<T> : IEnumerable<IView>
|
||||
public void Add<TDataContext>(ChildWithDataContextMapper<T, TDataContext> item)
|
||||
=> _childContainer.AddChild(item.Child, item.DataContextMapper);
|
||||
|
||||
public void Add<TDataContext>(ChildWithDataContextBinding<T, TDataContext> item)
|
||||
{
|
||||
item.Child.Bind(
|
||||
_childContainer,
|
||||
item.DataContextExpression,
|
||||
c => c.DataContext
|
||||
);
|
||||
_childContainer.AddChild(item.Child);
|
||||
}
|
||||
|
||||
public IEnumerator<IView> GetEnumerator() => _childContainer.Children.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
@@ -55,6 +55,7 @@ public interface IView<T> : IView
|
||||
TChild CreateChild<TChild, TDataContext>(Func<T?, TDataContext?> dataContextMapper)
|
||||
where TChild : IView<TDataContext>, new();
|
||||
|
||||
void AddChild(IView child);
|
||||
TChild AddChild<TChild>(TChild child) where TChild : IView<T>;
|
||||
|
||||
TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
||||
|
||||
@@ -26,12 +26,14 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, 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<T> : View<TextBlock<T>, 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;
|
||||
}
|
||||
|
||||
@@ -376,6 +376,12 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
||||
return child;
|
||||
}
|
||||
|
||||
public virtual void AddChild(IView child)
|
||||
{
|
||||
Debug.Assert(child != null);
|
||||
SetupNewChild(child, null);
|
||||
}
|
||||
|
||||
public virtual TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
||||
where TChild : IView<TDataContext>
|
||||
{
|
||||
@@ -387,15 +393,18 @@ public abstract partial class View<TConcrete, T> : IView<T> 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<TDataContext>(IView<TDataContext> child)
|
||||
|
||||
@@ -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()
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ public static class ViewExtensions
|
||||
Func<TSourceDataContext?, TTargetDataContext?> dataContextMapper)
|
||||
=> new(view, dataContextMapper);
|
||||
|
||||
public static ChildWithDataContextBinding<TSourceDataContext, TTargetDataContext> WithDataContextBinding<TSourceDataContext, TTargetDataContext>(
|
||||
this IView<TTargetDataContext> view,
|
||||
Expression<Func<TSourceDataContext?, TTargetDataContext?>> dataContextMapper)
|
||||
=> new(view, dataContextMapper);
|
||||
|
||||
public static TView Setup<TView>(this TView view, Action<TView> action)
|
||||
{
|
||||
action(view);
|
||||
|
||||
Reference in New Issue
Block a user