diff --git a/components/Marquee/samples/Dependencies.props b/components/Marquee/samples/Dependencies.props
index e622e1df4..b0471fc89 100644
--- a/components/Marquee/samples/Dependencies.props
+++ b/components/Marquee/samples/Dependencies.props
@@ -11,21 +11,21 @@
-
+
-
+
-
+
-
+
diff --git a/components/Marquee/samples/Marquee.md b/components/Marquee/samples/Marquee.md
index 69f8e9a96..f5a7b8dd5 100644
--- a/components/Marquee/samples/Marquee.md
+++ b/components/Marquee/samples/Marquee.md
@@ -15,6 +15,8 @@ icon: Assets/Marquee.png
The Marquee control allows text or other content to scroll in a marquee fashion. The control is heavily templated and many changes can be made by modifying the style. The control can also be adjusted using the Speed, Behavior, RepeatBehavior, and Direction properties.
+> [!Sample MarqueeTextSample]
+
## Speed
The speed property determines how quickly the content moves in pixels per second. The speed can be adjusted mid-animation and handled continously.
@@ -23,6 +25,8 @@ The speed property determines how quickly the content moves in pixels per second
The Marquee control has 3 behaviors
+Default: `Ticker`
+
### Ticker
Ticker mode starts with all content off the screen then scrolls the content across across the screen. When the animation finishes in the mode no content will be on screen.
@@ -39,14 +43,34 @@ Looping mode will begin with the start of the content fully in frame then scroll
The repeat behavior determines how many times the marquee will loop before the animation finishes. It can be a number of iteration, a duration, or forever.
+Default: `1x`
+
## Direction
The default direction is left, meaning the content will move leftwards, but this can be changed to right, up, or down. Direction changed between left and right or up and down are handled continously, meaning that the animation will resume from its current position if changed between these directions.
-> [!Sample MarqueeTextSample]
+Default: `Left`
+
+## Speed
+
+The speed property determines how quickly the content moves in pixels-per-second. The speed can be adjusted mid-animation and handled continously. Because the speed is in pixels per second the actual time it takes to scroll will depend on the size of the content. The content can also change size dynamically and the speed will remain constant.
+
+Default: `32`
+
+## AutoPlay
+
+The AutoPlay property determines if the marquee will start animating when it is loaded, upon on size changes, or when the content changes. If set to false the marquee can be started manually by calling the `StartMarquee` method.
+
+Default: `false`
## Non-Text Content
It is possible to use non-text content in the Marquee control. However templating must be used because the control will need to be duplicated for the looping animation.
> [!Sample MarqueeSample]
+
+# Level Up with Behaviors
+
+Use behaviors to triggers the Marquee on events
+
+> [!Sample MarqueeBehaviorSample]
diff --git a/components/Marquee/samples/MarqueeBehaviorSample.xaml b/components/Marquee/samples/MarqueeBehaviorSample.xaml
new file mode 100644
index 000000000..1b8772d64
--- /dev/null
+++ b/components/Marquee/samples/MarqueeBehaviorSample.xaml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ consectetur adipiscing elit
+
+ Ut enim ad minim veniam
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/Marquee/samples/MarqueeBehaviorSample.xaml.cs b/components/Marquee/samples/MarqueeBehaviorSample.xaml.cs
new file mode 100644
index 000000000..e4bc7a8bf
--- /dev/null
+++ b/components/Marquee/samples/MarqueeBehaviorSample.xaml.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace MarqueeExperiment.Samples;
+
+[ToolkitSample(id: nameof(MarqueeBehaviorSample), "Marquee", description: "A control for scrolling content in a marquee fashion.")]
+public sealed partial class MarqueeBehaviorSample : Page
+{
+ public MarqueeBehaviorSample()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/Marquee/samples/MarqueeSample.xaml b/components/Marquee/samples/MarqueeSample.xaml
index 5a37d7777..ad9325679 100644
--- a/components/Marquee/samples/MarqueeSample.xaml
+++ b/components/Marquee/samples/MarqueeSample.xaml
@@ -1,4 +1,4 @@
-
+
-
-
-
- MarqueeTextSample.xaml
-
-
-
-
- MarqueeSample.xaml
-
-
diff --git a/components/Marquee/samples/MarqueeTextSample.xaml b/components/Marquee/samples/MarqueeTextSample.xaml
index 3912726e9..7d5d25bd6 100644
--- a/components/Marquee/samples/MarqueeTextSample.xaml
+++ b/components/Marquee/samples/MarqueeTextSample.xaml
@@ -1,4 +1,4 @@
-
+
-
-
+
+
+
+
+
+
diff --git a/components/Marquee/samples/MarqueeTextSample.xaml.cs b/components/Marquee/samples/MarqueeTextSample.xaml.cs
index d42d73b9c..8ef7eccb7 100644
--- a/components/Marquee/samples/MarqueeTextSample.xaml.cs
+++ b/components/Marquee/samples/MarqueeTextSample.xaml.cs
@@ -16,6 +16,7 @@ namespace MarqueeExperiment.Samples;
#else
[ToolkitSampleMultiChoiceOption("MQBehavior", "Ticker", "Looping", Title = "Marquee Behavior")]
#endif
+[ToolkitSampleBoolOption("MQAuto", true, Title = "Auto Play")]
public sealed partial class MarqueeTextSample : Page
{
public MarqueeTextSample()
@@ -41,4 +42,12 @@ public MarqueeTextSample()
"Down" => MarqueeDirection.Down,
_ => throw new System.NotImplementedException(),
};
+
+ private void StartMarquee_Click(object sender, RoutedEventArgs e) => MarqueeControl.StartMarquee();
+
+ private void StopMarquee_Click(object sender, RoutedEventArgs e) => MarqueeControl.StopMarquee();
+
+ private void ResumeMarquee_Click(object sender, RoutedEventArgs e) => MarqueeControl.ResumeMarquee();
+
+ private void PauseMarquee_Click(object sender, RoutedEventArgs e) => MarqueeControl.PauseMarquee();
}
diff --git a/components/Marquee/src/Marquee.Events.cs b/components/Marquee/src/Marquee.Events.cs
index 53502d7e2..c6e8463ef 100644
--- a/components/Marquee/src/Marquee.Events.cs
+++ b/components/Marquee/src/Marquee.Events.cs
@@ -12,13 +12,23 @@ public partial class Marquee
///
/// Event raised when the Marquee begins scrolling.
///
- public event EventHandler? MarqueeBegan;
+ public event EventHandler? MarqueeStarted;
///
- /// Event raised when the Marquee stops scrolling for any reason.
+ /// Event raised when the Marquee is stopped manually or completed.
///
public event EventHandler? MarqueeStopped;
+ ///
+ /// Event raised when the Marquee is resumed from a pause.
+ ///
+ public event EventHandler? MarqueeResumed;
+
+ ///
+ /// Event raised when the Marquee is paused.
+ ///
+ public event EventHandler? MarqueePaused;
+
///
/// Event raised when the Marquee completes scrolling.
///
@@ -28,7 +38,7 @@ private void Marquee_Loaded(object sender, RoutedEventArgs e)
{
// While loaded, detach the loaded event and attach the unloaded event
this.Loaded -= this.Marquee_Loaded;
- this.Unloaded += Marquee_Unloaded;
+ this.Unloaded += this.Marquee_Unloaded;
// Attach other events
if (_marqueeContainer is not null)
@@ -36,10 +46,28 @@ private void Marquee_Loaded(object sender, RoutedEventArgs e)
_marqueeContainer.SizeChanged += Container_SizeChanged;
}
+ if (_segment1 is not null)
+ {
+ _segment1.SizeChanged += Segment_SizeChanged;
+ }
+
if (_marqueeStoryboard is not null)
{
_marqueeStoryboard.Completed += StoryBoard_Completed;
}
+
+ // The size may have channged while unloaded.
+ // Clip the marquee
+ ClipMarquee();
+
+ // Setup the animation
+ UpdateMarquee(false);
+
+ // The marquee should run when loaded if auto play is enabled
+ if (AutoPlay)
+ {
+ StartMarquee();
+ }
}
private void Marquee_Unloaded(object sender, RoutedEventArgs e)
@@ -53,6 +81,11 @@ private void Marquee_Unloaded(object sender, RoutedEventArgs e)
_marqueeContainer.SizeChanged -= Container_SizeChanged;
}
+ if (_segment1 is not null)
+ {
+ _segment1.SizeChanged -= Segment_SizeChanged;
+ }
+
if (_marqueeStoryboard is not null)
{
_marqueeStoryboard.Completed -= StoryBoard_Completed;
@@ -62,35 +95,41 @@ private void Marquee_Unloaded(object sender, RoutedEventArgs e)
private void Container_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (_marqueeContainer is null)
- {
return;
- }
- // Clip the marquee within its bounds
- _marqueeContainer.Clip = new RectangleGeometry
- {
- Rect = new Rect(0, 0, e.NewSize.Width, e.NewSize.Height)
- };
+ // Clip the marquee
+ ClipMarquee(e.NewSize.Width, e.NewSize.Height);
+
+ // Update animation on the fly
+ UpdateMarquee(true);
// The marquee should run when the size changes in case the text gets cutoff
- StartMarquee();
+ // and auto play is enabled.
+ if (AutoPlay)
+ {
+ StartMarquee();
+ }
}
private void Segment_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (_segment1 is null)
- {
return;
- }
+
+ if (_marqueeContainer is null)
+ return;
+
+ // Cap the height of the container to the segment height
+ _marqueeContainer.Height = _segment1.ActualHeight;
// If the segment size changes, we need to update the storyboard,
// and seek to the correct position to maintain a smooth animation.
- UpdateAnimation(true);
+ UpdateMarquee(true);
}
private void StoryBoard_Completed(object? sender, object e)
{
- StopMarquee(true);
+ StopMarquee();
MarqueeCompleted?.Invoke(this, EventArgs.Empty);
}
}
diff --git a/components/Marquee/src/Marquee.Properties.cs b/components/Marquee/src/Marquee.Properties.cs
index 000817633..107896ebf 100644
--- a/components/Marquee/src/Marquee.Properties.cs
+++ b/components/Marquee/src/Marquee.Properties.cs
@@ -11,6 +11,9 @@ namespace CommunityToolkit.WinUI.Controls;
///
public partial class Marquee
{
+ private static readonly DependencyProperty AutoPlayProperty =
+ DependencyProperty.Register(nameof(AutoPlay), typeof(bool), typeof(Marquee), new PropertyMetadata(false));
+
private static readonly DependencyProperty SpeedProperty =
DependencyProperty.Register(nameof(Speed), typeof(double), typeof(Marquee), new PropertyMetadata(32d, PropertyChanged));
@@ -18,11 +21,20 @@ public partial class Marquee
DependencyProperty.Register(nameof(RepeatBehavior), typeof(RepeatBehavior), typeof(Marquee), new PropertyMetadata(new RepeatBehavior(1), PropertyChanged));
private static readonly DependencyProperty BehaviorProperty =
- DependencyProperty.Register(nameof(Behavior), typeof(MarqueeBehavior), typeof(Marquee), new PropertyMetadata(0, BehaviorPropertyChanged));
+ DependencyProperty.Register(nameof(Behavior), typeof(MarqueeBehavior), typeof(Marquee), new PropertyMetadata(MarqueeBehavior.Ticker, BehaviorPropertyChanged));
private static readonly DependencyProperty DirectionProperty =
DependencyProperty.Register(nameof(Direction), typeof(MarqueeDirection), typeof(Marquee), new PropertyMetadata(MarqueeDirection.Left, DirectionPropertyChanged));
+ ///
+ /// Gets or sets whether or not the Marquee plays immediately upon loading or updating a property.
+ ///
+ public bool AutoPlay
+ {
+ get => (bool)GetValue(AutoPlayProperty);
+ set => SetValue(AutoPlayProperty, value);
+ }
+
///
/// Gets or sets the speed the text moves in the Marquee.
///
@@ -79,17 +91,16 @@ public MarqueeDirection Direction
private static void BehaviorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not Marquee control)
- {
return;
- }
- bool active = control._isActive;
var newBehavior = (MarqueeBehavior)e.NewValue;
VisualStateManager.GoToState(control, GetVisualStateName(newBehavior), true);
- control.StopMarquee(false);
- if (active)
+ // It is always impossible to perform an on the fly behavior change.
+ control.UpdateMarquee(false);
+
+ if (control.AutoPlay)
{
control.StartMarquee();
}
@@ -98,11 +109,8 @@ private static void BehaviorPropertyChanged(DependencyObject d, DependencyProper
private static void DirectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not Marquee control)
- {
return;
- }
- bool active = control._isActive;
var oldDirection = (MarqueeDirection)e.OldValue;
var newDirection = (MarqueeDirection)e.NewValue;
bool oldAxisX = oldDirection is MarqueeDirection.Left or MarqueeDirection.Right;
@@ -110,12 +118,11 @@ private static void DirectionPropertyChanged(DependencyObject d, DependencyPrope
VisualStateManager.GoToState(control, GetVisualStateName(newDirection), true);
- if (oldAxisX != newAxisX)
- {
- control.StopMarquee(false);
- }
+ // If the axis changed we cannot update the animation on the fly.
+ // Otherwise, the animation can be updated and resumed seamlessly
+ control.UpdateMarquee(oldAxisX == newAxisX);
- if (active)
+ if (control.AutoPlay)
{
control.StartMarquee();
}
@@ -124,10 +131,15 @@ private static void DirectionPropertyChanged(DependencyObject d, DependencyPrope
private static void PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not Marquee control)
- {
return;
- }
- control.UpdateAnimation();
+ // It is always possible to update these properties on the fly.
+ // NOTE: The RepeatBehavior will reset its count though. Can this be fixed?
+ control.UpdateMarquee(true);
+
+ if (control.AutoPlay)
+ {
+ control.StartMarquee();
+ }
}
}
diff --git a/components/Marquee/src/Marquee.cs b/components/Marquee/src/Marquee.cs
index d29d2e04a..a7a0ef36b 100644
--- a/components/Marquee/src/Marquee.cs
+++ b/components/Marquee/src/Marquee.cs
@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Diagnostics.CodeAnalysis;
+
namespace CommunityToolkit.WinUI.Controls;
///
@@ -26,6 +28,7 @@ public partial class Marquee : ContentControl
private const string MarqueeTransformPartName = "MarqueeTransform";
private const string MarqueeActiveState = "MarqueeActive";
+ private const string MarqueePausedState = "MarqueePaused";
private const string MarqueeStoppedState = "MarqueeStopped";
private const string DirectionVisualStateGroupName = "DirectionStateGroup";
@@ -45,7 +48,16 @@ public partial class Marquee : ContentControl
private TranslateTransform? _marqueeTransform;
private Storyboard? _marqueeStoryboard;
- private bool _isActive;
+ // Used to track if the marquee is active or not.
+ // This signifies being mid animation. A paused marquee is still active!
+ private bool _isActive = false;
+ private bool _isPaused = false;
+
+ // This is used to track the position when stopped.
+ // If the animation update happens while running, this position
+ // is lost and must be set when the animation stops.
+ private double _stoppedPosition;
+ private DependencyProperty? _animationProperty;
///
/// Initializes a new instance of the class.
@@ -55,6 +67,14 @@ public Marquee()
DefaultStyleKey = typeof(Marquee);
}
+ ///
+ /// Unsubscribes from the loaded event when the control is being disposed.
+ ///
+ ~Marquee()
+ {
+ Loaded -= this.Marquee_Loaded;
+ }
+
///
protected override void OnApplyTemplate()
{
@@ -66,12 +86,10 @@ protected override void OnApplyTemplate()
_segment2 = (ContentPresenter)GetTemplateChild(Segment2PartName);
_marqueeTransform = (TranslateTransform)GetTemplateChild(MarqueeTransformPartName);
- _marqueeContainer.SizeChanged += Container_SizeChanged;
- _segment1.SizeChanged += Segment_SizeChanged;
-
// Swapping tabs in TabView caused errors where the control would unload and never reattach events.
- // Hotfix: Track the loaded event. This should be fine because the GC will handle detaching the Loaded
- // event on disposal. However, more research is required
+ // Fix: Track the loaded event. This should be fine because the GC will handle detaching the Loaded
+ // event on disposal. However, more research is required.
+ // As a result, all other events should be attached in the Loaded event handler.
Loaded += this.Marquee_Loaded;
VisualStateManager.GoToState(this, GetVisualStateName(Direction), false);
@@ -104,19 +122,54 @@ private static string GetVisualStateName(MarqueeBehavior behavior)
}
///
- /// Begins the Marquee animation if not running.
+ /// Begins the Marquee animation if not running or resumes if paused.
///
/// Thrown when template parts are not supplied.
- public void StartMarquee()
+ public void StartMarquee() => PlayMarquee(fromStart: false);
+
+ ///
+ /// Restarts the Marquee from the start of the animation regardless of current state.
+ ///
+ ///
+ /// will not be raised if the marquee was already active.
+ ///
+ public void RestartMarquee() => PlayMarquee(fromStart: true);
+
+ ///
+ /// Resumes the Marquee animation if paused.
+ ///
+ public void ResumeMarquee()
{
- bool initial = _isActive;
- _isActive = true;
- bool playing = UpdateAnimation(initial);
+ // If not paused or not active, do nothing
+ if (!_isPaused || !_isActive)
+ return;
+
+ // Resume the storyboard
+ _isPaused = false;
+ _marqueeStoryboard?.Resume();
+
+ // Apply state transitions
+ VisualStateManager.GoToState(this, MarqueeActiveState, false);
+ MarqueeResumed?.Invoke(this, EventArgs.Empty);
+ }
+
+ ///
+ /// Pauses the Marquee animation.
+ ///
+ public void PauseMarquee()
+ {
+ // Log initial paused status
+ bool wasPaused = _isPaused;
+
+ // Ensure paused status
+ _marqueeStoryboard?.Pause();
+ _isPaused = true;
- // Invoke MarqueeBegan if Marquee is now playing and was not before
- if (playing && !initial)
+ if (!wasPaused)
{
- MarqueeBegan?.Invoke(this, EventArgs.Empty);
+ // Apply state transitions
+ VisualStateManager.GoToState(this, MarqueePausedState, false);
+ MarqueePaused?.Invoke(this, EventArgs.Empty);
}
}
@@ -126,48 +179,125 @@ public void StartMarquee()
/// Thrown when template parts are not supplied.
public void StopMarquee()
{
- StopMarquee(_isActive);
- }
+ bool wasStopped = !_isActive;
- private void StopMarquee(bool initialState)
- {
- // Set _isActive and update the animation to match
+ // Ensure stopped status
+ _marqueeStoryboard?.Stop();
_isActive = false;
- bool playing = UpdateAnimation(false);
+ _isPaused = false;
+
+ // Set the transform to the stopped position if provided.
+ if (_animationProperty is not null)
+ {
+ _marqueeTransform?.SetValue(_animationProperty, _stoppedPosition);
+ }
- // Invoke MarqueeStopped if Marquee is not playing and was before
- if (!playing && initialState)
+ if (!wasStopped)
{
+ // Apply state transitions
+ VisualStateManager.GoToState(this, MarqueeStoppedState, false);
MarqueeStopped?.Invoke(this, EventArgs.Empty);
}
}
+ private void PlayMarquee(bool fromStart = false)
+ {
+ // Resume if paused and not playing from start
+ if (!fromStart && _isPaused && _isActive)
+ {
+ ResumeMarquee();
+ return;
+ }
+
+ // Do nothing if storyboard is null or already playing and not from start.
+ if (_marqueeStoryboard is null || (_isActive && !fromStart))
+ return;
+
+ bool wasActive = _isActive;
+
+ // Stop the storboard if it is already active and playing from start
+ if (fromStart)
+ {
+ _marqueeStoryboard.Stop();
+ }
+
+ // Start the storyboard
+ _marqueeStoryboard.Begin();
+
+ // Update the status variables
+ _isActive = true;
+ _isPaused = false;
+
+ if (!wasActive)
+ {
+ // Apply state transitions
+ VisualStateManager.GoToState(this, MarqueeActiveState, false);
+ MarqueeStarted?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ private void UpdateMarquee(bool onTheFly)
+ {
+ // Check for crucial template parts
+ if (_marqueeTransform is null)
+ return;
+
+ // If the update cannot be made on the fly,
+ // stop the marquee and reset the transform
+ if (!onTheFly)
+ {
+ StopMarquee();
+ _marqueeTransform.X = 0;
+ _marqueeTransform.Y = 0;
+ }
+
+ // Apply the animation update
+ bool hasAnimation = UpdateAnimation(out var seek);
+
+ // If updating on the fly, and there is an animation,
+ // seek to the correct position
+ if (onTheFly && hasAnimation && _isActive)
+ {
+ _marqueeStoryboard?.Begin();
+ _marqueeStoryboard?.Seek(seek);
+
+ // Restore paused state if necessary
+ if (_isPaused)
+ {
+ PauseMarquee();
+ }
+ }
+ }
+
///
/// Updates the animation to match the current control state.
///
- /// True if animation should resume from its current position, false if it should restart.
+ ///
+ /// When in looping mode, it is possible that no animation is necessary.
+ ///
+ /// The seek point to resume the animation (if possible or appropriate.
/// Thrown when template parts are not supplied.
- /// True if the Animation is now playing.
- private bool UpdateAnimation(bool resume = true)
+ /// Returns whether or not an animation is neccesary.
+ private bool UpdateAnimation(out TimeSpan seekPoint)
{
- // Crucial template parts are missing!
- // This can happen during initialization of certain properties.
- // Gracefully return when this happens. Proper checks for these template parts happen in OnApplyTemplate.
+ seekPoint = TimeSpan.Zero;
+
+ // Check for crucial template parts
if (_marqueeContainer is null ||
_marqueeTransform is null ||
_segment1 is null ||
_segment2 is null)
{
+ // Crucial template parts are missing!
+ // This can happen during initialization of certain properties.
+ // Gracefully return when this happens. Proper checks for these template parts happen in OnApplyTemplate.
return false;
}
- // The marquee is stopped.
- // Update the animation to the stopped position.
- if (!_isActive)
+ // Unbind events from the old storyboard
+ if (_marqueeStoryboard is not null)
{
- VisualStateManager.GoToState(this, MarqueeStoppedState, false);
-
- return false;
+ _marqueeStoryboard.Completed -= StoryBoard_Completed;
}
// Get the size of the container and segment, based on the orientation.
@@ -204,18 +334,18 @@ _segment1 is null ||
// If the marquee is in looping mode and the segment is smaller
// than the container, then the animation does not not need to play.
- // NOTE: Use resume as initial because _isActive is updated before
- // calling update animation. If _isActive were passed, it would allow for
- // MarqueeStopped to be invoked when the marquee was already stopped.
- StopMarquee(resume);
+ // Reset the transform to 0 and hide the second segment
+ _marqueeContainer.SetValue(dp, 0);
_segment2.Visibility = Visibility.Collapsed;
-
+
+ _marqueeStoryboard = null;
+ _marqueeStoryboard?.Stop();
return false;
}
// The start position is offset 100% if in ticker mode
// Otherwise it's 0
- double start = IsTicker ? containerSize : 0;
+ double start = IsTicker ? containerSize + 1 : 0;
// The end is when the end of the text reaches the border if in bouncing mode
// Otherwise it is when the first set of text is 100% out of view
@@ -228,6 +358,8 @@ _segment1 is null ||
// If the distance is zero, don't play an animation
if (distance is 0)
{
+ _marqueeStoryboard = null;
+ _marqueeStoryboard?.Stop();
return false;
}
@@ -243,29 +375,12 @@ _segment1 is null ||
// Calculate the animation duration by dividing the distance by the speed
TimeSpan duration = TimeSpan.FromSeconds(distance / Speed);
- // Unbind events from the old storyboard
- if (_marqueeStoryboard is not null)
- {
- _marqueeStoryboard.Completed -= StoryBoard_Completed;
- }
-
// Create new storyboard and animation
_marqueeStoryboard = CreateMarqueeStoryboardAnimation(start, end, duration, targetProperty);
// Bind the storyboard completed event
_marqueeStoryboard.Completed += StoryBoard_Completed;
- // Set the visual state to active and begin the animation
- VisualStateManager.GoToState(this, MarqueeActiveState, true);
- _marqueeStoryboard.Begin();
-
- // If resuming, seek the animation so the text resumes from its current position.
- if (resume)
- {
- double progress = Math.Abs(start - value) / distance;
- _marqueeStoryboard.Seek(TimeSpan.FromTicks((long)(duration.Ticks * progress)));
- }
-
// NOTE: Can this be optimized to remove or reduce the need for this callback?
// Invalidate the segment measures when the transform changes.
// This forces virtualized panels to re-measure the segments
@@ -275,6 +390,22 @@ _segment1 is null ||
_segment2.InvalidateMeasure();
});
+ // Calculate the seek point for seamless animation updates
+ double progress = Math.Abs(start - value) / distance;
+ seekPoint = TimeSpan.FromTicks((long)(duration.Ticks * progress));
+
+ // Set the value of the transform to the start position if not active.
+ // This puts the content in the correct starting position without using the animation.
+ if (!_isActive)
+ {
+ _marqueeTransform.SetValue(dp, start);
+ }
+
+ // Set stopped position and animation property regardless of the active state.
+ // This will be used when the animation stops.
+ _stoppedPosition = start;
+ _animationProperty = dp;
+
return true;
}
@@ -325,4 +456,19 @@ private Storyboard CreateMarqueeStoryboardAnimation(double start, double end, Ti
return marqueeStoryboard;
}
+
+ private void ClipMarquee(double width = default, double height = default)
+ {
+ if (_marqueeContainer is null)
+ return;
+
+ width = width is default(double) ? _marqueeContainer.ActualWidth : width;
+ height = height is default(double) ? _marqueeContainer.ActualHeight : height;
+
+ // Clip the marquee within the bounds of the container
+ _marqueeContainer.Clip = new RectangleGeometry
+ {
+ Rect = new Rect(0, 0, width, height)
+ };
+ }
}
diff --git a/components/Marquee/src/Marquee.xaml b/components/Marquee/src/Marquee.xaml
index 73f6ec52c..ab1367556 100644
--- a/components/Marquee/src/Marquee.xaml
+++ b/components/Marquee/src/Marquee.xaml
@@ -1,4 +1,4 @@
-
+
-
-
-
-
-
-
+
+