With Line
:
class AnotherTrig : FrameworkElement
{
VisualCollection children;
EllipseGeometry circle, ball;
ArcSegment angle;
Path circlePath, ballpath, anglePath;
Line sine, cosine, hypoteneous;
double cx, cy, radius, padding;
public AnotherTrig() {
padding = 20;
circle = new EllipseGeometry();
angle = new ArcSegment() { IsStroked = true, SweepDirection = SweepDirection.Clockwise, Size = new Size(40, 40), IsLargeArc = true };
ball = new EllipseGeometry() { Center = new Point(0, 0), RadiusX = 5, RadiusY = 5 };
circlePath = new Path() { Stroke = Brushes.LightBlue, StrokeThickness = 3, Data = circle };
ballpath = new Path() { Fill = Brushes.LightCoral, Data = ball, RenderTransform = new MatrixTransform() };
anglePath = new Path() {
Fill = new SolidColorBrush(Color.FromArgb(50, 0, 0, 100)),
Stroke = Brushes.Black,
StrokeThickness = 1,
Data = new PathGeometry() {
Figures = {
new PathFigure() {
Segments = {
new LineSegment(){ IsStroked = false},
angle
}
}
}
}
};
sine = new Line() { Stroke = Brushes.Blue, StrokeThickness = 1 };
cosine = new Line() { Stroke = Brushes.Red, StrokeThickness = 1 };
hypoteneous = new Line() {
Stroke = Brushes.Black,
StrokeThickness = 0.5,
StrokeDashCap = PenLineCap.Round,
StrokeDashArray = new DoubleCollection(new double[] { 5, 5 }),
};
children = new VisualCollection(this) { circlePath, sine, cosine, ballpath, hypoteneous, anglePath };
ballpath.RenderTransform.Changed += angleChanged;
Loaded += animate;
}
void angleChanged(object sender, EventArgs e) {
var mat = (sender as MatrixTransform).Matrix;
hypoteneous.X2 = cosine.X2 = sine.X1 = sine.X2 = mat.OffsetX;
hypoteneous.Y2 = sine.Y2 = mat.OffsetY;
}
void animate(object s, EventArgs e) {
var circleGeo = PathGeometry.CreateFromGeometry(circle);
var angleGeo = new PathGeometry() {
Figures = {
new PathFigure() {
StartPoint = new Point(cx + 40, cy),
Segments ={(anglePath.Data as PathGeometry).Figures[0].Segments[1].Clone()}
}
}
};
circleGeo.Freeze();
angleGeo.Freeze();
var ballAnim = new MatrixAnimationUsingPath() {
Duration = TimeSpan.FromSeconds(10),
RepeatBehavior = RepeatBehavior.Forever,
PathGeometry = circleGeo
};
var angleAnim = new PointAnimationUsingPath() {
PathGeometry = angleGeo,
RepeatBehavior = RepeatBehavior.Forever,
Duration = TimeSpan.FromSeconds(10)
};
var angleIsLargeAnim = new BooleanAnimationUsingKeyFrames() {
KeyFrames = {
new DiscreteBooleanKeyFrame(false, TimeSpan.FromSeconds(0)),
new DiscreteBooleanKeyFrame(true, TimeSpan.FromSeconds(5)),
new DiscreteBooleanKeyFrame(false, TimeSpan.FromSeconds(10))
},
RepeatBehavior = RepeatBehavior.Forever
};
ballpath.RenderTransform.BeginAnimation(MatrixTransform.MatrixProperty, ballAnim);
angle.BeginAnimation(ArcSegment.IsLargeArcProperty, angleIsLargeAnim);
angle.BeginAnimation(ArcSegment.PointProperty, angleAnim);
}
protected override Size MeasureOverride(Size availableSize) {
cx = availableSize.Width / 2;
cy = availableSize.Height / 2;
radius = Math.Min((availableSize.Width - 2 * padding) / 2, (availableSize.Height - 2 * padding) / 2);
circle.RadiusX = circle.RadiusY = radius;
circle.Center = ((PathGeometry)anglePath.Data).Figures[0].StartPoint = new Point(cx, cy);
hypoteneous.X1 = cosine.X1 = cx;
hypoteneous.Y1 = cosine.Y1 = cosine.Y2 = sine.Y1 = cy;
(((PathGeometry)anglePath.Data).Figures[0].Segments[0] as LineSegment).Point = new Point(cx + 40, cy);
angle.Point = new Point(cx + 40, cy - 0.1);
foreach (UIElement child in children)
child.Measure(availableSize);
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize) {
foreach (UIElement child in children)
child.Arrange(new Rect(child.DesiredSize));
return finalSize;
}
protected override Visual GetVisualChild(int index) => children[index];
protected override int VisualChildrenCount => children.Count;
}
It's perfect:
All that's left is handle resize.