Skip to content

Commit

Permalink
Implement pulse animation (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
helluvamatt authored Jun 5, 2020
1 parent c459f61 commit 6411be6
Show file tree
Hide file tree
Showing 7 changed files with 396 additions and 18 deletions.
99 changes: 98 additions & 1 deletion src/FontAwesome.Shared/Extensions/ControlExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;

#if WINDOWS_UWP
using Windows.Foundation;
using Windows.UI.Xaml;
Expand Down Expand Up @@ -89,6 +88,104 @@ public static void StopSpin<T>(this T control)
control.Resources.Remove(SpinnerStoryBoardName);
}

/// <summary>
/// The key used for storing the spinner Storyboard.
/// </summary>
private static readonly string PulseStoryBoardName = String.Format("{0}Pulse", typeof(FontAwesome).Name);

/// <summary>
/// Start the pulse animation
/// </summary>
/// <typeparam name="T">FrameworkElement and IPulsable</typeparam>
/// <param name="control">Control to apply the pulse animation</param>
public static void BeginPulse<T>(this T control)
where T : FrameworkElement, IPulsable
{
var transformGroup = control.RenderTransform as TransformGroup ?? new TransformGroup();

var rotateTransform = transformGroup.Children.OfType<RotateTransform>().FirstOrDefault();

if (rotateTransform != null)
{
rotateTransform.Angle = 0;
}
else
{
transformGroup.Children.Add(new RotateTransform() { Angle = 0.0 });
control.RenderTransform = transformGroup;
control.RenderTransformOrigin = new Point(0.5, 0.5);
}

var storyboard = new Storyboard();

#if WINDOWS_UWP
var animation = new DoubleAnimationUsingKeyFrames()
{
AutoReverse = false,
RepeatBehavior = RepeatBehavior.Forever,
Duration = new Duration(TimeSpan.FromSeconds(control.PulseDuration)),
};
animation.KeyFrames.Add(new DiscreteDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(control.PulseDuration * 0)), Value = 0 });
animation.KeyFrames.Add(new DiscreteDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(control.PulseDuration * 0.125)), Value = 45 });
animation.KeyFrames.Add(new DiscreteDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(control.PulseDuration * 0.25)), Value = 90 });
animation.KeyFrames.Add(new DiscreteDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(control.PulseDuration * 0.375)), Value = 135 });
animation.KeyFrames.Add(new DiscreteDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(control.PulseDuration * 0.5)), Value = 180 });
animation.KeyFrames.Add(new DiscreteDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(control.PulseDuration * 0.625)), Value = 225 });
animation.KeyFrames.Add(new DiscreteDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(control.PulseDuration * 0.75)), Value = 270 });
animation.KeyFrames.Add(new DiscreteDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(control.PulseDuration * 0.875)), Value = 315 });
animation.KeyFrames.Add(new DiscreteDoubleKeyFrame() { KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(control.PulseDuration * 1.0)), Value = 360 });
#else
var animation = new DoubleAnimationUsingKeyFrames
{
KeyFrames = new DoubleKeyFrameCollection
{
new DiscreteDoubleKeyFrame { KeyTime = KeyTime.FromPercent(0), Value = 0 },
new DiscreteDoubleKeyFrame { KeyTime = KeyTime.FromPercent(0.125), Value = 45 },
new DiscreteDoubleKeyFrame { KeyTime = KeyTime.FromPercent(0.25), Value = 90 },
new DiscreteDoubleKeyFrame { KeyTime = KeyTime.FromPercent(0.375), Value = 135 },
new DiscreteDoubleKeyFrame { KeyTime = KeyTime.FromPercent(0.5), Value = 180 },
new DiscreteDoubleKeyFrame { KeyTime = KeyTime.FromPercent(0.625), Value = 225 },
new DiscreteDoubleKeyFrame { KeyTime = KeyTime.FromPercent(0.75), Value = 270 },
new DiscreteDoubleKeyFrame { KeyTime = KeyTime.FromPercent(0.875), Value = 315 },
new DiscreteDoubleKeyFrame { KeyTime = KeyTime.FromPercent(1.0), Value = 360 },
},
AutoReverse = false,
RepeatBehavior = RepeatBehavior.Forever,
Duration = new Duration(TimeSpan.FromSeconds(control.PulseDuration))
};
#endif
storyboard.Children.Add(animation);

Storyboard.SetTarget(animation, control);
#if WINDOWS_UWP
Storyboard.SetTargetProperty(animation, "(FrameworkElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)");
#else
Storyboard.SetTargetProperty(animation,
new PropertyPath("(0).(1)[0].(2)", UIElement.RenderTransformProperty,
TransformGroup.ChildrenProperty, RotateTransform.AngleProperty));
#endif

