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;
}
}
}