//-----------------------------------------------------------------------
// <copyright file="RadialPanel.cs" company="Jeow Li Huan">
// Copyright (c) Jeow Li Huan. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
namespace TetonWhitewaterKayak.WinUI
{
/// <summary>
/// Arranges child elements along the circumference of a circle.
/// </summary>
public class RadialPanel : Panel
{
/// <summary>
/// Identifies the RadialPanel.Orientation dependency property.
/// </summary>
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
"Orientation",
typeof(Orientation),
typeof(RadialPanel),
new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// Identifies the RadialPanel.ForegroundProperty dependency property.
/// </summary>
public static readonly DependencyProperty ForegroundProperty = TextElement.ForegroundProperty.AddOwner(typeof(RadialPanel), new FrameworkPropertyMetadata(SystemColors.ControlTextBrush, FrameworkPropertyMetadataOptions.Inherits));
/// <summary>
/// Backing field for the ShowPieLines property.
/// </summary>
private bool showPieLines;
/// <summary>
/// The angle of the slice that each child occupies.
/// </summary>
private double angleEach;
/// <summary>
/// Size of the largest child.
/// </summary>
private Size sizeLargest;
/// <summary>
/// Radius of the circle that surrounds the children.
/// </summary>
private double radius;
/// <summary>
/// The distance from the top of any child to the center of the circle.
/// </summary>
private double outerEdgeFromCenter;
/// <summary>
/// The distance from the bottom of any child to the center of the circle.
/// </summary>
private double innerEdgeFromCenter;
/// <summary>
/// Gets or sets a brush that describes the foreground color. This is a dependency property.
/// </summary>
[Category("Appearance")]
[Bindable(true)]
public Brush Foreground
{
get { return (Brush)GetValue(ForegroundProperty); }
set { SetValue(ForegroundProperty, value); }
}
/// <summary>
/// Gets or sets a value that indicates whether child elements span the circumference of the panel by their widths or by their heights.
/// This is a dependency property.
/// </summary>
[Category("Layout")]
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether to draw lines along the circumference and
/// along the spooks that separates the child elements.
/// </summary>
[Category("Appearance")]
[DefaultValue(false)]
public bool ShowPieLines
{
set
{
if (this.showPieLines != value)
this.showPieLines = value;
this.InvalidateVisual();
}
get
{
return this.showPieLines;
}
}
/// <summary>
/// Measures the child elements of a RadialPanel in anticipation
/// of arranging them during the RadialPanel.ArrangeOverride(System.Windows.Size)
/// pass.
/// </summary>
/// <param name="sizeAvailable">
/// An upper limit <see cref="T:System.Windows.Size"/> that should not be exceeded.
/// </param>
/// <returns>
/// The <see cref="T:System.Windows.Size"/> that represents the desired size of the element.
/// </returns>
protected override Size MeasureOverride(Size sizeAvailable)
{
if (this.InternalChildren.Count == 0)
return new Size();
this.angleEach = 360.0 / this.InternalChildren.Count;
this.sizeLargest = new Size();
foreach (UIElement child in this.InternalChildren)
{
// Call Measure for each child ...
child.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
// Examine DesiredSize property of child.
this.sizeLargest.Width = Math.Max(this.sizeLargest.Width, child.DesiredSize.Width);
this.sizeLargest.Height = Math.Max(this.sizeLargest.Height, child.DesiredSize.Height);
}
if (this.InternalChildren.Count == 1)
{
double diagonal = Math.Sqrt((this.sizeLargest.Width * this.sizeLargest.Width) + (this.sizeLargest.Height * this.sizeLargest.Height));
return new Size(diagonal, diagonal);
}
double halfLargestSpan;
double largestHeight;
if (this.Orientation == Orientation.Horizontal)
{
halfLargestSpan = this.sizeLargest.Width / 2.0;
largestHeight = this.sizeLargest.Height;
}
else
{
halfLargestSpan = this.sizeLargest.Height / 2.0;
largestHeight = this.sizeLargest.Width;
}
// Calculate the distance from the center to element edges.
this.innerEdgeFromCenter = (this.InternalChildren.Count == 2) ? 0.0 : halfLargestSpan / Math.Tan(this.angleEach * Math.PI / 360.0);
this.outerEdgeFromCenter = this.innerEdgeFromCenter + largestHeight;
// Calculate the radius of the circle based on the largest child.
this.radius = Math.Sqrt((halfLargestSpan * halfLargestSpan) + (this.outerEdgeFromCenter * this.outerEdgeFromCenter));
// Return the size of that circle.
return new Size(2.0 * this.radius, 2.0 * this.radius);
}
/// <summary>
/// Arranges the content of a RadialPanel element.
/// </summary>
/// <param name="sizeFinal">
/// The <see cref="T:System.Windows.Size"/> that this element should use to arrange its child elements.
/// </param>
/// <returns>
/// The <see cref="T:System.Windows.Size"/> that represents the arranged size of this RadialPanel
/// element and its child elements.
/// </returns>
protected override Size ArrangeOverride(Size sizeFinal)
{
if (this.InternalChildren.Count == 0)
return sizeFinal;
if (this.InternalChildren.Count == 1)
{
UIElement child = this.InternalChildren[0];
child.RenderTransform = Transform.Identity;
Point center = new Point(
(sizeFinal.Width - this.sizeLargest.Width) / 2.0,
(sizeFinal.Height - this.sizeLargest.Height) / 2.0);
child.Arrange(new Rect(center, new Size(this.sizeLargest.Width, this.sizeLargest.Height)));
if (this.Orientation == Orientation.Vertical)
{
Point rotatePoint = this.TranslatePoint(center, child);
rotatePoint.X += this.sizeLargest.Width / 2.0;
rotatePoint.Y += this.sizeLargest.Height / 2.0;
child.RenderTransform = new RotateTransform(-90.0, rotatePoint.X, rotatePoint.Y);
}
return sizeFinal;
}
double angleChild = (this.Orientation == Orientation.Horizontal) ? 0.0 : -90.0;
Point centerPoint = new Point(sizeFinal.Width / 2.0, sizeFinal.Height / 2.0);
double multiplier = Math.Min(sizeFinal.Width, sizeFinal.Height) / (2.0 * this.radius);
foreach (UIElement child in this.InternalChildren)
{
// Reset RenderTransform.
child.RenderTransform = Transform.Identity;
if (this.Orientation == Orientation.Horizontal)
{
// Position the child at the top.
child.Arrange(
new Rect(
centerPoint.X - (multiplier * this.sizeLargest.Width / 2.0),
centerPoint.Y - (multiplier * this.outerEdgeFromCenter),
multiplier * this.sizeLargest.Width,
multiplier * this.sizeLargest.Height));
}
else
{
// Position the child at the right.
child.Arrange(
new Rect(
centerPoint.X + (multiplier * this.innerEdgeFromCenter),
centerPoint.Y - (multiplier * this.sizeLargest.Height / 2.0),
multiplier * this.sizeLargest.Width,
multiplier * this.sizeLargest.Height));
}
// Rotate the child around the center (relative to the child).
Point pt = TranslatePoint(centerPoint, child);
child.RenderTransform = new RotateTransform(angleChild, pt.X, pt.Y);
angleChild += this.angleEach;
}
return sizeFinal;
}
/// <summary>
/// Draws the content of a <see cref="T:System.Windows.Media.DrawingContext"/> object during
/// the render pass of a RadialPanel element.
/// </summary>
/// <param name="dc">
/// The <see cref="T:System.Windows.Media.DrawingContext"/> object to draw.
/// </param>
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
if (this.ShowPieLines)
{
Point centerPoint = new Point(RenderSize.Width / 2.0, RenderSize.Height / 2.0);
double radius = Math.Min(RenderSize.Width, RenderSize.Height) / 2;
Pen pen = new Pen(this.Foreground, 1.0);
pen.DashStyle = DashStyles.Dash;
// Display circle.
dc.DrawEllipse(null, pen, centerPoint, radius, radius);
if (this.InternalChildren.Count == 1)
return;
// Initialize angle.
double angleChild = -(this.angleEach / 2.0) - 90.0;
// Loop through each child to draw radial lines from center.
foreach (UIElement child in this.InternalChildren)
{
double angleChildInRadian = 2.0 * Math.PI * angleChild / 360;
dc.DrawLine(pen, centerPoint, new Point(centerPoint.X + (radius * Math.Cos(angleChildInRadian)), centerPoint.Y + (radius * Math.Sin(angleChildInRadian))));
angleChild += this.angleEach;
}
}
}
}
}
1 comment:
cool
Post a Comment