Preview refactor, Console rename form
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
using FileTime.Core.Interactions;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Interactions;
|
||||||
|
|
||||||
|
public partial class DoubleItemNamePartListPreview : IPreviewElement
|
||||||
|
{
|
||||||
|
[Notify] private List<ItemNamePart> _itemNameParts1 = new();
|
||||||
|
[Notify] private List<ItemNamePart> _itemNameParts2 = new();
|
||||||
|
public PreviewType PreviewType => PreviewType.DoubleItemNamePartList;
|
||||||
|
object IPreviewElement.PreviewType => PreviewType;
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
using System.Collections.ObjectModel;
|
|
||||||
using FileTime.Core.Interactions;
|
|
||||||
|
|
||||||
namespace FileTime.App.Core.Interactions;
|
|
||||||
|
|
||||||
public class DoubleTextListPreview : IPreviewElement
|
|
||||||
{
|
|
||||||
public ObservableCollection<DoubleTextPreview> Items { get; } = new();
|
|
||||||
public PreviewType PreviewType { get; } = PreviewType.DoubleTextList;
|
|
||||||
object IPreviewElement.PreviewType => PreviewType;
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
using System.Reactive.Subjects;
|
using System.Reactive.Subjects;
|
||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
|
||||||
namespace FileTime.App.Core.Interactions;
|
namespace FileTime.App.Core.Interactions;
|
||||||
|
|
||||||
public class DoubleTextPreview : IPreviewElement
|
public partial class DoubleTextPreview : IPreviewElement
|
||||||
{
|
{
|
||||||
public IObservable<List<ItemNamePart>> Text1 { get; init; } = new BehaviorSubject<List<ItemNamePart>>(new());
|
[Notify] private string _text1;
|
||||||
public IObservable<List<ItemNamePart>> Text2 { get; init; } = new BehaviorSubject<List<ItemNamePart>>(new());
|
[Notify] private string _text2;
|
||||||
|
|
||||||
public PreviewType PreviewType => PreviewType.DoubleTextList;
|
public PreviewType PreviewType => PreviewType.DoubleText;
|
||||||
object IPreviewElement.PreviewType => PreviewType;
|
object IPreviewElement.PreviewType => PreviewType;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using FileTime.Core.Interactions;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Interactions;
|
||||||
|
|
||||||
|
public class PreviewList : IPreviewElement
|
||||||
|
{
|
||||||
|
public ObservableCollection<IPreviewElement> Items { get; } = new();
|
||||||
|
public PreviewType PreviewType { get; } = PreviewType.PreviewList;
|
||||||
|
object IPreviewElement.PreviewType => PreviewType;
|
||||||
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
public enum PreviewType
|
public enum PreviewType
|
||||||
{
|
{
|
||||||
DoubleText,
|
DoubleText,
|
||||||
DoubleTextList
|
PreviewList,
|
||||||
|
DoubleItemNamePartList
|
||||||
}
|
}
|
||||||
@@ -61,8 +61,9 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
|
|||||||
|
|
||||||
public async Task HandleInputKey(GeneralKeyEventArgs args)
|
public async Task HandleInputKey(GeneralKeyEventArgs args)
|
||||||
{
|
{
|
||||||
|
if (args.Key is not { } key) return;
|
||||||
var keyWithModifiers = new KeyConfig(
|
var keyWithModifiers = new KeyConfig(
|
||||||
args.Key,
|
key,
|
||||||
shift: args.SpecialKeysStatus.IsShiftPressed,
|
shift: args.SpecialKeysStatus.IsShiftPressed,
|
||||||
alt: args.SpecialKeysStatus.IsAltPressed,
|
alt: args.SpecialKeysStatus.IsAltPressed,
|
||||||
ctrl: args.SpecialKeysStatus.IsCtrlPressed);
|
ctrl: args.SpecialKeysStatus.IsCtrlPressed);
|
||||||
@@ -72,7 +73,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
|
|||||||
var selectedCommandBinding = _keyboardConfigurationService.UniversalCommandBindings.FirstOrDefault(c => c.Keys.AreKeysEqual(_appState.PreviousKeys));
|
var selectedCommandBinding = _keyboardConfigurationService.UniversalCommandBindings.FirstOrDefault(c => c.Keys.AreKeysEqual(_appState.PreviousKeys));
|
||||||
selectedCommandBinding ??= _keyboardConfigurationService.CommandBindings.FirstOrDefault(c => c.Keys.AreKeysEqual(_appState.PreviousKeys));
|
selectedCommandBinding ??= _keyboardConfigurationService.CommandBindings.FirstOrDefault(c => c.Keys.AreKeysEqual(_appState.PreviousKeys));
|
||||||
|
|
||||||
if (args.Key == Keys.Escape)
|
if (key == Keys.Escape)
|
||||||
{
|
{
|
||||||
var doGeneralReset = _appState.PreviousKeys.Count > 1;
|
var doGeneralReset = _appState.PreviousKeys.Count > 1;
|
||||||
|
|
||||||
|
|||||||
@@ -54,9 +54,10 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
|||||||
|
|
||||||
public async Task HandleInputKey(GeneralKeyEventArgs args)
|
public async Task HandleInputKey(GeneralKeyEventArgs args)
|
||||||
{
|
{
|
||||||
var keyString = args.Key.Humanize();
|
if (args.Key is not { } key) return;
|
||||||
|
var keyString = key.Humanize();
|
||||||
|
|
||||||
if (args.Key == Keys.Escape)
|
if (key == Keys.Escape)
|
||||||
{
|
{
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
if (_modalService.OpenModals.Count > 0)
|
if (_modalService.OpenModals.Count > 0)
|
||||||
@@ -68,7 +69,7 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
|||||||
await CallCommandAsync(ExitRapidTravelCommand.Instance);
|
await CallCommandAsync(ExitRapidTravelCommand.Instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (args.Key == Keys.Backspace)
|
else if (key == Keys.Backspace)
|
||||||
{
|
{
|
||||||
if (_appState.RapidTravelText.Value!.Length > 0)
|
if (_appState.RapidTravelText.Value!.Length > 0)
|
||||||
{
|
{
|
||||||
@@ -87,7 +88,7 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var currentKeyAsList = new List<KeyConfig> {new(args.Key)};
|
var currentKeyAsList = new List<KeyConfig> {new(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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -217,12 +217,15 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi
|
|||||||
{
|
{
|
||||||
BehaviorSubject<string> templateRegexValue = new(string.Empty);
|
BehaviorSubject<string> templateRegexValue = new(string.Empty);
|
||||||
BehaviorSubject<string> newNameSchemaValue = new(string.Empty);
|
BehaviorSubject<string> newNameSchemaValue = new(string.Empty);
|
||||||
|
List<IDisposable> subscriptions = new();
|
||||||
|
|
||||||
var itemsToRename = new List<FullName>(_markedItems.Value!);
|
var itemsToRename = new List<FullName>(_markedItems.Value!);
|
||||||
|
|
||||||
var itemPreviews = itemsToRename
|
var itemPreviews = itemsToRename
|
||||||
.Select(item =>
|
.Select(item =>
|
||||||
{
|
{
|
||||||
|
var preview = new DoubleItemNamePartListPreview();
|
||||||
|
|
||||||
var originalName = item.GetName();
|
var originalName = item.GetName();
|
||||||
|
|
||||||
var decoratedOriginalName = templateRegexValue.Select(templateRegex =>
|
var decoratedOriginalName = templateRegexValue.Select(templateRegex =>
|
||||||
@@ -284,17 +287,19 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var preview = new DoubleTextPreview
|
subscriptions.Add(decoratedOriginalName.Subscribe(
|
||||||
{
|
n => preview.ItemNameParts1 = n
|
||||||
Text1 = decoratedOriginalName,
|
));
|
||||||
Text2 = text2
|
subscriptions.Add(text2.Subscribe(
|
||||||
};
|
n => preview.ItemNameParts2 = n
|
||||||
|
));
|
||||||
|
|
||||||
return preview;
|
return preview;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
DoubleTextListPreview doubleTextListPreview = new();
|
PreviewList previewList = new();
|
||||||
doubleTextListPreview.Items.AddRange(itemPreviews);
|
previewList.Items.AddRange(itemPreviews);
|
||||||
|
|
||||||
var templateRegex = new TextInputElement("Template regex", string.Empty,
|
var templateRegex = new TextInputElement("Template regex", string.Empty,
|
||||||
s => templateRegexValue.OnNext(s!));
|
s => templateRegexValue.OnNext(s!));
|
||||||
@@ -303,7 +308,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi
|
|||||||
|
|
||||||
var success = await _userCommunicationService.ReadInputs(
|
var success = await _userCommunicationService.ReadInputs(
|
||||||
new[] {templateRegex, newNameSchema},
|
new[] {templateRegex, newNameSchema},
|
||||||
new[] {doubleTextListPreview}
|
new[] {previewList}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
@@ -338,6 +343,8 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi
|
|||||||
itemsToMove.AddRange(itemsToMoveWithPath);
|
itemsToMove.AddRange(itemsToMoveWithPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subscriptions.ForEach(s => s.Dispose());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace FileTime.ConsoleUI.App.Styling;
|
|||||||
public interface ITheme
|
public interface ITheme
|
||||||
{
|
{
|
||||||
IColor? DefaultForegroundColor { get; }
|
IColor? DefaultForegroundColor { get; }
|
||||||
|
IColor? DefaultForegroundAccentColor { get; }
|
||||||
IColor? DefaultBackgroundColor { get; }
|
IColor? DefaultBackgroundColor { get; }
|
||||||
IColor? ElementColor { get; }
|
IColor? ElementColor { get; }
|
||||||
IColor? ContainerColor { get; }
|
IColor? ContainerColor { get; }
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using FileTime.App.Core.Models;
|
|
||||||
using FileTime.App.Core.Services;
|
using FileTime.App.Core.Services;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
using FileTime.ConsoleUI.App.KeyInputHandling;
|
using FileTime.ConsoleUI.App.KeyInputHandling;
|
||||||
using GeneralInputKey;
|
using GeneralInputKey;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using TerminalUI;
|
using TerminalUI;
|
||||||
using TerminalUI.ConsoleDrivers;
|
using TerminalUI.ConsoleDrivers;
|
||||||
using TerminalUI.Traits;
|
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI.App;
|
namespace FileTime.ConsoleUI.App;
|
||||||
|
|
||||||
@@ -27,6 +26,7 @@ public class App : IApplication
|
|||||||
private readonly IApplicationContext _applicationContext;
|
private readonly IApplicationContext _applicationContext;
|
||||||
private readonly IConsoleDriver _consoleDriver;
|
private readonly IConsoleDriver _consoleDriver;
|
||||||
private readonly IAppState _appState;
|
private readonly IAppState _appState;
|
||||||
|
private readonly ILogger<App> _logger;
|
||||||
private readonly IKeyInputHandlerService _keyInputHandlerService;
|
private readonly IKeyInputHandlerService _keyInputHandlerService;
|
||||||
private readonly Thread _renderThread;
|
private readonly Thread _renderThread;
|
||||||
|
|
||||||
@@ -38,7 +38,8 @@ public class App : IApplication
|
|||||||
MainWindow mainWindow,
|
MainWindow mainWindow,
|
||||||
IApplicationContext applicationContext,
|
IApplicationContext applicationContext,
|
||||||
IConsoleDriver consoleDriver,
|
IConsoleDriver consoleDriver,
|
||||||
IAppState appState)
|
IAppState appState,
|
||||||
|
ILogger<App> logger)
|
||||||
{
|
{
|
||||||
_lifecycleService = lifecycleService;
|
_lifecycleService = lifecycleService;
|
||||||
_keyInputHandlerService = keyInputHandlerService;
|
_keyInputHandlerService = keyInputHandlerService;
|
||||||
@@ -48,6 +49,7 @@ public class App : IApplication
|
|||||||
_applicationContext = applicationContext;
|
_applicationContext = applicationContext;
|
||||||
_consoleDriver = consoleDriver;
|
_consoleDriver = consoleDriver;
|
||||||
_appState = appState;
|
_appState = appState;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
_renderThread = new Thread(Render);
|
_renderThread = new Thread(Render);
|
||||||
}
|
}
|
||||||
@@ -73,13 +75,14 @@ public class App : IApplication
|
|||||||
var focusManager = _applicationContext.FocusManager;
|
var focusManager = _applicationContext.FocusManager;
|
||||||
|
|
||||||
while (_applicationContext.IsRunning)
|
while (_applicationContext.IsRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (_consoleDriver.CanRead())
|
if (_consoleDriver.CanRead())
|
||||||
{
|
{
|
||||||
var key = _consoleDriver.ReadKey();
|
var key = _consoleDriver.ReadKey();
|
||||||
|
|
||||||
if (_appKeyService.MapKey(key.Key) is { } mappedKey)
|
var mappedKey = _appKeyService.MapKey(key.Key);
|
||||||
{
|
|
||||||
SpecialKeysStatus specialKeysStatus = new(
|
SpecialKeysStatus specialKeysStatus = new(
|
||||||
(key.Modifiers & ConsoleModifiers.Alt) != 0,
|
(key.Modifiers & ConsoleModifiers.Alt) != 0,
|
||||||
(key.Modifiers & ConsoleModifiers.Shift) != 0,
|
(key.Modifiers & ConsoleModifiers.Shift) != 0,
|
||||||
@@ -100,12 +103,16 @@ public class App : IApplication
|
|||||||
_applicationContext.FocusManager.HandleKeyInput(keyEventArgs);
|
_applicationContext.FocusManager.HandleKeyInput(keyEventArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (focused is null || (!keyEventArgs.Handled && KeysToFurtherProcess.Contains(keyEventArgs.Key)))
|
if (focused is null || (keyEventArgs is {Handled: false, Key: { } k} && KeysToFurtherProcess.Contains(k)))
|
||||||
{
|
{
|
||||||
_keyInputHandlerService.HandleKeyInput(keyEventArgs, specialKeysStatus);
|
_keyInputHandlerService.HandleKeyInput(keyEventArgs, specialKeysStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Error while handling key input");
|
||||||
|
}
|
||||||
|
|
||||||
Thread.Sleep(10);
|
Thread.Sleep(10);
|
||||||
}
|
}
|
||||||
|
|||||||
356
src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Dialogs.cs
Normal file
356
src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Dialogs.cs
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using FileTime.App.Core.Interactions;
|
||||||
|
using FileTime.ConsoleUI.App.Styling;
|
||||||
|
using FileTime.Core.Interactions;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
using GeneralInputKey;
|
||||||
|
using TerminalUI.Controls;
|
||||||
|
using TerminalUI.Extensions;
|
||||||
|
using TerminalUI.Models;
|
||||||
|
using TerminalUI.Traits;
|
||||||
|
using TerminalUI.ViewExtensions;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App.Controls;
|
||||||
|
|
||||||
|
public class Dialogs
|
||||||
|
{
|
||||||
|
private readonly IRootViewModel _rootViewModel;
|
||||||
|
private readonly ITheme _theme;
|
||||||
|
private ItemsControl<IRootViewModel, IInputElement> _readInputs = null!;
|
||||||
|
private IInputElement? _inputElementToFocus;
|
||||||
|
|
||||||
|
private Action? _readInputChildHandlerUnSubscriber;
|
||||||
|
|
||||||
|
public Dialogs(IRootViewModel rootViewModel, ITheme theme)
|
||||||
|
{
|
||||||
|
_rootViewModel = rootViewModel;
|
||||||
|
_theme = theme;
|
||||||
|
|
||||||
|
rootViewModel.FocusReadInputElement += element =>
|
||||||
|
{
|
||||||
|
_inputElementToFocus = element;
|
||||||
|
UpdateReadInputsFocus();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateReadInputsFocus()
|
||||||
|
{
|
||||||
|
foreach (var readInputsChild in _readInputs.Children)
|
||||||
|
{
|
||||||
|
if (readInputsChild.DataContext == _inputElementToFocus)
|
||||||
|
{
|
||||||
|
if (FindFocusable(readInputsChild) is { } focusable)
|
||||||
|
{
|
||||||
|
focusable.Focus();
|
||||||
|
_inputElementToFocus = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IFocusable? FindFocusable(IView view)
|
||||||
|
{
|
||||||
|
if (view is IFocusable focusable) return focusable;
|
||||||
|
foreach (var viewVisualChild in view.VisualChildren)
|
||||||
|
{
|
||||||
|
if (FindFocusable(viewVisualChild) is { } focusableChild)
|
||||||
|
return focusableChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IView<IRootViewModel> View()
|
||||||
|
{
|
||||||
|
var root = new Border<IRootViewModel>
|
||||||
|
{
|
||||||
|
Margin = 5,
|
||||||
|
BorderThickness = 1,
|
||||||
|
Content = new Grid<IRootViewModel>
|
||||||
|
{
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
ReadInputs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
root.Bind(
|
||||||
|
root,
|
||||||
|
d => d.DialogService.ReadInput.Value != null,
|
||||||
|
v => v.IsVisible);
|
||||||
|
|
||||||
|
((INotifyPropertyChanged) _readInputs).PropertyChanged += (_, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(ItemsControl<object, object>.Children))
|
||||||
|
{
|
||||||
|
_readInputChildHandlerUnSubscriber?.Invoke();
|
||||||
|
|
||||||
|
if (_readInputs.Children.Count > 0)
|
||||||
|
{
|
||||||
|
UpdateReadInputsFocus();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_inputElementToFocus = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_readInputs.Children is INotifyCollectionChanged notifyCollectionChanged)
|
||||||
|
{
|
||||||
|
notifyCollectionChanged.CollectionChanged += NotifyCollectionChangedEventHandler;
|
||||||
|
_readInputChildHandlerUnSubscriber = () => { notifyCollectionChanged.CollectionChanged -= NotifyCollectionChangedEventHandler; };
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotifyCollectionChangedEventHandler(
|
||||||
|
object? sender,
|
||||||
|
NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
UpdateReadInputsFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IView<IRootViewModel> ReadInputs()
|
||||||
|
=> new Grid<IRootViewModel>
|
||||||
|
{
|
||||||
|
RowDefinitionsObject = "Auto Auto",
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
ReadInputsList(),
|
||||||
|
new ItemsControl<IRootViewModel, IPreviewElement>
|
||||||
|
{
|
||||||
|
ItemTemplate = ReadInputPreviewItemTemplate
|
||||||
|
}
|
||||||
|
.Setup(i => i.Bind(
|
||||||
|
i,
|
||||||
|
dc => dc.DialogService.ReadInput.Value.Previews,
|
||||||
|
c => c.ItemsSource
|
||||||
|
))
|
||||||
|
.WithExtension(new GridPositionExtension(0, 1))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private IView<IPreviewElement> ReadInputPreviewItemTemplate()
|
||||||
|
{
|
||||||
|
var grid = new Grid<IPreviewElement>
|
||||||
|
{
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
new ItemsControl<IPreviewElement, IPreviewElement>
|
||||||
|
{
|
||||||
|
ItemTemplate = ReadInputPreviewItemTemplate
|
||||||
|
}
|
||||||
|
.Setup(i => i.Bind(
|
||||||
|
i,
|
||||||
|
dc => (PreviewType) dc.PreviewType == PreviewType.PreviewList,
|
||||||
|
c => c.IsVisible))
|
||||||
|
.Setup(i => i.Bind(
|
||||||
|
i,
|
||||||
|
dc => ((PreviewList) dc).Items,
|
||||||
|
c => c.ItemsSource)),
|
||||||
|
new Grid<IPreviewElement>
|
||||||
|
{
|
||||||
|
ColumnDefinitionsObject = "* *",
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
new TextBlock<IPreviewElement>()
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
dc => ((DoubleTextPreview) dc).Text1,
|
||||||
|
tb => tb.Text
|
||||||
|
)),
|
||||||
|
new TextBlock<IPreviewElement>
|
||||||
|
{
|
||||||
|
Extensions =
|
||||||
|
{
|
||||||
|
new GridPositionExtension(1, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
dc => ((DoubleTextPreview) dc).Text2,
|
||||||
|
tb => tb.Text
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}.Setup(g => g.Bind(
|
||||||
|
g,
|
||||||
|
dc => (PreviewType) dc.PreviewType == PreviewType.DoubleText,
|
||||||
|
g => g.IsVisible)),
|
||||||
|
new Grid<IPreviewElement>
|
||||||
|
{
|
||||||
|
ColumnDefinitionsObject = "* *",
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
new ItemsControl<IPreviewElement, ItemNamePart>()
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
ItemTemplate = ItemNamePartItemTemplate
|
||||||
|
}.Setup(i => i.Bind(
|
||||||
|
i,
|
||||||
|
dc => ((DoubleItemNamePartListPreview) dc).ItemNameParts1,
|
||||||
|
c => c.ItemsSource,
|
||||||
|
v => v)),
|
||||||
|
new ItemsControl<IPreviewElement, ItemNamePart>()
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
Extensions =
|
||||||
|
{
|
||||||
|
new GridPositionExtension(1, 0)
|
||||||
|
},
|
||||||
|
ItemTemplate = ItemNamePartItemTemplate
|
||||||
|
}.Setup(i => i.Bind(
|
||||||
|
i,
|
||||||
|
dc => ((DoubleItemNamePartListPreview) dc).ItemNameParts2,
|
||||||
|
c => c.ItemsSource))
|
||||||
|
}
|
||||||
|
}.Setup(g => g.Bind(
|
||||||
|
g,
|
||||||
|
dc => (PreviewType) dc.PreviewType == PreviewType.DoubleItemNamePartList,
|
||||||
|
g => g.IsVisible))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return grid;
|
||||||
|
|
||||||
|
IView<ItemNamePart> ItemNamePartItemTemplate()
|
||||||
|
{
|
||||||
|
var textBlock = new TextBlock<ItemNamePart>();
|
||||||
|
textBlock.Bind(
|
||||||
|
textBlock,
|
||||||
|
dc => dc.Text,
|
||||||
|
tb => tb.Text
|
||||||
|
);
|
||||||
|
textBlock.Bind(
|
||||||
|
textBlock,
|
||||||
|
dc => dc.IsSpecial ? _theme.DefaultForegroundAccentColor : null,
|
||||||
|
tb => tb.Foreground
|
||||||
|
);
|
||||||
|
|
||||||
|
return textBlock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IView<IRootViewModel> ReadInputsList()
|
||||||
|
{
|
||||||
|
var readInputs = new ItemsControl<IRootViewModel, IInputElement>
|
||||||
|
{
|
||||||
|
IsFocusBoundary = true,
|
||||||
|
ItemTemplate = () =>
|
||||||
|
{
|
||||||
|
var root = new Grid<IInputElement>
|
||||||
|
{
|
||||||
|
ColumnDefinitionsObject = "* *",
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
new TextBlock<IInputElement>()
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
c => c.Label,
|
||||||
|
tb => tb.Text
|
||||||
|
)),
|
||||||
|
new Grid<IInputElement>
|
||||||
|
{
|
||||||
|
Extensions =
|
||||||
|
{
|
||||||
|
new GridPositionExtension(1, 0)
|
||||||
|
},
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
new Border<IInputElement>
|
||||||
|
{
|
||||||
|
Content =
|
||||||
|
new TextBox<IInputElement>()
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
d => ((TextInputElement) d).Value,
|
||||||
|
tb => tb.Text,
|
||||||
|
v => v ?? string.Empty,
|
||||||
|
fallbackValue: string.Empty
|
||||||
|
))
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
d => ((TextInputElement) d).Label,
|
||||||
|
tb => tb.Name))
|
||||||
|
.WithTextHandler((tb, t) =>
|
||||||
|
{
|
||||||
|
if (tb.DataContext is TextInputElement textInputElement)
|
||||||
|
textInputElement.Value = t;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
d => d.Type == InputType.Text,
|
||||||
|
tb => tb.IsVisible
|
||||||
|
)),
|
||||||
|
new Border<IInputElement>
|
||||||
|
{
|
||||||
|
Content =
|
||||||
|
new TextBox<IInputElement>
|
||||||
|
{
|
||||||
|
PasswordChar = '*'
|
||||||
|
}
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
d => ((PasswordInputElement) d).Value,
|
||||||
|
tb => tb.Text,
|
||||||
|
v => v ?? string.Empty,
|
||||||
|
fallbackValue: string.Empty
|
||||||
|
))
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
d => ((PasswordInputElement) d).Label,
|
||||||
|
tb => tb.Name))
|
||||||
|
.WithTextHandler((tb, t) =>
|
||||||
|
{
|
||||||
|
if (tb.DataContext is PasswordInputElement textInputElement)
|
||||||
|
textInputElement.Value = t;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
d => d.Type == InputType.Password,
|
||||||
|
tb => tb.IsVisible
|
||||||
|
))
|
||||||
|
//TODO: OptionInputElement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
d => d.DialogService.ReadInput.Value.Inputs,
|
||||||
|
c => c.ItemsSource,
|
||||||
|
v => v
|
||||||
|
));
|
||||||
|
|
||||||
|
readInputs.WithKeyHandler((_, e) =>
|
||||||
|
{
|
||||||
|
if (e.Key == Keys.Enter)
|
||||||
|
{
|
||||||
|
if (_rootViewModel.DialogService.ReadInput.Value is { } readInputsViewModel)
|
||||||
|
readInputsViewModel.Process();
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
else if (e.Key == Keys.Escape)
|
||||||
|
{
|
||||||
|
if (_rootViewModel.DialogService.ReadInput.Value is { } readInputsViewModel)
|
||||||
|
readInputsViewModel.Cancel();
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_readInputs = readInputs;
|
||||||
|
|
||||||
|
return readInputs;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ using FileTime.ConsoleUI.App.Controls;
|
|||||||
using FileTime.ConsoleUI.App.Styling;
|
using FileTime.ConsoleUI.App.Styling;
|
||||||
using FileTime.Core.Enums;
|
using FileTime.Core.Enums;
|
||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
using GeneralInputKey;
|
|
||||||
using TerminalUI;
|
using TerminalUI;
|
||||||
using TerminalUI.Color;
|
using TerminalUI.Color;
|
||||||
using TerminalUI.Controls;
|
using TerminalUI.Controls;
|
||||||
@@ -23,57 +22,23 @@ public class MainWindow
|
|||||||
private readonly IApplicationContext _applicationContext;
|
private readonly IApplicationContext _applicationContext;
|
||||||
private readonly ITheme _theme;
|
private readonly ITheme _theme;
|
||||||
private readonly CommandPalette _commandPalette;
|
private readonly CommandPalette _commandPalette;
|
||||||
|
private readonly Dialogs _dialogs;
|
||||||
private readonly Lazy<IView> _root;
|
private readonly Lazy<IView> _root;
|
||||||
|
|
||||||
private ItemsControl<IRootViewModel, IInputElement> _readInputs = null!;
|
|
||||||
private IInputElement? _inputElementToFocus;
|
|
||||||
private Action? _readInputChildHandlerUnsubscriber;
|
|
||||||
|
|
||||||
public MainWindow(
|
public MainWindow(
|
||||||
IRootViewModel rootViewModel,
|
IRootViewModel rootViewModel,
|
||||||
IApplicationContext applicationContext,
|
IApplicationContext applicationContext,
|
||||||
ITheme theme,
|
ITheme theme,
|
||||||
CommandPalette commandPalette)
|
CommandPalette commandPalette,
|
||||||
|
Dialogs dialogs)
|
||||||
{
|
{
|
||||||
_rootViewModel = rootViewModel;
|
_rootViewModel = rootViewModel;
|
||||||
_applicationContext = applicationContext;
|
_applicationContext = applicationContext;
|
||||||
_theme = theme;
|
_theme = theme;
|
||||||
_commandPalette = commandPalette;
|
_commandPalette = commandPalette;
|
||||||
|
_dialogs = dialogs;
|
||||||
_root = new Lazy<IView>(Initialize);
|
_root = new Lazy<IView>(Initialize);
|
||||||
|
|
||||||
rootViewModel.FocusReadInputElement += element =>
|
|
||||||
{
|
|
||||||
_inputElementToFocus = element;
|
|
||||||
UpdateReadInputsFocus();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateReadInputsFocus()
|
|
||||||
{
|
|
||||||
foreach (var readInputsChild in _readInputs.Children)
|
|
||||||
{
|
|
||||||
if (readInputsChild.DataContext == _inputElementToFocus)
|
|
||||||
{
|
|
||||||
if (FindFocusable(readInputsChild) is { } focusable)
|
|
||||||
{
|
|
||||||
focusable.Focus();
|
|
||||||
_inputElementToFocus = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IFocusable? FindFocusable(IView view)
|
|
||||||
{
|
|
||||||
if (view is IFocusable focusable) return focusable;
|
|
||||||
foreach (var viewVisualChild in view.VisualChildren)
|
|
||||||
{
|
|
||||||
if (FindFocusable(viewVisualChild) is { } focusableChild)
|
|
||||||
return focusableChild;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<IView> RootViews() => new[]
|
public IEnumerable<IView> RootViews() => new[]
|
||||||
@@ -93,28 +58,7 @@ public class MainWindow
|
|||||||
{
|
{
|
||||||
MainContent(),
|
MainContent(),
|
||||||
_commandPalette.View(),
|
_commandPalette.View(),
|
||||||
Dialogs(),
|
_dialogs.View(),
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
((INotifyPropertyChanged) _readInputs).PropertyChanged += (_, e) =>
|
|
||||||
{
|
|
||||||
if (e.PropertyName == nameof(ItemsControl<object, object>.Children))
|
|
||||||
{
|
|
||||||
_readInputChildHandlerUnsubscriber?.Invoke();
|
|
||||||
UpdateReadInputsFocus();
|
|
||||||
if (_readInputs.Children is INotifyCollectionChanged notifyCollectionChanged)
|
|
||||||
{
|
|
||||||
notifyCollectionChanged.CollectionChanged += NotifyCollectionChangedEventHandler;
|
|
||||||
_readInputChildHandlerUnsubscriber = () => { notifyCollectionChanged.CollectionChanged -= NotifyCollectionChangedEventHandler; };
|
|
||||||
}
|
|
||||||
|
|
||||||
void NotifyCollectionChangedEventHandler(
|
|
||||||
object? sender,
|
|
||||||
NotifyCollectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
UpdateReadInputsFocus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return root;
|
return root;
|
||||||
@@ -441,146 +385,4 @@ public class MainWindow
|
|||||||
(ItemViewMode.MarkedAlternative, _) => _theme.MarkedItemBackgroundColor,
|
(ItemViewMode.MarkedAlternative, _) => _theme.MarkedItemBackgroundColor,
|
||||||
_ => throw new NotImplementedException()
|
_ => throw new NotImplementedException()
|
||||||
};
|
};
|
||||||
|
|
||||||
private IView<IRootViewModel> Dialogs()
|
|
||||||
{
|
|
||||||
var root = new Border<IRootViewModel>()
|
|
||||||
{
|
|
||||||
Margin = 5,
|
|
||||||
BorderThickness = 1,
|
|
||||||
Content = new Grid<IRootViewModel>()
|
|
||||||
{
|
|
||||||
ChildInitializer =
|
|
||||||
{
|
|
||||||
ReadInputs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
root.Bind(
|
|
||||||
root,
|
|
||||||
d => d.DialogService.ReadInput.Value != null,
|
|
||||||
v => v.IsVisible);
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ItemsControl<IRootViewModel, IInputElement> ReadInputs()
|
|
||||||
{
|
|
||||||
var readInputs = new ItemsControl<IRootViewModel, IInputElement>
|
|
||||||
{
|
|
||||||
IsFocusBoundary = true,
|
|
||||||
ItemTemplate = () =>
|
|
||||||
{
|
|
||||||
var root = new Grid<IInputElement>
|
|
||||||
{
|
|
||||||
ColumnDefinitionsObject = "* *",
|
|
||||||
ChildInitializer =
|
|
||||||
{
|
|
||||||
new TextBlock<IInputElement>()
|
|
||||||
.Setup(t => t.Bind(
|
|
||||||
t,
|
|
||||||
c => c.Label,
|
|
||||||
tb => tb.Text
|
|
||||||
)),
|
|
||||||
new Grid<IInputElement>()
|
|
||||||
{
|
|
||||||
Extensions =
|
|
||||||
{
|
|
||||||
new GridPositionExtension(1, 0)
|
|
||||||
},
|
|
||||||
ChildInitializer =
|
|
||||||
{
|
|
||||||
new Border<IInputElement>
|
|
||||||
{
|
|
||||||
Content =
|
|
||||||
new TextBox<IInputElement>()
|
|
||||||
.Setup(t => t.Bind(
|
|
||||||
t,
|
|
||||||
d => ((TextInputElement) d).Value,
|
|
||||||
tb => tb.Text,
|
|
||||||
v => v ?? string.Empty,
|
|
||||||
fallbackValue: string.Empty
|
|
||||||
))
|
|
||||||
.Setup(t => t.Bind(
|
|
||||||
t,
|
|
||||||
d => ((TextInputElement) d).Label,
|
|
||||||
tb => tb.Name))
|
|
||||||
.WithTextHandler((tb, t) =>
|
|
||||||
{
|
|
||||||
if (tb.DataContext is TextInputElement textInputElement)
|
|
||||||
textInputElement.Value = t;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
.Setup(t => t.Bind(
|
|
||||||
t,
|
|
||||||
d => d.Type == InputType.Text,
|
|
||||||
tb => tb.IsVisible
|
|
||||||
)),
|
|
||||||
new Border<IInputElement>
|
|
||||||
{
|
|
||||||
Content =
|
|
||||||
new TextBox<IInputElement>
|
|
||||||
{
|
|
||||||
PasswordChar = '*'
|
|
||||||
}
|
|
||||||
.Setup(t => t.Bind(
|
|
||||||
t,
|
|
||||||
d => ((PasswordInputElement) d).Value,
|
|
||||||
tb => tb.Text,
|
|
||||||
v => v ?? string.Empty,
|
|
||||||
fallbackValue: string.Empty
|
|
||||||
))
|
|
||||||
.Setup(t => t.Bind(
|
|
||||||
t,
|
|
||||||
d => ((PasswordInputElement) d).Label,
|
|
||||||
tb => tb.Name))
|
|
||||||
.WithTextHandler((tb, t) =>
|
|
||||||
{
|
|
||||||
if (tb.DataContext is PasswordInputElement textInputElement)
|
|
||||||
textInputElement.Value = t;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
.Setup(t => t.Bind(
|
|
||||||
t,
|
|
||||||
d => d.Type == InputType.Password,
|
|
||||||
tb => tb.IsVisible
|
|
||||||
))
|
|
||||||
//TODO: OptionInputElement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.Setup(t => t.Bind(
|
|
||||||
t,
|
|
||||||
d => d.DialogService.ReadInput.Value.Inputs,
|
|
||||||
c => c.ItemsSource,
|
|
||||||
v => v
|
|
||||||
));
|
|
||||||
|
|
||||||
readInputs.WithKeyHandler((_, e) =>
|
|
||||||
{
|
|
||||||
if (e.Key == Keys.Enter)
|
|
||||||
{
|
|
||||||
if (_rootViewModel.DialogService.ReadInput.Value is { } readInputsViewModel)
|
|
||||||
readInputsViewModel.Process();
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
else if (e.Key == Keys.Escape)
|
|
||||||
{
|
|
||||||
if (_rootViewModel.DialogService.ReadInput.Value is { } readInputsViewModel)
|
|
||||||
readInputsViewModel.Cancel();
|
|
||||||
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_readInputs = readInputs;
|
|
||||||
|
|
||||||
return readInputs;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,7 @@ public static class Startup
|
|||||||
{
|
{
|
||||||
services.TryAddSingleton<MainWindow>();
|
services.TryAddSingleton<MainWindow>();
|
||||||
services.TryAddSingleton<CommandPalette>();
|
services.TryAddSingleton<CommandPalette>();
|
||||||
|
services.TryAddSingleton<Dialogs>();
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ namespace FileTime.ConsoleUI.Styles;
|
|||||||
|
|
||||||
public record Theme(
|
public record Theme(
|
||||||
IColor? DefaultForegroundColor,
|
IColor? DefaultForegroundColor,
|
||||||
|
IColor? DefaultForegroundAccentColor,
|
||||||
IColor? DefaultBackgroundColor,
|
IColor? DefaultBackgroundColor,
|
||||||
IColor? ElementColor,
|
IColor? ElementColor,
|
||||||
IColor? ContainerColor,
|
IColor? ContainerColor,
|
||||||
@@ -23,6 +24,7 @@ public static class DefaultThemes
|
|||||||
{
|
{
|
||||||
public static Theme Color256Theme => new(
|
public static Theme Color256Theme => new(
|
||||||
DefaultForegroundColor: Color256Colors.Foregrounds.Gray,
|
DefaultForegroundColor: Color256Colors.Foregrounds.Gray,
|
||||||
|
DefaultForegroundAccentColor: Color256Colors.Foregrounds.Red,
|
||||||
DefaultBackgroundColor: null,
|
DefaultBackgroundColor: null,
|
||||||
ElementColor: Color256Colors.Foregrounds.Gray,
|
ElementColor: Color256Colors.Foregrounds.Gray,
|
||||||
ContainerColor: Color256Colors.Foregrounds.Blue,
|
ContainerColor: Color256Colors.Foregrounds.Blue,
|
||||||
@@ -42,6 +44,7 @@ public static class DefaultThemes
|
|||||||
|
|
||||||
public static Theme ConsoleColorTheme => new(
|
public static Theme ConsoleColorTheme => new(
|
||||||
DefaultForegroundColor: ConsoleColors.Foregrounds.Gray,
|
DefaultForegroundColor: ConsoleColors.Foregrounds.Gray,
|
||||||
|
DefaultForegroundAccentColor: ConsoleColors.Foregrounds.Red,
|
||||||
DefaultBackgroundColor: null,
|
DefaultBackgroundColor: null,
|
||||||
ElementColor: ConsoleColors.Foregrounds.Gray,
|
ElementColor: ConsoleColors.Foregrounds.Gray,
|
||||||
ContainerColor: ConsoleColors.Foregrounds.Blue,
|
ContainerColor: ConsoleColors.Foregrounds.Blue,
|
||||||
|
|||||||
@@ -840,72 +840,7 @@
|
|||||||
<ItemsControl Grid.Row="1" ItemsSource="{Binding DialogService.ReadInput.Value.Previews}">
|
<ItemsControl Grid.Row="1" ItemsSource="{Binding DialogService.ReadInput.Value.Previews}">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<Grid>
|
<local:ReadInputPreview />
|
||||||
<Grid IsVisible="{Binding PreviewType, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static appInteractions:PreviewType.DoubleTextList}}">
|
|
||||||
<ItemsControl ItemsSource="{Binding Items}" x:DataType="appInteractions:DoubleTextListPreview">
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Grid ColumnDefinitions="*,*">
|
|
||||||
<ItemsControl ItemsSource="{Binding Text1^}">
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<StackPanel Orientation="Horizontal" />
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<TextBlock Text="{Binding Text}" TextDecorations="{Binding IsSpecial, Converter={StaticResource TextDecorationConverter}}" />
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
<ItemsControl Grid.Column="1" ItemsSource="{Binding Text2^}">
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<StackPanel Orientation="Horizontal" />
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<TextBlock Text="{Binding Text}" TextDecorations="{Binding IsSpecial, Converter={StaticResource TextDecorationConverter}}" />
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
</Grid>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
<!--DataGrid ItemsSource="{Binding Items}" x:DataType="appInteractions:DoubleTextListPreview">
|
|
||||||
<DataGrid.Columns>
|
|
||||||
<DataGridTemplateColumn>
|
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<ItemsControl
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
ItemsSource="{Binding Text1^}"
|
|
||||||
Margin="5,0,0,0"
|
|
||||||
VerticalAlignment="Center">
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<StackPanel Orientation="Horizontal" />
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate x:DataType="x:String">
|
|
||||||
<Grid>
|
|
||||||
<TextBlock Text="{Binding}" />
|
|
||||||
</Grid>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
</ItemsControl>
|
|
||||||
</DataTemplate>
|
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
|
||||||
</DataGridTemplateColumn>
|
|
||||||
</DataGrid.Columns>
|
|
||||||
</DataGrid-->
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
|
|
||||||
</Grid>
|
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<UserControl
|
||||||
|
d:DesignHeight="450"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
x:Class="FileTime.GuiApp.App.Views.ReadInputPreview"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="interactionsCore:IPreviewElement"
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:interactions="clr-namespace:FileTime.App.Core.Interactions;assembly=FileTime.App.Core.Abstraction"
|
||||||
|
xmlns:interactionsCore="clr-namespace:FileTime.Core.Interactions;assembly=FileTime.Core.Abstraction"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:views="clr-namespace:FileTime.GuiApp.App.Views"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Grid>
|
||||||
|
<Grid IsVisible="{Binding PreviewType, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static interactions:PreviewType.PreviewList}}">
|
||||||
|
<ItemsControl ItemsSource="{Binding Items}" x:DataType="interactions:PreviewList">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid>
|
||||||
|
<views:ReadInputPreview />
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
<Grid IsVisible="{Binding PreviewType, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static interactions:PreviewType.DoubleItemNamePartList}}">
|
||||||
|
<Grid ColumnDefinitions="*,*" x:DataType="interactions:DoubleTextPreview">
|
||||||
|
<TextBlock Text="{Binding Text1}" />
|
||||||
|
<TextBlock Grid.Column="1" Text="{Binding Text2}" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid IsVisible="{Binding PreviewType, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static interactions:PreviewType.DoubleItemNamePartList}}">
|
||||||
|
<Grid ColumnDefinitions="*,*" x:DataType="interactions:DoubleItemNamePartListPreview">
|
||||||
|
<ItemsControl ItemsSource="{Binding ItemNameParts1}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding Text}" TextDecorations="{Binding IsSpecial, Converter={StaticResource TextDecorationConverter}}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
<ItemsControl Grid.Column="1" ItemsSource="{Binding ItemNameParts2}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding Text}" TextDecorations="{Binding IsSpecial, Converter={StaticResource TextDecorationConverter}}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace FileTime.GuiApp.App.Views;
|
||||||
|
|
||||||
|
public partial class ReadInputPreview : UserControl
|
||||||
|
{
|
||||||
|
public ReadInputPreview()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ public class GeneralKeyEventArgs
|
|||||||
{
|
{
|
||||||
private readonly Action<bool>? _handledChanged;
|
private readonly Action<bool>? _handledChanged;
|
||||||
private bool _handled;
|
private bool _handled;
|
||||||
public required Keys Key { get; init; }
|
public required Keys? Key { get; init; }
|
||||||
public required char KeyChar { get; init; }
|
public required char KeyChar { get; init; }
|
||||||
public required SpecialKeysStatus SpecialKeysStatus { get; init; }
|
public required SpecialKeysStatus SpecialKeysStatus { get; init; }
|
||||||
|
|
||||||
|
|||||||
@@ -78,8 +78,14 @@ public sealed class Binding<TDataContext, TExpressionResult, TResult> : Property
|
|||||||
value = _fallbackValue;
|
value = _fallbackValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
_targetProperty.SetValue(_propertySource, value);
|
_targetProperty.SetValue(_propertySource, value);
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public sealed partial class ItemsControl<TDataContext, TItem>
|
|||||||
private object? _itemsSource;
|
private object? _itemsSource;
|
||||||
[Notify] private Orientation _orientation = Orientation.Vertical;
|
[Notify] private Orientation _orientation = Orientation.Vertical;
|
||||||
|
|
||||||
public Func<IView<TItem>?> ItemTemplate { get; set; } = DefaultItemTemplate;
|
public Func<IView<TItem>> ItemTemplate { get; set; } = DefaultItemTemplate;
|
||||||
|
|
||||||
public IReadOnlyList<IView<TItem>> Children => _children.AsReadOnly();
|
public IReadOnlyList<IView<TItem>> Children => _children.AsReadOnly();
|
||||||
|
|
||||||
@@ -41,7 +41,6 @@ public sealed partial class ItemsControl<TDataContext, TItem>
|
|||||||
var consumer = new OcConsumer();
|
var consumer = new OcConsumer();
|
||||||
_children = observableDeclarative
|
_children = observableDeclarative
|
||||||
.Selecting(i => CreateItem(i))
|
.Selecting(i => CreateItem(i))
|
||||||
.OfTypeComputing<IView<TItem>>()
|
|
||||||
.For(consumer);
|
.For(consumer);
|
||||||
_itemsDisposables.Add(consumer);
|
_itemsDisposables.Add(consumer);
|
||||||
}
|
}
|
||||||
@@ -50,16 +49,15 @@ public sealed partial class ItemsControl<TDataContext, TItem>
|
|||||||
var consumer = new OcConsumer();
|
var consumer = new OcConsumer();
|
||||||
_children = readOnlyObservableDeclarative
|
_children = readOnlyObservableDeclarative
|
||||||
.Selecting(i => CreateItem(i))
|
.Selecting(i => CreateItem(i))
|
||||||
.OfTypeComputing<IView<TItem>>()
|
|
||||||
.For(consumer);
|
.For(consumer);
|
||||||
_itemsDisposables.Add(consumer);
|
_itemsDisposables.Add(consumer);
|
||||||
}
|
}
|
||||||
else if (_itemsSource is ICollection<TItem> collection)
|
else if (_itemsSource is ICollection<TItem> collection)
|
||||||
_children = collection.Select(CreateItem).OfType<IView<TItem>>().ToList();
|
_children = collection.Select(CreateItem).ToList();
|
||||||
else if (_itemsSource is TItem[] array)
|
else if (_itemsSource is TItem[] array)
|
||||||
_children = array.Select(CreateItem).OfType<IView<TItem>>().ToList();
|
_children = array.Select(CreateItem).ToList();
|
||||||
else if (_itemsSource is IEnumerable<TItem> enumerable)
|
else if (_itemsSource is IEnumerable<TItem> enumerable)
|
||||||
_children = enumerable.Select(CreateItem).OfType<IView<TItem>>().ToList();
|
_children = enumerable.Select(CreateItem).ToList();
|
||||||
else if (value is null)
|
else if (value is null)
|
||||||
{
|
{
|
||||||
_children = new List<IView<TItem>>();
|
_children = new List<IView<TItem>>();
|
||||||
@@ -173,14 +171,14 @@ public sealed partial class ItemsControl<TDataContext, TItem>
|
|||||||
return neededRerender;
|
return neededRerender;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IView<TItem>? CreateItem(TItem dataContext)
|
private IView<TItem> CreateItem(TItem dataContext)
|
||||||
{
|
{
|
||||||
var newItem = ItemTemplate();
|
var newItem = ItemTemplate();
|
||||||
AddChild(newItem, _ => dataContext);
|
AddChild(newItem, _ => dataContext);
|
||||||
return newItem;
|
return newItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IView<TItem>? DefaultItemTemplate() => null;
|
private static IView<TItem> DefaultItemTemplate() => new TextBlock<TItem> {Text = typeof(TItem).ToString()};
|
||||||
|
|
||||||
public void ChildVisibilityChanged(IView child)
|
public void ChildVisibilityChanged(IView child)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using GeneralInputKey;
|
using GeneralInputKey;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
@@ -321,6 +322,7 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
|||||||
|
|
||||||
public virtual TChild AddChild<TChild>(TChild child) where TChild : IView<T>
|
public virtual TChild AddChild<TChild>(TChild child) where TChild : IView<T>
|
||||||
{
|
{
|
||||||
|
Debug.Assert(child != null);
|
||||||
child.DataContext = DataContext;
|
child.DataContext = DataContext;
|
||||||
var mapper = new DataContextMapper<T, T>(this, child, d => d);
|
var mapper = new DataContextMapper<T, T>(this, child, d => d);
|
||||||
SetupNewChild(child, mapper);
|
SetupNewChild(child, mapper);
|
||||||
@@ -331,6 +333,7 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
|||||||
public virtual TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
public virtual TChild AddChild<TChild, TDataContext>(TChild child, Func<T?, TDataContext?> dataContextMapper)
|
||||||
where TChild : IView<TDataContext>
|
where TChild : IView<TDataContext>
|
||||||
{
|
{
|
||||||
|
Debug.Assert(child != null);
|
||||||
child.DataContext = dataContextMapper(DataContext);
|
child.DataContext = dataContextMapper(DataContext);
|
||||||
var mapper = new DataContextMapper<T, TDataContext>(this, child, dataContextMapper);
|
var mapper = new DataContextMapper<T, TDataContext>(this, child, dataContextMapper);
|
||||||
SetupNewChild(child, mapper);
|
SetupNewChild(child, mapper);
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
namespace TerminalUI;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace TerminalUI;
|
||||||
|
|
||||||
public class EventLoop : IEventLoop
|
public class EventLoop : IEventLoop
|
||||||
{
|
{
|
||||||
private readonly IApplicationContext _applicationContext;
|
private readonly IApplicationContext _applicationContext;
|
||||||
|
private readonly ILogger<EventLoop> _logger;
|
||||||
private readonly List<Action> _permanentQueue = new();
|
private readonly List<Action> _permanentQueue = new();
|
||||||
|
|
||||||
public EventLoop(IApplicationContext applicationContext)
|
public EventLoop(
|
||||||
|
IApplicationContext applicationContext,
|
||||||
|
ILogger<EventLoop> logger)
|
||||||
{
|
{
|
||||||
_applicationContext = applicationContext;
|
_applicationContext = applicationContext;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddToPermanentQueue(Action action) => _permanentQueue.Add(action);
|
public void AddToPermanentQueue(Action action) => _permanentQueue.Add(action);
|
||||||
@@ -25,8 +31,15 @@ public class EventLoop : IEventLoop
|
|||||||
private void ProcessQueues()
|
private void ProcessQueues()
|
||||||
{
|
{
|
||||||
foreach (var action in _permanentQueue)
|
foreach (var action in _permanentQueue)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
action();
|
action();
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Error while processing action in permanent queue");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ public class FocusManager : IFocusManager
|
|||||||
{
|
{
|
||||||
private readonly IRenderEngine _renderEngine;
|
private readonly IRenderEngine _renderEngine;
|
||||||
private IFocusable? _focused;
|
private IFocusable? _focused;
|
||||||
|
private DateTime _focusLostCandidateTime = DateTime.MinValue;
|
||||||
|
|
||||||
public IFocusable? Focused
|
public IFocusable? Focused
|
||||||
{
|
{
|
||||||
@@ -18,15 +19,30 @@ public class FocusManager : IFocusManager
|
|||||||
{
|
{
|
||||||
var visible = _focused.IsVisible;
|
var visible = _focused.IsVisible;
|
||||||
var parent = _focused.VisualParent;
|
var parent = _focused.VisualParent;
|
||||||
while (parent != null)
|
while (parent != null && visible)
|
||||||
{
|
{
|
||||||
visible &= parent.IsVisible;
|
visible = parent.IsVisible && visible;
|
||||||
parent = parent.VisualParent;
|
parent = parent.VisualParent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!visible)
|
if (!visible)
|
||||||
|
{
|
||||||
|
if (_focusLostCandidateTime != DateTime.MinValue)
|
||||||
|
{
|
||||||
|
if (DateTime.Now - _focusLostCandidateTime > TimeSpan.FromMilliseconds(10))
|
||||||
{
|
{
|
||||||
_focused = null;
|
_focused = null;
|
||||||
|
_focusLostCandidateTime = DateTime.MinValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_focusLostCandidateTime = DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_focusLostCandidateTime = DateTime.MinValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,52 +69,66 @@ public class FocusManager : IFocusManager
|
|||||||
{
|
{
|
||||||
if (keyEventArgs.Handled || Focused is null) return;
|
if (keyEventArgs.Handled || Focused is null) return;
|
||||||
|
|
||||||
if (keyEventArgs.Key == Keys.Tab && keyEventArgs.SpecialKeysStatus.IsShiftPressed)
|
if (keyEventArgs is {Key: Keys.Tab, SpecialKeysStatus.IsShiftPressed: true})
|
||||||
{
|
{
|
||||||
FocusElement(
|
FocusLastElement(Focused);
|
||||||
(views, from) => views.TakeWhile(x => x != from).Reverse(),
|
|
||||||
c => c.Reverse()
|
|
||||||
);
|
|
||||||
keyEventArgs.Handled = true;
|
keyEventArgs.Handled = true;
|
||||||
}
|
}
|
||||||
else if (keyEventArgs.Key == Keys.Tab)
|
else if (keyEventArgs.Key == Keys.Tab)
|
||||||
{
|
{
|
||||||
FocusElement(
|
FocusFirstElement(Focused);
|
||||||
(views, from) => views.SkipWhile(x => x != from).Skip(1),
|
|
||||||
c => c
|
|
||||||
);
|
|
||||||
keyEventArgs.Handled = true;
|
keyEventArgs.Handled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void FocusFirstElement(IView view, IView? from = null) =>
|
||||||
|
FocusElement(
|
||||||
|
view,
|
||||||
|
(views, fromView) => views.SkipWhile(x => x != fromView).Skip(1),
|
||||||
|
c => c.Reverse(),
|
||||||
|
from
|
||||||
|
);
|
||||||
|
|
||||||
|
public void FocusLastElement(IView view, IView? from = null) =>
|
||||||
|
FocusElement(
|
||||||
|
view,
|
||||||
|
(views, fromView) => views.TakeWhile(x => x != fromView).Reverse(),
|
||||||
|
c => c,
|
||||||
|
from
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
private void FocusElement(
|
private void FocusElement(
|
||||||
|
IView view,
|
||||||
Func<IEnumerable<IView>, IView, IEnumerable<IView>> fromChildSelector,
|
Func<IEnumerable<IView>, IView, IEnumerable<IView>> fromChildSelector,
|
||||||
Func<IEnumerable<IView>, IEnumerable<IView>> childSelector
|
Func<IEnumerable<IView>, IEnumerable<IView>> childSelector,
|
||||||
|
IView? from = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (Focused is null) return;
|
if (Focused is null) return;
|
||||||
|
|
||||||
var element = FindElement(Focused,
|
var element = FindElement(view,
|
||||||
Focused,
|
view,
|
||||||
fromChildSelector
|
fromChildSelector,
|
||||||
|
from: from
|
||||||
);
|
);
|
||||||
|
|
||||||
if (element is null)
|
if (element is null)
|
||||||
{
|
{
|
||||||
var topParent = FindLastFocusParent(Focused);
|
var topParent = FindLastFocusParent(view);
|
||||||
element = FindElement(
|
element = FindElement(
|
||||||
topParent,
|
topParent,
|
||||||
Focused,
|
view,
|
||||||
fromChildSelector,
|
fromChildSelector,
|
||||||
childSelector
|
childSelector,
|
||||||
|
from
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element is null) return;
|
if (element is null) return;
|
||||||
|
|
||||||
_renderEngine.RequestRerender(element);
|
_renderEngine.RequestRerender(element);
|
||||||
_renderEngine.RequestRerender(Focused);
|
_renderEngine.RequestRerender(view);
|
||||||
Focused = element;
|
Focused = element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using GeneralInputKey;
|
using GeneralInputKey;
|
||||||
|
using TerminalUI.Controls;
|
||||||
using TerminalUI.Traits;
|
using TerminalUI.Traits;
|
||||||
|
|
||||||
namespace TerminalUI;
|
namespace TerminalUI;
|
||||||
@@ -9,4 +10,6 @@ public interface IFocusManager
|
|||||||
void UnFocus(IFocusable focusable);
|
void UnFocus(IFocusable focusable);
|
||||||
IFocusable? Focused { get; }
|
IFocusable? Focused { get; }
|
||||||
void HandleKeyInput(GeneralKeyEventArgs keyEventArgs);
|
void HandleKeyInput(GeneralKeyEventArgs keyEventArgs);
|
||||||
|
void FocusFirstElement(IView view, IView? from = null);
|
||||||
|
void FocusLastElement(IView view, IView? from = null);
|
||||||
}
|
}
|
||||||
@@ -101,7 +101,7 @@ public abstract class PropertyTrackerBase<TSource, TExpressionResult> : IDisposa
|
|||||||
}
|
}
|
||||||
else if (expression is UnaryExpression unaryExpression)
|
else if (expression is UnaryExpression unaryExpression)
|
||||||
{
|
{
|
||||||
SavePropertyPath(FindReactiveProperties(unaryExpression.Operand, properties));
|
return FindReactiveProperties(unaryExpression.Operand, properties);
|
||||||
}
|
}
|
||||||
else if (expression is ParameterExpression parameterExpression)
|
else if (expression is ParameterExpression parameterExpression)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user