-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(desktop): Add back navigation between views
- Loading branch information
1 parent
0e8338d
commit 0d03c26
Showing
7 changed files
with
264 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
source/Gnomeshade.Avalonia.Core/Interactivity/HotKeyBehaviour.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// Copyright 2021 Valters Melnalksnis | ||
// Licensed under the GNU Affero General Public License v3.0 or later. | ||
// See LICENSE.txt file in the project root for full license information. | ||
|
||
using System.Windows.Input; | ||
|
||
using Avalonia; | ||
using Avalonia.Controls; | ||
using Avalonia.Input; | ||
using Avalonia.Xaml.Interactivity; | ||
|
||
namespace Gnomeshade.Avalonia.Core.Interactivity; | ||
|
||
/// <summary>Behaviour that executes a command when a hotkey has been pressed.</summary> | ||
public sealed class HotKeyBehaviour : Trigger<Control> | ||
{ | ||
/// <summary>Identifies the <seealso cref="TopLevel"/> avalonia property.</summary> | ||
public static readonly DirectProperty<HotKeyBehaviour, TopLevel?> TopLevelProperty = | ||
AvaloniaProperty.RegisterDirect<HotKeyBehaviour, TopLevel?>( | ||
nameof(TopLevel), | ||
behaviour => behaviour.TopLevel, | ||
(behaviour, topLevel) => behaviour.TopLevel = topLevel); | ||
|
||
/// <summary>Identifies the <seealso cref="HotKey"/> avalonia property.</summary> | ||
public static readonly DirectProperty<HotKeyBehaviour, KeyGesture?> HotKeyProperty = | ||
AvaloniaProperty.RegisterDirect<HotKeyBehaviour, KeyGesture?>( | ||
nameof(HotKey), | ||
behaviour => behaviour.HotKey, | ||
(behaviour, keyGesture) => behaviour.HotKey = keyGesture); | ||
|
||
/// <summary>Identifies the <seealso cref="Command"/> avalonia property.</summary> | ||
public static readonly DirectProperty<HotKeyBehaviour, ICommand?> CommandProperty = | ||
AvaloniaProperty.RegisterDirect<HotKeyBehaviour, ICommand?>( | ||
nameof(Command), | ||
behaviour => behaviour.Command, | ||
(behaviour, command) => behaviour.Command = command); | ||
|
||
private KeyBinding? _gesture; | ||
|
||
/// <summary>Gets or sets the <see cref="TopLevel"/> control in which to register the <see cref="HotKey"/>.</summary> | ||
public TopLevel? TopLevel { get; set; } | ||
|
||
/// <summary>Gets or sets the <see cref="KeyGesture"/> which will trigger the <see cref="Command"/>.</summary> | ||
public KeyGesture? HotKey { get; set; } | ||
|
||
/// <summary>Gets or sets the <see cref="ICommand"/> that will be executed when <see cref="HotKey"/> is pressed.</summary> | ||
public ICommand? Command { get; set; } | ||
|
||
/// <inheritdoc /> | ||
protected override void OnAttachedToVisualTree() | ||
{ | ||
if (TopLevel is { } topLevel && Command is { } command && HotKey is { } hotKey) | ||
{ | ||
_gesture = new() { Command = command, Gesture = hotKey }; | ||
topLevel.KeyBindings.Add(_gesture); | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override void OnDetachedFromVisualTree() | ||
{ | ||
if (TopLevel is { } topLevel && _gesture is { } gesture) | ||
{ | ||
topLevel.KeyBindings.Remove(gesture); | ||
} | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
source/Gnomeshade.Avalonia.Core/Interactivity/PointerReleasedTrigger.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// Copyright 2021 Valters Melnalksnis | ||
// Licensed under the GNU Affero General Public License v3.0 or later. | ||
// See LICENSE.txt file in the project root for full license information. | ||
|
||
using Avalonia; | ||
using Avalonia.Input; | ||
using Avalonia.Interactivity; | ||
using Avalonia.Xaml.Interactivity; | ||
|
||
namespace Gnomeshade.Avalonia.Core.Interactivity; | ||
|
||
/// <summary>Triggers actions based on <see cref="InputElement.PointerReleasedEvent"/> routed event.</summary> | ||
public sealed class PointerReleasedTrigger : Trigger<InputElement> | ||
{ | ||
/// <summary>Identifies the <seealso cref="SourceInteractive"/> avalonia property.</summary> | ||
public static readonly StyledProperty<Interactive?> SourceInteractiveProperty = | ||
AvaloniaProperty.Register<PointerReleasedTrigger, Interactive?>(nameof(SourceInteractive)); | ||
|
||
/// <summary>Identifies the <seealso cref="MouseButton"/> avalonia property.</summary> | ||
public static readonly StyledProperty<MouseButton> MouseButtonProperty = | ||
AvaloniaProperty.Register<PointerReleasedTrigger, MouseButton>(nameof(MouseButton)); | ||
|
||
private bool _isInitialized; | ||
|
||
/// <summary> | ||
/// Gets or sets the source object from which this behavior listens for events. | ||
/// If <seealso cref="SourceInteractive"/> is not set, the source will default to <seealso cref="Behavior.AssociatedObject"/>. This is an avalonia property. | ||
/// </summary> | ||
public Interactive? SourceInteractive | ||
{ | ||
get => GetValue(SourceInteractiveProperty); | ||
set => SetValue(SourceInteractiveProperty, value); | ||
} | ||
|
||
/// <summary>Gets or sets the mouse button for which the trigger will list for.</summary> | ||
public MouseButton MouseButton | ||
{ | ||
get => GetValue(MouseButtonProperty); | ||
set => SetValue(MouseButtonProperty, value); | ||
} | ||
|
||
private static RoutedEvent<PointerReleasedEventArgs> RoutedEvent => InputElement.PointerReleasedEvent; | ||
|
||
private Interactive? Interactive => SourceInteractive ?? AssociatedObject; | ||
|
||
/// <inheritdoc /> | ||
protected override void OnAttachedToVisualTree() | ||
{ | ||
if (Interactive is { } interactive) | ||
{ | ||
interactive.AddHandler(RoutedEvent, Handler, RoutingStrategies.Tunnel); | ||
_isInitialized = true; | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override void OnDetachedFromVisualTree() | ||
{ | ||
if (Interactive is { } interactive && _isInitialized) | ||
{ | ||
interactive.RemoveHandler(RoutedEvent, Handler); | ||
_isInitialized = false; | ||
} | ||
} | ||
|
||
private void Handler(object? sender, PointerReleasedEventArgs e) | ||
{ | ||
if (Interactive is { } interactive && e.InitialPressMouseButton == MouseButton) | ||
{ | ||
Interaction.ExecuteActions(interactive, Actions, e); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
tests/Gnomeshade.Avalonia.Core.Tests/MainWindowViewModelTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// Copyright 2021 Valters Melnalksnis | ||
// Licensed under the GNU Affero General Public License v3.0 or later. | ||
// See LICENSE.txt file in the project root for full license information. | ||
|
||
using System.Threading.Tasks; | ||
|
||
using Gnomeshade.Avalonia.Core.Configuration; | ||
using Gnomeshade.Avalonia.Core.Counterparties; | ||
using Gnomeshade.Avalonia.Core.DesignTime; | ||
using Gnomeshade.Avalonia.Core.Products; | ||
using Gnomeshade.Avalonia.Core.Transactions; | ||
|
||
namespace Gnomeshade.Avalonia.Core.Tests; | ||
|
||
[TestOf(typeof(MainWindowViewModel))] | ||
public sealed class MainWindowViewModelTests | ||
{ | ||
[Test] | ||
public async Task NavigateBack() | ||
{ | ||
var viewModel = DesignTimeData.MainWindowViewModel; | ||
|
||
viewModel.Initialize.Execute(null); | ||
while (viewModel.IsBusy) | ||
{ | ||
await Task.Delay(100); | ||
} | ||
|
||
using (new AssertionScope()) | ||
{ | ||
viewModel.ActiveView.Should().BeOfType<ConfigurationWizardViewModel>("configuration should not be valid"); | ||
viewModel.NavigateBack.CanExecute(null).Should().BeFalse(); | ||
} | ||
|
||
await viewModel.SwitchToTransactionOverviewAsync(); | ||
using (new AssertionScope()) | ||
{ | ||
viewModel.ActiveView.Should().BeOfType<TransactionViewModel>(); | ||
viewModel.NavigateBack.CanExecute(null).Should().BeFalse(); | ||
} | ||
|
||
await viewModel.SwitchToCategoriesAsync(); | ||
using (new AssertionScope()) | ||
{ | ||
viewModel.ActiveView.Should().BeOfType<CategoryViewModel>(); | ||
viewModel.NavigateBack.CanExecute(null).Should().BeTrue(); | ||
} | ||
|
||
await viewModel.SwitchToCounterpartiesAsync(); | ||
using (new AssertionScope()) | ||
{ | ||
viewModel.ActiveView.Should().BeOfType<CounterpartyViewModel>(); | ||
viewModel.NavigateBack.CanExecute(null).Should().BeTrue(); | ||
} | ||
|
||
viewModel.NavigateBack.Execute(null); | ||
while (viewModel.IsBusy) | ||
{ | ||
await Task.Delay(100); | ||
} | ||
|
||
using (new AssertionScope()) | ||
{ | ||
viewModel.ActiveView.Should().BeOfType<CategoryViewModel>(); | ||
viewModel.NavigateBack.CanExecute(null).Should().BeTrue(); | ||
} | ||
|
||
viewModel.NavigateBack.Execute(null); | ||
while (viewModel.IsBusy) | ||
{ | ||
await Task.Delay(100); | ||
} | ||
|
||
using (new AssertionScope()) | ||
{ | ||
viewModel.NavigateBack.CanExecute(null).Should().BeFalse(); | ||
viewModel.ActiveView.Should().BeOfType<TransactionViewModel>(); | ||
} | ||
} | ||
} |