Nimble Coder

Adventures in Nimble Coding
posts - 77, comments - 56, trackbacks - 1

Wednesday, October 29, 2008

Spinning Wait Symbol in Silverlight, Part3

SpinningCursor3-TestPage

Series History

In this post, I add curvature to the slices and refactor the code to support upcoming features. The goal of these posts is to build a spinning cursor similar to the Mac OS X wait cursor through programmatic means in Silverlight. One of the reasons to build the cursor programmatically is to create it will different number of slices or rotation or other parameters. At this point, the cursor is still very rough and just beginning to resemble the final result. In upcoming posts, I will animate the cursor and adjust the appearance to more closely resemble the desired cursor.

Step 1: Adjust the cursor properties

I added AlternateSlices and Origin properties, and also DefaultSliceCount and DefaultRotationAngle to the class.

public static readonly int DefaultSliceCount = 10;
public static readonly double DefaultSliceRotationAngle = 360.0 / DefaultSliceCount;

/// <summary>
/// Show alternating slices (true) or all slices (false)        
/// </summary> 
public bool AlternateSlices { get; set; }

/// <summary>        
/// The origin of the ellipse for the spinning cursor 
/// </summary> 
public Point Origin { get; private set; }

Step 2: Set the default parameter values

I specified the default slice count and the radius and origin of the cursor.

void Page_Loaded(object sender, RoutedEventArgs e)
{
    txtVersion.Text = this.GetType().Name;
    AlternateSlices = true;
    SliceCount = DefaultSliceCount;
    txtSliceCount.Text = Convert.ToString(SliceCount);

    SliceRotationAngle = DefaultSliceRotationAngle;
    txtRotation.Text = Convert.ToString(SliceRotationAngle);

    RadiusX = SpinningCanvas.Width / 2.0;
    RadiusY = SpinningCanvas.Height / 2.0;
    Origin = new Point(RadiusX, RadiusY);

    Update();
}

Step 3: Create a background circle (ellipse)

I used a simple linear gradient for now because the final gradient looks much more complicated and will take more time.

void CreateBackground(Canvas cursorCanvas)
{
    GradientBrush brush = new LinearGradientBrush();
    GradientStop stop1 = new GradientStop();
    stop1.Color = Color.FromArgb(255, 255, 0, 0);
    stop1.Offset = 0.25;
    brush.GradientStops.Add(stop1);

    GradientStop stop2 = new GradientStop();
    stop2.Color = Color.FromArgb(255, 0, 255, 0);
    stop2.Offset = 0.5; // cursorCanvas.Width / 2.0;
    brush.GradientStops.Add(stop2);

    GradientStop stop3 = new GradientStop();
    stop3.Color = Color.FromArgb(255, 0, 0, 255);
    stop3.Offset = 0.75; // cursorCanvas.Width;
    brush.GradientStops.Add(stop3);

    Ellipse ellipse = new Ellipse();
    ellipse.Height = cursorCanvas.Height;
    ellipse.Width = cursorCanvas.Width;
    ellipse.Fill = brush;
    cursorCanvas.Children.Add(ellipse);
}

Step 4: Implement the alternating slices

Implementing the alternating slices was simple:

/// <summary>
/// Create an ellipse using rotated slices to build the ellipse
/// </summary>
void CreateSlicePaths(Canvas cursorCanvas)
{
    // Create Slices
    for (int index = 0; index < SliceCount; ++index)
    {
        PathFigure pathFigure = CreateSliceFigure();

        PathGeometry pathGeometry = new PathGeometry();
        pathGeometry.Figures.Add(pathFigure);

        Path path = new Path();
        path.Stroke = new SolidColorBrush(Color.FromArgb(128, 0, 0, 0));
        path.StrokeThickness = 1.0;
        path.Fill = new SolidColorBrush(Color.FromArgb(192, 128, 128, 128));
        path.Data = pathGeometry;

        // Rotate the slice for all slices after the first slice
        if (index > 0)
        {
            RotateTransform t1 = new RotateTransform();
            t1.CenterX = RadiusX;
            t1.CenterY = RadiusY;
            t1.Angle = SliceCenterAngle * index;

            TransformGroup transformGroup = new TransformGroup();
            transformGroup.Children.Add(t1);
            path.RenderTransform = transformGroup;
        }

        cursorCanvas.Children.Add(path);

        if (AlternateSlices)
            ++index;
    }
}

Step 5: Add the curvature to the slices

The curvature isn't perfect, but it looks fine with the default settings (10 slices and 36° rotation). I used a Bezier curve, but perhaps the standard arc would work better in the future.

// Curve control weighting
double curveWeight = 0.75;

// Create the first line
BezierSegment seg1 = new BezierSegment();
seg1.Point1 = point0;
seg1.Point2 = CalculatePointOnEllipse(0.0, RadiusX * curveWeight, RadiusY * curveWeight, Origin);
seg1.Point3 = point1;
pathFigure.Segments.Add(seg1);

// Use an arc for the circular side
ArcSegment seg2 = new ArcSegment();
seg2.Point = point2;
seg2.Size = new Size(RadiusX, RadiusY);
seg2.RotationAngle = SliceCenterAngle;
seg2.IsLargeArc = (SliceCenterAngle > 180.0);
seg2.SweepDirection = SweepDirection.Counterclockwise;
pathFigure.Segments.Add(seg2);

// Close shape by going back to the starting point
BezierSegment seg3 = new BezierSegment();
seg3.Point1 = point2;
seg3.Point2 = CalculatePointOnEllipse(SliceCenterAngle, RadiusX * curveWeight, RadiusY * curveWeight, Origin);
seg3.Point3 = point0;
pathFigure.Segments.Add(seg3);

Conclusion

The spinning cursor now has a dynamic number of slices and rotation angle, and it is starting to look more like the desired result. In the next post, I will animate the cursor to give it the spinning effect.

Technorati tags: ,
kick it on DotNetKicks.com

posted @ Wednesday, October 29, 2008 11:16 AM | Feedback (4) | Filed Under [ C# SilverLight ]

Powered by: