/// https://github.com/sanje2v/MarqueeTextControl/blob/master/MarqueeText/MarqueeTextControl.cs
/// Heavily modfied Control created by sanje2v
using System;
using System.Linq;
using Windows.Foundation;
using Windows.Graphics.Display;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;
namespace OwlCore.WinUI.Controls
{
///
/// A Control that displays Text in a Marquee style.
///
[TemplatePart(Name = "canvas", Type = typeof(Canvas))]
[TemplatePart(Name = "stackpanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "textblock", Type = typeof(TextBlock))]
[TemplatePart(Name = "textblock2", Type = typeof(TextBlock))]
[ContentProperty(Name = "Text")]
public sealed partial class MarqueeTextBlock : Control
{
private static string CANVAS_NAME = "canvas";
private static string RECT_GEOMETRY_CANVAS_NAME = "rectanglegeometeryClipCanvas";
private static string TEXTBLOCK_NAME = "textblock";
private static string TEXTBLOCK2_NAME = "textblock2";
private static string STACKPANEL_NAME = "stackpanel";
private static string VISUALSTATE_STOPPED = "visualstateStopped";
private static string VISUALSTATE_MARQUEE = "visualstateMarquee";
private Storyboard? _storyboardMarquee;
private static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(MarqueeTextBlock), new PropertyMetadata(null,
(sender, e) =>
{
var control = (MarqueeTextBlock)sender;
control.StartMarqueeAnimationIfNeeded();
}));
private static readonly DependencyProperty IsStoppedProperty = DependencyProperty.Register(nameof(IsStopped), typeof(bool), typeof(MarqueeTextBlock), new PropertyMetadata(false,
(sender, e) =>
{
var control = (MarqueeTextBlock)sender;
control.StartMarqueeAnimationIfNeeded();
}));
private static readonly DependencyProperty EasingFunctionProperty = DependencyProperty.Register(nameof(EasingFunction), typeof(EasingFunctionBase), typeof(MarqueeTextBlock), new PropertyMetadata(null,
(sender, e) =>
{
var control = (MarqueeTextBlock)sender;
control.StartMarqueeAnimationIfNeeded();
}));
private static readonly DependencyProperty IsAbsoluteSpeedProperty = DependencyProperty.Register(nameof(IsAbsoluteSpeed), typeof(bool), typeof(MarqueeTextBlock), new PropertyMetadata(false,
(sender, e) =>
{
var control = (MarqueeTextBlock)sender;
control.StartMarqueeAnimationIfNeeded();
}));
private static readonly DependencyProperty AbsoluteSpeedProperty = DependencyProperty.Register(nameof(AbsoluteSpeed), typeof(double), typeof(MarqueeTextBlock), new PropertyMetadata(32d,
(sender, e) =>
{
var control = (MarqueeTextBlock)sender;
control.StartMarqueeAnimationIfNeeded();
}));
private static readonly DependencyProperty AnimationDurationProperty = DependencyProperty.Register(nameof(AnimationDuration), typeof(TimeSpan), typeof(MarqueeTextBlock), new PropertyMetadata(TimeSpan.FromSeconds(4),
(sender, e) =>
{
var control = (MarqueeTextBlock)sender;
control.StartMarqueeAnimationIfNeeded();
}));
private static readonly DependencyProperty IsTickerProperty = DependencyProperty.Register(nameof(IsTicker), typeof(bool), typeof(MarqueeTextBlock), new PropertyMetadata(false,
(sender, e) =>
{
var control = (MarqueeTextBlock)sender;
control.StartMarqueeAnimationIfNeeded();
}));
//private static readonly DependencyProperty AnimationSpeedRatioProperty = DependencyProperty.Register(nameof(AnimationSpeedRatio), typeof(double), typeof(MarqueeTextControl), new PropertyMetadata(1.0d,
// (sender, e) =>
// {
// var control = (MarqueeTextControl)sender;
// control.StartMarqueeAnimationIfNeeded();
// }));
private static readonly DependencyProperty IsRepeatingProperty = DependencyProperty.Register(nameof(IsRepeating), typeof(bool), typeof(MarqueeTextBlock), new PropertyMetadata(true,
(sender, e) =>
{
var control = (MarqueeTextBlock)sender;
control.StartMarqueeAnimationIfNeeded();
}));
private static readonly DependencyProperty TextMarginProperty = DependencyProperty.Register(nameof(TextMargin), typeof(Thickness), typeof(MarqueeTextBlock), new PropertyMetadata(new Thickness(8,0,0,0),
(sender, e) =>
{
var control = (MarqueeTextBlock)sender;
control.StartMarqueeAnimationIfNeeded();
}));
///
/// Initializes a new instance of the class.
///
public MarqueeTextBlock()
: base()
{
this.DefaultStyleKey = typeof(MarqueeTextBlock);
FontSize_OnChanged(this, FontSizeProperty);
RegisterPropertyChangedCallback(FontSizeProperty, FontSize_OnChanged);
}
void FontSize_OnChanged(DependencyObject sender, DependencyProperty dp)
{
// Set minimum height
var displayInfo = DisplayInformation.GetForCurrentView();
var minimumHeight = ((this.FontSize / 72.0d) * displayInfo.LogicalDpi) / displayInfo.RawPixelsPerViewPixel;
this.MinHeight = minimumHeight;
}
///
/// Gets or sets the text being displayed in Marquee.
///
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
///
/// Gets or sets whether or not the Marquee is stopped.
///
public bool IsStopped
{
get { return (bool)GetValue(IsStoppedProperty); }
set { SetValue(IsStoppedProperty, value); }
}
///
/// Gets or sets the easing of the animation rate.
///
public EasingFunctionBase EasingFunction
{
get { return (EasingFunctionBase)GetValue(EasingFunctionProperty); }
set { SetValue(EasingFunctionProperty, value); }
}
///
/// Gets or sets whether or not the speed of the text is absolute or frequency based.
///
public bool IsAbsoluteSpeed
{
get { return (bool)GetValue(IsAbsoluteSpeedProperty); }
set { SetValue(IsAbsoluteSpeedProperty, value); }
}
///
/// Gets or sets the absolute speed of the text if the speed is absolute.
///
public double AbsoluteSpeed
{
get { return (double)GetValue(AbsoluteSpeedProperty); }
set { SetValue(AbsoluteSpeedProperty, value); }
}
///
/// The frequency of a loop if the text speed is not absolute.
///
public TimeSpan AnimationDuration
{
get { return (TimeSpan)GetValue(AnimationDurationProperty); }
set { SetValue(AnimationDurationProperty, value); }
}
//public double AnimationSpeedRatio
//{
// get { return (double)GetValue(AnimationSpeedRatioProperty); }
// set { SetValue(AnimationSpeedRatioProperty, value); }
//}
///
/// Whether or not the Marquee is a ticker or quick scroll.
///
public bool IsTicker
{
get { return (bool)GetValue(IsTickerProperty); }
set { SetValue(IsTickerProperty, value); }
}
///
/// Gets or sets whether or not the Marquee scroll should repeat.
///
public bool IsRepeating
{
get { return (bool)GetValue(IsRepeatingProperty); }
set { SetValue(IsRepeatingProperty, value); }
}
///
/// Gets or sets the margin between the redundent texts.
///
public double TextMarginLeft
{
get { return ((Thickness)GetValue(TextMarginProperty)).Left; }
set
{
SetValue(TextMarginProperty, new Thickness(value, 0, 0, 0));
}
}
///
/// Gets the margin between the redundent texts.
///
public Thickness TextMargin
{
get { return (Thickness)GetValue(TextMarginProperty); }
}
///
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
StopMarqueeAnimation(useTransitions: false);
this.SizeChanged += MarqueeTextControl_SizeChanged; // NOTE: This event will start animation, if needed
}
private void MarqueeTextControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
var a = DisplayInformation.GetForCurrentView();
var b = this.FontSize;
StopMarqueeAnimation();
StartMarqueeAnimationIfNeeded();
}
private void StopMarqueeAnimation(bool useTransitions = true)
{
VisualStateManager.GoToState(this, VISUALSTATE_STOPPED, useTransitions);
}
private void StartMarqueeAnimationIfNeeded(bool useTransitions = true)
{
var canvas = (Canvas)GetTemplateChild(CANVAS_NAME);
if (canvas == null)
return;
// Change clip rectangle for new canvas size
var rectanglegeometryClipCanvas = (RectangleGeometry)GetTemplateChild(RECT_GEOMETRY_CANVAS_NAME);
if (rectanglegeometryClipCanvas != null)
rectanglegeometryClipCanvas.Rect = new Rect(0.0d, 0.0d, canvas.ActualWidth, canvas.ActualHeight);
if (this.IsStopped)
{
StopMarqueeAnimation(useTransitions);
return;
}
// Add an animation handler
var stackpanel = (StackPanel)GetTemplateChild(STACKPANEL_NAME);
var textblock = (TextBlock)GetTemplateChild(TEXTBLOCK_NAME);
var textblock2 = (TextBlock)GetTemplateChild(TEXTBLOCK2_NAME);
if (textblock != null)
{
// Animation is only needed if 'textblock' is larger than canvas
if (textblock.ActualWidth > canvas.ActualWidth)
{
TimeSpan duration = this.AnimationDuration;
if (this.IsAbsoluteSpeed)
{
duration = TimeSpan.FromSeconds(textblock.ActualWidth / this.AbsoluteSpeed);
}
var visualstateGroups = VisualStateManager.GetVisualStateGroups(canvas).First();
var visualstateMarquee = visualstateGroups.States.Single(l => l.Name == VISUALSTATE_MARQUEE);
if (_storyboardMarquee != null)
_storyboardMarquee.Completed -= StoryboardMarquee_Completed;
_storyboardMarquee = new Storyboard()
{
//AutoReverse = false,
Duration = duration,
RepeatBehavior = IsRepeating ? RepeatBehavior.Forever : new RepeatBehavior(1),
//SpeedRatio = this.AnimationSpeedRatio
};
_storyboardMarquee.Completed += StoryboardMarquee_Completed;
if (IsTicker)
{
// Redundent text not neccesary if ticker.
textblock2.Visibility = Visibility.Collapsed;
var animationMarquee = new DoubleAnimationUsingKeyFrames
{
//AutoReverse = _storyboardMarquee.AutoReverse,
Duration = _storyboardMarquee.Duration,
RepeatBehavior = _storyboardMarquee.RepeatBehavior,
//SpeedRatio = _storyboardMarquee.SpeedRatio,
};
var frame1 = new DiscreteDoubleKeyFrame
{
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)),
Value = canvas.ActualWidth
};
var frame2 = new EasingDoubleKeyFrame
{
KeyTime = KeyTime.FromTimeSpan(_storyboardMarquee.Duration.TimeSpan),
Value = -(textblock.ActualWidth + TextMarginLeft),
EasingFunction = this.EasingFunction
};
animationMarquee.KeyFrames.Add(frame1);
animationMarquee.KeyFrames.Add(frame2);
_storyboardMarquee.Children.Add(animationMarquee);
Storyboard.SetTarget(animationMarquee, textblock.RenderTransform);
Storyboard.SetTargetProperty(animationMarquee, "(TranslateTransform.X)");
}
else
{
var animationMarquee = new DoubleAnimation
{
//AutoReverse = _storyboardMarquee.AutoReverse,
Duration = _storyboardMarquee.Duration,
RepeatBehavior = _storyboardMarquee.RepeatBehavior,
//SpeedRatio = _storyboardMarquee.SpeedRatio,
From = 0.0d,
To = -(textblock.ActualWidth + TextMarginLeft),
EasingFunction = this.EasingFunction
};
_storyboardMarquee.Children.Add(animationMarquee);
Storyboard.SetTarget(animationMarquee, stackpanel.RenderTransform);
Storyboard.SetTargetProperty(animationMarquee, "(TranslateTransform.X)");
}
visualstateMarquee.Storyboard = _storyboardMarquee;
VisualStateManager.GoToState(this, VISUALSTATE_MARQUEE, useTransitions);
}
else
{
textblock2.Visibility = Visibility.Collapsed;
}
}
}
private void StoryboardMarquee_Completed(object sender, object e)
{
IsStopped = true;
if (_storyboardMarquee != null)
_storyboardMarquee.Completed -= StoryboardMarquee_Completed;
}
}
}