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