Skip to content

Commit

Permalink
Add demo for swipe navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
stevemonaco committed Jan 2, 2025
1 parent acf2aec commit 62654c8
Show file tree
Hide file tree
Showing 26 changed files with 1,174 additions and 10 deletions.
6 changes: 6 additions & 0 deletions AvaloniaDemos.sln
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Debugging", "Debugging\Debu
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TextHighlighting", "TextHighlighting\TextHighlighting.csproj", "{B93F9F04-5F52-45A9-AE01-7135D8287979}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SwipeNavigation", "SwipeNavigation\SwipeNavigation.csproj", "{18539E36-6717-4A9C-9683-2D77DD7CEDA6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -81,6 +83,10 @@ Global
{B93F9F04-5F52-45A9-AE01-7135D8287979}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B93F9F04-5F52-45A9-AE01-7135D8287979}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B93F9F04-5F52-45A9-AE01-7135D8287979}.Release|Any CPU.Build.0 = Release|Any CPU
{18539E36-6717-4A9C-9683-2D77DD7CEDA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{18539E36-6717-4A9C-9683-2D77DD7CEDA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18539E36-6717-4A9C-9683-2D77DD7CEDA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18539E36-6717-4A9C-9683-2D77DD7CEDA6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
<TargetFramework>net9.0</TargetFramework>
<LangVersion>13</LangVersion>
<Nullable>enable</Nullable>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
</Project>
20 changes: 10 additions & 10 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@
<AvsIncludeSkiaSharp3>true</AvsIncludeSkiaSharp3>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.2.0" />
<PackageVersion Include="Avalonia.Desktop" Version="11.2.0" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.2.0" />
<PackageVersion Include="Avalonia.Themes.Simple" Version="11.2.0" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.2.0" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.2.0" />
<PackageVersion Include="Avalonia" Version="11.2.3" />
<PackageVersion Include="Avalonia.Desktop" Version="11.2.3" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.2.3" />
<PackageVersion Include="Avalonia.Themes.Simple" Version="11.2.3" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.2.3" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.2.3" />
<PackageVersion Include="Avalonia.Xaml.Behaviors" Version="11.1.0" />
<PackageVersion Include="Avalonia.Controls.TreeDataGrid" Version="11.0.10" />
<PackageVersion Include="SkiaSharp" Version="3.118.0-preview.1.2" />
<PackageVersion Include="Avalonia.Controls.TreeDataGrid" Version="11.1.0" />
<PackageVersion Include="SkiaSharp" Version="3.116.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="9.0.0" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.3.2" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageVersion Include="ReactiveProperty" Version="9.6.0" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.1.0" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.2.0" />
<PackageVersion Include="Jot" Version="2.1.17" />
<PackageVersion Include="Bogus" Version="35.6.1" />
<PackageVersion Include="CsvHelper" Version="33.0.1" />
Expand Down
13 changes: 13 additions & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>

<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
8 changes: 8 additions & 0 deletions SwipeNavigation/App.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Application x:Class="SwipeNavigation.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
RequestedThemeVariant="Default">
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
23 changes: 23 additions & 0 deletions SwipeNavigation/App.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using SwipeNavigation.Views;

namespace SwipeNavigation;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}

public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}

base.OnFrameworkInitializationCompleted();
}
}
13 changes: 13 additions & 0 deletions SwipeNavigation/Gestures/CustomGestures.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Avalonia.Interactivity;

namespace SwipeNavigation.Gestures;
public static class CustomGestures
{
public static readonly RoutedEvent<SwipeGestureEventArgs> SwipeGestureEvent =
RoutedEvent.Register<SwipeGestureEventArgs>(
"Swipe", RoutingStrategies.Bubble, typeof(CustomGestures));

public static readonly RoutedEvent<SwipeGestureEndedEventArgs> SwipeGestureEndedEvent =
RoutedEvent.Register<SwipeGestureEndedEventArgs>(
"SwipeEnded", RoutingStrategies.Bubble, typeof(CustomGestures));
}
18 changes: 18 additions & 0 deletions SwipeNavigation/Gestures/SwipeGestureEndedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Avalonia;
using Avalonia.Interactivity;

