Skip to content

Commit

Permalink
feat: 支持向后导航的反向动画
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyfairy committed Apr 16, 2024
1 parent cd16bbe commit 8abeb53
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 113 deletions.
67 changes: 56 additions & 11 deletions EleCho.WpfSuite/Controls/Frame.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace EleCho.WpfSuite
{
[TemplatePart(Name = "PART_ContentControl", Type = typeof(TransitioningContentControl))]
public class Frame : System.Windows.Controls.Frame
{
static Frame()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Frame), new FrameworkPropertyMetadata(typeof(Frame)));
}

ContentProperty.OverrideMetadata(typeof(Frame), new FrameworkPropertyMetadata(null, OnFrameContentChanged));
}

private TransitioningContentControl? _contentControl;
private object? _pendingNewContent;
private int _lastBackStackSize;

public IContentTransition Transition
{
get { return (IContentTransition)GetValue(TransitionProperty); }
set { SetValue(TransitionProperty, value); }
}

public override void OnApplyTemplate()
{
base.OnApplyTemplate();

_contentControl = GetTemplateChild("PART_ContentControl") as TransitioningContentControl;
if (_contentControl is not null &&
_pendingNewContent is not null)
{
ApplyFrameContentChange(_pendingNewContent);
}
}

private int GetBackStackSize()
{
if (BackStack is null)
return 0;

int count = 0;
foreach (object _ in BackStack)
{
count++;
}

return count;
}

private void ApplyFrameContentChange(object? content)
{
if (_contentControl is null)
{
_pendingNewContent = content;
return;
}

var currentBackStackSize = GetBackStackSize();
var forward = currentBackStackSize >= _lastBackStackSize;

_pendingNewContent = null;
_contentControl.SetContent(content, forward);
_lastBackStackSize = currentBackStackSize;
}

private static void OnFrameContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Frame frame)
{
frame.ApplyFrameContentChange(e.NewValue);
}
}

// Using a DependencyProperty as the backing store for Transition. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TransitionProperty =
TransitioningContentControl.TransitionProperty.AddOwner(typeof(Frame));
Expand Down
10 changes: 4 additions & 6 deletions EleCho.WpfSuite/Controls/FrameResources.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -392,9 +392,8 @@
</Button.LayoutTransform>
</Button>
</Grid>
<ws:TransitioningContentControl x:Name="PART_FrameCP"
Transition="{TemplateBinding Transition}"
Content="{TemplateBinding Content}"/>
<ws:TransitioningContentControl x:Name="PART_ContentControl"
Transition="{TemplateBinding Transition}"/>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
Expand All @@ -412,9 +411,8 @@
<Setter.Value>
<ControlTemplate TargetType="{x:Type ws:Frame}">
<Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
<ws:TransitioningContentControl x:Name="PART_FrameCP"
Transition="{TemplateBinding Transition}"
Content="{TemplateBinding Content}"/>
<ws:TransitioningContentControl x:Name="PART_ContentControl"
Transition="{TemplateBinding Transition}"/>
</Border>
</ControlTemplate>
</Setter.Value>
Expand Down
10 changes: 9 additions & 1 deletion EleCho.WpfSuite/Controls/Image.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,15 @@ protected override Size MeasureOverride(Size constraint)
var borderThickness = BorderThickness;

var imageSize = new Size(imageSource.Width, imageSource.Height);
var imageConstraint = new Size(constraint.Width - borderThickness.Left - borderThickness.Right, constraint.Height - borderThickness.Top - borderThickness.Bottom);

var imageConstraintWidth = constraint.Width - borderThickness.Left - borderThickness.Right;
var imageConstraintHeight = constraint.Height - borderThickness.Top - borderThickness.Bottom;
if(imageConstraintWidth < 0 || imageConstraintHeight < 0)
{
return constraint;
}

var imageConstraint = new Size(imageConstraintWidth, imageConstraintHeight);
var factor = imageSize.Width / imageSize.Height;

