Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Added swipe gesture for back/forward navigation #12043

Merged
merged 48 commits into from
Apr 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
057d31c
Add invalid name warning
heftymouse Feb 16, 2023
dc0c048
Add string
heftymouse Feb 16, 2023
da9ef96
fix regression
heftymouse Feb 16, 2023
a949b58
Tabify
heftymouse Feb 17, 2023
4d142e9
Merge branch 'main' into main
yaira2 Feb 17, 2023
31de5cf
Merge branch 'main' of https://github.com/files-community/Files
heftymouse Feb 18, 2023
9f0c951
fix color change not updating button
heftymouse Feb 18, 2023
968bd67
Merge branch 'main' into main
yaira2 Feb 19, 2023
8056161
fix changing back to original color not updating button
heftymouse Feb 19, 2023
77eac60
Merge branch 'main' of https://github.com/heftymouse/Files
heftymouse Feb 19, 2023
6518deb
Switch to infobar and add tooltip
heftymouse Feb 28, 2023
6232f3e
Made infobar compact
heftymouse Mar 12, 2023
56a9969
Move style
heftymouse Mar 12, 2023
4f233e3
Unswitch to infobar
heftymouse Mar 16, 2023
0ef9905
Merge branch 'main' of https://github.com/Files-community/Files
heftymouse Mar 17, 2023
e9d1917
Readd tooltip
heftymouse Mar 17, 2023
feb3663
Merge branch 'main' of https://github.com/Files-community/Files
heftymouse Mar 22, 2023
c571d92
remove merge conflict markers
heftymouse Mar 22, 2023
fbb739c
Stop creating tags with existing name, formatting
heftymouse Mar 22, 2023
ff7ba88
Change servicing branch pattern
heftymouse Apr 5, 2023
8155003
big oopsie
heftymouse Apr 5, 2023
a1496ee
Merge branch 'files-community:main' into main
heftymouse Apr 10, 2023
4da33df
Merge branch 'main' of https://github.com/heftymouse/Files
heftymouse Apr 10, 2023
a01127b
Add swipe gesture for back/forward navigation
heftymouse Apr 11, 2023
ac93652
remove weird extra usings
heftymouse Apr 11, 2023
8a34e8c
fix animation not running on reaching history start/end
heftymouse Apr 11, 2023
67f830e
changes
heftymouse Apr 13, 2023
8389ce7
do not fire event if navigation cannot occur
heftymouse Apr 13, 2023
0519bf6
dispose stuff
heftymouse Apr 14, 2023
35c1532
fix overscroll
heftymouse Apr 15, 2023
e2335d4
add setting
heftymouse Apr 15, 2023
155f920
change style
heftymouse Apr 15, 2023
2ca3b65
Update src/Files.App/Files.App.csproj
yaira2 Apr 18, 2023
699e152
Merge branch 'main' into pr/12043
yaira2 Apr 18, 2023
dce7720
Fixed reference to GeneralSettingsService
yaira2 Apr 18, 2023
141cfae
remove setting
heftymouse Apr 20, 2023
f8709ce
Increased threshold
yaira2 Apr 20, 2023
a4bffc1
Merge branch 'main' into main
yaira2 Apr 20, 2023
946003f
add animation for reaching navigation threshold
heftymouse Apr 22, 2023
7d2f80b
tweak scale anim
heftymouse Apr 22, 2023
7bf5a80
more adjustments
heftymouse Apr 22, 2023
dd451c6
add license header
heftymouse Apr 23, 2023
8af9582
use windows 11 accent brush
heftymouse Apr 23, 2023
9422e53
remove ellipse
heftymouse Apr 23, 2023
ca5c848
Merge branch 'main' into main
yaira2 Apr 23, 2023
4c51132
change to border
heftymouse Apr 23, 2023
a22a4cb
Merge branch 'main' of https://github.com/heftymouse/Files
heftymouse Apr 23, 2023
d3c5b23
Merge branch 'main' into main
yaira2 Apr 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 48 additions & 7 deletions src/Files.App/Views/ModernShellPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Files.App.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wct="using:CommunityToolkit.WinUI.UI"
xmlns:wctconverters="using:CommunityToolkit.WinUI.UI.Converters"
x:Name="RootPage"
KeyboardAcceleratorPlacementMode="Hidden"
Expand Down Expand Up @@ -51,12 +52,52 @@
Modifiers="Menu" />
</local:BaseShellPage.KeyboardAccelerators>

