Preview refactor, Console rename form

This commit is contained in:
2023-08-14 16:42:22 +02:00
parent 2a595b2548
commit 8aa8d83598
25 changed files with 610 additions and 348 deletions

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
} }

View File

@@ -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;
}

View File

@@ -3,5 +3,6 @@
public enum PreviewType public enum PreviewType
{ {
DoubleText, DoubleText,
DoubleTextList PreviewList,
DoubleItemNamePartList
} }

View File

@@ -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;

View File

@@ -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)
{ {

View File

@@ -83,7 +83,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi
{ {
list.AddRange(_markedItems.Value!); list.AddRange(_markedItems.Value!);
} }
else if(_currentSelectedItem?.Value?.BaseItem?.FullName is { } selectedItemName) else if (_currentSelectedItem?.Value?.BaseItem?.FullName is { } selectedItemName)
{ {
list.Add(selectedItemName); list.Add(selectedItemName);
} }
@@ -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
{ {

View File

@@ -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; }

View File

@@ -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);
} }
@@ -74,12 +76,13 @@ public class App : IApplication
while (_applicationContext.IsRunning) while (_applicationContext.IsRunning)
{ {
if (_consoleDriver.CanRead()) try
{ {
var key = _consoleDriver.ReadKey(); if (_consoleDriver.CanRead())
if (_appKeyService.MapKey(key.Key) is { } mappedKey)
{ {
var key = _consoleDriver.ReadKey();
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);
} }

View 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;
}
}

View File

@@ -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;
}
} }

View File

@@ -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;
} }
} }

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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; }

View File

@@ -78,7 +78,13 @@ public sealed class Binding<TDataContext, TExpressionResult, TResult> : Property
value = _fallbackValue; value = _fallbackValue;
} }
_targetProperty.SetValue(_propertySource, value); try
{
_targetProperty.SetValue(_propertySource, value);
}
catch
{
}
} }
public override void Dispose() public override void Dispose()

View File

@@ -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)
{ {

View File

@@ -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);
@@ -411,7 +414,7 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
{ {
foreach (var keyHandler in KeyHandlers) foreach (var keyHandler in KeyHandlers)
{ {
keyHandler((TConcrete)this, keyEventArgs); keyHandler((TConcrete) this, keyEventArgs);
if (keyEventArgs.Handled) return; if (keyEventArgs.Handled) return;
} }
} }
@@ -427,6 +430,6 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
public TConcrete WithKeyHandler(Action<TConcrete, GeneralKeyEventArgs> keyHandler) public TConcrete WithKeyHandler(Action<TConcrete, GeneralKeyEventArgs> keyHandler)
{ {
KeyHandlers.Add(keyHandler); KeyHandlers.Add(keyHandler);
return (TConcrete)this; return (TConcrete) this;
} }
} }

View File

@@ -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);
@@ -26,7 +32,14 @@ public class EventLoop : IEventLoop
{ {
foreach (var action in _permanentQueue) foreach (var action in _permanentQueue)
{ {
action(); try
{
action();
}
catch (Exception e)
{
_logger.LogError(e, "Error while processing action in permanent queue");
}
} }
} }
} }

View File

@@ -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)
{ {
_focused = null; if (_focusLostCandidateTime != DateTime.MinValue)
{
if (DateTime.Now - _focusLostCandidateTime > TimeSpan.FromMilliseconds(10))
{
_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;
} }

View File

@@ -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);
} }

View File

@@ -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)
{ {