using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace PapaMufflon.Tools.MessageForDummies { /// /// This StackPanel orders his children along a circle segment. /// The bend is the fraction of the circle-angle times two. /// public class BendingStackPanel : StackPanel { private Size _maxElement; /// /// Amount of bending of the StackPanel. /// 0 = no bend, 1 = max bend. /// Max bend is like a 'U' turned 90° counter-clockwise /// when 0 is like a 'I'. /// public double Bend { get { return (double)GetValue(BendProperty); } set { SetValue(BendProperty, value); } } // Using a DependencyProperty as the backing store for Bend. This enables animation, styling, binding, etc... public static readonly DependencyProperty BendProperty = DependencyProperty.Register("Bend", typeof(double), typeof(BendingStackPanel), new UIPropertyMetadata(0.0, new PropertyChangedCallback(BendingChanged), new CoerceValueCallback(CoerceBending))); private static void BendingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var me = (BendingStackPanel)d; me.InvalidateMeasure(); } private static object CoerceBending(DependencyObject d, object value) { double bending = (double)value; if (bending > 1) bending = 1; if (bending <= 0.001) bending = 0.001; return bending; } protected override Size MeasureOverride(Size constraint) { var count = Children.Count; // special case no child if (count == 0) { return base.MeasureOverride(constraint); } // special case one child if (count == 1) { Children[0].Measure(constraint); return Children[0].DesiredSize; } _maxElement = new Size(0, 0); // we want the maximums foreach (UIElement child in Children) { child.Measure(constraint); if (child.DesiredSize.Width > _maxElement.Width) _maxElement.Width = child.DesiredSize.Width; if (child.DesiredSize.Height > _maxElement.Height) _maxElement.Height = child.DesiredSize.Height; } // get the height var height = Children.Count * _maxElement.Height; // now bend it: // calculate the fraction of the sector of the circle var sectorFraction = Bend / 2; var fractionAngle = 2 * Math.PI * sectorFraction; // get the radius var radius = height / fractionAngle; // bend the height height = 2 * radius * Math.Sin(height / (2 * radius)); // get the width: width of a child and the bulb of the bending plus // the part of the height of the first child var width = _maxElement.Width + radius - Math.Sqrt(radius * radius - height / 2 * height / 2) + Math.Sin(fractionAngle / 2) * _maxElement.Height / 2; // add upper and lower element to the height height += 2 * Math.Sin(fractionAngle) * _maxElement.Height; return new Size(width, height); } protected override Size ArrangeOverride(Size arrangeBounds) { var angle = 2 * Math.PI * Bend / 2; var angleStep = angle / (Children.Count - 1) * 2; var radius = Children.Count * _maxElement.Height / angle; var centerX = -Math.Cos(angle / 2) * radius; var centerY = Math.Sin(angle / 2) * radius; var offsetX = Math.Sin(angle / 2) * _maxElement.Height / 2; var offsetY = Math.Sin(angle / 2) * _maxElement.Width + Math.Cos(angle / 2) * _maxElement.Height / 2; // arrange the children from top to bottom angle *= -1; // arrange the children along a circle segment foreach (UIElement child in Children) { var childLeftMiddleX = Math.Cos(angle / 2) * radius + centerX + offsetX; var childLeftMiddleY = Math.Sin(angle / 2) * radius + centerY + offsetY; child.Arrange(new Rect(childLeftMiddleX, childLeftMiddleY - _maxElement.Height / 2, _maxElement.Width, _maxElement.Height)); var rotation = new RotateTransform(angle * 180 / (2 * Math.PI), 0, child.DesiredSize.Height / 2); child.RenderTransform = rotation; angle += angleStep; } return arrangeBounds; } } }