if (imageConstraint.Width < imageSize.Width)
Expand Down
10 changes: 9 additions & 1 deletion EleCho.WpfSuite/Controls/SlicedImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,15 @@ protected override Size MeasureOverride(Size constraint)
var borderThickness = BorderThickness;

var imageSize = new Size(imageSource.Width, imageSource.Height);
var imageConstraint = new Size(constraint.Width - borderThickness.Left - borderThickness.Right, constraint.Height - borderThickness.Top - borderThickness.Bottom);

var imageConstraintWidth = constraint.Width - borderThickness.Left - borderThickness.Right;
var imageConstraintHeight = constraint.Height - borderThickness.Top - borderThickness.Bottom;
if (imageConstraintWidth < 0 || imageConstraintHeight < 0)
{
return constraint;
}

var imageConstraint = new Size(imageConstraintWidth, imageConstraintHeight);
var factor = imageSize.Width / imageSize.Height;

if (imageConstraint.Width < imageSize.Width)
Expand Down
74 changes: 41 additions & 33 deletions EleCho.WpfSuite/Controls/TransitioningContentControl.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace EleCho.WpfSuite
{
[TemplatePart(Name = "PART_Contents")]
[TemplatePart(Name = "PART_Contents", Type = typeof(Panel))]
public class TransitioningContentControl : Control
{
static TransitioningContentControl()
Expand All @@ -28,6 +18,7 @@ static TransitioningContentControl()
private UIElement? _lastOldControl;
private CancellationTokenSource? _lastTaskCancellation;
private object? _pendingNewContent;
private bool _backward;

public override void OnApplyTemplate()
{
Expand Down Expand Up @@ -65,6 +56,16 @@ public IContentTransition? Transition
set { SetValue(TransitionProperty, value); }
}

public void SetContent(object? content)
{
Content = content;
}

public void SetContent(object? content, bool forward)
{
_backward = !forward;
Content = content;
}

public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register(nameof(Content), typeof(object), typeof(TransitioningContentControl), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure, new PropertyChangedCallback(OnContentChanged)));
Expand All @@ -86,7 +87,7 @@ private static void OnContentChanged(DependencyObject d, DependencyPropertyChang
}
}

private async Task ApplyContentChangeAsync(object? oldContent, object newContent)
private async Task ApplyContentChangeAsync(object? oldContent, object? newContent)
{
if (_contentsPanel is null)
{
Expand All @@ -105,38 +106,45 @@ private async Task ApplyContentChangeAsync(object? oldContent, object newContent
}

UIElement? oldContentElement = null;
UIElement? newContentElement = null;
if (_contentsPanel.Children.Count > 0)
{
oldContentElement = _contentsPanel.Children[_contentsPanel.Children.Count - 1];
}

ContentPresenter newContentElement = new ContentPresenter()
if(newContent is not null)
{
Content = newContent
};

newContentElement.SetBinding(ContentPresenter.ContentTemplateProperty,
new Binding()
{
Source = this,
Path = new PropertyPath(nameof(ContentTemplate)),
});

newContentElement.SetBinding(ContentPresenter.ContentTemplateSelectorProperty,
new Binding()
var contentPresenter = new ContentPresenter()
{
Source = this,
Path = new PropertyPath(nameof(ContentTemplateSelector)),
});

Content = newContent
};

contentPresenter.SetBinding(ContentPresenter.ContentTemplateProperty,
new Binding()
{
Source = this,
Path = new PropertyPath(nameof(ContentTemplate)),
});

contentPresenter.SetBinding(ContentPresenter.ContentTemplateSelectorProperty,
new Binding()
{
Source = this,
Path = new PropertyPath(nameof(ContentTemplateSelector)),
});

newContentElement = contentPresenter;
}

