2

I am trying to create a 3..2..1 countdown in the form of a user control. Something like this. My Idea was to create two rectangles on top of each other, one light and one dark and have a radial circle as the clipper for the dark rectangle. The radial circle would have Angle property animated so it would turn around.

I found an implementation of the radial circle and bound the Clip property of the rectangle on the RenderedGeometry property of my circle. Here is the result :

Problem Screenshot

The red stroke is the shape of my clipper. This seems to be an odd behavior of the clipping but I sort of understand it but I would like to know if there was a way of going around the fact that my clipped object seems to use the RenderedGeometry in a weird way.

Edit 1 : The effect I am looking for http://www.youtube.com/watch?v=9FPHTo5V2BQ

Tristan Dubé
  • 702
  • 1
  • 15
  • 30
  • This seems a bit overcomplicated, what's the effect you're after exactly? – Chris W. Apr 05 '13 at 16:03
  • Something like this : http://www.youtube.com/watch?v=9FPHTo5V2BQ . And yes I sometimes do over complicate things but it's part of my research process. I have not yet found a simple way of doing it. – Tristan Dubé Apr 05 '13 at 16:07
  • I'll have to check it out later, unfortunately they restrict youtube at the office for us :( – Chris W. Apr 05 '13 at 16:11
  • Could you post the XAML code and any relevant code-behind (and not just a screenshot)? – Sphinxxx Apr 05 '13 at 18:25

2 Answers2

3

The simple derived Shape control shown below draws the countdown rectangle. You have to set its Fill (and perhaps Stroke), Width, Height and Angle properties, and you can animate Angle from 0 to 360.

public class CountdownRect : Shape
{
    static CountdownRect()
    {
        WidthProperty.OverrideMetadata(typeof(CountdownRect),
            new FrameworkPropertyMetadata((o, e) => ((CountdownRect)o).UpdateGeometry()));

        HeightProperty.OverrideMetadata(typeof(CountdownRect),
            new FrameworkPropertyMetadata((o, e) => ((CountdownRect)o).UpdateGeometry()));

        StrokeLineJoinProperty.OverrideMetadata(typeof(CountdownRect),
            new FrameworkPropertyMetadata(PenLineJoin.Round));
    }

    public static readonly DependencyProperty AngleProperty =
        DependencyProperty.Register("Angle", typeof(double), typeof(CountdownRect),
            new FrameworkPropertyMetadata((o, e) => ((CountdownRect)o).UpdateGeometry()));

    public double Angle
    {
        get { return (double)GetValue(AngleProperty); }
        set { SetValue(AngleProperty, value); }
    }

    private readonly StreamGeometry geometry = new StreamGeometry();

    protected override Geometry DefiningGeometry
    {
        get { return geometry; }
    }

