| using System; |
| using System.Windows; |
| |
| namespace Abr.CodeLibrary.Sl.Animation |
| { |
| /// <summary> |
| /// Swiped from Telerik example; Base interface for all transitions. |
| /// </summary> |
| public interface ITransition |
| { |
| /// <summary> |
| /// Gets or sets a value indicating whether the transition is running. |
| /// </summary> |
| bool IsRunning { get; set; } |
| |
| /// <summary> |
| /// Transitions the page.Transition between previous and next page. |
| /// </summary> |
| /// <param name="page">The next page.</param> |
| void Begin(FrameworkElement page); |
| |
| /// <summary> |
| /// Stops the currently running transition. |
| /// </summary> |
| void StopTransition(); |
| |
| /// <summary> |
| /// Event to fire when animation completes (I did not know that it was possible |
| /// to templatize your custom event handler!). |
| /// </summary> |
| event EventHandler<TransitionEventArgs> TransitionAnimationCompleted; |
| } //ITransition |
| } //namespace |
| |
| using System; |
| using System.Windows; |
| |
| namespace Abr.CodeLibrary.Sl.Animation |
| { |
| /// <summary> |
| /// The supporting class for transition event support |
| /// </summary> |
| public class TransitionEventArgs : EventArgs |
| { |
| /// <summary> |
| /// The target page for the transition |
| /// </summary> |
| public FrameworkElement TargetExamplePage |
| { |
| get; |
| set; |
| } //TargetExamplePage |
| |
| /// <summary> |
| /// Create the event arguments |
| /// </summary> |
| /// <param name="targetExample">The target page for the transition</param> |
| public TransitionEventArgs(FrameworkElement targetExample) |
| { |
| this.TargetExamplePage = targetExample; |
| } //ctor |
| } //TransitionEventArgs |
| } //namespace |
| |
| using System.Windows; |
| |
| namespace Abr.CodeLibrary.Sl.Animation |
| { |
| /// <summary> |
| /// Swiped from Telerik, a helper class, used in code-initiated animations. |
| /// </summary> |
| /// <remarks> |
| /// Wow, I do not understand splines *at all*. I read the wiki very briefly and get Very Confused. |
| /// I *think* these things are to help make the kewl page horizontal rotation but I can't say |
| /// I understand even that much! Oh, well, always did suck at maths. |
| /// </remarks> |
| public class Spline |
| { |
| /// <summary> |
| /// Create one of these here spline-thingies. I have *got* to concentrate on some calculus! |
| /// </summary> |
| /// <param name="x1">Value 1 on x-axis</param> |
| /// <param name="y1">Value 1 on y-axis</param> |
| /// <param name="x2">Value 2 on x-axis</param> |
| /// <param name="y2">Value 2 on y-axis</param> |
| public Spline(double x1, double y1, double x2, double y2) |
| { |
| this.Point1 = new Point(x1, y1); |
| this.Point2 = new Point(x2, y2); |
| } //ctor |
| |
| /// <summary> |
| /// Defines the x1-y1 point |
| /// </summary> |
| public Point Point1 |
| { |
| get; |
| set; |
| } //Point1 |
| |
| /// <summary> |
| /// Defines the x2-y2 point |
| /// </summary> |
| public Point Point2 |
| { |
| get; |
| set; |
| } //Point2 |
| } //Spline |
| } //namespace |
| |
| using System; |
| using System.Windows; |
| using System.Windows.Media; |
| using System.Windows.Media.Animation; |
| |
| namespace Abr.CodeLibrary.Sl.Animation |
| { |
| /// <summary> |
| /// Taken from Telerik example Silverlight application, this class is used to |
| /// define all animation extensions. Experimenting with advanced animation |
| /// in Silverlight. |
| /// </summary> |
| public static class Extensions |
| { |
| /// <summary> |
| /// Linq not available in my Silverlight .NET references, rolling this myself. |
| /// </summary> |
| /// <param name="args"></param> |
| /// <returns></returns> |
| internal static System.Collections.Generic.List<double> ToList(params double[] args) |
| { |
| System.Collections.Generic.List<double> result = new System.Collections.Generic.List<double>(); |
| foreach (double arg in args) { result.Add(arg); } |
| return result; |
| } //ToList |
| |
| /// <summary> |
| /// Create an animation object for a given element. Operations can |
| /// be performed on this animation. |
| /// </summary> |
| /// <param name="target">The UI element to animate</param> |
| /// <returns>The created animation context</returns> |
| public static Context Animate(this FrameworkElement target) |
| { |
| var result = new Context(); |
| result.Targets.Add(target); |
| return result; |
| } //ctor |
| |
| /// <summary> |
| /// Perform a scaling using time-value pairs |
| /// </summary> |
| /// <param name="target">The animation to affect</param> |
| /// <param name="args">Time and value pairs</param> |
| /// <returns>The passed-in animation context</returns> |
| public static Context Scale(this Context target, params double[] args) |
| { |
| var values = ToList(args); |
| |
| if (args.Length % 2 != 0) |
| { |
| throw new InvalidOperationException("Params should come in a time-value pair"); |
| } //if |
| |
| foreach (var element in target.Targets) |
| { |
| var scaleX = new DoubleAnimationUsingKeyFrames(); |
| Storyboard.SetTarget(scaleX, element); |
| Storyboard.SetTargetProperty(scaleX, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)")); |
| |
| var scaleY = new DoubleAnimationUsingKeyFrames(); |
| Storyboard.SetTarget(scaleY, element); |
| Storyboard.SetTargetProperty(scaleY, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)")); |
| |
| for (int i = 0; i < values.Count; i += 2) |
| { |
| scaleX.KeyFrames.Add(new SplineDoubleKeyFrame() |
| { |
| KeyTimeKeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(values[i])), |
| Value = values[i + 1] |
| }); |
| |
| scaleY.KeyFrames.Add(new SplineDoubleKeyFrame() |
| { |
| KeyTimeKeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(values[i])), |
| Value = values[i + 1] |
| }); |
| } //for |
| |
| target.Instance.Children.Add(scaleX); |
| target.Instance.Children.Add(scaleY); |
| } //for |
| |
| targettarget.StartIndex = target.EndIndex; |
| target.EndIndex += 2 * target.Targets.Count; |
| |
| return target; |
| } //Scale |
| |
| /// <summary> |
| /// Move the target on the x-axis |
| /// </summary> |
| /// <param name="target">Animation context</param> |
| /// <param name="args">Values to affect the <code>TranslateTransform.X</code> transform element</param> |
| /// <returns>The passed-in animation context</returns> |
| public static Context MoveX(this Context target, params double[] args) |
| { |
| return target.SingleProperty("(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)", args); |
| } //MoveX |
| |
| #if !WPF |
| /// <summary> |
| /// Move the target on the y-axis |
| /// </summary> |
| /// <param name="target">Animation context</param> |
| /// <param name="args">Values to affect the <code>TranslateTransform.Y</code> transform element</param> |
| /// <returns>The passed-in animation context</returns> |
| public static Context MoveY(this Context target, params double[] args) |
| { |
| return target.SingleProperty("(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)", args); |
| } |
| #endif |
| |
| /// <summary> |
| /// Affect target opacity |
| /// </summary> |
| /// <param name="target">Animation context</param> |
| /// <param name="args">Values to affect the <code>UIElement.Opacity</code> property</param> |
| /// <returns>The passed-in animation context</returns> |
| public static Context Opacity(this Context target, params double[] args) |
| { |
| return target.SingleProperty("(UIElement.Opacity)", args); |
| } |
| |
| /// <summary> |
| /// Splines are weird to me--I don't really understand the math. So I take on faith |
| /// that these are useful and exactly what we need to get the turning effect that |
| /// looks so kewl on the Telerik example application. |
| /// </summary> |
| /// <param name="target">Animation context</param> |
| /// <param name="spline">A spline object to use for setting our animation splines</param> |
| /// <returns>The passed-in animation context</returns> |
| public static Context Splines(this Context target, Spline spline) |
| { |
| return target.Splines(1, spline.Point1.X, spline.Point1.Y, spline.Point2.X, spline.Point2.Y); |
| } //Splines |
| |
| /// <summary> |
| /// Set animation splines based on a specific index |
| /// </summary> |
| /// <param name="target">Animation context</param> |
| /// <param name="index">The index to set within our animation splines</param> |
| /// <param name="spline">A spline object to use for setting our animation splines</param> |
| /// <returns>The passed-in animation context</returns> |
| public static Context Splines(this Context target, int index, Spline spline) |
| { |
| return target.Splines(index, spline.Point1.X, spline.Point1.Y, spline.Point2.X, spline.Point2.Y); |
| } //Splines |
| |
| /// <summary> |
| /// The main worker function for setting splines. Allows an index and the individual |
| /// spline values to be set explicitly. |
| /// </summary> |
| /// <param name="target">Animation context</param> |
| /// <param name="index">The index to set within our animation splines</param> |
| /// <param name="x1">Weird spline parameter</param> |
| /// <param name="y1">Weird spline parameter</param> |
| /// <param name="x2">Weird spline parameter</param> |
| /// <param name="y2">Weird spline parameter</param> |
| /// <returns>The passed-in animation context</returns> |
| public static Context Splines(this Context target, int index, double x1, double y1, double x2, double y2) |
| { |
| for (var num = target.StartIndex; num < target.EndIndex; num++) |
| { |
| ((target.Instance.Children[num] as DoubleAnimationUsingKeyFrames).KeyFrames[index] as SplineDoubleKeyFrame).KeySpline = new KeySpline() |
| { |
| ControlPoint1 = new Point(x1, y1), |
| ControlPoint2 = new Point(x2, y2) |
| }; |
| } //for |
| return target; |
| } //Splines |
| |
| /// <summary> |
| /// Set the transform origin within the targets of an animation context |
| /// </summary> |
| /// <param name="target">Animation context</param> |
| /// <param name="x1">Starting origin on the x-axis</param> |
| /// <param name="x2">Although named 'x2' this is passed as the 'y' value on a point. Not sure |
| /// if this means that the parameter is misnamed or (more probably) that perhaps it's a |
| /// transform destination for the origin on the x-axis.</param> |
| /// <returns>The passed-in animation context</returns> |
| public static Context Origin(this Context target, double x1, double x2) |
| { |
| foreach (var element in target.Targets) |
| { |
| element.RenderTransformOrigin = new Point(x1, x2); |
| } //foreach |
| return target; |
| } //Origin |
| |
| /// <summary> |
| /// Perform an animation on a group of UI elements. Basically adds these elements |
| /// to the animation context targets (all of them). |
| /// </summary> |
| /// <param name="target">Animation context</param> |
| /// <param name="newTargets">Array of UI elements to animate</param> |
| /// <returns>The passed-in animation context</returns> |
| public static Context Animate(this Context target, params FrameworkElement[] newTargets) |
| { |
| target.Targets.Clear(); |
| foreach (var newElement in newTargets) |
| { |
| target.Targets.Add(newElement); |
| } //foreach |
| return target; |
| } //Animate |
| |
| /// <summary> |
| /// For our class to work, it's important that all elements have a common set of |
| /// transforms defined. This allows us to perform many types of transforms as desired |
| /// while ensuring that the appropriate transform support object does indeed exist and |
| /// is indeed associated with each animation context target. Transforms that are not |
| /// set are basically NOP elements and have no effect on the overall animation. |
| /// </summary> |
| /// <param name="target">Animation context</param> |
| /// <returns>The passed-in animation context</returns> |
| public static Context EnsureDefaultTransforms(this Context target) |
| { |
| foreach (var element in target.Targets) |
| { |
| TransformGroup group = element.RenderTransform as TransformGroup; |
| |
| group = new TransformGroup(); |
| group.Children.Add(new ScaleTransform()); |
| group.Children.Add(new SkewTransform()); |
| group.Children.Add(new RotateTransform()); |
| group.Children.Add(new TranslateTransform()); |
| |
| element.RenderTransform = group; |
| } //foreach |
| return target; |
| } //EnsureDefaultTransforms |
| |
| /// <summary> |
| /// Include a series of UI elements within an animation context. |
| /// </summary> |
| /// <param name="target">Animation context</param> |
| /// <param name="newElements">List of UI elements to include in the animation</param> |
| /// <returns>The passed-in animation context</returns> |
| public static Context With(this Context target, params FrameworkElement[] newElements) |
| { |
| foreach (var elements in newElements) |
| { |
| target.Targets.Add(elements); |
| } //foreach |
| return target; |
| } //With |
| |
| /// <summary> |
| /// Exclude a series of UI elements from an animation context. |
| /// </summary> |
| /// <param name="target">Animation context</param> |
| /// <param name="newElements">List of UI elements to exclude from the animation</param> |
| /// <returns>The passed-in animation context</returns> |
| public static Context Without(this Context target, params FrameworkElement[] newElements) |
| { |
| foreach (var elements in newElements) |
| { |
| target.Targets.Remove(elements); |
| } //foreach |
| return target; |
| } //Without |
| |
| /// <summary> |
| /// Given one of the weird-looking (but I *think* I understand the syntax) |
| /// property path language (reflection on crack) and a set of arguments, |
| /// set the value of the properties. This type of delayed binding is |
| /// necessary so that the animation sequence can work by setting values |
| /// using the simple 1..N design pattern--the animation simply needs to |
| /// be able to locate and update the properties in question. |
| /// </summary> |
| /// <param name="target">Animation context</param> |
| /// <param name="propertyPath"></param> |
| /// <param name="args">The list of values to set as time-value paris. Presumably this would need to be the set of |
| /// values matching the for loop in the outer-level animation?</param> |
| /// <returns>The passed-in animation context</returns> |
| private static Context SingleProperty(this Context target, string propertyPath, params double[] args) |
| { |
| // had to modify--no LINQ available in my list of .NET assemblies |
| var values = ToList(args); |
| |
| if (args.Length % 2 != 0) |
| { |
| throw new InvalidOperationException("Params should come in a time-value pair"); |
| } //if |
| |
| foreach (var element in target.Targets) |
| { |
| var moveX = new DoubleAnimationUsingKeyFrames(); |
| Storyboard.SetTarget(moveX, element); |
| Storyboard.SetTargetProperty(moveX, new PropertyPath(propertyPath)); |
| |
| for (int i = 0; i < values.Count; i += 2) |
| { |
| moveX.KeyFrames.Add(new SplineDoubleKeyFrame() |
| { |
| KeyTimeKeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(values[i])), |
| Value = values[i + 1] |
| }); |
| } //for |
| |
| target.Instance.Children.Add(moveX); |
| } //foreach |
| |
| targettarget.StartIndex = target.EndIndex; |
| target.EndIndex += target.Targets.Count; |
| |
| return target; |
| } //SingleProperty |
| |
| /// <summary> |
| /// A single animation context--which appears to be really a storyboard (which |
| /// makes perfect sense). |
| /// </summary> |
| public class Context |
| { |
| /// <summary> |
| /// Create the context |
| /// </summary> |
| public Context() |
| { |
| // set the storyboard we use |
| this.Instance = new Storyboard() |
| { |
| FillBehaviorFillBehavior = FillBehavior.HoldEnd |
| }; |
| |
| this.Targets = new System.Collections.Generic.List<FrameworkElement>(4); |
| } //ctor |
| |
| /// <summary> |
| /// The starting index for the storyboard--storyboards work as simple for loops |
| /// with dynamic property bindings. |
| /// </summary> |
| public int StartIndex |
| { |
| get; |
| set; |
| } //StartIndex |
| |
| /// <summary> |
| /// The ending index for the storyboard--storyboards work as simple for loops |
| /// with dynamic property bindings. |
| /// </summary> |
| public int EndIndex |
| { |
| get; |
| set; |
| } //EndIndex |
| |
| /// <summary> |
| /// The list of UI elements affected by the storyboard animation |
| /// </summary> |
| internal System.Collections.Generic.ICollection<FrameworkElement> Targets |
| { |
| get; |
| private set; |
| } //Targets |
| |
| /// <summary> |
| /// The storyboard itself |
| /// </summary> |
| internal Storyboard Instance |
| { |
| get; |
| set; |
| } //Instance |
| } //Context |
| } //AnimationExtensions |
| } //namespace |
| |
| using System; |
| using System.Windows; |
| using System.Windows.Controls; |
| using System.Windows.Media; |
| using System.Windows.Media.Animation; |
| |
| namespace Abr.CodeLibrary.Sl.Animation |
| { |
| /// <summary> |
| /// Swiped from Telerik; perform a transition from one page to another using |
| /// kewl effects. |
| /// </summary> |
| public class ExampleTransition : ITransition |
| { |
| private Storyboard animation = new Storyboard(); |
| private FrameworkElement item1; |
| private FrameworkElement item2; |
| private FrameworkElement mask1; |
| private FrameworkElement mask2; |
| private bool isRunning; |
| |
| /// <summary> |
| /// Provides a common event handler for the transition |
| /// </summary> |
| public event EventHandler<TransitionEventArgs> TransitionAnimationCompleted = delegate { }; |
| |
| #if WPF |
| public ExampleTransition(FrameworkElement mask1, FrameworkElement mask2) |
| { |
| #else |
| /// <summary> |
| /// Create an example transition--works against a grid as the main container. |
| /// </summary> |
| /// <param name="pageWidth">Should be the ActualWidth of the passed panel (grid)</param> |
| /// <param name="panel">The container that should have page transitioned (from page to page navigation)</param> |
| /// <param name="mask1">Not sure what the masks do, probably the fill color? If so needs |
| /// to be driven from config to match the background of course.</param> |
| /// <param name="mask2">Mask used for (I believe) the navigation on the destination ("to") page</param> |
| public ExampleTransition(double pageWidth, Grid panel, FrameworkElement mask1, FrameworkElement mask2) |
| { |
| this.PageWidth = pageWidth; |
| this.Container = panel; |
| #endif |
| this.mask1 = mask1; |
| this.mask2 = mask2; |
| } //ctor |
| |
| /// <summary> |
| /// Passed in; should be the panel's ActualWidth from Telerik sample |
| /// </summary> |
| public double PageWidth |
| { |
| get; |
| set; |
| } //PageWidth |
| |
| /// <summary> |
| /// The passed on panel--always assumed to be grid (probably fine) |
| /// </summary> |
| public Grid Container |
| { |
| get; |
| set; |
| } //Container |
| |
| /// <summary> |
| /// Is the transition animation running? Note that this value *cannot* be |
| /// updated. |
| /// </summary> |
| public bool IsRunning |
| { |
| get |
| { |
| return isRunning; |
| } //get |
| #if !WPF |
| set |
| { |
| throw new NotImplementedException(); |
| } //set |
| #endif |
| } //IsRunning |
| |
| #if WPF |
| /// <summary> |
| /// WPF only--which I fortunately don't have to worry about this particular Saturday |
| /// morning. I'm sure I will live to regret that sentence--can you say "Azure" guys? |
| /// </summary> |
| /// <param name="mockItem1"></param> |
| /// <param name="container"></param> |
| /// <param name="newPage"></param> |
| public void Begin(FrameworkElement mockItem1, Grid container, FrameworkElement newPage) |
| { |
| this.Container = container; |
| this.PageWidth = container.ActualWidth; |
| this.item1 = mockItem1; |
| this.item2 = newPage; |
| |
| this.Container.Children.Add(this.item1); |
| |
| var width = PageWidth; |
| |
| var scaleFactor = 0.8; |
| var halfShrink = (1.0 - scaleFactor) / 2; |
| #else |
| /// <summary> |
| /// The Silverlight version of the animation begin. My goal is to remove the |
| /// Telerik-specific features and implement navigation anyways. |
| /// </summary> |
| /// <param name="page">As originally designed, this is a RadFrame that supports |
| /// a <code>NavigateTo</code> method. Since this base class wants to be independent |
| /// of Telerik trying to avoid this by putting in "just enuf" code to get first-cut |
| /// navigation. Future versions will probably delegate this transitioning to |
| /// derived class--where it is perfectly acceptable to leverage Telerik frame support.</param> |
| public void Begin(FrameworkElement page) |
| { |
| double width = this.PageWidth; |
| |
| // the destination |
| this.item2 = page as FrameworkElement; |
| |
| // handle empty source (no effects for first time) |
| if (this.Container.Children.Count < 1) |
| { |
| this.Container.Children.Add(this.item2); |
| return; |
| } //if |
| |
| // save source |
| thisthis.item1 = this.Container.Children[0] as FrameworkElement; |
| |
| // add dest |
| this.Container.Children.Add(this.item2); |
| |
| var scaleFactor = 0.8; |
| var page1 = item1 as FrameworkElement; |
| |
| var halfShrink = (1.0 - scaleFactor) / 2; |
| #endif |
| this.Container.Children.Add(this.mask1); |
| this.Container.Children.Add(this.mask2); |
| |
| var scaleDownSpline = new Spline(0.504000008106232, 0.256000012159348, 0.458999991416931, 1); |
| var moveSpline = new Spline(0.247999995946884, 0, 1, 1); |
| var scaleUpSpline = new Spline(0.321000009775162, 0, 0.45100000500679, 1); |
| var spacing = 15.0; |
| |
| thisthis.animation = this.item1 |
| .Animate() |
| .With(this.mask1) |
| .EnsureDefaultTransforms() |
| .Origin(0, 0.5) |
| .Scale(0, 1, 0.2, 1, 0.7, scaleFactor) |
| .Splines(2, scaleDownSpline) |
| .MoveX(0.7, 0, 1.365, (-width * (scaleFactor - halfShrink)) - spacing, 1.9, (-width * scaleFactor) - spacing) |
| .Splines(1, moveSpline) |
| .Splines(2, scaleUpSpline) |
| .Without(this.item1) |
| .Opacity(0, 0, 0.2, 0, 0.7, 1) |
| .Splines(1, scaleDownSpline) |
| .Animate(this.mask2) |
| .Opacity(1.35, 1, 1.9, 0) |
| .Splines(scaleUpSpline) |
| .With(this.item2) |
| .EnsureDefaultTransforms() |
| .Origin(0.5, 0.5) |
| .Scale(0, scaleFactor, 1.365, scaleFactor, 1.9, 1) |
| .Splines(1, moveSpline) |
| .Splines(2, scaleUpSpline) |
| .MoveX(0.0, ((scaleFactor + halfShrink) * width) + spacing, 0.2, ((scaleFactor + halfShrink) * width) + spacing, 0.7, ((scaleFactor - halfShrink) * width) + spacing, 1.365, 0) |
| .Splines(2, scaleDownSpline) |
| .Splines(3, moveSpline) |
| .Instance; |
| |
| this.animation.SpeedRatio = 420.0 / width * 1.5; |
| |
| this.isRunning = true; |
| #if WPF |
| this.animation.Completed += (sender, e) => |
| { |
| this.TransitionAnimationCompleted(this, new TransitionEventArgs(item2)); |
| |
| Container.Children.Remove(mask1); |
| Container.Children.Remove(mask2); |
| RemoveVisualBrush(); |
| Container.Children.Remove(item1); |
| item1 = null; |
| item2 = null; |
| }; |
| #else |
| this.animation.Completed += (sender, e) => |
| { |
| this.TransitionAnimationCompleted(this, new TransitionEventArgs(item2)); |
| |
| // Unregister! |
| Container.Children.Remove(mask1); |
| Container.Children.Remove(mask2); |
| this.Container.Children.Remove(this.item1); |
| item1 = null; |
| item2 = null; |
| |
| }; |
| #endif |
| this.animation.Begin(); |
| } //Begin |
| |
| #if WPF |
| private void RemoveVisualBrush() |
| { |
| var shape = item1 as Shape; |
| if (shape != null) |
| { |
| var visBrush = shape.Fill as VisualBrush; |
| if (visBrush != null) |
| { |
| visBrush.Visual = null; |
| } |
| } |
| } |
| #endif |
| |
| /// <summary> |
| /// Stop an outstanding transition |
| /// </summary> |
| public void StopTransition() |
| { |
| // Move to initial state: |
| this.animation.Stop(); |
| #if WPF |
| this.Container.Children.Remove(this.item1); |
| #else |
| this.Container.Children.Remove(this.item1); |
| #endif |
| this.Container.Children.Remove(this.mask1); |
| this.Container.Children.Remove(this.mask2); |
| this.item1 = null; |
| this.item2 = null; |
| this.isRunning = false; |
| } //StopTransition |
| } //ExampleTransition |
| } //namespace |
| |