var forward = !_backward;
_contentsPanel.Children.Add(newContentElement);
_lastOldControl = oldContentElement;
_pendingNewContent = null;
if (Transition is IContentTransition transition &&
oldContentElement is FrameworkElement oldContentFrameworkElement)
_backward = false;
if (Transition is IContentTransition transition)
{
_lastTaskCancellation = new();
await transition.Run(this, oldContentFrameworkElement, newContentElement, true, _lastTaskCancellation.Token);
await transition.Run(this, oldContentElement as FrameworkElement, newContentElement as FrameworkElement, forward, _lastTaskCancellation.Token);
}

if (oldContentElement is not null)
Expand Down
21 changes: 16 additions & 5 deletions EleCho.WpfSuite/Transitions/ContentTransition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,34 @@ public IEasingFunction EasingFunction
protected abstract Storyboard CreateNewContentStoryboard(UIElement container, UIElement newContent, bool forward);


public async Task Run(FrameworkElement container, FrameworkElement? oldContent, FrameworkElement newContent, bool forward, CancellationToken cancellationToken)
public async Task Run(FrameworkElement container, FrameworkElement? oldContent, FrameworkElement? newContent, bool forward, CancellationToken cancellationToken)
{
bool completed = false;
Storyboard newContentStoryboard = CreateNewContentStoryboard(container, newContent, forward);
Storyboard? newContentStoryboard = null;
Storyboard? oldContentStorybaord = null;

if (oldContent is not null)
{
oldContentStorybaord = CreateOldContentStoryboard(container, oldContent, forward);
}
if (newContent is not null)
{
newContentStoryboard = CreateNewContentStoryboard(container, newContent, forward);
}

var availableStoryboard = oldContentStorybaord ?? newContentStoryboard;

if (availableStoryboard is null)
{
return;
}

newContentStoryboard.Completed += (s, e) =>
availableStoryboard.Completed += (s, e) =>
{
completed = true;
};

newContentStoryboard.Begin(newContent);
newContentStoryboard?.Begin(newContent);
oldContentStorybaord?.Begin(oldContent);

while (true)
Expand All @@ -59,7 +70,7 @@ public async Task Run(FrameworkElement container, FrameworkElement? oldContent,

if (cancellationToken.IsCancellationRequested)
{
newContentStoryboard.Stop(newContent);
newContentStoryboard?.Stop(newContent);
oldContentStorybaord?.Stop(oldContent);
break;
}
Expand Down
2 changes: 1 addition & 1 deletion EleCho.WpfSuite/Transitions/IContentTransition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ namespace EleCho.WpfSuite
{
public interface IContentTransition
{
public Task Run(FrameworkElement container, FrameworkElement? oldContent, FrameworkElement newContent, bool forward, CancellationToken cancellationToken);
public Task Run(FrameworkElement container, FrameworkElement? oldContent, FrameworkElement? newContent, bool forward, CancellationToken cancellationToken);
}
}
15 changes: 4 additions & 11 deletions WpfTest/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,12 @@
</ListBox.ItemTemplate>
</ListBox>
<ws:Frame Name="AppFrame"
NavigationUIVisibility="Hidden"
NavigationUIVisibility="Visible"
ClipToBounds="True">
<ws:Frame.Transition>
<!--<ws:SlideFadeTransition Orientation="Vertical"
Duration="0:0:0.300"
Distance="100"
Reverse="{Binding AppFrameTransitionReverse}">
<ws:SlideFadeTransition.EasingFunction>
<CircleEase EasingMode="EaseInOut"/>
</ws:SlideFadeTransition.EasingFunction>
</ws:SlideFadeTransition>-->
<ws:ScaleTransition Duration="0:0:0.300"
Reverse="{Binding AppFrameTransitionReverse}"/>
<ws:ScaleTransition Duration="0:0:0.300">
</ws:ScaleTransition>

</ws:Frame.Transition>
</ws:Frame>
</DockPanel>
Expand Down
Loading

0 comments on commit 8abeb53

Please sign in to comment.