storyboard.Begin();
control.Resources.Add(PulseStoryBoardName, storyboard);
}

/// <summary>
/// Stop the pulse animation
/// </summary>
/// <typeparam name="T">FrameworkElement and IPulsable</typeparam>
/// <param name="control">Control to stop the pulse animation</param>
public static void StopPulse<T>(this T control)
where T : FrameworkElement, IPulsable
{
var storyboard = control.Resources[PulseStoryBoardName] as Storyboard;

if (storyboard == null) return;

storyboard.Stop();

control.Resources.Remove(PulseStoryBoardName);
}

/// <summary>
/// Sets the rotation for the control
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/FontAwesome.Shared/FontAwesome.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Extensions\ControlExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\EFontAwesomeIconExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IFlippable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IPulsable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IRotatable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ISpinable.cs" />
</ItemGroup>
Expand Down
18 changes: 18 additions & 0 deletions src/FontAwesome.Shared/IPulsable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace FontAwesome5
{
/// <summary>
/// Represents a control that can have the pulse animation
/// </summary>
public interface IPulsable
{
/// <summary>
/// Gets or sets the state of the pulse animation
/// </summary>
bool Pulse { get; set; }

/// <summary>
/// Gets or sets the duration of the pulse animation
/// </summary>
double PulseDuration { get; set; }
}
}
91 changes: 81 additions & 10 deletions src/FontAwesome5.Net/FontAwesome.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace FontAwesome5
/// <summary>
/// Provides a lightweight control for displaying a FontAwesome icon as text.
/// </summary>
public class FontAwesome : TextBlock, ISpinable, IRotatable, IFlippable
public class FontAwesome : TextBlock, ISpinable, IRotatable, IFlippable, IPulsable
{
/// <summary>
/// Identifies the FontAwesome.WPF.FontAwesome.Icon dependency property.
Expand All @@ -29,6 +29,18 @@ public class FontAwesome : TextBlock, ISpinable, IRotatable, IFlippable
public static readonly DependencyProperty SpinDurationProperty =
DependencyProperty.Register("SpinDuration", typeof(double), typeof(FontAwesome), new PropertyMetadata(1d, SpinDurationChanged, SpinDurationCoerceValue));

/// <summary>
/// Identifies the FontAwesome.WPF.FontAwesome.Pulse dependency property.
/// </summary>
public static readonly DependencyProperty PulseProperty =
DependencyProperty.Register("Pulse", typeof(bool), typeof(FontAwesome), new PropertyMetadata(false, OnPulsePropertyChanged, PulseCoerceValue));

/// <summary>
/// Identifies the FontAwesome.WPF.FontAwesome.PulseDuration dependency property.
/// </summary>
public static readonly DependencyProperty PulseDurationProperty =
DependencyProperty.Register("PulseDuration", typeof(double), typeof(FontAwesome), new PropertyMetadata(1d, PulseDurationChanged, PulseDurationCoerceValue));

/// <summary>
/// Identifies the FontAwesome.WPF.FontAwesome.Rotation dependency property.
/// </summary>
Expand Down Expand Up @@ -65,15 +77,6 @@ public EFontAwesomeIcon Icon
set { SetValue(IconProperty, value); }
}

/// <summary>
/// Gets or sets the current spin (angle) animation of the icon.
/// </summary>
public bool Spin
{
get { return (bool)GetValue(SpinProperty); }
set { SetValue(SpinProperty, value); }
}

private static void OnIconPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
#if NET40
Expand All @@ -84,6 +87,15 @@ private static void OnIconPropertyChanged(DependencyObject d, DependencyProperty
d.SetValue(TextProperty, ((EFontAwesomeIcon)e.NewValue).GetUnicode());
}

/// <summary>
/// Gets or sets the current spin (angle) animation of the icon.
/// </summary>
public bool Spin
{
get { return (bool)GetValue(SpinProperty); }
set { SetValue(SpinProperty, value); }
}

private static void OnSpinPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var fontAwesome = d as FontAwesome;
Expand Down Expand Up @@ -134,6 +146,65 @@ private static object SpinDurationCoerceValue(DependencyObject d, object value)
return val < 0 ? 0d : value;
}

/// <summary>
/// Gets or sets the current pulse animation of the icon.
/// </summary>
public bool Pulse
{
get { return (bool)GetValue(PulseProperty); }
set { SetValue(PulseProperty, value); }
}

private static void OnPulsePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var fontAwesome = d as FontAwesome;

if (fontAwesome == null) return;

if ((bool)e.NewValue)
fontAwesome.BeginPulse();
else
{
fontAwesome.StopPulse();
fontAwesome.SetRotation();
}
}