namespace SwipeNavigation.Gestures;
public class SwipeGestureEndedEventArgs : RoutedEventArgs
{
public int Id { get; }
public Vector? Delta { get; }
public SwipeDirection Direction { get; }

public SwipeGestureEndedEventArgs(int id, Vector? delta, SwipeDirection direction) :
base(CustomGestures.SwipeGestureEndedEvent)
{
Id = id;
Delta = delta;
Direction = direction;
}
}
22 changes: 22 additions & 0 deletions SwipeNavigation/Gestures/SwipeGestureEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Avalonia;
using Avalonia.Interactivity;

namespace SwipeNavigation.Gestures;
public class SwipeGestureEventArgs : RoutedEventArgs
{
public int Id { get; }
public Vector? Delta { get; }
public SwipeDirection ExpectedDirection { get; }

private static int _nextId = 1;

internal static int GetNextFreeId() => _nextId++;

public SwipeGestureEventArgs(int id, Vector? delta, SwipeDirection expectedDirection)
: base(CustomGestures.SwipeGestureEvent)
{
Id = id;
Delta = delta;
ExpectedDirection = expectedDirection;
}
}
101 changes: 101 additions & 0 deletions SwipeNavigation/Gestures/SwipeGestureRecognizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using Avalonia.Input.GestureRecognizers;
using Avalonia.Input;
using Avalonia;

namespace SwipeNavigation.Gestures;

public enum SwipeDirection
{
Left,
Right,
Bottom,
Top,
None
}

/// <summary>
/// Implements a swipe gesture recognizer that can detect swipe gestures on top of a page
/// </summary>
public partial class SwipeGestureRecognizer : GestureRecognizer
{
private Point _initialPosition;
private int _gestureId;
private IPointer? _tracking;
private bool _pullInProgress;
private SwipeDirection _expectedDirection = SwipeDirection.None;

public SwipeGestureRecognizer() { }

protected override void PointerCaptureLost(IPointer pointer)
{
if (_tracking == pointer)
{
EndSwipe(null);
}
}

protected override void PointerMoved(PointerEventArgs e)
{
if (_tracking == e.Pointer && Target is Visual visual)
{
var currentPosition = e.GetPosition(visual);
Capture(e.Pointer);

var delta = new Vector(currentPosition.X - _initialPosition.X, currentPosition.Y - _initialPosition.Y);
_expectedDirection = CalculateDirection(delta);

_pullInProgress = true;
var pullEventArgs = new SwipeGestureEventArgs(_gestureId, delta, _expectedDirection);
Target?.RaiseEvent(pullEventArgs);

e.Handled = pullEventArgs.Handled;
}
}

protected override void PointerPressed(PointerPressedEventArgs e)
{
if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
{
var position = e.GetPosition(visual);

_gestureId = SwipeGestureEventArgs.GetNextFreeId();
_tracking = e.Pointer;
_initialPosition = position;
}
}

protected override void PointerReleased(PointerReleasedEventArgs e)
{
if (_tracking == e.Pointer && _pullInProgress && Target is Visual visual)
{
var currentPosition = e.GetPosition(visual);
var delta = new Vector(currentPosition.X - _initialPosition.X, currentPosition.Y - _initialPosition.Y);
_expectedDirection = CalculateDirection(delta);

EndSwipe(delta);
}
}

private SwipeDirection CalculateDirection(Vector delta)
{
if (delta.X <= -SwipeThreshold && CanSwipeLeft)
return SwipeDirection.Left;
else if (delta.X >= SwipeThreshold && CanSwipeRight)
return SwipeDirection.Right;
else if (delta.Y <= -SwipeThreshold && CanSwipeUp)
return SwipeDirection.Top;
else if (delta.Y >= SwipeThreshold && CanSwipeDown)
return SwipeDirection.Bottom;
else
return SwipeDirection.None;
}

private void EndSwipe(Vector? delta)
{
_tracking = null;
_initialPosition = default;
_pullInProgress = false;

Target?.RaiseEvent(new SwipeGestureEndedEventArgs(_gestureId, delta, _expectedDirection));
}
}
69 changes: 69 additions & 0 deletions SwipeNavigation/Gestures/SwipeGestureRecognizer.props.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using Avalonia;

