Files
BeWoPlaner/Controls/Controls/FishEyePanel.cs
2016-06-27 01:45:38 +02:00

368 lines
13 KiB
C#

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace BeWo.Controls
{
/// <summary>
/// FishEyePanel
/// </summary>
public class FishEyePanel : Panel
{
// Using a DependencyProperty as the backing store for AnimationMilliseconds. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AnimationMillisecondsProperty = DependencyProperty.Register("AnimationMilliseconds", typeof(int), typeof(FishEyePanel), new UIPropertyMetadata(525));
public static readonly DependencyProperty MagnificationProperty = DependencyProperty.Register("Magnification", typeof(double), typeof(FishEyePanel), new UIPropertyMetadata(2d));
// If set true we scale different sized children to a constant width
// Using a DependencyProperty as the backing store for ScaleToFit. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScaleToFitProperty = DependencyProperty.Register("ScaleToFit", typeof(bool), typeof(FishEyePanel), new UIPropertyMetadata(true));
private bool animating;
private Size ourSize;
private double totalChildWidth;
private bool wasMouseOver;
public FishEyePanel()
{
this.Background = Brushes.Transparent;
this.MouseMove += this.FishEyePanel_MouseMove;
this.MouseEnter += this.FishEyePanel_MouseEnter;
this.MouseLeave += this.FishEyePanel_MouseLeave;
}
private enum AnimateState
{
None,
Up,
Down
} ;
public int AnimationMilliseconds
{
get
{
return (int)this.GetValue(AnimationMillisecondsProperty);
}
set
{
this.SetValue(AnimationMillisecondsProperty, value);
}
}
public double Magnification
{
get
{
return (double)this.GetValue(MagnificationProperty);
}
set
{
this.SetValue(MagnificationProperty, value);
}
}
public bool ScaleToFit
{
get
{
return (bool)this.GetValue(ScaleToFitProperty);
}
set
{
this.SetValue(ScaleToFitProperty, value);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
if (this.Children == null || this.Children.Count == 0)
{
return finalSize;
}
this.ourSize = finalSize;
this.totalChildWidth = 0;
foreach (UIElement child in this.Children)
{
// If this is the first time we've seen this child, add our transforms
if (child.RenderTransform as TransformGroup == null)
{
child.RenderTransformOrigin = new Point(0, 1);
TransformGroup group = new TransformGroup();
child.RenderTransform = group;
group.Children.Add(new ScaleTransform());
group.Children.Add(new TranslateTransform());
group.Children.Add(new RotateTransform());
}
child.Arrange(new Rect(0, 0, child.DesiredSize.Width, child.DesiredSize.Height));
this.totalChildWidth += child.DesiredSize.Width;
}
this.AnimateAll();
return finalSize;
}
protected override Size MeasureOverride(Size availableSize)
{
Size idealSize = new Size(0, 0);
// Allow children as much room as they want - then scale them
Size size = new Size(Double.PositiveInfinity, Double.PositiveInfinity);
foreach (UIElement child in this.Children)
{
child.Measure(size);
idealSize.Width += child.DesiredSize.Width;
idealSize.Height = Math.Max(idealSize.Height, child.DesiredSize.Height);
}
// EID calls us with infinity, but framework doesn't like us to return infinity
if (double.IsInfinity(availableSize.Height) || double.IsInfinity(availableSize.Width))
{
return idealSize;
}
else
{
return availableSize;
}
}
private void AnimateAll()
{
if (this.Children == null || this.Children.Count == 0)
{
return;
}
this.animating = true;
double childWidth = this.ourSize.Width / this.Children.Count;
// Scale the children so they fit in our size
double overallScaleFactor = this.ourSize.Width / this.totalChildWidth;
UIElement prevChild = null;
UIElement theChild = null;
UIElement nextChild = null;
double widthSoFar = 0;
double theChildX = 0;
double ratio = 0;
if (this.IsMouseOver)
{
double x = Mouse.GetPosition(this).X;
foreach (UIElement child in this.Children)
{
if (theChild == null)
{
theChildX = widthSoFar;
}
widthSoFar += this.ScaleToFit ? childWidth : child.DesiredSize.Width * overallScaleFactor;
if (x < widthSoFar && theChild == null)
{
theChild = child;
}
if (theChild == null)
{
prevChild = child;
}
if (nextChild == null && theChild != child && theChild != null)
{
nextChild = child;
break;
}
}
if (theChild != null)
{
ratio = (x - theChildX) / (this.ScaleToFit ? childWidth : (theChild.DesiredSize.Width * overallScaleFactor)); // Range 0-1 of where the mouse is inside the child
}
}
// These next few lines took two of us hours to write!
double mag = this.Magnification;
double extra = 0;
if (theChild != null)
{
extra += mag - 1;
}
if (prevChild == null)
{
extra += ratio * (mag - 1);
}
else if (nextChild == null)
{
extra += (mag - 1) * (1 - ratio);
}
else
{
extra += mag - 1;
}
double prevScale = this.Children.Count * (1 + ((mag - 1) * (1 - ratio))) / (this.Children.Count + extra);
double theScale = (mag * this.Children.Count) / (this.Children.Count + extra);
double nextScale = this.Children.Count * (1 + ((mag - 1) * ratio)) / (this.Children.Count + extra);
double otherScale = this.Children.Count / (this.Children.Count + extra); // Applied to all non-interesting children
// Adjust for different sized children - we overmagnify large children, so shrink the others
if (!this.ScaleToFit && this.IsMouseOver)
{
double bigWidth = 0;
double actualWidth = 0;
if (prevChild != null)
{
bigWidth += prevScale * prevChild.DesiredSize.Width * overallScaleFactor;
actualWidth += prevChild.DesiredSize.Width;
}
if (theChild != null)
{
bigWidth += theScale * theChild.DesiredSize.Width * overallScaleFactor;
actualWidth += theChild.DesiredSize.Width;
}
if (nextChild != null)
{
bigWidth += nextScale * nextChild.DesiredSize.Width * overallScaleFactor;
actualWidth += nextChild.DesiredSize.Width;
}
double w = (this.totalChildWidth - actualWidth) * overallScaleFactor * otherScale;
otherScale *= (this.ourSize.Width - bigWidth) / w;
}
widthSoFar = 0;
double duration = 0;
if (this.wasMouseOver != this.IsMouseOver)
{
duration = this.AnimationMilliseconds;
}
foreach (UIElement child in this.Children)
{
double scale = otherScale;
if (child == prevChild)
{
scale = prevScale;
}
else if (child == theChild)
{
scale = theScale;
}
else if (child == nextChild)
{
scale = nextScale;
}
if (this.ScaleToFit)
{
// Now scale each individual child so it is a standard width
scale *= childWidth / child.DesiredSize.Width;
}
else
{
// Apply overall scale so all children fit our width
scale *= overallScaleFactor;
}
this.AnimateTo(child, 0, widthSoFar, this.ourSize.Height - child.DesiredSize.Height, scale, duration);
widthSoFar += child.DesiredSize.Width * scale;
}
this.wasMouseOver = this.IsMouseOver;
}
private void AnimateTo(UIElement child, double r, double x, double y, double s, double duration)
{
TransformGroup group = (TransformGroup)child.RenderTransform;
ScaleTransform scale = (ScaleTransform)group.Children[0];
TranslateTransform trans = (TranslateTransform)group.Children[1];
// RotateTransform rot = (RotateTransform)group.Children[2];
if (duration == 0)
{
trans.BeginAnimation(TranslateTransform.XProperty, null);
trans.BeginAnimation(TranslateTransform.YProperty, null);
scale.BeginAnimation(ScaleTransform.ScaleXProperty, null);
scale.BeginAnimation(ScaleTransform.ScaleYProperty, null);
// rot.BeginAnimation(RotateTransform.AngleProperty, null);
trans.X = x;
trans.Y = y;
scale.ScaleX = s;
scale.ScaleY = s;
// rot.AngleProperty = r;
this.animation_Completed(null, null);
}
else
{
trans.BeginAnimation(TranslateTransform.XProperty, this.MakeAnimation(x, duration, this.animation_Completed));
trans.BeginAnimation(TranslateTransform.YProperty, this.MakeAnimation(y, duration));
scale.BeginAnimation(ScaleTransform.ScaleXProperty, this.MakeAnimation(s, duration));
scale.BeginAnimation(ScaleTransform.ScaleYProperty, this.MakeAnimation(s, duration));
// rot.BeginAnimation(RotateTransform.AngleProperty, MakeAnimation(r, duration));
}
}
private void FishEyePanel_MouseEnter(object sender, MouseEventArgs e)
{
this.InvalidateArrange();
}
private void FishEyePanel_MouseLeave(object sender, MouseEventArgs e)
{
this.InvalidateArrange();
}
private void FishEyePanel_MouseMove(object sender, MouseEventArgs e)
{
if (!this.animating)
{
this.InvalidateArrange();
}
}
private DoubleAnimation MakeAnimation(double to, double duration)
{
return this.MakeAnimation(to, duration, null);
}
private DoubleAnimation MakeAnimation(double to, double duration, EventHandler endEvent)
{
DoubleAnimation anim = new DoubleAnimation(to, TimeSpan.FromMilliseconds(duration));
anim.AccelerationRatio = 0.2;
anim.DecelerationRatio = 0.7;
if (endEvent != null)
{
anim.Completed += endEvent;
}
return anim;
}
private void animation_Completed(object sender, EventArgs e)
{
this.animating = false;
}
}
}