private static object PulseCoerceValue(DependencyObject d, object basevalue)
{
var fontAwesome = (FontAwesome)d;

if (!fontAwesome.IsVisible || fontAwesome.Opacity == 0.0 || fontAwesome.PulseDuration == 0.0)
return false;

return basevalue;
}

/// <summary>
/// Gets or sets the duration of the pulse animation (in seconds). This will stop and start the pulse animation.
/// </summary>
public double PulseDuration
{
get { return (double)GetValue(PulseDurationProperty); }
set { SetValue(PulseDurationProperty, value); }
}

private static void PulseDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var fontAwesome = d as FontAwesome;

if (null == fontAwesome || !fontAwesome.Pulse || !(e.NewValue is double) || e.NewValue.Equals(e.OldValue)) return;

fontAwesome.StopPulse();
fontAwesome.BeginPulse();
}

private static object PulseDurationCoerceValue(DependencyObject d, object value)
{
double val = (double)value;
return val < 0 ? 0d : value;
}

/// <summary>
/// Gets or sets the current rotation (angle).
/// </summary>
Expand Down
75 changes: 72 additions & 3 deletions src/FontAwesome5.Net/ImageAwesome.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
using System;
using FontAwesome5.Extensions;
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using FontAwesome5.Extensions;

namespace FontAwesome5
{
/// <summary>
/// Represents a control that draws an FontAwesome icon as an image.
/// </summary>
public class ImageAwesome
: Image, ISpinable, IRotatable, IFlippable
: Image, ISpinable, IRotatable, IFlippable, IPulsable
{
/// <summary>
/// Identifies the FontAwesome.WPF.ImageAwesome.Foreground dependency property.
Expand All @@ -34,6 +34,16 @@ public class ImageAwesome
public static readonly DependencyProperty SpinDurationProperty =
DependencyProperty.Register("SpinDuration", typeof(double), typeof(ImageAwesome), new PropertyMetadata(1d, SpinDurationChanged, SpinDurationCoerceValue));
/// <summary>
/// Identifies the FontAwesome.WPF.FontAwesome.Pulse dependency property.
/// </summary>
public static readonly DependencyProperty PulseProperty =
DependencyProperty.Register("Pulse", typeof(bool), typeof(ImageAwesome), new PropertyMetadata(false, OnPulsePropertyChanged, PulseCoerceValue));
/// <summary>
/// Identifies the FontAwesome.WPF.FontAwesome.PulseDuration dependency property.
/// </summary>
public static readonly DependencyProperty PulseDurationProperty =
DependencyProperty.Register("PulseDuration", typeof(double), typeof(ImageAwesome), new PropertyMetadata(1d, PulseDurationChanged, PulseDurationCoerceValue));
/// <summary>
/// Identifies the FontAwesome.WPF.ImageAwesome.Rotation dependency property.
/// </summary>
public static readonly DependencyProperty RotationProperty =
Expand Down Expand Up @@ -136,6 +146,65 @@ private static object SpinDurationCoerceValue(DependencyObject d, object value)
return val < 0 ? 0d : value;
}

/// <summary>
/// Gets or sets the current pulse animation of the icon.
/// </summary>
public bool Pulse
{
get { return (bool)GetValue(PulseProperty); }
set { SetValue(PulseProperty, value); }
}

private static void OnPulsePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var fontAwesome = d as ImageAwesome;

if (fontAwesome == null) return;

if ((bool)e.NewValue)
fontAwesome.BeginPulse();
else
{
fontAwesome.StopPulse();
fontAwesome.SetRotation();
}
}

private static object PulseCoerceValue(DependencyObject d, object basevalue)
{
var fontAwesome = (ImageAwesome)d;

if (!fontAwesome.IsVisible || fontAwesome.Opacity == 0.0 || fontAwesome.PulseDuration == 0.0)
return false;

return basevalue;
}

/// <summary>
/// Gets or sets the duration of the pulse animation (in seconds). This will stop and start the pulse animation.
/// </summary>
public double PulseDuration
{
get { return (double)GetValue(PulseDurationProperty); }
set { SetValue(PulseDurationProperty, value); }
}

private static void PulseDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var fontAwesome = d as ImageAwesome;

if (null == fontAwesome || !fontAwesome.Pulse || !(e.NewValue is double) || e.NewValue.Equals(e.OldValue)) return;

fontAwesome.StopPulse();
fontAwesome.BeginPulse();
}

private static object PulseDurationCoerceValue(DependencyObject d, object value)
{
double val = (double)value;
return val < 0 ? 0d : value;
}

/// <summary>
/// Gets or sets the current rotation (angle).
/// </summary>
Expand Down
Loading

0 comments on commit 6411be6

Please sign in to comment.