Changed behavior to accept keyboard overrides, added keyboard mouse state validator and indirect rules to allow keyboard and mouse navigation
This commit is contained in:
@@ -64,6 +64,8 @@
|
||||
Background="Transparent"
|
||||
Foreground="#EDEDED"
|
||||
Margin="5,0,0,0"
|
||||
IsTabStop="False"
|
||||
Focusable="False"
|
||||
>
|
||||
<!--
|
||||
<MenuItem Header="_File">
|
||||
@@ -71,8 +73,8 @@
|
||||
<MenuItem Header="_Close" Command="{Binding CloseDirectory}"/>
|
||||
</MenuItem>
|
||||
-->
|
||||
<MenuItem Header="_Open Library" IsVisible="{Binding !PhotosLoaded}" Command="{Binding OpenDirectory}"/>
|
||||
<MenuItem Header="_Close Library" IsVisible="{Binding PhotosLoaded}" Command="{Binding CloseDirectory}"/>
|
||||
<MenuItem Header="_Open Library" Focusable="False" IsVisible="{Binding !PhotosLoaded}" Command="{Binding OpenDirectory}"/>
|
||||
<MenuItem Header="_Close Library" Focusable="False" IsVisible="{Binding PhotosLoaded}" Command="{Binding CloseDirectory}"/>
|
||||
</Menu>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
@@ -114,11 +116,13 @@
|
||||
Grid.RowSpan="2"
|
||||
IsVisible="{Binding PhotosLoaded}">
|
||||
<Border Padding="72,88,72,48">
|
||||
<ItemsRepeater ItemsSource="{Binding Photos}"
|
||||
<ItemsRepeater x:Name="Repeater"
|
||||
ItemsSource="{Binding Photos}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="False"
|
||||
IsTabStop="True">
|
||||
IsTabStop="True"
|
||||
>
|
||||
<ItemsRepeater.Layout>
|
||||
<UniformGridLayout MinItemWidth="220"
|
||||
MinItemHeight="290"
|
||||
@@ -136,7 +140,8 @@
|
||||
BoxShadow="0 10 24 0 #30000000"
|
||||
anim:CardHoverEffectBehavior.IsEnabled="True"
|
||||
anim:CardHoverEffectBehavior.MaxRotationAngle="2.5"
|
||||
anim:CardHoverEffectBehavior.ScaleFactor="1.08">
|
||||
anim:CardHoverEffectBehavior.ScaleFactor="1.08"
|
||||
>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<i:Interaction.Behaviors>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.VisualTree;
|
||||
using Avalonia.Xaml.Interactions.DragAndDrop;
|
||||
using Photos.Animations;
|
||||
using Photos.Models;
|
||||
using Photos.ViewModels;
|
||||
|
||||
@@ -9,8 +13,17 @@ namespace Photos.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private enum NavigationInputMode
|
||||
{
|
||||
Mouse,
|
||||
Keyboard
|
||||
}
|
||||
|
||||
private NavigationInputMode _navigationInputMode = NavigationInputMode.Mouse;
|
||||
|
||||
private IFolderPickerService _folderPickerService;
|
||||
private int _currentImageIndex = 0;
|
||||
private Control? _currentHoveredCard;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
@@ -24,10 +37,25 @@ public partial class MainWindow : Window
|
||||
(DataContext as MainWindowViewModel)?.WindowDropHandler(e);
|
||||
}
|
||||
|
||||
private void OnKeyDownHandler(object sender, KeyEventArgs e)
|
||||
private async void OnKeyDownHandler(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (DataContext is not MainWindowViewModel vm) return;
|
||||
|
||||
// input state changer
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Enter:
|
||||
case Key.Escape:
|
||||
case Key.Left:
|
||||
case Key.Right:
|
||||
case Key.Up:
|
||||
case Key.Down:
|
||||
_navigationInputMode = NavigationInputMode.Keyboard;
|
||||
UpdatePointerHoverSuppression();
|
||||
UpdateKeyboardHover();
|
||||
break;
|
||||
}
|
||||
|
||||
async Task HandleEnter()
|
||||
{
|
||||
if (vm.IsPhotoFocused()) vm.UnfocusPhoto();
|
||||
@@ -45,28 +73,98 @@ public partial class MainWindow : Window
|
||||
// TODO: move basic selection
|
||||
}
|
||||
|
||||
int CalculateCurrentIndexHop()
|
||||
{
|
||||
// Values of UniformGridLayout
|
||||
var minItemWidth = 220;
|
||||
var minColumnSpacing = 18;
|
||||
|
||||
var repeaterWidth = Repeater.Bounds.Width;
|
||||
|
||||
var abstractedColumnCount = Math.Floor(repeaterWidth / (minItemWidth + minColumnSpacing));
|
||||
return Math.Max(1, (int)abstractedColumnCount);
|
||||
}
|
||||
|
||||
void HandleArrowKeyDown()
|
||||
{
|
||||
var nextIdx = CalculateCurrentIndexHop();
|
||||
if (_currentImageIndex + nextIdx >= vm.Photos.Count) _currentImageIndex = vm.Photos.Count - 1;
|
||||
else _currentImageIndex += nextIdx;
|
||||
}
|
||||
|
||||
void HandleArrowKeyUp()
|
||||
{
|
||||
var nextIdx = CalculateCurrentIndexHop();
|
||||
if (_currentImageIndex - nextIdx < 0) _currentImageIndex = 0;
|
||||
else _currentImageIndex -= nextIdx;
|
||||
}
|
||||
|
||||
Control? FindCardControlForCurrentIndex()
|
||||
{
|
||||
if (_currentImageIndex < 0 || _currentImageIndex >= vm.Photos.Count)
|
||||
return null;
|
||||
|
||||
var currentPhoto = vm.Photos[_currentImageIndex];
|
||||
|
||||
return Repeater.GetVisualDescendants()
|
||||
.OfType<Control>()
|
||||
.FirstOrDefault(c =>
|
||||
ReferenceEquals(c.DataContext, currentPhoto) &&
|
||||
CardHoverEffectBehavior.GetIsEnabled(c));
|
||||
}
|
||||
|
||||
void UpdateKeyboardHover()
|
||||
{
|
||||
if (DataContext is not MainWindowViewModel vm)
|
||||
return;
|
||||
|
||||
if (_currentHoveredCard is not null)
|
||||
{
|
||||
CardHoverEffectBehavior.ResetHover(_currentHoveredCard);
|
||||
_currentHoveredCard = null;
|
||||
}
|
||||
|
||||
var newCard = FindCardControlForCurrentIndex();
|
||||
if (newCard is null) return;
|
||||
|
||||
CardHoverEffectBehavior.ApplyHover(newCard);
|
||||
_currentHoveredCard = newCard;
|
||||
_currentHoveredCard.BringIntoView();
|
||||
}
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Enter:
|
||||
HandleEnter().GetAwaiter();
|
||||
case Key.Enter: case Key.Space:
|
||||
if (!vm.PhotosLoaded) await vm.OpenDirectory();
|
||||
else await HandleEnter();
|
||||
break;
|
||||
case Key.Escape:
|
||||
HandleEscape();
|
||||
break;
|
||||
case Key.Down:
|
||||
HandleArrowKeyDown();
|
||||
UpdateKeyboardHover();
|
||||
await HandleArrowKey();
|
||||
break;
|
||||
case Key.Up:
|
||||
HandleArrowKeyUp();
|
||||
UpdateKeyboardHover();
|
||||
await HandleArrowKey();
|
||||
break;
|
||||
case Key.Left:
|
||||
if (_currentImageIndex > 0)
|
||||
{
|
||||
_currentImageIndex--;
|
||||
HandleArrowKey().GetAwaiter();
|
||||
UpdateKeyboardHover();
|
||||
await HandleArrowKey();
|
||||
}
|
||||
break;
|
||||
case Key.Right:
|
||||
if (_currentImageIndex < vm.Photos.Count - 1)
|
||||
{
|
||||
_currentImageIndex++;
|
||||
HandleArrowKey().GetAwaiter();
|
||||
UpdateKeyboardHover();
|
||||
await HandleArrowKey();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -74,9 +172,34 @@ public partial class MainWindow : Window
|
||||
|
||||
private void PointerEnteredPhotoHandler(object sender, PointerEventArgs e)
|
||||
{
|
||||
if (sender is not Image i || DataContext is not MainWindowViewModel vm) return;
|
||||
var idx = vm.GetImageIndex(i);
|
||||
if (idx == -1) return;
|
||||
if (sender is not Image image || DataContext is not MainWindowViewModel vm) return;
|
||||
|
||||
var photo = image.DataContext as Photo;
|
||||
if (photo is null) return;
|
||||
|
||||
var idx = vm.Photos.IndexOf(photo);
|
||||
if (idx < 0) return;
|
||||
|
||||
_navigationInputMode = NavigationInputMode.Mouse;
|
||||
UpdatePointerHoverSuppression();
|
||||
_currentImageIndex = idx;
|
||||
|
||||
if (_currentHoveredCard is null) return;
|
||||
|
||||
CardHoverEffectBehavior.ResetHover(_currentHoveredCard);
|
||||
_currentHoveredCard = null;
|
||||
}
|
||||
|
||||
private void UpdatePointerHoverSuppression()
|
||||
{
|
||||
foreach (var control in Repeater.GetVisualDescendants().OfType<Control>())
|
||||
{
|
||||
if (CardHoverEffectBehavior.GetIsEnabled(control))
|
||||
{
|
||||
CardHoverEffectBehavior.SetSuppressPointerHover(
|
||||
control,
|
||||
_navigationInputMode == NavigationInputMode.Keyboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user