<Frame
x:Name="ItemDisplayFrame"
HorizontalAlignment="Stretch"
x:FieldModifier="public"
BorderBrush="{x:Bind CurrentInstanceBorderBrush, Mode=OneWay}"
BorderThickness="{x:Bind CurrentInstanceBorderThickness, Mode=OneWay}"
Navigated="ItemDisplayFrame_Navigated" />
<Grid HorizontalAlignment="Stretch" wct:UIElementExtensions.ClipToBounds="True">
<Border
x:Name="BackIcon"
Width="48"
Height="48"
Margin="-1,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
wct:VisualExtensions.NormalizedCenterPoint="0.5, 0.5"
Background="{ThemeResource AccentFillColorDefaultBrush}"
BorderBrush="{ThemeResource AccentControlElevationBorderBrush}"
BorderThickness="1"
Canvas.ZIndex="64"
CornerRadius="24">
<FontIcon
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
Glyph="&#xE72B;" />
</Border>
<Border
x:Name="ForwardIcon"
Width="48"
Height="48"
Margin="1,0,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
wct:VisualExtensions.NormalizedCenterPoint="0.5, 0.5"
Background="{ThemeResource AccentFillColorDefaultBrush}"
BorderBrush="{ThemeResource AccentControlElevationBorderBrush}"
BorderThickness="1"
Canvas.ZIndex="64"
CornerRadius="24">
<SymbolIcon
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
Symbol="Forward" />
</Border>
<Frame
x:Name="ItemDisplayFrame"
HorizontalAlignment="Stretch"
x:FieldModifier="public"
BorderBrush="{x:Bind CurrentInstanceBorderBrush, Mode=OneWay}"
BorderThickness="{x:Bind CurrentInstanceBorderThickness, Mode=OneWay}"
Navigated="ItemDisplayFrame_Navigated" />
</Grid>

</local:BaseShellPage>
24 changes: 23 additions & 1 deletion src/Files.App/Views/ModernShellPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public sealed partial class ModernShellPage : BaseShellPage

protected override Frame ItemDisplay => ItemDisplayFrame;

private NavigationInteractionTracker _navigationInteractionTracker;

public Thickness CurrentInstanceBorderThickness
{
get => (Thickness)GetValue(CurrentInstanceBorderThicknessProperty);
Expand All @@ -56,8 +58,10 @@ public ModernShellPage() : base(new CurrentInstanceViewModel())
FilesystemViewModel.GitDirectoryUpdated += FilesystemViewModel_GitDirectoryUpdated;

ToolbarViewModel.PathControlDisplayText = "Home".GetLocalizedResource();

ToolbarViewModel.RefreshWidgetsRequested += ModernShellPage_RefreshWidgetsRequested;

_navigationInteractionTracker = new NavigationInteractionTracker(this, BackIcon, ForwardIcon);
_navigationInteractionTracker.NavigationRequested += OverscrollNavigationRequested;
}

private void ModernShellPage_RefreshWidgetsRequested(object sender, EventArgs e)
Expand Down Expand Up @@ -179,6 +183,8 @@ private async void ItemDisplayFrame_Navigated(object sender, NavigationEventArgs

if (parameters.IsLayoutSwitch)
FilesystemViewModel_DirectoryInfoUpdated(sender, EventArgs.Empty);
_navigationInteractionTracker.CanNavigateBackward = CanNavigateBackward;
_navigationInteractionTracker.CanNavigateForward = CanNavigateForward;
}

private async void KeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
Expand Down Expand Up @@ -225,6 +231,20 @@ private async void KeyboardAccelerator_Invoked(KeyboardAccelerator sender, Keybo
}
}

private void OverscrollNavigationRequested(object? sender, OverscrollNavigationEventArgs e)
{
switch (e)
{
case OverscrollNavigationEventArgs.Forward:
Forward_Click();
break;

case OverscrollNavigationEventArgs.Back:
Back_Click();
break;
}
}

