RapidTravel impr, GoBack/Forward, header navigation
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
namespace FileTime.App.Core.UserCommand;
|
||||||
|
|
||||||
|
public class GoBackCommand : IIdentifiableUserCommand
|
||||||
|
{
|
||||||
|
public const string CommandName = "go_back";
|
||||||
|
|
||||||
|
public static GoBackCommand Instance { get; } = new();
|
||||||
|
|
||||||
|
private GoBackCommand()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UserCommandID => CommandName;
|
||||||
|
public string Title => "Go back";
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace FileTime.App.Core.UserCommand;
|
||||||
|
|
||||||
|
public class GoForwardCommand : IIdentifiableUserCommand
|
||||||
|
{
|
||||||
|
public const string CommandName = "go_forward";
|
||||||
|
|
||||||
|
public static GoForwardCommand Instance { get; } = new();
|
||||||
|
|
||||||
|
private GoForwardCommand()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UserCommandID => CommandName;
|
||||||
|
public string Title => "Go forward";
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ public interface IAppState
|
|||||||
IObservable<string?> SearchText { get; }
|
IObservable<string?> SearchText { get; }
|
||||||
IDeclarativeProperty<ViewMode> ViewMode { get; }
|
IDeclarativeProperty<ViewMode> ViewMode { get; }
|
||||||
DeclarativeProperty<string?> RapidTravelText { get; }
|
DeclarativeProperty<string?> RapidTravelText { get; }
|
||||||
|
IDeclarativeProperty<string?> RapidTravelTextDebounced { get; }
|
||||||
ITimelineViewModel TimelineViewModel { get; }
|
ITimelineViewModel TimelineViewModel { get; }
|
||||||
IDeclarativeProperty<string?> ContainerStatus { get; }
|
IDeclarativeProperty<string?> ContainerStatus { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
new TypeUserCommandHandler<CloseTabCommand>(CloseTab),
|
new TypeUserCommandHandler<CloseTabCommand>(CloseTab),
|
||||||
new TypeUserCommandHandler<EnterRapidTravelCommand>(EnterRapidTravel),
|
new TypeUserCommandHandler<EnterRapidTravelCommand>(EnterRapidTravel),
|
||||||
new TypeUserCommandHandler<ExitRapidTravelCommand>(ExitRapidTravel),
|
new TypeUserCommandHandler<ExitRapidTravelCommand>(ExitRapidTravel),
|
||||||
|
new TypeUserCommandHandler<GoBackCommand>(GoBack),
|
||||||
new TypeUserCommandHandler<GoByFrequencyCommand>(GoByFrequency),
|
new TypeUserCommandHandler<GoByFrequencyCommand>(GoByFrequency),
|
||||||
|
new TypeUserCommandHandler<GoForwardCommand>(GoForward),
|
||||||
new TypeUserCommandHandler<GoToHomeCommand>(GoToHome),
|
new TypeUserCommandHandler<GoToHomeCommand>(GoToHome),
|
||||||
new TypeUserCommandHandler<GoToPathCommand>(GoToPath),
|
new TypeUserCommandHandler<GoToPathCommand>(GoToPath),
|
||||||
new TypeUserCommandHandler<GoToProviderCommand>(GoToProvider),
|
new TypeUserCommandHandler<GoToProviderCommand>(GoToProvider),
|
||||||
@@ -89,6 +91,20 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task GoBack()
|
||||||
|
{
|
||||||
|
if (_selectedTab?.Tab is null) return;
|
||||||
|
|
||||||
|
await _selectedTab.Tab.GoBackAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GoForward()
|
||||||
|
{
|
||||||
|
if (_selectedTab?.Tab is null) return;
|
||||||
|
|
||||||
|
await _selectedTab.Tab.GoForwardAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RunOrOpen(RunOrOpenCommand command)
|
private async Task RunOrOpen(RunOrOpenCommand command)
|
||||||
{
|
{
|
||||||
var item = command.Item ?? _currentSelectedItem?.Value;
|
var item = command.Item ?? _currentSelectedItem?.Value;
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ public class DefaultIdentifiableCommandHandlerRegister : IStartupHandler
|
|||||||
AddUserCommand(DeleteCommand.SoftDelete);
|
AddUserCommand(DeleteCommand.SoftDelete);
|
||||||
AddUserCommand(EnterRapidTravelCommand.Instance);
|
AddUserCommand(EnterRapidTravelCommand.Instance);
|
||||||
AddUserCommand(ExitRapidTravelCommand.Instance);
|
AddUserCommand(ExitRapidTravelCommand.Instance);
|
||||||
|
AddUserCommand(GoBackCommand.Instance);
|
||||||
AddUserCommand(GoByFrequencyCommand.Instance);
|
AddUserCommand(GoByFrequencyCommand.Instance);
|
||||||
|
AddUserCommand(GoForwardCommand.Instance);
|
||||||
AddUserCommand(GoToHomeCommand.Instance);
|
AddUserCommand(GoToHomeCommand.Instance);
|
||||||
AddUserCommand(GoToPathCommand.Instance);
|
AddUserCommand(GoToPathCommand.Instance);
|
||||||
AddUserCommand(GoToProviderCommand.Instance);
|
AddUserCommand(GoToProviderCommand.Instance);
|
||||||
|
|||||||
@@ -26,12 +26,21 @@ public abstract partial class AppStateBase : IAppState
|
|||||||
|
|
||||||
public IDeclarativeProperty<ITabViewModel?> SelectedTab { get; private set; }
|
public IDeclarativeProperty<ITabViewModel?> SelectedTab { get; private set; }
|
||||||
public DeclarativeProperty<string?> RapidTravelText { get; private set; }
|
public DeclarativeProperty<string?> RapidTravelText { get; private set; }
|
||||||
|
public IDeclarativeProperty<string?> RapidTravelTextDebounced { get; private set; }
|
||||||
|
|
||||||
public IDeclarativeProperty<string?> ContainerStatus { get; private set; }
|
public IDeclarativeProperty<string?> ContainerStatus { get; private set; }
|
||||||
|
|
||||||
partial void OnInitialize()
|
partial void OnInitialize()
|
||||||
{
|
{
|
||||||
RapidTravelText = new("");
|
RapidTravelText = new("");
|
||||||
|
RapidTravelTextDebounced = RapidTravelText
|
||||||
|
.Debounce(v =>
|
||||||
|
string.IsNullOrEmpty(v)
|
||||||
|
? TimeSpan.Zero
|
||||||
|
: TimeSpan.FromMilliseconds(200)
|
||||||
|
, resetTimer: true
|
||||||
|
);
|
||||||
|
|
||||||
ViewMode = _viewMode;
|
ViewMode = _viewMode;
|
||||||
|
|
||||||
SearchText = _searchText.AsObservable();
|
SearchText = _searchText.AsObservable();
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public abstract partial class ItemViewModel : IItemViewModel
|
|||||||
|
|
||||||
var displayName = itemViewModelType switch
|
var displayName = itemViewModelType switch
|
||||||
{
|
{
|
||||||
ItemViewModelType.Main => _appState.RapidTravelText.Map(async (s, _) =>
|
ItemViewModelType.Main => _appState.RapidTravelTextDebounced.Map(async (s, _) =>
|
||||||
_appState.ViewMode.Value != Models.Enums.ViewMode.RapidTravel
|
_appState.ViewMode.Value != Models.Enums.ViewMode.RapidTravel
|
||||||
&& _appState.SelectedTab.Value?.CurrentLocation.Value?.Provider is IItemNameConverterProvider nameConverterProvider
|
&& _appState.SelectedTab.Value?.CurrentLocation.Value?.Provider is IItemNameConverterProvider nameConverterProvider
|
||||||
? (IReadOnlyList<ItemNamePart>) await nameConverterProvider.GetItemNamePartsAsync(item)
|
? (IReadOnlyList<ItemNamePart>) await nameConverterProvider.GetItemNamePartsAsync(item)
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ public partial class TabViewModel : ITabViewModel
|
|||||||
CurrentSelectedItemAsContainer = CurrentSelectedItem.Map(i => i as IContainerViewModel);
|
CurrentSelectedItemAsContainer = CurrentSelectedItem.Map(i => i as IContainerViewModel);
|
||||||
|
|
||||||
SelectedsChildren = CurrentSelectedItem
|
SelectedsChildren = CurrentSelectedItem
|
||||||
.Debounce(() => _refreshSmoothnessCalculator.RefreshDelay, resetTimer: true)
|
.Debounce(_ => _refreshSmoothnessCalculator.RefreshDelay, resetTimer: true)
|
||||||
.DistinctUntilChanged()
|
.DistinctUntilChanged()
|
||||||
.Map(item =>
|
.Map(item =>
|
||||||
{
|
{
|
||||||
|
|||||||
809
src/Core/FileTime.Core.Abstraction/Collections/CircularBuffer.cs
Normal file
809
src/Core/FileTime.Core.Abstraction/Collections/CircularBuffer.cs
Normal file
@@ -0,0 +1,809 @@
|
|||||||
|
// https://github.com/ASolomatin/CircularBuffer-CSharp/blob/master/CircularBuffer/
|
||||||
|
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace CircularBuffer;
|
||||||
|
#if (NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reference struct that contains two read only spans of type T
|
||||||
|
/// </summary>
|
||||||
|
public ref struct SpanTuple<T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// First span
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlySpan<T> A { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Second span
|
||||||
|
/// </summary>
|
||||||
|
public ReadOnlySpan<T> B { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="a">First span</param>
|
||||||
|
/// <param name="b">Second span</param>
|
||||||
|
public SpanTuple(ReadOnlySpan<T> a, ReadOnlySpan<T> b)
|
||||||
|
{
|
||||||
|
A = a;
|
||||||
|
B = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deconstructs the current <see cref="SpanTuple<T>"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="a">First span target</param>
|
||||||
|
/// <param name="b">Second span target</param>
|
||||||
|
public void Deconstruct(out ReadOnlySpan<T> a, out ReadOnlySpan<T> b)
|
||||||
|
{
|
||||||
|
a = A;
|
||||||
|
b = B;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Circular buffer.
|
||||||
|
///
|
||||||
|
/// When writing to a full buffer:
|
||||||
|
/// PushBack -> removes this[0] / Front()
|
||||||
|
/// PushFront -> removes this[Size-1] / Back()
|
||||||
|
///
|
||||||
|
/// this implementation is inspired by
|
||||||
|
/// http://www.boost.org/doc/libs/1_53_0/libs/circular_buffer/doc/circular_buffer.html
|
||||||
|
/// because I liked their interface.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICircularBuffer<T> : IReadOnlyCollection<T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Index access to elements in buffer.
|
||||||
|
/// Index does not loop around like when adding elements,
|
||||||
|
/// valid interval is [0;Size[
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of element to access.</param>
|
||||||
|
/// <exception cref="IndexOutOfRangeException">Thrown when index is outside of [; Size[ interval.</exception>
|
||||||
|
T this[int index] { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum capacity of the buffer. Elements pushed into the buffer after
|
||||||
|
/// maximum capacity is reached (IsFull = true), will remove an element.
|
||||||
|
/// </summary>
|
||||||
|
int Capacity { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Boolean indicating if Circular is at full capacity.
|
||||||
|
/// Adding more elements when the buffer is full will
|
||||||
|
/// cause elements to be removed from the other end
|
||||||
|
/// of the buffer.
|
||||||
|
/// </summary>
|
||||||
|
bool IsFull { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if has no elements.
|
||||||
|
/// </summary>
|
||||||
|
bool IsEmpty { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current buffer size (the number of elements that the buffer has).
|
||||||
|
/// </summary>
|
||||||
|
[Obsolete("Use Count property instead")]
|
||||||
|
int Size { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Element at the back of the buffer - this[Size - 1].
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The value of the element of type T at the back of the buffer.</returns>
|
||||||
|
[Obsolete("Use Last() method instead")]
|
||||||
|
T Back();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Element at the front of the buffer - this[0].
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The value of the element of type T at the front of the buffer.</returns>
|
||||||
|
[Obsolete("Use First() method instead")]
|
||||||
|
T Front();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Element at the back of the buffer - this[Size - 1].
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The value of the element of type T at the back of the buffer.</returns>
|
||||||
|
T First();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Element at the front of the buffer - this[0].
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The value of the element of type T at the front of the buffer.</returns>
|
||||||
|
T Last();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the contents of the array. Size = 0, Capacity is unchanged.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="NotImplementedException"></exception>
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the element at the back of the buffer. Decreasing the
|
||||||
|
/// Buffer size by 1.
|
||||||
|
/// </summary>
|
||||||
|
T PopBack();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the element at the front of the buffer. Decreasing the
|
||||||
|
/// Buffer size by 1.
|
||||||
|
/// </summary>
|
||||||
|
T PopFront();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pushes a new element to the back of the buffer. Back()/this[Size-1]
|
||||||
|
/// will now return this element.
|
||||||
|
///
|
||||||
|
/// When the buffer is full, the element at Front()/this[0] will be
|
||||||
|
/// popped to allow for this new element to fit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">Item to push to the back of the buffer</param>
|
||||||
|
void PushBack(T item);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pushes a new element to the front of the buffer. Front()/this[0]
|
||||||
|
/// will now return this element.
|
||||||
|
///
|
||||||
|
/// When the buffer is full, the element at Back()/this[Size-1] will be
|
||||||
|
/// popped to allow for this new element to fit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">Item to push to the front of the buffer</param>
|
||||||
|
void PushFront(T item);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the buffer contents to an array, according to the logical
|
||||||
|
/// contents of the buffer (i.e. independent of the internal
|
||||||
|
/// order/contents)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new array with a copy of the buffer contents.</returns>
|
||||||
|
T[] ToArray();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the buffer contents to the array, according to the logical
|
||||||
|
/// contents of the buffer (i.e. independent of the internal
|
||||||
|
/// order/contents)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array">The array that is the destination of the elements copied from the current buffer.</param>
|
||||||
|
void CopyTo(T[] array);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the buffer contents to the array, according to the logical
|
||||||
|
/// contents of the buffer (i.e. independent of the internal
|
||||||
|
/// order/contents)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array">The array that is the destination of the elements copied from the current buffer.</param>
|
||||||
|
/// <param name="index">A 32-bit integer that represents the index in array at which copying begins.</param>
|
||||||
|
void CopyTo(T[] array, int index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the buffer contents to the array, according to the logical
|
||||||
|
/// contents of the buffer (i.e. independent of the internal
|
||||||
|
/// order/contents)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array">The array that is the destination of the elements copied from the current buffer.</param>
|
||||||
|
/// <param name="index">A 64-bit integer that represents the index in array at which copying begins.</param>
|
||||||
|
void CopyTo(T[] array, long index);
|
||||||
|
|
||||||
|
#if (NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the buffer contents to the <see cref="Memory<T>"/>, according to the logical
|
||||||
|
/// contents of the buffer (i.e. independent of the internal
|
||||||
|
/// order/contents)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memory">The memory that is the destination of the elements copied from the current buffer.</param>
|
||||||
|
void CopyTo(Memory<T> memory);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the buffer contents to the <see cref="Span<T>"/>, according to the logical
|
||||||
|
/// contents of the buffer (i.e. independent of the internal
|
||||||
|
/// order/contents)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="span">The span that is the destination of the elements copied from the current buffer.</param>
|
||||||
|
void CopyTo(Span<T> span);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the contents of the buffer as 2 ArraySegments.
|
||||||
|
/// Respects the logical contents of the buffer, where
|
||||||
|
/// each segment and items in each segment are ordered
|
||||||
|
/// according to insertion.
|
||||||
|
///
|
||||||
|
/// Fast: does not copy the array elements.
|
||||||
|
/// Useful for methods like <c>Send(IList<ArraySegment<Byte>>)</c>.
|
||||||
|
///
|
||||||
|
/// <remarks>Segments may be empty.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An IList with 2 segments corresponding to the buffer content.</returns>
|
||||||
|
IList<ArraySegment<T>> ToArraySegments();
|
||||||
|
|
||||||
|
#if (NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the contents of the buffer as ref struct with 2 read only spans (<see cref="SpanTuple<T>"/>).
|
||||||
|
/// Respects the logical contents of the buffer, where
|
||||||
|
/// each segment and items in each segment are ordered
|
||||||
|
/// according to insertion.
|
||||||
|
///
|
||||||
|
/// <remarks>Segments may be empty.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="SpanTuple<T>"/> with 2 read only spans corresponding to the buffer content.</returns>
|
||||||
|
SpanTuple<T> ToSpan();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the contents of the buffer as tuple with 2 <see cref="ReadOnlyMemory<T>"/>.
|
||||||
|
/// Respects the logical contents of the buffer, where
|
||||||
|
/// each segment and items in each segment are ordered
|
||||||
|
/// according to insertion.
|
||||||
|
///
|
||||||
|
/// <remarks>Segments may be empty.</remarks>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A tuple with 2 read only spans corresponding to the buffer content.</returns>
|
||||||
|
(ReadOnlyMemory<T> A, ReadOnlyMemory<T> B) ToMemory();
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public class CircularBuffer<T> : ICircularBuffer<T>
|
||||||
|
{
|
||||||
|
private readonly T[] _buffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _start. Index of the first element in buffer.
|
||||||
|
/// </summary>
|
||||||
|
private int _start;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _end. Index after the last element in the buffer.
|
||||||
|
/// </summary>
|
||||||
|
private int _end;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _size. Buffer size.
|
||||||
|
/// </summary>
|
||||||
|
private int _size;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CircularBuffer{T}"/> class.
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name='capacity'>
|
||||||
|
/// Buffer capacity. Must be positive.
|
||||||
|
/// </param>
|
||||||
|
public CircularBuffer(int capacity)
|
||||||
|
: this(capacity, new T[] { })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="CircularBuffer{T}"/> class.
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name='capacity'>
|
||||||
|
/// Buffer capacity. Must be positive.
|
||||||
|
/// </param>
|
||||||
|
/// <param name='items'>
|
||||||
|
/// Items to fill buffer with. Items length must be less than capacity.
|
||||||
|
/// Suggestion: use Skip(x).Take(y).ToArray() to build this argument from
|
||||||
|
/// any enumerable.
|
||||||
|
/// </param>
|
||||||
|
public CircularBuffer(int capacity, T[] items)
|
||||||
|
{
|
||||||
|
if (capacity < 1)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
"Circular buffer cannot have negative or zero capacity.", nameof(capacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(items));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.Length > capacity)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
"Too many items to fit circular buffer", nameof(items));
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffer = new T[capacity];
|
||||||
|
|
||||||
|
Array.Copy(items, _buffer, items.Length);
|
||||||
|
_size = items.Length;
|
||||||
|
|
||||||
|
_start = 0;
|
||||||
|
_end = _size == capacity ? 0 : _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Capacity
|
||||||
|
{
|
||||||
|
get { return _buffer.Length; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsFull
|
||||||
|
{
|
||||||
|
get { return Count == Capacity; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsEmpty
|
||||||
|
{
|
||||||
|
get { return Count == 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[Obsolete("Use Count property instead")]
|
||||||
|
public int Size => Count;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get { return _size; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[Obsolete("Use First() method instead")]
|
||||||
|
public T Front() => First();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[Obsolete("Use Last() method instead")]
|
||||||
|
public T Back() => Last();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public T First()
|
||||||
|
{
|
||||||
|
ThrowIfEmpty();
|
||||||
|
return _buffer[_start];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public T Last()
|
||||||
|
{
|
||||||
|
ThrowIfEmpty();
|
||||||
|
return _buffer[(_end != 0 ? _end : Capacity) - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public T this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (IsEmpty)
|
||||||
|
{
|
||||||
|
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= _size)
|
||||||
|
{
|
||||||
|
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}", index, _size));
|
||||||
|
}
|
||||||
|
|
||||||
|
int actualIndex = InternalIndex(index);
|
||||||
|
return _buffer[actualIndex];
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (IsEmpty)
|
||||||
|
{
|
||||||
|
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= _size)
|
||||||
|
{
|
||||||
|
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}", index, _size));
|
||||||
|
}
|
||||||
|
|
||||||
|
int actualIndex = InternalIndex(index);
|
||||||
|
_buffer[actualIndex] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void PushBack(T item)
|
||||||
|
{
|
||||||
|
if (IsFull)
|
||||||
|
{
|
||||||
|
_buffer[_end] = item;
|
||||||
|
Increment(ref _end);
|
||||||
|
_start = _end;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_buffer[_end] = item;
|
||||||
|
Increment(ref _end);
|
||||||
|
++_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void PushFront(T item)
|
||||||
|
{
|
||||||
|
if (IsFull)
|
||||||
|
{
|
||||||
|
Decrement(ref _start);
|
||||||
|
_end = _start;
|
||||||
|
_buffer[_start] = item;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Decrement(ref _start);
|
||||||
|
_buffer[_start] = item;
|
||||||
|
++_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public T PopBack()
|
||||||
|
{
|
||||||
|
ThrowIfEmpty("Cannot take elements from an empty buffer.");
|
||||||
|
Decrement(ref _end);
|
||||||
|
var value = _buffer[_start];
|
||||||
|
_buffer[_end] = default(T);
|
||||||
|
--_size;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public T PopFront()
|
||||||
|
{
|
||||||
|
ThrowIfEmpty("Cannot take elements from an empty buffer.");
|
||||||
|
var value = _buffer[_start];
|
||||||
|
_buffer[_start] = default(T);
|
||||||
|
Increment(ref _start);
|
||||||
|
--_size;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
// to clear we just reset everything.
|
||||||
|
_start = 0;
|
||||||
|
_end = 0;
|
||||||
|
_size = 0;
|
||||||
|
Array.Clear(_buffer, 0, _buffer.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public T[] ToArray()
|
||||||
|
{
|
||||||
|
T[] newArray = new T[Count];
|
||||||
|
CopyToInternal(newArray, 0);
|
||||||
|
return newArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void CopyTo(T[] array)
|
||||||
|
{
|
||||||
|
if (array is null)
|
||||||
|
throw new ArgumentNullException(nameof(array));
|
||||||
|
|
||||||
|
if (array.Length < _size)
|
||||||
|
throw new ArgumentException($"The number of elements in the source {nameof(CircularBuffer)} is greater than the available " +
|
||||||
|
"number of elements of the destination array.", nameof(array));
|
||||||
|
|
||||||
|
CopyToInternal(array, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void CopyTo(T[] array, int index)
|
||||||
|
{
|
||||||
|
if (array is null)
|
||||||
|
throw new ArgumentNullException(nameof(array));
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(index), $"{nameof(index)} is less than the lower bound of {nameof(array)}.");
|
||||||
|
|
||||||
|
if (array.Length - index < _size)
|
||||||
|
throw new ArgumentException($"The number of elements in the source {nameof(CircularBuffer)} is greater than the available " +
|
||||||
|
"number of elements from index to the end of the destination array.", nameof(array));
|
||||||
|
|
||||||
|
CopyToInternal(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void CopyTo(T[] array, long index)
|
||||||
|
{
|
||||||
|
if (array is null)
|
||||||
|
throw new ArgumentNullException(nameof(array));
|
||||||
|
|
||||||
|
if (index < 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(index), $"{nameof(index)} is less than the lower bound of {nameof(array)}.");
|
||||||
|
|
||||||
|
if (array.LongLength - index < _size)
|
||||||
|
throw new ArgumentException($"The number of elements in the source {nameof(CircularBuffer)} is greater than the available " +
|
||||||
|
"number of elements from index to the end of the destination array.", nameof(array));
|
||||||
|
|
||||||
|
CopyToInternal(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER)
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void CopyTo(Memory<T> memory)
|
||||||
|
{
|
||||||
|
if (memory.Length < _size)
|
||||||
|
throw new ArgumentException($"The number of elements in the source {nameof(CircularBuffer)} is greater than the available " +
|
||||||
|
"number of elements of the destination Memory.", nameof(memory));
|
||||||
|
|
||||||
|
CopyToInternal(memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void CopyTo(Span<T> span)
|
||||||
|
{
|
||||||
|
if (span.Length < _size)
|
||||||
|
throw new ArgumentException($"The number of elements in the source {nameof(CircularBuffer)} is greater than the available " +
|
||||||
|
"number of elements of the destination Span.", nameof(span));
|
||||||
|
|
||||||
|
CopyToInternal(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IList<ArraySegment<T>> ToArraySegments()
|
||||||
|
{
|
||||||
|
return new[] {ArrayOne(), ArrayTwo()};
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER)
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public SpanTuple<T> ToSpan()
|
||||||
|
{
|
||||||
|
return new SpanTuple<T>(SpanOne(), SpanTwo());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public (ReadOnlyMemory<T> A, ReadOnlyMemory<T> B) ToMemory()
|
||||||
|
{
|
||||||
|
return (MemoryOne(), MemoryTwo());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#region IEnumerable<T> implementation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an enumerator that iterates through this buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An enumerator that can be used to iterate this collection.</returns>
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
var segments = ToArraySegments();
|
||||||
|
foreach (ArraySegment<T> segment in segments)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < segment.Count; i++)
|
||||||
|
{
|
||||||
|
yield return segment.Array[segment.Offset + i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IEnumerable implementation
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return (IEnumerator) GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#if (NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER)
|
||||||
|
|
||||||
|
private void CopyToInternal(T[] array, int index)
|
||||||
|
{
|
||||||
|
CopyToInternal(array.AsSpan(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyToInternal(Memory<T> memory)
|
||||||
|
{
|
||||||
|
CopyToInternal(memory.Span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyToInternal(Span<T> span)
|
||||||
|
{
|
||||||
|
var segments = ToSpan();
|
||||||
|
segments.A.CopyTo(span);
|
||||||
|
segments.B.CopyTo(span.Slice(segments.A.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
private void CopyToInternal(T[] array, int index)
|
||||||
|
{
|
||||||
|
var segments = ToArraySegments();
|
||||||
|
var segment = segments[0];
|
||||||
|
Array.Copy(segment.Array, segment.Offset, array, index, segment.Count);
|
||||||
|
index += segment.Count;
|
||||||
|
segment = segments[1];
|
||||||
|
Array.Copy(segment.Array, segment.Offset, array, index, segment.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private void CopyToInternal(T[] array, long index)
|
||||||
|
{
|
||||||
|
var segments = ToArraySegments();
|
||||||
|
var segment = segments[0];
|
||||||
|
Array.Copy(segment.Array, segment.Offset, array, index, segment.Count);
|
||||||
|
index += segment.Count;
|
||||||
|
segment = segments[1];
|
||||||
|
Array.Copy(segment.Array, segment.Offset, array, index, segment.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThrowIfEmpty(string message = "Cannot access an empty buffer.")
|
||||||
|
{
|
||||||
|
if (IsEmpty)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Increments the provided index variable by one, wrapping
|
||||||
|
/// around if necessary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
private void Increment(ref int index)
|
||||||
|
{
|
||||||
|
if (++index == Capacity)
|
||||||
|
{
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decrements the provided index variable by one, wrapping
|
||||||
|
/// around if necessary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
private void Decrement(ref int index)
|
||||||
|
{
|
||||||
|
if (index == 0)
|
||||||
|
{
|
||||||
|
index = Capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the index in the argument to an index in <code>_buffer</code>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The transformed index.
|
||||||
|
/// </returns>
|
||||||
|
/// <param name='index'>
|
||||||
|
/// External index.
|
||||||
|
/// </param>
|
||||||
|
private int InternalIndex(int index)
|
||||||
|
{
|
||||||
|
return _start + (index < (Capacity - _start) ? index : index - Capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// doing ArrayOne and ArrayTwo methods returning ArraySegment<T> as seen here:
|
||||||
|
// http://www.boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/circular_buffer.html#classboost_1_1circular__buffer_1957cccdcb0c4ef7d80a34a990065818d
|
||||||
|
// http://www.boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/circular_buffer.html#classboost_1_1circular__buffer_1f5081a54afbc2dfc1a7fb20329df7d5b
|
||||||
|
// should help a lot with the code.
|
||||||
|
|
||||||
|
#region Array items easy access.
|
||||||
|
|
||||||
|
// The array is composed by at most two non-contiguous segments,
|
||||||
|
// the next two methods allow easy access to those.
|
||||||
|
|
||||||
|
private ArraySegment<T> ArrayOne()
|
||||||
|
{
|
||||||
|
if (IsEmpty)
|
||||||
|
{
|
||||||
|
return new ArraySegment<T>(new T[0]);
|
||||||
|
}
|
||||||
|
else if (_start < _end)
|
||||||
|
{
|
||||||
|
return new ArraySegment<T>(_buffer, _start, _end - _start);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ArraySegment<T>(_buffer, _start, _buffer.Length - _start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArraySegment<T> ArrayTwo()
|
||||||
|
{
|
||||||
|
if (IsEmpty)
|
||||||
|
{
|
||||||
|
return new ArraySegment<T>(new T[0]);
|
||||||
|
}
|
||||||
|
else if (_start < _end)
|
||||||
|
{
|
||||||
|
return new ArraySegment<T>(_buffer, _end, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new ArraySegment<T>(_buffer, 0, _end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER)
|
||||||
|
|
||||||
|
private Span<T> SpanOne()
|
||||||
|
{
|
||||||
|
if (IsEmpty)
|
||||||
|
{
|
||||||
|
return Span<T>.Empty;
|
||||||
|
}
|
||||||
|
else if (_start < _end)
|
||||||
|
{
|
||||||
|
return _buffer.AsSpan(_start, _end - _start);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _buffer.AsSpan(_start, _buffer.Length - _start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Span<T> SpanTwo()
|
||||||
|
{
|
||||||
|
if (IsEmpty)
|
||||||
|
{
|
||||||
|
return Span<T>.Empty;
|
||||||
|
}
|
||||||
|
else if (_start < _end)
|
||||||
|
{
|
||||||
|
return _buffer.AsSpan(_end, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _buffer.AsSpan(0, _end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Memory<T> MemoryOne()
|
||||||
|
{
|
||||||
|
if (IsEmpty)
|
||||||
|
{
|
||||||
|
return Memory<T>.Empty;
|
||||||
|
}
|
||||||
|
else if (_start < _end)
|
||||||
|
{
|
||||||
|
return _buffer.AsMemory(_start, _end - _start);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _buffer.AsMemory(_start, _buffer.Length - _start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Memory<T> MemoryTwo()
|
||||||
|
{
|
||||||
|
if (IsEmpty)
|
||||||
|
{
|
||||||
|
return Memory<T>.Empty;
|
||||||
|
}
|
||||||
|
else if (_start < _end)
|
||||||
|
{
|
||||||
|
return _buffer.AsMemory(_end, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return _buffer.AsMemory(0, _end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -18,4 +18,6 @@ public interface ITab : IAsyncInitable<IContainer>, IDisposable
|
|||||||
void RemoveItemFilter(string name);
|
void RemoveItemFilter(string name);
|
||||||
Task SetSelectedItem(AbsolutePath newSelectedItem);
|
Task SetSelectedItem(AbsolutePath newSelectedItem);
|
||||||
Task ForceSetCurrentLocation(IContainer newLocation);
|
Task ForceSetCurrentLocation(IContainer newLocation);
|
||||||
|
Task GoBackAsync();
|
||||||
|
Task GoForwardAsync();
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Reactive.Subjects;
|
using CircularBuffer;
|
||||||
using DeclarativeProperty;
|
using DeclarativeProperty;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using DynamicData.Binding;
|
|
||||||
using FileTime.App.Core.Services;
|
using FileTime.App.Core.Services;
|
||||||
using FileTime.Core.Helper;
|
using FileTime.Core.Helper;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
@@ -16,10 +15,11 @@ public class Tab : ITab
|
|||||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||||
private readonly ITabEvents _tabEvents;
|
private readonly ITabEvents _tabEvents;
|
||||||
private readonly DeclarativeProperty<IContainer?> _currentLocation = new(null);
|
private readonly DeclarativeProperty<IContainer?> _currentLocation = new(null);
|
||||||
private readonly BehaviorSubject<IContainer?> _currentLocationForced = new(null);
|
private readonly DeclarativeProperty<IContainer?> _currentLocationForced = new(null);
|
||||||
private readonly DeclarativeProperty<AbsolutePath?> _currentRequestItem = new(null);
|
private readonly DeclarativeProperty<AbsolutePath?> _currentRequestItem = new(null);
|
||||||
private readonly ObservableCollection<ItemFilter> _itemFilters = new();
|
private readonly ObservableCollection<ItemFilter> _itemFilters = new();
|
||||||
private readonly DeclarativeProperty<ObservableCollection<ItemFilter>?> _itemFiltersProperty;
|
private readonly CircularBuffer<FullName> _history = new(20);
|
||||||
|
private readonly CircularBuffer<FullName> _future = new(20);
|
||||||
private AbsolutePath? _currentSelectedItemCached;
|
private AbsolutePath? _currentSelectedItemCached;
|
||||||
private PointInTime _currentPointInTime;
|
private PointInTime _currentPointInTime;
|
||||||
private CancellationTokenSource? _setCurrentLocationCancellationTokenSource;
|
private CancellationTokenSource? _setCurrentLocationCancellationTokenSource;
|
||||||
@@ -38,11 +38,16 @@ public class Tab : ITab
|
|||||||
_timelessContentProvider = timelessContentProvider;
|
_timelessContentProvider = timelessContentProvider;
|
||||||
_tabEvents = tabEvents;
|
_tabEvents = tabEvents;
|
||||||
_currentPointInTime = null!;
|
_currentPointInTime = null!;
|
||||||
_itemFiltersProperty = new(_itemFilters);
|
var itemFiltersProperty = new DeclarativeProperty<ObservableCollection<ItemFilter>>(_itemFilters)
|
||||||
|
.Watch<ObservableCollection<ItemFilter>, ItemFilter>();
|
||||||
|
|
||||||
_timelessContentProvider.CurrentPointInTime.Subscribe(p => _currentPointInTime = p);
|
_timelessContentProvider.CurrentPointInTime.Subscribe(p => _currentPointInTime = p);
|
||||||
|
|
||||||
CurrentLocation = _currentLocation;
|
CurrentLocation = DeclarativePropertyHelpers.Merge(
|
||||||
|
_currentLocation.DistinctUntilChanged(),
|
||||||
|
_currentLocationForced
|
||||||
|
);
|
||||||
|
|
||||||
CurrentLocation.Subscribe((c, _) =>
|
CurrentLocation.Subscribe((c, _) =>
|
||||||
{
|
{
|
||||||
if (_currentSelectedItemCached is not null)
|
if (_currentSelectedItemCached is not null)
|
||||||
@@ -55,7 +60,7 @@ public class Tab : ITab
|
|||||||
|
|
||||||
CurrentItems = DeclarativePropertyHelpers.CombineLatest(
|
CurrentItems = DeclarativePropertyHelpers.CombineLatest(
|
||||||
CurrentLocation,
|
CurrentLocation,
|
||||||
_itemFiltersProperty.Watch<ObservableCollection<ItemFilter>, ItemFilter>(),
|
itemFiltersProperty,
|
||||||
(container, filters) =>
|
(container, filters) =>
|
||||||
{
|
{
|
||||||
ObservableCollection<IItem>? items = null;
|
ObservableCollection<IItem>? items = null;
|
||||||
@@ -101,15 +106,6 @@ public class Tab : ITab
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void UpdateConsumer<T>(ObservableCollection<T>? collection, ref OcConsumer? consumer)
|
|
||||||
{
|
|
||||||
if (collection is not IComputing computing) return;
|
|
||||||
|
|
||||||
consumer?.Dispose();
|
|
||||||
consumer = new OcConsumer();
|
|
||||||
computing.For(consumer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IItem MapItem(AbsolutePath item)
|
private static IItem MapItem(AbsolutePath item)
|
||||||
{
|
{
|
||||||
var t = Task.Run(async () => await item.ResolveAsync(true));
|
var t = Task.Run(async () => await item.ResolveAsync(true));
|
||||||
@@ -117,15 +113,9 @@ public class Tab : ITab
|
|||||||
return t.Result;
|
return t.Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SortExpressionComparer<IItem> SortItems()
|
|
||||||
//TODO: Order
|
|
||||||
=> SortExpressionComparer<IItem>
|
|
||||||
.Ascending(i => i.Type)
|
|
||||||
.ThenByAscending(i => i.DisplayName.ToLower());
|
|
||||||
|
|
||||||
|
|
||||||
public async Task InitAsync(IContainer currentLocation)
|
public async Task InitAsync(IContainer currentLocation)
|
||||||
=> await _currentLocation.SetValue(currentLocation);
|
=> await SetCurrentLocation(currentLocation);
|
||||||
|
|
||||||
private AbsolutePath? GetSelectedItemByItems(IReadOnlyCollection<IItem> items)
|
private AbsolutePath? GetSelectedItemByItems(IReadOnlyCollection<IItem> items)
|
||||||
{
|
{
|
||||||
@@ -158,9 +148,24 @@ public class Tab : ITab
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetCurrentLocation(IContainer newLocation)
|
public async Task SetCurrentLocation(IContainer newLocation)
|
||||||
|
{
|
||||||
|
_future.Clear();
|
||||||
|
await SetCurrentLocation(newLocation, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SetCurrentLocation(IContainer newLocation, bool addToHistory)
|
||||||
{
|
{
|
||||||
_setCurrentLocationCancellationTokenSource?.Cancel();
|
_setCurrentLocationCancellationTokenSource?.Cancel();
|
||||||
_setCurrentLocationCancellationTokenSource = new CancellationTokenSource();
|
_setCurrentLocationCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
if (addToHistory
|
||||||
|
&& newLocation.FullName is { } fullName
|
||||||
|
&& (_history.Count == 0
|
||||||
|
|| _history.Last() != fullName))
|
||||||
|
{
|
||||||
|
_history.PushFront(fullName);
|
||||||
|
}
|
||||||
|
|
||||||
await _currentLocation.SetValue(newLocation, _setCurrentLocationCancellationTokenSource.Token);
|
await _currentLocation.SetValue(newLocation, _setCurrentLocationCancellationTokenSource.Token);
|
||||||
|
|
||||||
if (newLocation.FullName != null)
|
if (newLocation.FullName != null)
|
||||||
@@ -173,7 +178,7 @@ public class Tab : ITab
|
|||||||
{
|
{
|
||||||
_setCurrentLocationCancellationTokenSource?.Cancel();
|
_setCurrentLocationCancellationTokenSource?.Cancel();
|
||||||
_setCurrentLocationCancellationTokenSource = new CancellationTokenSource();
|
_setCurrentLocationCancellationTokenSource = new CancellationTokenSource();
|
||||||
await _currentLocation.SetValue(newLocation, _setCurrentLocationCancellationTokenSource.Token);
|
await _currentLocationForced.SetValue(newLocation, _setCurrentLocationCancellationTokenSource.Token);
|
||||||
|
|
||||||
if (newLocation.FullName != null)
|
if (newLocation.FullName != null)
|
||||||
{
|
{
|
||||||
@@ -181,6 +186,32 @@ public class Tab : ITab
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task GoBackAsync()
|
||||||
|
{
|
||||||
|
if (_history.Count < 2) return;
|
||||||
|
|
||||||
|
var currentLocationFullName = _history.PopFront();
|
||||||
|
_future.PushFront(currentLocationFullName);
|
||||||
|
|
||||||
|
var lastLocationFullName = _history.First();
|
||||||
|
var container = (IContainer) await _timelessContentProvider.GetItemByFullNameAsync(
|
||||||
|
lastLocationFullName,
|
||||||
|
PointInTime.Present);
|
||||||
|
await SetCurrentLocation(container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task GoForwardAsync()
|
||||||
|
{
|
||||||
|
if (_future.Count == 0) return;
|
||||||
|
|
||||||
|
var fullName = _future.PopFront();
|
||||||
|
_history.PushFront(fullName);
|
||||||
|
var container = (IContainer) await _timelessContentProvider.GetItemByFullNameAsync(
|
||||||
|
fullName,
|
||||||
|
PointInTime.Present);
|
||||||
|
await SetCurrentLocation(container, false);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SetSelectedItem(AbsolutePath newSelectedItem)
|
public async Task SetSelectedItem(AbsolutePath newSelectedItem)
|
||||||
{
|
{
|
||||||
_setCurrentItemCancellationTokenSource?.Cancel();
|
_setCurrentItemCancellationTokenSource?.Cancel();
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ public static class MainConfiguration
|
|||||||
//new CommandBindingConfiguration(ConfigCommand.Edit, new KeyConfig(Key.F4)),
|
//new CommandBindingConfiguration(ConfigCommand.Edit, new KeyConfig(Key.F4)),
|
||||||
new(EnterRapidTravelCommand.CommandName, new KeyConfig(Key.OemComma, shift: true)),
|
new(EnterRapidTravelCommand.CommandName, new KeyConfig(Key.OemComma, shift: true)),
|
||||||
new(EnterRapidTravelCommand.CommandName, new KeyConfig(Key.OemQuestion, shift: true)),
|
new(EnterRapidTravelCommand.CommandName, new KeyConfig(Key.OemQuestion, shift: true)),
|
||||||
//new CommandBindingConfiguration(ConfigCommand.FindByName, new[] { Key.F, Key.N }),
|
new(GoBackCommand.CommandName, new KeyConfig(Key.Left, alt: true)),
|
||||||
//new CommandBindingConfiguration(ConfigCommand.FindByNameRegex, new[] { Key.F, Key.R }),
|
|
||||||
new(GoByFrequencyCommand.CommandName, Key.Z),
|
new(GoByFrequencyCommand.CommandName, Key.Z),
|
||||||
|
new(GoForwardCommand.CommandName, new KeyConfig(Key.Right, alt: true)),
|
||||||
new(GoToHomeCommand.CommandName, new[] {Key.G, Key.H}),
|
new(GoToHomeCommand.CommandName, new[] {Key.G, Key.H}),
|
||||||
new(GoToPathCommand.CommandName, new KeyConfig(Key.L, ctrl: true)),
|
new(GoToPathCommand.CommandName, new KeyConfig(Key.L, ctrl: true)),
|
||||||
new(GoToPathCommand.CommandName, new[] {Key.G, Key.P}),
|
new(GoToPathCommand.CommandName, new[] {Key.G, Key.P}),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Threading;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.GuiApp.App.ViewModels;
|
using FileTime.GuiApp.App.ViewModels;
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@ namespace FileTime.GuiApp.App.Converters;
|
|||||||
public class NamePartShrinkerConverter : IMultiValueConverter
|
public class NamePartShrinkerConverter : IMultiValueConverter
|
||||||
{
|
{
|
||||||
private const int PixelPerChar = 8;
|
private const int PixelPerChar = 8;
|
||||||
|
|
||||||
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
if (values.Count > 0 && values[0] is IList<ItemNamePart> nameParts)
|
if (values.Count > 0 && values[0] is IList<ItemNamePart> nameParts)
|
||||||
@@ -20,8 +22,13 @@ public class NamePartShrinkerConverter : IMultiValueConverter
|
|||||||
newNameParts = GetNamePartsForWidth(nameParts, width - attributeWidth);
|
newNameParts = GetNamePartsForWidth(nameParts, width - attributeWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
return newNameParts.Select(p => new ItemNamePartViewModel(p.Text, p.IsSpecial ? TextDecorations.Underline : null)).ToList();
|
var result = /* Dispatcher.UIThread.Invoke(() => */
|
||||||
|
newNameParts.Select(p => new ItemNamePartViewModel(p.Text, p.IsSpecial ? TextDecorations.Underline : null)).ToList();
|
||||||
|
/* ); */
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +82,7 @@ public class NamePartShrinkerConverter : IMultiValueConverter
|
|||||||
proposedText = proposedText[0..^1];
|
proposedText = proposedText[0..^1];
|
||||||
trimmed = true;
|
trimmed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
newNameParts[trimmedIndex] = new ItemNamePart(proposedText + (trimmed ? "..." : ""));
|
newNameParts[trimmedIndex] = new ItemNamePart(proposedText + (trimmed ? "..." : ""));
|
||||||
if (trimmed) break;
|
if (trimmed) break;
|
||||||
}
|
}
|
||||||
@@ -100,6 +108,7 @@ public class NamePartShrinkerConverter : IMultiValueConverter
|
|||||||
if (!string.IsNullOrWhiteSpace(proposedText)) newNameParts.Add(new ItemNamePart(proposedText, namePart.IsSpecial));
|
if (!string.IsNullOrWhiteSpace(proposedText)) newNameParts.Add(new ItemNamePart(proposedText, namePart.IsSpecial));
|
||||||
if (trimmed) break;
|
if (trimmed) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newNameParts.Last().IsSpecial)
|
if (newNameParts.Last().IsSpecial)
|
||||||
{
|
{
|
||||||
newNameParts.Add(new ItemNamePart("..."));
|
newNameParts.Add(new ItemNamePart("..."));
|
||||||
@@ -109,6 +118,7 @@ public class NamePartShrinkerConverter : IMultiValueConverter
|
|||||||
var last = newNameParts.Last();
|
var last = newNameParts.Last();
|
||||||
newNameParts[^1] = new ItemNamePart(last.Text + "...");
|
newNameParts[^1] = new ItemNamePart(last.Text + "...");
|
||||||
}
|
}
|
||||||
|
|
||||||
return newNameParts;
|
return newNameParts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,6 +67,11 @@
|
|||||||
<Setter Property="Background" Value="{DynamicResource AppBackgroundColor}" />
|
<Setter Property="Background" Value="{DynamicResource AppBackgroundColor}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.PathPresenterItem:pointerover">
|
||||||
|
<Setter Property="TextDecorations" Value="Underline" />
|
||||||
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Border.SelectedTimelineCommand">
|
<Style Selector="Border.SelectedTimelineCommand">
|
||||||
<Setter Property="BorderBrush" Value="{DynamicResource ForegroundBrush}" />
|
<Setter Property="BorderBrush" Value="{DynamicResource ForegroundBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|||||||
@@ -43,12 +43,22 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
|||||||
_appState.SelectedTab.Subscribe(t => _selectedTab = t);
|
_appState.SelectedTab.Subscribe(t => _selectedTab = t);
|
||||||
|
|
||||||
_openModals = modalService.OpenModals.ToBindedCollection();
|
_openModals = modalService.OpenModals.ToBindedCollection();
|
||||||
|
|
||||||
|
_appState.RapidTravelTextDebounced.Subscribe((v, _) =>
|
||||||
|
{
|
||||||
|
if (_selectedTab?.Tab is not { } tab) return Task.CompletedTask;
|
||||||
|
tab.RemoveItemFilter(RapidTravelFilterName);
|
||||||
|
|
||||||
|
if (v is null) return Task.CompletedTask;
|
||||||
|
|
||||||
|
tab.AddItemFilter(new ItemFilter(RapidTravelFilterName, i => i.Name.ToLower().Contains(v)));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task HandleInputKey(Key key, SpecialKeysStatus specialKeysStatus, Action<bool> setHandled)
|
public async Task HandleInputKey(Key key, SpecialKeysStatus specialKeysStatus, Action<bool> setHandled)
|
||||||
{
|
{
|
||||||
var keyString = key.ToString();
|
var keyString = key.ToString();
|
||||||
var updateRapidTravelFilter = false;
|
|
||||||
|
|
||||||
if (key == Key.Escape)
|
if (key == Key.Escape)
|
||||||
{
|
{
|
||||||
@@ -70,7 +80,6 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
|||||||
await _appState.RapidTravelText.SetValue(
|
await _appState.RapidTravelText.SetValue(
|
||||||
_appState.RapidTravelText.Value![..^1]
|
_appState.RapidTravelText.Value![..^1]
|
||||||
);
|
);
|
||||||
updateRapidTravelFilter = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (keyString.Length == 1)
|
else if (keyString.Length == 1)
|
||||||
@@ -79,11 +88,10 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
|||||||
await _appState.RapidTravelText.SetValue(
|
await _appState.RapidTravelText.SetValue(
|
||||||
_appState.RapidTravelText.Value + keyString.ToLower()
|
_appState.RapidTravelText.Value + keyString.ToLower()
|
||||||
);
|
);
|
||||||
updateRapidTravelFilter = true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var currentKeyAsList = new List<KeyConfig>() { new KeyConfig(key) };
|
var currentKeyAsList = new List<KeyConfig>() {new KeyConfig(key)};
|
||||||
var selectedCommandBinding = _keyboardConfigurationService.UniversalCommandBindings.FirstOrDefault(c => c.Keys.AreKeysEqual(currentKeyAsList));
|
var selectedCommandBinding = _keyboardConfigurationService.UniversalCommandBindings.FirstOrDefault(c => c.Keys.AreKeysEqual(currentKeyAsList));
|
||||||
if (selectedCommandBinding != null)
|
if (selectedCommandBinding != null)
|
||||||
{
|
{
|
||||||
@@ -91,42 +99,6 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
|||||||
await CallCommandAsync(_identifiableUserCommandService.GetCommand(selectedCommandBinding.Command));
|
await CallCommandAsync(_identifiableUserCommandService.GetCommand(selectedCommandBinding.Command));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateRapidTravelFilter)
|
|
||||||
{
|
|
||||||
if (_selectedTab?.Tab is not ITab tab) return;
|
|
||||||
|
|
||||||
tab.RemoveItemFilter(RapidTravelFilterName);
|
|
||||||
tab.AddItemFilter(new ItemFilter(RapidTravelFilterName, i => i.Name.ToLower().Contains(_appState.RapidTravelText.Value!)));
|
|
||||||
/*var currentLocation = await _appState.SelectedTab.CurrentLocation.Container.WithoutVirtualContainer(MainPageViewModel.RAPIDTRAVEL);
|
|
||||||
var newLocation = new VirtualContainer(
|
|
||||||
currentLocation,
|
|
||||||
new List<Func<IEnumerable<IContainer>, IEnumerable<IContainer>>>()
|
|
||||||
{
|
|
||||||
container => container.Where(c => c.Name.ToLower().Contains(_appState.RapidTravelText))
|
|
||||||
},
|
|
||||||
new List<Func<IEnumerable<IElement>, IEnumerable<IElement>>>()
|
|
||||||
{
|
|
||||||
element => element.Where(e => e.Name.ToLower().Contains(_appState.RapidTravelText))
|
|
||||||
},
|
|
||||||
virtualContainerName: MainPageViewModel.RAPIDTRAVEL
|
|
||||||
);
|
|
||||||
|
|
||||||
await newLocation.Init();
|
|
||||||
|
|
||||||
await _appState.SelectedTab.OpenContainer(newLocation);
|
|
||||||
|
|
||||||
var selectedItemName = _appState.SelectedTab.SelectedItem?.Item.Name;
|
|
||||||
var currentLocationItems = await _appState.SelectedTab.CurrentLocation.GetItems();
|
|
||||||
if (currentLocationItems.FirstOrDefault(i => string.Equals(i.Item.Name, _appState.RapidTravelText, StringComparison.OrdinalIgnoreCase)) is IItemViewModel matchItem)
|
|
||||||
{
|
|
||||||
await _appState.SelectedTab.SetCurrentSelectedItem(matchItem.Item);
|
|
||||||
}
|
|
||||||
else if (!currentLocationItems.Select(i => i.Item.Name).Any(n => n == selectedItemName))
|
|
||||||
{
|
|
||||||
await _appState.SelectedTab.MoveCursorToFirst();
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CallCommandAsync(IUserCommand command)
|
private async Task CallCommandAsync(IUserCommand command)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using FileTime.App.CommandPalette.Services;
|
using DeclarativeProperty;
|
||||||
|
using FileTime.App.CommandPalette.Services;
|
||||||
using FileTime.App.Core.Services;
|
using FileTime.App.Core.Services;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
using FileTime.App.FrequencyNavigation.Services;
|
using FileTime.App.FrequencyNavigation.Services;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
|
d:DesignHeight="450"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
mc:Ignorable="d"
|
||||||
x:Class="FileTime.GuiApp.App.Views.PathPresenter"
|
x:Class="FileTime.GuiApp.App.Views.PathPresenter"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:coremodels="using:FileTime.Core.Models"
|
xmlns:coremodels="using:FileTime.Core.Models"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
d:DesignHeight="450"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
d:DesignWidth="800"
|
|
||||||
mc:Ignorable="d">
|
|
||||||
<ItemsControl ItemsSource="{Binding Converter={StaticResource SplitStringConverter}, ConverterParameter={x:Static coremodels:Constants.SeparatorChar}}">
|
<ItemsControl ItemsSource="{Binding Converter={StaticResource SplitStringConverter}, ConverterParameter={x:Static coremodels:Constants.SeparatorChar}}">
|
||||||
<ItemsControl.ItemsPanel>
|
<ItemsControl.ItemsPanel>
|
||||||
<ItemsPanelTemplate>
|
<ItemsPanelTemplate>
|
||||||
@@ -17,10 +17,13 @@
|
|||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBlock Text="{Binding}" />
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="5,0,5,0"
|
Classes="PathPresenterItem"
|
||||||
|
PointerPressed="InputElement_OnPointerPressed"
|
||||||
|
Text="{Binding}" />
|
||||||
|
<TextBlock
|
||||||
Foreground="{DynamicResource LightForegroundBrush}"
|
Foreground="{DynamicResource LightForegroundBrush}"
|
||||||
|
Margin="5,0,5,0"
|
||||||
Text="/" />
|
Text="/" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|||||||
@@ -1,11 +1,53 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using FileTime.App.Core.Services;
|
||||||
|
using FileTime.App.Core.UserCommand;
|
||||||
|
using FileTime.Core.Enums;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Timeline;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace FileTime.GuiApp.App.Views;
|
namespace FileTime.GuiApp.App.Views;
|
||||||
|
|
||||||
public partial class PathPresenter : UserControl
|
public partial class PathPresenter : UserControl
|
||||||
{
|
{
|
||||||
|
private readonly Lazy<ILogger<PathPresenter>> _logger;
|
||||||
|
|
||||||
public PathPresenter()
|
public PathPresenter()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
_logger = new Lazy<ILogger<PathPresenter>>(
|
||||||
|
() => DI.ServiceProvider.GetRequiredService<ILogger<PathPresenter>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void InputElement_OnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed
|
||||||
|
&& DataContext is string fullPath
|
||||||
|
&& sender is TextBlock textBlock)
|
||||||
|
{
|
||||||
|
var pathPart = textBlock.Text;
|
||||||
|
var path = fullPath[..(fullPath.IndexOf(pathPart) + pathPart.Length)];
|
||||||
|
var timelessContentProvider = DI.ServiceProvider.GetRequiredService<ITimelessContentProvider>();
|
||||||
|
var userCommandHandlerService = DI.ServiceProvider.GetRequiredService<IUserCommandHandlerService>();
|
||||||
|
await userCommandHandlerService.HandleCommandAsync(
|
||||||
|
new OpenContainerCommand(
|
||||||
|
new AbsolutePath(
|
||||||
|
timelessContentProvider,
|
||||||
|
PointInTime.Present,
|
||||||
|
new FullName(path),
|
||||||
|
AbsolutePathType.Container)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.Value.LogError(exception, "Failed to open container");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
|
public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
|
||||||
{
|
{
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
private readonly Func<TimeSpan> _interval;
|
private readonly Func<T?, TimeSpan> _interval;
|
||||||
private DateTime _startTime = DateTime.MinValue;
|
private DateTime _startTime = DateTime.MinValue;
|
||||||
private T? _nextValue;
|
private T? _nextValue;
|
||||||
private CancellationToken _nextCancellationToken;
|
private CancellationToken _nextCancellationToken;
|
||||||
@@ -13,7 +13,7 @@ public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
|
|||||||
|
|
||||||
public DebounceProperty(
|
public DebounceProperty(
|
||||||
IDeclarativeProperty<T> from,
|
IDeclarativeProperty<T> from,
|
||||||
Func<TimeSpan> interval,
|
Func<T?, TimeSpan> interval,
|
||||||
Action<T?>? setValueHook = null) : base(from.Value, setValueHook)
|
Action<T?>? setValueHook = null) : base(from.Value, setValueHook)
|
||||||
{
|
{
|
||||||
_interval = interval;
|
_interval = interval;
|
||||||
@@ -46,7 +46,7 @@ public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
|
|||||||
|
|
||||||
private async Task StartDebounceTask()
|
private async Task StartDebounceTask()
|
||||||
{
|
{
|
||||||
while (DateTime.Now - _startTime < _interval())
|
while (DateTime.Now - _startTime < _interval(_nextValue))
|
||||||
{
|
{
|
||||||
await Task.Delay(WaitInterval);
|
await Task.Delay(WaitInterval);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ public static class DeclarativePropertyHelpers
|
|||||||
Func<T1, T2, T3, Task<TResult>> func,
|
Func<T1, T2, T3, Task<TResult>> func,
|
||||||
Action<TResult?>? setValueHook = null)
|
Action<TResult?>? setValueHook = null)
|
||||||
=> new(prop1, prop2, prop3, func, setValueHook);
|
=> new(prop1, prop2, prop3, func, setValueHook);
|
||||||
|
|
||||||
|
public static MergeProperty<T> Merge<T>(params IDeclarativeProperty<T>[] props)
|
||||||
|
=> new(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DeclarativeProperty<T> : DeclarativePropertyBase<T>
|
public sealed class DeclarativeProperty<T> : DeclarativePropertyBase<T>
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ namespace DeclarativeProperty;
|
|||||||
public static class DeclarativePropertyExtensions
|
public static class DeclarativePropertyExtensions
|
||||||
{
|
{
|
||||||
public static IDeclarativeProperty<T> Debounce<T>(this IDeclarativeProperty<T> from, TimeSpan interval, bool resetTimer = false)
|
public static IDeclarativeProperty<T> Debounce<T>(this IDeclarativeProperty<T> from, TimeSpan interval, bool resetTimer = false)
|
||||||
=> new DebounceProperty<T>(from, () => interval) {ResetTimer = resetTimer};
|
=> new DebounceProperty<T>(from, _ => interval) {ResetTimer = resetTimer};
|
||||||
|
|
||||||
public static IDeclarativeProperty<T> Debounce<T>(this IDeclarativeProperty<T> from, Func<TimeSpan> interval, bool resetTimer = false)
|
public static IDeclarativeProperty<T> Debounce<T>(this IDeclarativeProperty<T> from, Func<T?, TimeSpan> interval, bool resetTimer = false)
|
||||||
=> new DebounceProperty<T>(from, interval) {ResetTimer = resetTimer};
|
=> new DebounceProperty<T>(from, interval) {ResetTimer = resetTimer};
|
||||||
|
|
||||||
public static IDeclarativeProperty<T> Throttle<T>(this IDeclarativeProperty<T> from, TimeSpan interval)
|
public static IDeclarativeProperty<T> Throttle<T>(this IDeclarativeProperty<T> from, TimeSpan interval)
|
||||||
|
|||||||
17
src/Library/DeclarativeProperty/MergeProperty.cs
Normal file
17
src/Library/DeclarativeProperty/MergeProperty.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace DeclarativeProperty;
|
||||||
|
|
||||||
|
public class MergeProperty<T> : DeclarativePropertyBase<T>
|
||||||
|
{
|
||||||
|
public MergeProperty(params IDeclarativeProperty<T>[] props)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(props);
|
||||||
|
|
||||||
|
foreach (var prop in props)
|
||||||
|
{
|
||||||
|
prop.Subscribe(UpdateAsync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateAsync(T? newValue, CancellationToken token)
|
||||||
|
=> await SetNewValueAsync(newValue, token);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user