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; }
|
||||
IDeclarativeProperty<ViewMode> ViewMode { get; }
|
||||
DeclarativeProperty<string?> RapidTravelText { get; }
|
||||
IDeclarativeProperty<string?> RapidTravelTextDebounced { get; }
|
||||
ITimelineViewModel TimelineViewModel { get; }
|
||||
IDeclarativeProperty<string?> ContainerStatus { get; }
|
||||
|
||||
|
||||
@@ -68,7 +68,9 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
||||
new TypeUserCommandHandler<CloseTabCommand>(CloseTab),
|
||||
new TypeUserCommandHandler<EnterRapidTravelCommand>(EnterRapidTravel),
|
||||
new TypeUserCommandHandler<ExitRapidTravelCommand>(ExitRapidTravel),
|
||||
new TypeUserCommandHandler<GoBackCommand>(GoBack),
|
||||
new TypeUserCommandHandler<GoByFrequencyCommand>(GoByFrequency),
|
||||
new TypeUserCommandHandler<GoForwardCommand>(GoForward),
|
||||
new TypeUserCommandHandler<GoToHomeCommand>(GoToHome),
|
||||
new TypeUserCommandHandler<GoToPathCommand>(GoToPath),
|
||||
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)
|
||||
{
|
||||
var item = command.Item ?? _currentSelectedItem?.Value;
|
||||
|
||||
@@ -22,7 +22,9 @@ public class DefaultIdentifiableCommandHandlerRegister : IStartupHandler
|
||||
AddUserCommand(DeleteCommand.SoftDelete);
|
||||
AddUserCommand(EnterRapidTravelCommand.Instance);
|
||||
AddUserCommand(ExitRapidTravelCommand.Instance);
|
||||
AddUserCommand(GoBackCommand.Instance);
|
||||
AddUserCommand(GoByFrequencyCommand.Instance);
|
||||
AddUserCommand(GoForwardCommand.Instance);
|
||||
AddUserCommand(GoToHomeCommand.Instance);
|
||||
AddUserCommand(GoToPathCommand.Instance);
|
||||
AddUserCommand(GoToProviderCommand.Instance);
|
||||
|
||||
@@ -26,12 +26,21 @@ public abstract partial class AppStateBase : IAppState
|
||||
|
||||
public IDeclarativeProperty<ITabViewModel?> SelectedTab { get; private set; }
|
||||
public DeclarativeProperty<string?> RapidTravelText { get; private set; }
|
||||
public IDeclarativeProperty<string?> RapidTravelTextDebounced { get; private set; }
|
||||
|
||||
public IDeclarativeProperty<string?> ContainerStatus { get; private set; }
|
||||
|
||||
partial void OnInitialize()
|
||||
{
|
||||
RapidTravelText = new("");
|
||||
RapidTravelTextDebounced = RapidTravelText
|
||||
.Debounce(v =>
|
||||
string.IsNullOrEmpty(v)
|
||||
? TimeSpan.Zero
|
||||
: TimeSpan.FromMilliseconds(200)
|
||||
, resetTimer: true
|
||||
);
|
||||
|
||||
ViewMode = _viewMode;
|
||||
|
||||
SearchText = _searchText.AsObservable();
|
||||
|
||||
@@ -53,7 +53,7 @@ public abstract partial class ItemViewModel : IItemViewModel
|
||||
|
||||
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.SelectedTab.Value?.CurrentLocation.Value?.Provider is IItemNameConverterProvider nameConverterProvider
|
||||
? (IReadOnlyList<ItemNamePart>) await nameConverterProvider.GetItemNamePartsAsync(item)
|
||||
|
||||
@@ -138,7 +138,7 @@ public partial class TabViewModel : ITabViewModel
|
||||
CurrentSelectedItemAsContainer = CurrentSelectedItem.Map(i => i as IContainerViewModel);
|
||||
|
||||
SelectedsChildren = CurrentSelectedItem
|
||||
.Debounce(() => _refreshSmoothnessCalculator.RefreshDelay, resetTimer: true)
|
||||
.Debounce(_ => _refreshSmoothnessCalculator.RefreshDelay, resetTimer: true)
|
||||
.DistinctUntilChanged()
|
||||
.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);
|
||||
Task SetSelectedItem(AbsolutePath newSelectedItem);
|
||||
Task ForceSetCurrentLocation(IContainer newLocation);
|
||||
Task GoBackAsync();
|
||||
Task GoForwardAsync();
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Subjects;
|
||||
using CircularBuffer;
|
||||
using DeclarativeProperty;
|
||||
using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using FileTime.App.Core.Services;
|
||||
using FileTime.Core.Helper;
|
||||
using FileTime.Core.Models;
|
||||
@@ -16,10 +15,11 @@ public class Tab : ITab
|
||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||
private readonly ITabEvents _tabEvents;
|
||||
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 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 PointInTime _currentPointInTime;
|
||||
private CancellationTokenSource? _setCurrentLocationCancellationTokenSource;
|
||||
@@ -38,11 +38,16 @@ public class Tab : ITab
|
||||
_timelessContentProvider = timelessContentProvider;
|
||||
_tabEvents = tabEvents;
|
||||
_currentPointInTime = null!;
|
||||
_itemFiltersProperty = new(_itemFilters);
|
||||
var itemFiltersProperty = new DeclarativeProperty<ObservableCollection<ItemFilter>>(_itemFilters)
|
||||
.Watch<ObservableCollection<ItemFilter>, ItemFilter>();
|
||||
|
||||
_timelessContentProvider.CurrentPointInTime.Subscribe(p => _currentPointInTime = p);
|
||||
|
||||
CurrentLocation = _currentLocation;
|
||||
CurrentLocation = DeclarativePropertyHelpers.Merge(
|
||||
_currentLocation.DistinctUntilChanged(),
|
||||
_currentLocationForced
|
||||
);
|
||||
|
||||
CurrentLocation.Subscribe((c, _) =>
|
||||
{
|
||||
if (_currentSelectedItemCached is not null)
|
||||
@@ -55,7 +60,7 @@ public class Tab : ITab
|
||||
|
||||
CurrentItems = DeclarativePropertyHelpers.CombineLatest(
|
||||
CurrentLocation,
|
||||
_itemFiltersProperty.Watch<ObservableCollection<ItemFilter>, ItemFilter>(),
|
||||
itemFiltersProperty,
|
||||
(container, filters) =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
var t = Task.Run(async () => await item.ResolveAsync(true));
|
||||
@@ -117,15 +113,9 @@ public class Tab : ITab
|
||||
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)
|
||||
=> await _currentLocation.SetValue(currentLocation);
|
||||
=> await SetCurrentLocation(currentLocation);
|
||||
|
||||
private AbsolutePath? GetSelectedItemByItems(IReadOnlyCollection<IItem> items)
|
||||
{
|
||||
@@ -158,9 +148,24 @@ public class Tab : ITab
|
||||
}
|
||||
|
||||
public async Task SetCurrentLocation(IContainer newLocation)
|
||||
{
|
||||
_future.Clear();
|
||||
await SetCurrentLocation(newLocation, true);
|
||||
}
|
||||
|
||||
private async Task SetCurrentLocation(IContainer newLocation, bool addToHistory)
|
||||
{
|
||||
_setCurrentLocationCancellationTokenSource?.Cancel();
|
||||
_setCurrentLocationCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
if (addToHistory
|
||||
&& newLocation.FullName is { } fullName
|
||||
&& (_history.Count == 0
|
||||
|| _history.Last() != fullName))
|
||||
{
|
||||
_history.PushFront(fullName);
|
||||
}
|
||||
|
||||
await _currentLocation.SetValue(newLocation, _setCurrentLocationCancellationTokenSource.Token);
|
||||
|
||||
if (newLocation.FullName != null)
|
||||
@@ -173,7 +178,7 @@ public class Tab : ITab
|
||||
{
|
||||
_setCurrentLocationCancellationTokenSource?.Cancel();
|
||||
_setCurrentLocationCancellationTokenSource = new CancellationTokenSource();
|
||||
await _currentLocation.SetValue(newLocation, _setCurrentLocationCancellationTokenSource.Token);
|
||||
await _currentLocationForced.SetValue(newLocation, _setCurrentLocationCancellationTokenSource.Token);
|
||||
|
||||
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)
|
||||
{
|
||||
_setCurrentItemCancellationTokenSource?.Cancel();
|
||||
|
||||
@@ -63,9 +63,9 @@ public static class MainConfiguration
|
||||
//new CommandBindingConfiguration(ConfigCommand.Edit, new KeyConfig(Key.F4)),
|
||||
new(EnterRapidTravelCommand.CommandName, new KeyConfig(Key.OemComma, shift: true)),
|
||||
new(EnterRapidTravelCommand.CommandName, new KeyConfig(Key.OemQuestion, shift: true)),
|
||||
//new CommandBindingConfiguration(ConfigCommand.FindByName, new[] { Key.F, Key.N }),
|
||||
//new CommandBindingConfiguration(ConfigCommand.FindByNameRegex, new[] { Key.F, Key.R }),
|
||||
new(GoBackCommand.CommandName, new KeyConfig(Key.Left, alt: true)),
|
||||
new(GoByFrequencyCommand.CommandName, Key.Z),
|
||||
new(GoForwardCommand.CommandName, new KeyConfig(Key.Right, alt: true)),
|
||||
new(GoToHomeCommand.CommandName, new[] {Key.G, Key.H}),
|
||||
new(GoToPathCommand.CommandName, new KeyConfig(Key.L, ctrl: true)),
|
||||
new(GoToPathCommand.CommandName, new[] {Key.G, Key.P}),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.GuiApp.App.ViewModels;
|
||||
|
||||
@@ -9,6 +10,7 @@ namespace FileTime.GuiApp.App.Converters;
|
||||
public class NamePartShrinkerConverter : IMultiValueConverter
|
||||
{
|
||||
private const int PixelPerChar = 8;
|
||||
|
||||
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Count > 0 && values[0] is IList<ItemNamePart> nameParts)
|
||||
@@ -20,8 +22,13 @@ public class NamePartShrinkerConverter : IMultiValueConverter
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -75,6 +82,7 @@ public class NamePartShrinkerConverter : IMultiValueConverter
|
||||
proposedText = proposedText[0..^1];
|
||||
trimmed = true;
|
||||
}
|
||||
|
||||
newNameParts[trimmedIndex] = new ItemNamePart(proposedText + (trimmed ? "..." : ""));
|
||||
if (trimmed) break;
|
||||
}
|
||||
@@ -100,6 +108,7 @@ public class NamePartShrinkerConverter : IMultiValueConverter
|
||||
if (!string.IsNullOrWhiteSpace(proposedText)) newNameParts.Add(new ItemNamePart(proposedText, namePart.IsSpecial));
|
||||
if (trimmed) break;
|
||||
}
|
||||
|
||||
if (newNameParts.Last().IsSpecial)
|
||||
{
|
||||
newNameParts.Add(new ItemNamePart("..."));
|
||||
@@ -109,6 +118,7 @@ public class NamePartShrinkerConverter : IMultiValueConverter
|
||||
var last = newNameParts.Last();
|
||||
newNameParts[^1] = new ItemNamePart(last.Text + "...");
|
||||
}
|
||||
|
||||
return newNameParts;
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,11 @@
|
||||
<Setter Property="Background" Value="{DynamicResource AppBackgroundColor}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="TextBlock.PathPresenterItem:pointerover">
|
||||
<Setter Property="TextDecorations" Value="Underline" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Border.SelectedTimelineCommand">
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource ForegroundBrush}" />
|
||||
</Style>
|
||||
|
||||
@@ -43,12 +43,22 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
||||
_appState.SelectedTab.Subscribe(t => _selectedTab = t);
|
||||
|
||||
_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)
|
||||
{
|
||||
var keyString = key.ToString();
|
||||
var updateRapidTravelFilter = false;
|
||||
|
||||
if (key == Key.Escape)
|
||||
{
|
||||
@@ -70,7 +80,6 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
||||
await _appState.RapidTravelText.SetValue(
|
||||
_appState.RapidTravelText.Value![..^1]
|
||||
);
|
||||
updateRapidTravelFilter = true;
|
||||
}
|
||||
}
|
||||
else if (keyString.Length == 1)
|
||||
@@ -79,11 +88,10 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
||||
await _appState.RapidTravelText.SetValue(
|
||||
_appState.RapidTravelText.Value + keyString.ToLower()
|
||||
);
|
||||
updateRapidTravelFilter = true;
|
||||
}
|
||||
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));
|
||||
if (selectedCommandBinding != null)
|
||||
{
|
||||
@@ -91,42 +99,6 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
||||
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)
|
||||
|
||||
@@ -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.ViewModels;
|
||||
using FileTime.App.FrequencyNavigation.Services;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<UserControl
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d"
|
||||
x:Class="FileTime.GuiApp.App.Views.PathPresenter"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:coremodels="using:FileTime.Core.Models"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ItemsControl ItemsSource="{Binding Converter={StaticResource SplitStringConverter}, ConverterParameter={x:Static coremodels:Constants.SeparatorChar}}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
@@ -17,10 +17,13 @@
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
Classes="PathPresenterItem"
|
||||
PointerPressed="InputElement_OnPointerPressed"
|
||||
Text="{Binding}" />
|
||||
<TextBlock
|
||||
Foreground="{DynamicResource LightForegroundBrush}"
|
||||
Margin="5,0,5,0"
|
||||
Text="/" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -1,11 +1,53 @@
|
||||
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;
|
||||
|
||||
public partial class PathPresenter : UserControl
|
||||
{
|
||||
private readonly Lazy<ILogger<PathPresenter>> _logger;
|
||||
|
||||
public PathPresenter()
|
||||
{
|
||||
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>
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly Func<TimeSpan> _interval;
|
||||
private readonly Func<T?, TimeSpan> _interval;
|
||||
private DateTime _startTime = DateTime.MinValue;
|
||||
private T? _nextValue;
|
||||
private CancellationToken _nextCancellationToken;
|
||||
@@ -13,7 +13,7 @@ public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
|
||||
|
||||
public DebounceProperty(
|
||||
IDeclarativeProperty<T> from,
|
||||
Func<TimeSpan> interval,
|
||||
Func<T?, TimeSpan> interval,
|
||||
Action<T?>? setValueHook = null) : base(from.Value, setValueHook)
|
||||
{
|
||||
_interval = interval;
|
||||
@@ -46,7 +46,7 @@ public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
|
||||
|
||||
private async Task StartDebounceTask()
|
||||
{
|
||||
while (DateTime.Now - _startTime < _interval())
|
||||
while (DateTime.Now - _startTime < _interval(_nextValue))
|
||||
{
|
||||
await Task.Delay(WaitInterval);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ public static class DeclarativePropertyHelpers
|
||||
Func<T1, T2, T3, Task<TResult>> func,
|
||||
Action<TResult?>? setValueHook = null)
|
||||
=> 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>
|
||||
|
||||
@@ -7,9 +7,9 @@ namespace DeclarativeProperty;
|
||||
public static class DeclarativePropertyExtensions
|
||||
{
|
||||
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};
|
||||
|
||||
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