public override void Back_Click()
{
ToolbarViewModel.CanGoBack = false;
Expand Down Expand Up @@ -289,6 +309,8 @@ public override void Up_Click()
public override void Dispose()
{
ToolbarViewModel.RefreshWidgetsRequested -= ModernShellPage_RefreshWidgetsRequested;
_navigationInteractionTracker.NavigationRequested -= OverscrollNavigationRequested;
_navigationInteractionTracker.Dispose();

base.Dispose();
}
Expand Down
261 changes: 261 additions & 0 deletions src/Files.App/Views/NavigationInteractionTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
// Copyright (c) 2023 Files Community
// Licensed under the MIT License. See the LICENSE.

using Microsoft.UI.Composition;
using Microsoft.UI.Composition.Interactions;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Hosting;
using Microsoft.UI.Xaml.Input;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;

namespace Files.App.Views
{
internal class NavigationInteractionTracker : IDisposable
{
public bool CanNavigateForward
{
get
{
_props.TryGetBoolean(nameof(CanNavigateForward), out bool val);
return val;
}
set
{
_props.InsertBoolean(nameof(CanNavigateForward), value);
_tracker.MaxPosition = new(value ? 96f : 0f);
}
}

public bool CanNavigateBackward
{
get
{
_props.TryGetBoolean(nameof(CanNavigateBackward), out bool val);
return val;
}
set
{
_props.InsertBoolean(nameof(CanNavigateBackward), value);
_tracker.MinPosition = new(value ? -96f : 0f);
}
}

private UIElement _rootElement;
private UIElement _backIcon;
private UIElement _forwardIcon;

private PointerEventHandler _pointerPressedHandler;

private Visual _rootVisual;
private Visual _backVisual;
private Visual _forwardVisual;

private InteractionTracker _tracker;
private VisualInteractionSource _source;
private InteractionTrackerOwner _trackerOwner;
private CompositionPropertySet _props;

public event EventHandler<OverscrollNavigationEventArgs>? NavigationRequested;

private bool _disposed;

public NavigationInteractionTracker(UIElement rootElement, UIElement backIcon, UIElement forwardIcon)
{
_rootElement = rootElement;
_backIcon = backIcon;
_forwardIcon = forwardIcon;

ElementCompositionPreview.SetIsTranslationEnabled(_backIcon, true);
ElementCompositionPreview.SetIsTranslationEnabled(_forwardIcon, true);
_rootVisual = ElementCompositionPreview.GetElementVisual(_rootElement);
_backVisual = ElementCompositionPreview.GetElementVisual(_backIcon);
_forwardVisual = ElementCompositionPreview.GetElementVisual(_forwardIcon);

SetupInteractionTracker();

_props = _rootVisual.Compositor.CreatePropertySet();
CanNavigateBackward = false;
CanNavigateForward = false;

SetupAnimations();

_pointerPressedHandler = new(PointerPressed);
_rootElement.AddHandler(UIElement.PointerPressedEvent, _pointerPressedHandler, true);
}

[MemberNotNull(nameof(_tracker), nameof(_source), nameof(_trackerOwner))]
private void SetupInteractionTracker()
{
var compositor = _rootVisual.Compositor;

_trackerOwner = new(this);
_tracker = InteractionTracker.CreateWithOwner(compositor, _trackerOwner);
_tracker.MinPosition = new Vector3(-96f);
_tracker.MaxPosition = new Vector3(96f);

_source = VisualInteractionSource.Create(_rootVisual);
_source.ManipulationRedirectionMode = VisualInteractionSourceRedirectionMode.CapableTouchpadOnly;
_source.PositionXSourceMode = InteractionSourceMode.EnabledWithoutInertia;
_source.PositionXChainingMode = InteractionChainingMode.Always;
_source.PositionYSourceMode = InteractionSourceMode.Disabled;
_tracker.InteractionSources.Add(_source);
}

private void SetupAnimations()
{
var compositor = _rootVisual.Compositor;

var backResistance = CreateResistanceCondition(-96f, 0f);
var forwardResistance = CreateResistanceCondition(0f, 96f);
List<CompositionConditionalValue> conditionalValues = new() { backResistance, forwardResistance };
_source.ConfigureDeltaPositionXModifiers(conditionalValues);

var backAnim = compositor.CreateExpressionAnimation("(-clamp(tracker.Position.X, -96, 0) * 2) - 48");
backAnim.SetReferenceParameter("tracker", _tracker);
backAnim.SetReferenceParameter("props", _props);
_backVisual.StartAnimation("Translation.X", backAnim);

var forwardAnim = compositor.CreateExpressionAnimation("(-clamp(tracker.Position.X, 0, 96) * 2) + 48");
forwardAnim.SetReferenceParameter("tracker", _tracker);
forwardAnim.SetReferenceParameter("props", _props);
_forwardVisual.StartAnimation("Translation.X", forwardAnim);
}

private void PointerPressed(object sender, PointerRoutedEventArgs e)
{
if (e.Pointer.PointerDeviceType == PointerDeviceType.Touch)
{
_source.TryRedirectForManipulation(e.GetCurrentPoint(_rootElement));
}
}

private CompositionConditionalValue CreateResistanceCondition(float minValue, float maxValue)
{
var compositor = _rootVisual.Compositor;

var resistance = CompositionConditionalValue.Create(compositor);
var resistanceCondition = compositor.CreateExpressionAnimation($"tracker.Position.X > {minValue} && tracker.Position.X < {maxValue}");
resistanceCondition.SetReferenceParameter("tracker", _tracker);
var resistanceValue = compositor.CreateExpressionAnimation($"source.DeltaPosition.X * (1 - sqrt(1 - square((tracker.Position.X / {minValue + maxValue}) - 1)))");
resistanceValue.SetReferenceParameter("source", _source);
resistanceValue.SetReferenceParameter("tracker", _tracker);
resistance.Condition = resistanceCondition;
resistance.Value = resistanceValue;

return resistance;
}

~NavigationInteractionTracker()
{
Dispose();
}

public void Dispose()
{
if (_disposed)
return;

_rootElement.RemoveHandler(UIElement.PointerPressedEvent, _pointerPressedHandler);
_backVisual.StopAnimation("Translation.X");
_forwardVisual.StopAnimation("Translation.X");
_tracker.Dispose();
_source.Dispose();
_props.Dispose();

_disposed = true;
GC.SuppressFinalize(this);
}

private class InteractionTrackerOwner : IInteractionTrackerOwner
{
private NavigationInteractionTracker _parent;
private bool _shouldBounceBack;
private bool _shouldAnimate = true;
private Vector3KeyFrameAnimation _scaleAnimation;
private SpringVector3NaturalMotionAnimation _returnAnimation;

public InteractionTrackerOwner(NavigationInteractionTracker parent)
{
_parent = parent;

var compositor = _parent._rootVisual.Compositor;
_scaleAnimation = compositor.CreateVector3KeyFrameAnimation();
_scaleAnimation.InsertKeyFrame(0.5f, new(1.3f));
_scaleAnimation.InsertKeyFrame(1f, new(1f));
_scaleAnimation.Duration = TimeSpan.FromMilliseconds(275);

_returnAnimation = compositor.CreateSpringVector3Animation();
_returnAnimation.FinalValue = new(0f);
_returnAnimation.DampingRatio = 1f;
}

public void IdleStateEntered(InteractionTracker sender, InteractionTrackerIdleStateEnteredArgs args)
{
if (!_shouldBounceBack)
return;

if (Math.Abs(sender.Position.X) > 64)
{
_parent._tracker.TryUpdatePosition(new(0f));

EventHandler<OverscrollNavigationEventArgs>? navEvent = _parent.NavigationRequested;
if (navEvent is not null)
{
if (sender.Position.X > 0 && _parent.CanNavigateForward)
{
navEvent(_parent, OverscrollNavigationEventArgs.Forward);
}
else if (sender.Position.X < 0 && _parent.CanNavigateBackward)
{
navEvent(_parent, OverscrollNavigationEventArgs.Back);
}
}
}
else
{
_parent._tracker.TryUpdatePositionWithAnimation(_returnAnimation);
}
_shouldBounceBack = false;
_shouldAnimate = true;
}

public void InteractingStateEntered(InteractionTracker sender, InteractionTrackerInteractingStateEnteredArgs args)
{
_shouldBounceBack = true;
}

public void ValuesChanged(InteractionTracker sender, InteractionTrackerValuesChangedArgs args)
{
if (!_shouldAnimate)
return;

if (args.Position.X <= -64)
{
_parent._backVisual.StartAnimation("Scale", _scaleAnimation);
_shouldAnimate = false;
}
else if (args.Position.X >= 64)
{
_parent._forwardVisual.StartAnimation("Scale", _scaleAnimation);
_shouldAnimate = false;
}

}

// required to implement IInteractionTrackerOwner
public void CustomAnimationStateEntered(InteractionTracker sender, InteractionTrackerCustomAnimationStateEnteredArgs args) { }
public void InertiaStateEntered(InteractionTracker sender, InteractionTrackerInertiaStateEnteredArgs args) { }
public void RequestIgnored(InteractionTracker sender, InteractionTrackerRequestIgnoredArgs args) { }
}
}

public enum OverscrollNavigationEventArgs
{
Back,
Forward
}
}