namespace SwipeNavigation.Gestures;
public partial class SwipeGestureRecognizer
{
public static readonly StyledProperty<int> SwipeThresholdProperty =
AvaloniaProperty.Register<SwipeGestureRecognizer, int>(nameof(SwipeThreshold), 50);

/// <summary>
/// Gets or sets the threshold in pixels that must be exceeded for a swipe to have its Direction set.
/// </summary>
public int SwipeThreshold
{
get => GetValue(SwipeThresholdProperty);
set => SetValue(SwipeThresholdProperty, value);
}

public static readonly StyledProperty<bool> CanSwipeLeftProperty =
AvaloniaProperty.Register<SwipeGestureRecognizer, bool>(nameof(CanSwipeLeft));

/// <summary>
/// Gets or sets the CanSwipeLeft property. This StyledProperty
/// indicates ....
/// </summary>
public bool CanSwipeLeft
{
get => GetValue(CanSwipeLeftProperty);
set => SetValue(CanSwipeLeftProperty, value);
}

public static readonly StyledProperty<bool> CanSwipeRightProperty =
AvaloniaProperty.Register<SwipeGestureRecognizer, bool>(nameof(CanSwipeRight));

/// <summary>
/// Gets or sets the CanSwipeRight property. This StyledProperty
/// indicates ....
/// </summary>
public bool CanSwipeRight
{
get => GetValue(CanSwipeRightProperty);
set => SetValue(CanSwipeRightProperty, value);
}

public static readonly StyledProperty<bool> CanSwipeUpProperty =
AvaloniaProperty.Register<SwipeGestureRecognizer, bool>(nameof(CanSwipeUp));

/// <summary>
/// Gets or sets the CanSwipeUp property. This StyledProperty
/// indicates ....
/// </summary>
public bool CanSwipeUp
{
get => GetValue(CanSwipeUpProperty);
set => SetValue(CanSwipeUpProperty, value);
}

public static readonly StyledProperty<bool> CanSwipeDownProperty =
AvaloniaProperty.Register<SwipeGestureRecognizer, bool>(nameof(CanSwipeDown));

/// <summary>
/// Gets or sets the CanSwipeDown property. This StyledProperty
/// indicates ....
/// </summary>
public bool CanSwipeDown
{
get => GetValue(CanSwipeDownProperty);
set => SetValue(CanSwipeDownProperty, value);
}
}
24 changes: 24 additions & 0 deletions SwipeNavigation/MainView.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<UserControl x:Class="SwipeNavigation.Views.MainView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="using:SwipeNavigation.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:g="using:SwipeNavigation.Gestures"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:v="using:SwipeNavigation.Views"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Panel>
<c:TransitioningPageControl>
<!-- Example of how to attach via XAML. Already attached by TransitioningPageControl itself though -->
<!--<c:TransitioningPageControl.GestureRecognizers>
<g:SwipeGestureRecognizer SwipeThreshold="50" CanSwipeLeft="True" CanSwipeRight="True" />
</c:TransitioningPageControl.GestureRecognizers>-->

<v:PageOne />
<v:PageTwo />
<v:PageThree />
</c:TransitioningPageControl>
</Panel>
</UserControl>
10 changes: 10 additions & 0 deletions SwipeNavigation/MainView.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Avalonia.Controls;

namespace SwipeNavigation.Views;
public partial class MainView : UserControl
{
public MainView()
{
InitializeComponent();
}
}
12 changes: 12 additions & 0 deletions SwipeNavigation/MainWindow.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Window x:Class="SwipeNavigation.Views.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:v="using:SwipeNavigation.Views"
Title="SwipeNavigation"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<v:MainView />
</Window>
10 changes: 10 additions & 0 deletions SwipeNavigation/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Avalonia.Controls;

namespace SwipeNavigation.Views;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
Loading

0 comments on commit 62654c8

Please sign in to comment.