368 lines
13 KiB
C#
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;
|
|
}
|
|
}
|
|
} |