using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Tango.Core;
using Tango.Editors;
namespace Tango.Editors
{
///
/// Represents a base class for all element editors.
///
///
///
public abstract class ElementEditor : HybridControl, IElementEditor
{
private Point _startPoint;
private Point _centerPoint;
private Vector _startVector;
private double _initialAngle;
private double _anglePI;
private bool _preventMovingEvent;
#region Events
///
/// Occurs when the editor is moving.
///
public event EventHandler Moving;
///
/// Occurs before editor bounds change.
///
public event EventHandler BeforeBoundsChange;
///
/// Occurs after editor bounds change.
///
public event EventHandler AfterBoundsChange;
#endregion
#region Properties
///
/// Gets or sets the editor unique identifier.
///
[ParameterIgnore]
public String ID
{
get { return (String)GetValue(IDProperty); }
set { SetValue(IDProperty, value); }
}
public static readonly DependencyProperty IDProperty =
DependencyProperty.Register("ID", typeof(String), typeof(ElementEditor), new PropertyMetadata(null));
///
/// Gets or sets a value indicating whether the hosted element will be presented inside or outside the editor.
///
[ParameterIgnore]
public bool ElementDetached
{
get { return (bool)GetValue(ElementDetachedProperty); }
set { SetValue(ElementDetachedProperty, value); }
}
public static readonly DependencyProperty ElementDetachedProperty =
DependencyProperty.Register("ElementDetached", typeof(bool), typeof(ElementEditor), new PropertyMetadata(false, (d, e) => (d as ElementEditor).OnElementDetachedChanged()));
///
/// Gets or sets the editor top position.
///
[ParameterItem(null, null)]
public double Top
{
get { return (double)GetValue(TopProperty); }
set { SetValue(TopProperty, value); }
}
public static readonly DependencyProperty TopProperty =
DependencyProperty.Register("Top", typeof(double), typeof(ElementEditor), new PropertyMetadata(0.0, (d, e) => { (d as ElementEditor).OnPositionChanged(); }, (d, e) => { return (d as ElementEditor).OnCoerceTop(e); }));
///
/// Gets or sets the editor left position.
///
[ParameterItem(null, null)]
public double Left
{
get { return (double)GetValue(LeftProperty); }
set { SetValue(LeftProperty, value); }
}
public static readonly DependencyProperty LeftProperty =
DependencyProperty.Register("Left", typeof(double), typeof(ElementEditor), new PropertyMetadata(0.0, (d, e) => { (d as ElementEditor).OnPositionChanged(); }, (d, e) => { return (d as ElementEditor).OnCoerceLeft(e); }));
///
/// Gets or sets the editor angle.
///
[ParameterItem(0.0, 360.0)]
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(ElementEditor), new PropertyMetadata(0.0));
///
/// Gets or sets the corners brush.
///
[ParameterIgnore]
public Brush CornersBrush
{
get { return (Brush)GetValue(CornersBrushProperty); }
set { SetValue(CornersBrushProperty, value); }
}
public static readonly DependencyProperty CornersBrushProperty =
DependencyProperty.Register("CornersBrush", typeof(Brush), typeof(ElementEditor), new PropertyMetadata(null));
///
/// Gets or sets the editor inner content.
///
[ParameterIgnore]
public FrameworkElement InnerContent
{
get { return (FrameworkElement)GetValue(InnerContentProperty); }
set { SetValue(InnerContentProperty, value); }
}
public static readonly DependencyProperty InnerContentProperty =
DependencyProperty.Register("InnerContent", typeof(FrameworkElement), typeof(FrameworkElementEditor), new PropertyMetadata(null));
///
/// Gets an bind-able observable collection of the component properties.
///
[ParameterIgnore]
public ReadOnlyObservableCollection Parameters
{
get { return (ReadOnlyObservableCollection)GetValue(ParametersProperty); }
set { SetValue(ParametersProperty, value); }
}
public static readonly DependencyProperty ParametersProperty =
DependencyProperty.Register("Parameters", typeof(ReadOnlyObservableCollection), typeof(ElementEditor), new PropertyMetadata(null));
///
/// Gets or sets an optional attached element for editors mirroring mode.
///
[ParameterIgnore]
public IElementEditor AttachedEditor
{
get { return (IElementEditor)GetValue(AttachedEditorProperty); }
set { SetValue(AttachedEditorProperty, value); }
}
public static readonly DependencyProperty AttachedEditorProperty =
DependencyProperty.Register("AttachedEditor", typeof(IElementEditor), typeof(ElementEditor), new PropertyMetadata(null,(d,e) => (d as ElementEditor).OnAttachedEditorChanged()));
///
/// Gets the hosted element.
///
[ParameterIgnore]
public abstract Object HostedElement { get; }
protected int _zIndex;
///
/// Gets or sets the editor Z-index.
///
public virtual int ZIndex
{
get
{
return Canvas.GetZIndex(this as UIElement);
}
set
{
_zIndex = value;
Canvas.SetZIndex(this as UIElement, value);
RaisePropertyChanged(nameof(ZIndex));
}
}
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
public ElementEditor()
{
ID = Guid.NewGuid().ToString();
Parameters = new ReadOnlyObservableCollection(GetEditorParameters());
}
#endregion
#region Public Methods
///
/// Gets the collection of for the current editor and element properties.
///
/// The animation duration.
/// The animation setup mode.
///
///
/// The returned collection of animation setups can be inserted into a .
///
public virtual List GetAnimationSetups(TimeSpan duration, AnimationSetupMode animationSetupMode = AnimationSetupMode.Linear)
{
List setups = new List();
setups.Add(GetPropertyAnimationSetup(TopProperty, duration, animationSetupMode));
setups.Add(GetPropertyAnimationSetup(LeftProperty, duration, animationSetupMode));
setups.Add(GetPropertyAnimationSetup(WidthProperty, duration, animationSetupMode));
setups.Add(GetPropertyAnimationSetup(HeightProperty, duration, animationSetupMode));
setups.Add(GetPropertyAnimationSetup(AngleProperty, duration, animationSetupMode));
return setups;
}
///
/// Sets the editor bounds.
///
/// The bounds.
public virtual void SetBounds(Rect bounds)
{
Top = bounds.Top;
Left = bounds.Left;
Width = bounds.Width;
Height = bounds.Height;
}
///
/// Gets the editor bounds.
///
///
public virtual Rect GetBounds()
{
return new Rect(Left, Top, Width, Height);
}
#endregion
#region Protected Methods
///
/// Determines whether the control key is down.
///
protected bool IsCtrlDown()
{
return Keyboard.IsKeyDown(Key.LeftCtrl);
}
#endregion
#region Virtual Methods
///
/// Invoked when the attached editor has changed.
///
protected virtual void OnAttachedEditorChanged()
{
}
///
/// Gets the editor parameters.
///
///
protected virtual ObservableCollection GetEditorParameters()
{
var parameters = this.CreateParametersCollection(ParameterItemMode.Binding);
parameters.Add(this.CreateParameterItem("Width", new ParameterItemAttribute(null, null), parameters.Max(x => x.Index + 1), ParameterItemMode.Binding));
parameters.Add(this.CreateParameterItem("Height", new ParameterItemAttribute(null, null), parameters.Max(x => x.Index + 1), ParameterItemMode.Binding));
var angle = parameters.Single(x => x.Name == "Angle");
parameters.Remove(angle);
parameters.Add(angle);
return parameters;
}
///
/// Gets the property animation setup.
///
/// The dependency property.
/// The duration.
/// The animation setup mode.
///
protected virtual AnimationSetup GetPropertyAnimationSetup(DependencyProperty dependencyProperty, TimeSpan duration, AnimationSetupMode animationSetupMode)
{
return GetPropertyAnimationSetup(this, dependencyProperty, duration, animationSetupMode);
}
///
/// Gets the property animation setup.
///
/// The dependency object.
/// The dependency property.
/// The duration.
/// The animation setup mode.
///
protected virtual AnimationSetup GetPropertyAnimationSetup(DependencyObject dependencyObject, DependencyProperty dependencyProperty, TimeSpan duration, AnimationSetupMode animationSetupMode)
{
if (animationSetupMode == AnimationSetupMode.Linear && (dependencyProperty.PropertyType == typeof(double) || dependencyProperty.PropertyType == typeof(Point)))
{
if (dependencyProperty.PropertyType == typeof(double))
{
DoubleAnimation ani = new DoubleAnimation((double)dependencyObject.GetValue(dependencyProperty), duration);
return new AnimationSetup(ani, dependencyObject, dependencyProperty);
}
else
{
PointAnimation ani = new PointAnimation((Point)dependencyObject.GetValue(dependencyProperty), duration);
return new AnimationSetup(ani, dependencyObject, dependencyProperty);
}
}
else
{
ObjectAnimationUsingKeyFrames ani = new ObjectAnimationUsingKeyFrames();
ani.KeyFrames.Add(new DiscreteObjectKeyFrame(dependencyObject.GetValue(dependencyProperty), TimeSpan.FromSeconds(0)));
return new AnimationSetup(ani, dependencyObject, dependencyProperty);
}
}
///
/// Called when the position has changed.
///
protected virtual void OnPositionChanged()
{
Canvas.SetTop(this, Top);
Canvas.SetLeft(this, Left);
}
///
/// Drags the editor.
///
/// The sender.
/// The instance containing the event data.
protected virtual void MoveDrag(object sender, DragDeltaEventArgs e)
{
BeginAnimation(LeftProperty, null);
BeginAnimation(TopProperty, null);
Point dragDelta = this.RenderTransform.Transform(new Point(e.HorizontalChange, e.VerticalChange));
Left += dragDelta.X;
Top += dragDelta.Y;
if (!_preventMovingEvent)
{
if (Moving != null) Moving(this, e);
}
}
///
/// Called when editor dragging has started.
///
/// The sender.
/// The instance containing the event data.
protected virtual void DragStarted(object sender, DragStartedEventArgs e)
{
_initialAngle = Angle;
_centerPoint = this.TranslatePoint(new Point(Width * RenderTransformOrigin.X, Height * RenderTransformOrigin.Y), this.Parent as FrameworkElement);
_startPoint = Mouse.GetPosition(this.Parent as FrameworkElement);
_startVector = Point.Subtract(_startPoint, _centerPoint);
_anglePI = Angle * Math.PI / 180.0;
OnBeforeBoundsChange();
}
///
/// Called when the editor dragging has completed.
///
/// The sender.
/// The instance containing the event data.
protected virtual void OnDragEnded(object sender, DragCompletedEventArgs e)
{
OnAfterBoundsChange();
}
///
/// Drags the editor left side.
///
/// The sender.
/// The instance containing the event data.
protected virtual void DragLeft(object sender, DragDeltaEventArgs e)
{
BeginAnimation(LeftProperty, null);
BeginAnimation(WidthProperty, null);
BeginAnimation(TopProperty, null);
var deltaHorizontal = Math.Min(e.HorizontalChange, ActualWidth - MinWidth);
if (Width - deltaHorizontal > 0)
{
Top = Top + deltaHorizontal * Math.Sin(_anglePI) - RenderTransformOrigin.X * deltaHorizontal * Math.Sin(_anglePI);
Left = Left + deltaHorizontal * Math.Cos(_anglePI) + (RenderTransformOrigin.X * deltaHorizontal * (1 - Math.Cos(_anglePI)));
Width -= deltaHorizontal;
}
}
///
/// Drags the editor right side.
///
/// The sender.
/// The instance containing the event data.
protected virtual void DragRight(object sender, DragDeltaEventArgs e)
{
BeginAnimation(LeftProperty, null);
BeginAnimation(WidthProperty, null);
BeginAnimation(TopProperty, null);
var deltaHorizontal = Math.Min(-e.HorizontalChange, ActualWidth - MinWidth);
if (Width - deltaHorizontal > 0)
{
Top = Top - this.RenderTransformOrigin.X * deltaHorizontal * Math.Sin(_anglePI);
Left = Left + (deltaHorizontal * this.RenderTransformOrigin.X * (1 - Math.Cos(_anglePI)));
Width -= deltaHorizontal;
}
}
///
/// Drags the editor top.
///
/// The sender.
/// The instance containing the event data.
protected virtual void DragTop(object sender, DragDeltaEventArgs e)
{
BeginAnimation(TopProperty, null);
BeginAnimation(LeftProperty, null);
BeginAnimation(HeightProperty, null);
var deltaVertical = Math.Min(e.VerticalChange, ActualHeight - MinHeight);
if (Height - deltaVertical > 0)
{
Top = Top + deltaVertical * Math.Cos(-_anglePI) + (RenderTransformOrigin.Y * deltaVertical * (1 - Math.Cos(-_anglePI)));
Left = Left + deltaVertical * Math.Sin(-_anglePI) - (RenderTransformOrigin.Y * deltaVertical * Math.Sin(-_anglePI));
Height -= deltaVertical;
}
}
///
/// Drags the editor bottom.
///
/// The sender.
/// The instance containing the event data.
protected virtual void DragBottom(object sender, DragDeltaEventArgs e)
{
BeginAnimation(TopProperty, null);
BeginAnimation(LeftProperty, null);
BeginAnimation(HeightProperty, null);
var deltaVertical = Math.Min(-e.VerticalChange, ActualHeight - MinHeight);
if (Height - deltaVertical > 0)
{
Top = Top + (RenderTransformOrigin.Y * deltaVertical * (1 - Math.Cos(-_anglePI)));
Left = Left - deltaVertical * RenderTransformOrigin.Y * Math.Sin(-_anglePI);
Height -= deltaVertical;
}
}
///
/// Drags the top left corner.
///
/// The sender.
/// The instance containing the event data.
protected virtual void DragTopLeft(object sender, DragDeltaEventArgs e)
{
//if (!IsCtrlDown())
//{
DragLeft(sender, e);
DragTop(sender, e);
//}
//else
//{
// DragAngle(sender, e);
//}
}
///
/// Drags the top right corner.
///
/// The sender.
/// The instance containing the event data.
protected virtual void DragTopRight(object sender, DragDeltaEventArgs e)
{
//if (!IsCtrlDown())
//{
DragRight(sender, e);
DragTop(sender, e);
//}
//else
//{
// DragAngle(sender, e);
//}
}
///
/// Drags the bottom right corner.
///
/// The sender.
/// The instance containing the event data.
protected virtual void DragBottomRight(object sender, DragDeltaEventArgs e)
{
//if (!IsCtrlDown())
//{
DragRight(sender, e);
DragBottom(sender, e);
//}
//else
//{
// DragAngle(sender, e);
//}
}
///
/// Drags the bottom left corner.
///
/// The sender.
/// The instance containing the event data.
protected virtual void DragBottomLeft(object sender, DragDeltaEventArgs e)
{
//if (!IsCtrlDown())
//{
DragLeft(sender, e);
DragBottom(sender, e);
//}
//else
//{
// DragAngle(sender, e);
//}
}
///
/// Drags the editor angle.
///
/// The sender.
/// The instance containing the event data.
protected virtual void DragAngle(object sender, DragDeltaEventArgs e)
{
Point currentPoint = Mouse.GetPosition(this.Parent as FrameworkElement);
Vector deltaVector = Point.Subtract(currentPoint, _centerPoint);
double angle = Vector.AngleBetween(_startVector, deltaVector);
BeginAnimation(AngleProperty, null);
Angle = _initialAngle + angle;
}
///
/// Raises the event.
///
protected virtual void OnBeforeBoundsChange()
{
if (BeforeBoundsChange != null) BeforeBoundsChange(this, new EventArgs());
}
///
/// Raises the event.
///
protected virtual void OnAfterBoundsChange()
{
if (AfterBoundsChange != null) AfterBoundsChange(this, new EventArgs());
}
///
/// Called before the top property changes.
///
/// The value.
///
protected virtual object OnCoerceTop(object value)
{
return value;
}
///
/// Called before the left property changes.
///
/// The value.
///
protected virtual object OnCoerceLeft(object value)
{
return value;
}
///
/// Called when the editor detached property has changed.
///
protected virtual void OnElementDetachedChanged()
{
//Do Nothing ?
}
#endregion
#region Override Methods
///
/// Invoked when an unhandled attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
///
/// The that contains the event data. This event data reports details about the mouse button that was pressed and the handled state.
protected override void OnMouseDown(MouseButtonEventArgs e)
{
if (!Keyboard.IsKeyDown(Key.LeftShift))
{
e.Handled = true;
}
}
#endregion
#region Public Methods
///
/// Moves the editor by the specified delta arguments.
///
/// The instance containing the event data.
public void PushMove(DragDeltaEventArgs e)
{
_preventMovingEvent = true;
MoveDrag(this, e);
_preventMovingEvent = false;
}
///
/// Clones this instance.
///
///
public abstract IElementEditor Clone();
#endregion
#region IConfigurable Members
///
/// Gets a configuration object representing the configurable properties. This configuration can be serialized to a stream or file, and later be loaded and applied to the configurable object.
///
///
public virtual IConfiguration GetConfiguration()
{
ElementEditorConfiguration config = new ElementEditorConfiguration();
config.ConfigurableType = this.GetType();
config.ID = ID;
config.ElementDetached = ElementDetached;
config.BackgroundBrushString = Background != null ? Background.ConvertToString() : null;
config.Left = Left;
config.Top = Top;
config.Width = Width;
config.Height = Height;
config.Angle = Angle;
return config;
}
///
/// Applies the specified configuration with an optional animation if supported by the configurable type.
///
/// The configuration.
/// The animation.
public virtual void SetConfiguration(IConfiguration configuration, ConfigurationAnimation animation)
{
if (!(configuration is ElementEditorConfiguration))
{
throw new InvalidConfigurationException();
}
var config = configuration as ElementEditorConfiguration;
ID = config.ID;
ElementDetached = config.ElementDetached;
try
{
if (config.BackgroundBrushString != null)
{
Background = config.BackgroundBrushString.ToBrush();
}
}
catch
{ }
this.StartDoubleAnimation(LeftProperty, animation, config.Left);
this.StartDoubleAnimation(TopProperty, animation, config.Top);
this.StartDoubleAnimation(WidthProperty, animation, config.Width);
this.StartDoubleAnimation(HeightProperty, animation, config.Height);
this.StartDoubleAnimation(AngleProperty, animation, config.Angle);
}
#endregion
}
}