    private void UpdateGeometry()
    {
        if (!double.IsNaN(Width) && !double.IsNaN(Height))
        {
            var angle = ((Angle % 360d) + 360d) % 360d;
            var margin = StrokeThickness / 2d;
            var p0 = new Point(margin, margin);
            var p1 = new Point(Width - margin, margin);
            var p2 = new Point(Width - margin, Height - margin);
            var p3 = new Point(margin, Height - margin);

            using (var context = geometry.Open())
            {
                if (angle == 0d)
                {
                    context.BeginFigure(p0, true, true);
                    context.LineTo(p1, true, false);
                    context.LineTo(p2, true, false);
                    context.LineTo(p3, true, false);
                }
                else
                {
                    var x = p2.X / 2d;
                    var y = p2.Y / 2d;
                    var a = Math.Atan2(x, y) / Math.PI * 180d;
                    var t = Math.Tan(angle * Math.PI / 180d);

                    context.BeginFigure(new Point(x, y), true, true);

                    if (angle < a)
                    {
                        context.LineTo(new Point(x + y * t, p0.Y), true, false);
                        context.LineTo(p1, true, false);
                        context.LineTo(p2, true, false);
                        context.LineTo(p3, true, false);
                        context.LineTo(p0, true, false);
                    }
                    else if (angle < 180d - a)
                    {
                        context.LineTo(new Point(p2.X, y - x / t), true, false);
                        context.LineTo(p2, true, false);
                        context.LineTo(p3, true, false);
                        context.LineTo(p0, true, false);
                    }
                    else if (angle < 180d + a)
                    {
                        context.LineTo(new Point(x - y * t, p2.Y), true, false);
                        context.LineTo(p3, true, false);
                        context.LineTo(p0, true, false);
                    }
                    else if (angle < 360d - a)
                    {
                        context.LineTo(new Point(p0.X, y + x / t), true, false);
                        context.LineTo(p0, true, false);
                    }
                    else
                    {
                        context.LineTo(new Point(x + y * t, p0.Y), true, false);
                    }

                    context.LineTo(new Point(x, p0.Y), true, false);
                }
            }
        }
    }
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
3

You can clip your rectangle by using an ArcSegment in the clipping PathGeometry, and animate that ArcSegment's endpoint (Point).

The endpoint can be animated with a PointAnimationUsingPath animation, using an identical ArcSegment as its path. Below is a suggestion based on Charlez Petzold's excellent answer here: Drawing pie slices

<UserControl ... >

    <UserControl.Resources>
        <Point x:Key="SweepCenter" X="100" Y="100" />
        <Size x:Key="SweepRadius" Width="130" Height="130" />

        <!-- Start sweeping at twelve o'clock.. -->
        <Point x:Key="SweepStart" X="100" Y="-30" />
        <!-- ..and keep sweeping clockwise until we're (almost) back at the start point: -->
        <Point x:Key="SweepEnd" X="99.99" Y="-30" />

        <Storyboard x:Key="Sweeper" RepeatBehavior="Forever" AutoReverse="False" >

            <PointAnimationUsingPath Storyboard.TargetName="arc"
                                     Storyboard.TargetProperty="Point"
                                     Duration="0:0:5">
                <PointAnimationUsingPath.PathGeometry>
                    <PathGeometry>
                        <PathFigure StartPoint="{StaticResource SweepStart}">
                            <ArcSegment Size="{StaticResource SweepRadius}" 
                                        Point="{StaticResource SweepEnd}"
                                        SweepDirection="Clockwise"
                                        IsLargeArc="True" />
                        </PathFigure>
                    </PathGeometry>
                </PointAnimationUsingPath.PathGeometry>
            </PointAnimationUsingPath>

            <BooleanAnimationUsingKeyFrames Storyboard.TargetName="arc"
                                            Storyboard.TargetProperty="IsLargeArc" >
                <DiscreteBooleanKeyFrame KeyTime="0:0:2.5" Value="True" />
                <DiscreteBooleanKeyFrame KeyTime="0:0:5" Value="False" />
            </BooleanAnimationUsingKeyFrames>
        </Storyboard>

    </UserControl.Resources>

    <Grid Width="200" Height="200" >
        <Rectangle Fill="Black" />
        <Rectangle Fill="Gray" >
            <Rectangle.Triggers>
                <EventTrigger RoutedEvent="Loaded">
                    <BeginStoryboard Storyboard="{StaticResource Sweeper}" />
                </EventTrigger>
            </Rectangle.Triggers>
            <Rectangle.Clip>
                <PathGeometry>
                    <PathFigure StartPoint="{StaticResource SweepCenter}" 
                                IsClosed="True" >
                        <LineSegment Point="{StaticResource SweepStart}" />
                        <ArcSegment x:Name="arc"
                                    Size="{StaticResource SweepRadius}"
                                    Point="{StaticResource SweepStart}"
                                    SweepDirection="Clockwise" />
                    </PathFigure>
                </PathGeometry>
            </Rectangle.Clip>
        </Rectangle>
    </Grid>

</UserControl>
Sphinxxx
  • 12,484
  • 4
  • 54
  • 84