aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio
diff options
context:
space:
mode:
authorRoy Ben-Shabat <Roy@Twine-s.com>2018-02-08 11:52:30 +0200
committerRoy Ben-Shabat <Roy@Twine-s.com>2018-02-08 11:52:30 +0200
commit0907f58b81e29d9c0f415e2c7660ad60787f281b (patch)
tree4440c23e3805b5094dd939ff17c85656afc3298b /Software/Visual_Studio
parent781e923508aafa6d39c3d38f1b1d4664dedbb83d (diff)
downloadTango-0907f58b81e29d9c0f415e2c7660ad60787f281b.tar.gz
Tango-0907f58b81e29d9c0f415e2c7660ad60787f281b.zip
Embedded My Diagram Editing Components !
Diffstat (limited to 'Software/Visual_Studio')
-rw-r--r--Software/Visual_Studio/Tango.Editors/AnimationSetup.cs61
-rw-r--r--Software/Visual_Studio/Tango.Editors/AnimationSetupMode.cs24
-rw-r--r--Software/Visual_Studio/Tango.Editors/AttachedElementsEditorsUndoRedoState.cs40
-rw-r--r--Software/Visual_Studio/Tango.Editors/AttachedElementsEditorsUndoRedoStatesProvider.cs155
-rw-r--r--Software/Visual_Studio/Tango.Editors/BrushExtensions.cs42
-rw-r--r--Software/Visual_Studio/Tango.Editors/CanvasItemEditor.xaml228
-rw-r--r--Software/Visual_Studio/Tango.Editors/CanvasItemEditor.xaml.cs240
-rw-r--r--Software/Visual_Studio/Tango.Editors/ConfigurationAnimation.cs71
-rw-r--r--Software/Visual_Studio/Tango.Editors/Converters/BoolToVisibilityConverter.cs24
-rw-r--r--Software/Visual_Studio/Tango.Editors/Converters/BrushToColorConverter.cs38
-rw-r--r--Software/Visual_Studio/Tango.Editors/Converters/DetachedConverter.cs29
-rw-r--r--Software/Visual_Studio/Tango.Editors/Converters/DoubleToIntConverter.cs23
-rw-r--r--Software/Visual_Studio/Tango.Editors/Converters/EnumToBoolConverter.cs23
-rw-r--r--Software/Visual_Studio/Tango.Editors/Converters/HorizontalOffsetToMarginConverter.cs31
-rw-r--r--Software/Visual_Studio/Tango.Editors/Converters/InverseBoolToVisibilityConverter.cs24
-rw-r--r--Software/Visual_Studio/Tango.Editors/Converters/IsEnumConverter.cs24
-rw-r--r--Software/Visual_Studio/Tango.Editors/Converters/IsNullConverter.cs23
-rw-r--r--Software/Visual_Studio/Tango.Editors/Converters/StringFormatConverter.cs42
-rw-r--r--Software/Visual_Studio/Tango.Editors/Converters/TransformPointToPointConverter.cs26
-rw-r--r--Software/Visual_Studio/Tango.Editors/CustomScrollViewer.cs49
-rw-r--r--Software/Visual_Studio/Tango.Editors/DependencyObjectExtensions.cs138
-rw-r--r--Software/Visual_Studio/Tango.Editors/ElementCreationEventArgs.cs46
-rw-r--r--Software/Visual_Studio/Tango.Editors/ElementEditor.cs686
-rw-r--r--Software/Visual_Studio/Tango.Editors/ElementEditorConfiguration.cs81
-rw-r--r--Software/Visual_Studio/Tango.Editors/ElementsEditor.xaml221
-rw-r--r--Software/Visual_Studio/Tango.Editors/ElementsEditor.xaml.cs1476
-rw-r--r--Software/Visual_Studio/Tango.Editors/ElementsEditorConfiguration.cs50
-rw-r--r--Software/Visual_Studio/Tango.Editors/ElementsEditorMode.cs23
-rw-r--r--Software/Visual_Studio/Tango.Editors/ElementsEditorUndoRedoExecutedEventArgs.cs41
-rw-r--r--Software/Visual_Studio/Tango.Editors/ElementsEditorUndoRedoState.cs34
-rw-r--r--Software/Visual_Studio/Tango.Editors/ElementsEditorUndoRedoStatesProvider.cs133
-rw-r--r--Software/Visual_Studio/Tango.Editors/ElementsEventArgs.cs42
-rw-r--r--Software/Visual_Studio/Tango.Editors/EnumExtensions.cs33
-rw-r--r--Software/Visual_Studio/Tango.Editors/FrameworkElementCloner.cs75
-rw-r--r--Software/Visual_Studio/Tango.Editors/FrameworkElementEditor.xaml71
-rw-r--r--Software/Visual_Studio/Tango.Editors/FrameworkElementEditor.xaml.cs221
-rw-r--r--Software/Visual_Studio/Tango.Editors/FrameworkElementEditorConfiguration.cs39
-rw-r--r--Software/Visual_Studio/Tango.Editors/FrameworkElementExtensions.cs66
-rw-r--r--Software/Visual_Studio/Tango.Editors/HybridControl.cs67
-rw-r--r--Software/Visual_Studio/Tango.Editors/IConfigurable.cs27
-rw-r--r--Software/Visual_Studio/Tango.Editors/IConfigurableExtensions.cs92
-rw-r--r--Software/Visual_Studio/Tango.Editors/IConfiguration.cs20
-rw-r--r--Software/Visual_Studio/Tango.Editors/IConfigurationExtensions.cs114
-rw-r--r--Software/Visual_Studio/Tango.Editors/IElementEditor.cs110
-rw-r--r--Software/Visual_Studio/Tango.Editors/IParameterized.cs52
-rw-r--r--Software/Visual_Studio/Tango.Editors/IParameterizedExtensions.cs124
-rw-r--r--Software/Visual_Studio/Tango.Editors/ISupportEditingOperations.cs49
-rw-r--r--Software/Visual_Studio/Tango.Editors/ISupportUndoRedoOperations.cs44
-rw-r--r--Software/Visual_Studio/Tango.Editors/IUndoRedoState.cs19
-rw-r--r--Software/Visual_Studio/Tango.Editors/IUndoRedoStatesProvider.cs76
-rw-r--r--Software/Visual_Studio/Tango.Editors/InvalidConfigurationException.cs32
-rw-r--r--Software/Visual_Studio/Tango.Editors/ObjectExtensions.cs121
-rw-r--r--Software/Visual_Studio/Tango.Editors/ParameterIgnoreAttribute.cs17
-rw-r--r--Software/Visual_Studio/Tango.Editors/ParameterItem.cs152
-rw-r--r--Software/Visual_Studio/Tango.Editors/ParameterItemAttribute.cs140
-rw-r--r--Software/Visual_Studio/Tango.Editors/ParameterItemMode.cs23
-rw-r--r--Software/Visual_Studio/Tango.Editors/PixelRuler.cs488
-rw-r--r--Software/Visual_Studio/Tango.Editors/Properties/AssemblyInfo.cs19
-rw-r--r--Software/Visual_Studio/Tango.Editors/Properties/Resources.Designer.cs63
-rw-r--r--Software/Visual_Studio/Tango.Editors/Properties/Resources.resx117
-rw-r--r--Software/Visual_Studio/Tango.Editors/Properties/Settings.Designer.cs26
-rw-r--r--Software/Visual_Studio/Tango.Editors/Properties/Settings.settings7
-rw-r--r--Software/Visual_Studio/Tango.Editors/RelayCommand.cs128
-rw-r--r--Software/Visual_Studio/Tango.Editors/StringExtensions.cs107
-rw-r--r--Software/Visual_Studio/Tango.Editors/Tango.Editors.csproj182
-rw-r--r--Software/Visual_Studio/Tango.Editors/UIElementExtension.cs302
-rw-r--r--Software/Visual_Studio/Tango.Editors/UIHelper.cs407
-rw-r--r--Software/Visual_Studio/Tango.Editors/UndoRedoStateExecutedEventArgs.cs40
-rw-r--r--Software/Visual_Studio/Tango.Editors/UndoRedoStatesProviderBase.cs154
-rw-r--r--Software/Visual_Studio/Tango.Editors/app.config39
-rw-r--r--Software/Visual_Studio/Tango.Editors/packages.config4
-rw-r--r--Software/Visual_Studio/Tango.sln32
-rw-r--r--Software/Visual_Studio/Utilities/Tango.UITests/MainWindow.xaml5
-rw-r--r--Software/Visual_Studio/Utilities/Tango.UITests/Tango.UITests.csproj4
74 files changed, 8093 insertions, 3 deletions
diff --git a/Software/Visual_Studio/Tango.Editors/AnimationSetup.cs b/Software/Visual_Studio/Tango.Editors/AnimationSetup.cs
new file mode 100644
index 000000000..6313cc206
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/AnimationSetup.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media.Animation;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents an animation definition including the actual animation, the dependency object and dependency property.
+ /// </summary>
+ public class AnimationSetup
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AnimationSetup"/> class.
+ /// </summary>
+ public AnimationSetup()
+ {
+
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AnimationSetup"/> class.
+ /// </summary>
+ /// <param name="animation">The animation.</param>
+ /// <param name="dependencyObject">The dependency object.</param>
+ /// <param name="dependencyProperty">The dependency property.</param>
+ public AnimationSetup(AnimationTimeline animation, DependencyObject dependencyObject, DependencyProperty dependencyProperty)
+ {
+ Animation = animation;
+ DependencyObject = dependencyObject;
+ DependencyProperty = dependencyProperty;
+ }
+
+ /// <summary>
+ /// Gets or sets the animation.
+ /// </summary>
+ public AnimationTimeline Animation { get; set; }
+
+ /// <summary>
+ /// Gets or sets the dependency object.
+ /// </summary>
+ public DependencyObject DependencyObject { get; set; }
+
+ /// <summary>
+ /// Gets or sets the dependency property.
+ /// </summary>
+ public DependencyProperty DependencyProperty { get; set; }
+
+ /// <summary>
+ /// Clones animation setup.
+ /// </summary>
+ /// <returns></returns>
+ public AnimationSetup Clone()
+ {
+ return new AnimationSetup(Animation.Clone(), DependencyObject, DependencyProperty);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/AnimationSetupMode.cs b/Software/Visual_Studio/Tango.Editors/AnimationSetupMode.cs
new file mode 100644
index 000000000..758a010a5
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/AnimationSetupMode.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents <see cref="T:Tango.Editors.AnimationSetup"/> modes enumeration to be used with <see cref="ElementEditor.GetAnimationSetups"/>.
+ /// </summary>
+ [Serializable]
+ public enum AnimationSetupMode
+ {
+ /// <summary>
+ /// Directs the method to return a linear animations.
+ /// </summary>
+ Linear,
+ /// <summary>
+ /// Directs the method to return a discrete animations (no duration).
+ /// </summary>
+ Discrete
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/AttachedElementsEditorsUndoRedoState.cs b/Software/Visual_Studio/Tango.Editors/AttachedElementsEditorsUndoRedoState.cs
new file mode 100644
index 000000000..be65f2994
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/AttachedElementsEditorsUndoRedoState.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Tango.Editors;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents an attached <see cref="ElementsEditor"/> undo/redo state.
+ /// </summary>
+ /// <seealso cref="Tango.Editors.IUndoRedoState" />
+ public class AttachedElementsEditorsUndoRedoState : IUndoRedoState
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttachedElementsEditorsUndoRedoState"/> class.
+ /// </summary>
+ public AttachedElementsEditorsUndoRedoState()
+ {
+ ElementsSetups1 = new List<KeyValuePair<IElementEditor, List<AnimationSetup>>>();
+ ElementsSetups2 = new List<KeyValuePair<IElementEditor, List<AnimationSetup>>>();
+ }
+
+ /// <summary>
+ /// Gets or sets the animation setups which will determine the <see cref="IElementEditor"/> configuration for the first elements editor.
+ /// </summary>
+ public List<KeyValuePair<IElementEditor, List<AnimationSetup>>> ElementsSetups1 { get; set; }
+
+ /// <summary>
+ /// Gets or sets the animation setups which will determine the <see cref="IElementEditor"/> configuration for the second elements editor.
+ /// </summary>
+ public List<KeyValuePair<IElementEditor, List<AnimationSetup>>> ElementsSetups2 { get; set; }
+
+ /// <summary>
+ /// Gets or sets the undo/redo creation date time.
+ /// </summary>
+ public DateTime Date { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/AttachedElementsEditorsUndoRedoStatesProvider.cs b/Software/Visual_Studio/Tango.Editors/AttachedElementsEditorsUndoRedoStatesProvider.cs
new file mode 100644
index 000000000..2ed5260e0
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/AttachedElementsEditorsUndoRedoStatesProvider.cs
@@ -0,0 +1,155 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Media.Animation;
+using Tango.Editors;
+
+namespace Tango.Editors
+{
+ public class AttachedElementsEditorsUndoRedoStatesProvider : UndoRedoStatesProviderBase
+ {
+ private ElementsEditorUndoRedoExecutedEventArgs args;
+
+ /// <summary>
+ /// Gets or sets the first elements editor.
+ /// </summary>
+ public ElementsEditor ElementsEditor1 { get; private set; }
+
+ /// <summary>
+ /// Gets or sets the second elements editor.
+ /// </summary>
+ public ElementsEditor ElementsEditor2 { get; private set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AttachedElementsEditorsUndoRedoStatesProvider"/> class.
+ /// </summary>
+ /// <param name="firstElementsEditor">The first elements editor.</param>
+ /// <param name="secondElementsEditor">The second elements editor.</param>
+ public AttachedElementsEditorsUndoRedoStatesProvider(ElementsEditor firstElementsEditor,ElementsEditor secondElementsEditor)
+ {
+ ElementsEditor1 = firstElementsEditor;
+ ElementsEditor2 = secondElementsEditor;
+ }
+
+ /// <summary>
+ /// Creates the a new undo/redo state.
+ /// </summary>
+ /// <returns></returns>
+ public override IUndoRedoState CreateUndoRedoState()
+ {
+ AttachedElementsEditorsUndoRedoState state = new AttachedElementsEditorsUndoRedoState();
+
+ foreach (var element in ElementsEditor1.Elements)
+ {
+ var setups = element.GetAnimationSetups(TimeSpan.FromSeconds(0), AnimationSetupMode.Discrete);
+ state.Date = DateTime.Now;
+ state.ElementsSetups1.Add(new KeyValuePair<IElementEditor, List<AnimationSetup>>(element, setups));
+ }
+
+ foreach (var element in ElementsEditor2.Elements)
+ {
+ var setups = element.GetAnimationSetups(TimeSpan.FromSeconds(0), AnimationSetupMode.Discrete);
+ state.Date = DateTime.Now;
+ state.ElementsSetups2.Add(new KeyValuePair<IElementEditor, List<AnimationSetup>>(element, setups));
+ }
+
+ return state;
+ }
+
+ /// <summary>
+ /// Executes the an undo/redo state.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ public override void ExecuteState(IUndoRedoState state)
+ {
+ ExecuteState(state, ElementsEditor1, (state as AttachedElementsEditorsUndoRedoState).ElementsSetups1);
+ ExecuteState(state, ElementsEditor2, (state as AttachedElementsEditorsUndoRedoState).ElementsSetups2);
+ }
+
+ /// <summary>
+ /// Executes the state.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <param name="ElementsEditor">The elements editor.</param>
+ /// <param name="elementsSetups">The elements setups.</param>
+ private void ExecuteState(IUndoRedoState state,ElementsEditor ElementsEditor, List<KeyValuePair<IElementEditor, List<AnimationSetup>>> elementsSetups)
+ {
+ AttachedElementsEditorsUndoRedoState elementsEditorState = state as AttachedElementsEditorsUndoRedoState;
+
+ args = new ElementsEditorUndoRedoExecutedEventArgs();
+ args.State = elementsEditorState;
+
+ foreach (var elementSetups in elementsSetups)
+ {
+ var element = elementSetups.Key;
+ var setups = elementSetups.Value;
+
+ if (!ElementsEditor.Elements.Contains(element)) //Add elements missing on editor.
+ {
+ ElementsEditor.Elements.Add(element);
+ args.RestoredElements.Add(element);
+ }
+
+ Storyboard story = new Storyboard();
+
+ foreach (var setup in setups)
+ {
+ story.Children.Add(setup.Animation);
+ String name = UIHelper.GetRandomAnimationName();
+ ElementsEditor.RegisterName(name, setup.DependencyObject);
+ Storyboard.SetTargetName(setup.Animation, name);
+ Storyboard.SetTargetProperty(setup.Animation, new System.Windows.PropertyPath(setup.DependencyProperty));
+ }
+
+ story.Completed += (x, y) =>
+ {
+ List<KeyValuePair<AnimationSetup, object>> setupValues = new List<KeyValuePair<AnimationSetup, object>>();
+
+ foreach (var setup in setups)
+ {
+ KeyValuePair<AnimationSetup, object> setupvalue = new KeyValuePair<AnimationSetup, object>(setup, setup.DependencyObject.GetValue(setup.DependencyProperty));
+ setupValues.Add(setupvalue);
+ }
+
+ foreach (var setupValue in setupValues)
+ {
+ if (setupValue.Key.DependencyObject is IAnimatable)
+ {
+ (setupValue.Key.DependencyObject as IAnimatable).BeginAnimation(setupValue.Key.DependencyProperty, null);
+ }
+ setupValue.Key.DependencyObject.SetValue(setupValue.Key.DependencyProperty, setupValue.Value);
+ }
+
+ };
+
+ story.Duration = TimeSpan.FromMilliseconds(10);
+ story.Begin(ElementsEditor);
+ }
+
+ //Remove elements that does not exist on state.
+ var elementsToRemove = ElementsEditor.Elements.Where(x => !elementsSetups.Select(y => y.Key).ToList().Contains(x)).ToList();
+ elementsToRemove.ForEach(x =>
+ {
+ ElementsEditor.RemoveElement(x);
+ });
+
+ args.RemovedElements = elementsToRemove;
+ args.ModifiedElements = elementsSetups.
+ Select(x => x.Key).
+ ToList().
+ Where(x => !args.RestoredElements.Contains(x) && !args.RemovedElements.Contains(x)).ToList();
+ }
+
+ /// <summary>
+ /// Raises the <see cref="E:StateExecuted" /> event.
+ /// </summary>
+ /// <param name="e">The <see cref="T:Tango.Editors.UndoRedoStateExecutedEventArgs" /> instance containing the event data.</param>
+ public override void OnStateExecuted(UndoRedoStateExecutedEventArgs e)
+ {
+ args.Mode = e.Mode;
+ base.OnStateExecuted(args);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/BrushExtensions.cs b/Software/Visual_Studio/Tango.Editors/BrushExtensions.cs
new file mode 100644
index 000000000..88ef997a6
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/BrushExtensions.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Markup;
+using System.Windows.Media;
+using System.Xml;
+
+
+internal static class BrushExtensions
+{
+ internal static String ConvertToString(this Brush brush)
+ {
+ XmlWriterSettings settings = new XmlWriterSettings();
+ // You might want to wrap these in #if DEBUG's
+ settings.Indent = true;
+ settings.NewLineOnAttributes = true;
+ // this gets rid of the XML version
+ settings.ConformanceLevel = ConformanceLevel.Fragment;
+ // buffer to a stringbuilder
+ StringBuilder sb = new StringBuilder();
+ XmlWriter writer = XmlWriter.Create(sb, settings);
+ // Need moar documentation on the manager, plox MSDN
+ XamlDesignerSerializationManager manager = new XamlDesignerSerializationManager(writer);
+ manager.XamlWriterMode = XamlWriterMode.Expression;
+ // its extremely rare for this to throw an exception
+ XamlWriter.Save(brush, manager);
+
+ return sb.ToString();
+ }
+
+ internal static Brush ToBrush(this String str)
+ {
+ XmlDocument doc = new XmlDocument();
+ // may throw XmlException
+ doc.LoadXml(str);
+ // may throw XamlParseException
+ return XamlReader.Load(new XmlNodeReader(doc)) as Brush;
+ }
+}
+
diff --git a/Software/Visual_Studio/Tango.Editors/CanvasItemEditor.xaml b/Software/Visual_Studio/Tango.Editors/CanvasItemEditor.xaml
new file mode 100644
index 000000000..06fe70c73
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/CanvasItemEditor.xaml
@@ -0,0 +1,228 @@
+<local:ElementEditor x:Class="Tango.Editors.CanvasItemEditor"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:gif="http://wpfanimatedgif.codeplex.com"
+ xmlns:converters="clr-namespace:Tango.Editors.Converters"
+ xmlns:sharedConverters="clr-namespace:Tango.Shared.Converters;assembly=Tango.Shared"
+ xmlns:fa="http://schemas.fontawesome.io/icons/"
+ xmlns:local="clr-namespace:Tango.Editors"
+ xmlns:html="clr-namespace:TheArtOfDev.HtmlRenderer.WPF;assembly=HtmlRenderer.WPF"
+ mc:Ignorable="d"
+ d:DesignHeight="300" d:DesignWidth="300" Background="Transparent" ClipToBounds="False" BorderThickness="0" MinWidth="10" MinHeight="10" RenderTransformOrigin="0.5,0.5" Visibility="{Binding RelativeSource={RelativeSource Self},Path=CanvasItem.Visible,Converter={StaticResource BoolToVisibilityConverter}}" IsHitTestVisible="{Binding RelativeSource={RelativeSource Self},Path=CanvasItem.Visible}">
+
+ <UserControl.Resources>
+ <converters:InverseBoolToVisibilityConverter x:Key="InverseBoolToVisibilityConverter"></converters:InverseBoolToVisibilityConverter>
+ <converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"></converters:BoolToVisibilityConverter>
+ <converters:DetachedConverter x:Key="DetachedConverter"></converters:DetachedConverter>
+ <sharedConverters:OverlayToImageConverter x:Key="OverlayToImageConverter"></sharedConverters:OverlayToImageConverter>
+ <sharedConverters:CompositeColorToColorConverter x:Key="CompositeColorToColorConverter"></sharedConverters:CompositeColorToColorConverter>
+
+ <!--Theme-->
+ <SolidColorBrush x:Key="BorderBrush" Color="Transparent"></SolidColorBrush>
+ <SolidColorBrush x:Key="CornersBrush" Color="Red"></SolidColorBrush>
+ </UserControl.Resources>
+
+ <UserControl.RenderTransform>
+ <TransformGroup>
+ <RotateTransform Angle="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=Angle}"></RotateTransform>
+ </TransformGroup>
+ </UserControl.RenderTransform>
+
+ <Grid>
+ <Grid Visibility="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem.EnableHtml,Converter={StaticResource InverseBoolToVisibilityConverter}}">
+ <Grid x:Name="normalGrid">
+ <Grid.Style>
+ <Style TargetType="Grid">
+ <Setter Property="Margin" Value="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem.OverlayPadding}"></Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem.ItemOverlay}" Value="None">
+ <Setter Property="Margin" Value="0"></Setter>
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </Grid.Style>
+ <Image Stretch="Fill" gif:ImageBehavior.AnimatedSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem.CompositeImage.Image}" RenderOptions.BitmapScalingMode="Fant"></Image>
+ <fa:ImageAwesome Foreground="DimGray" Stretch="Fill" Icon="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem.DefaultIcon}">
+ <fa:ImageAwesome.Style>
+ <Style TargetType="fa:ImageAwesome">
+ <Setter Property="Visibility" Value="Hidden"></Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem.CompositeImage.Image}" Value="{x:Null}">
+ <Setter Property="Visibility" Value="Visible"></Setter>
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </fa:ImageAwesome.Style>
+ </fa:ImageAwesome>
+ </Grid>
+
+ <Grid>
+ <Grid.Style>
+ <Style TargetType="Grid">
+ <Setter Property="Margin" Value="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem.OverlayPadding}"></Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem.ItemOverlay}" Value="None">
+ <Setter Property="Margin" Value="0"></Setter>
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </Grid.Style>
+ <ContentControl x:Name="customContent" IsHitTestVisible="False"></ContentControl>
+ </Grid>
+
+ <Image RenderOptions.BitmapScalingMode="Fant" Source="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem.ItemOverlay,Converter={StaticResource OverlayToImageConverter}}" Stretch="Fill" IsHitTestVisible="False"></Image>
+
+ <!--Labels-->
+
+ <!--Bottom-->
+ <Label Margin="-10 0 -10 -105" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Height="100" Padding="0" VerticalContentAlignment="Top" HorizontalContentAlignment="Center" IsHitTestVisible="False" DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem}" FontSize="{Binding LabelFontSize}" FontFamily="{Binding LabelFontFamily}">
+ <Label.Foreground>
+ <SolidColorBrush Color="{Binding LabelColor,Converter={StaticResource CompositeColorToColorConverter}}"></SolidColorBrush>
+ </Label.Foreground>
+ <Label.Style>
+ <Style TargetType="Label">
+ <Setter Property="Visibility" Value="Hidden"></Setter>
+ <Setter Property="Effect" Value="{x:Null}"></Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding LabelPosition}" Value="Bottom">
+ <Setter Property="Visibility" Value="Visible"></Setter>
+ </DataTrigger>
+ <DataTrigger Binding="{Binding LabelDropShadow}" Value="True">
+ <Setter Property="Effect">
+ <Setter.Value>
+ <DropShadowEffect BlurRadius="10"></DropShadowEffect>
+ </Setter.Value>
+ </Setter>
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </Label.Style>
+ <TextBlock TextAlignment="Center" Text="{Binding Label}" TextWrapping="Wrap"></TextBlock>
+ </Label>
+
+ <!--Top-->
+ <Label Margin="-10 -105 -10 0" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="100" Padding="0" VerticalContentAlignment="Bottom" HorizontalContentAlignment="Center" IsHitTestVisible="False" DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem}" FontSize="{Binding LabelFontSize}" FontFamily="{Binding LabelFontFamily}">
+ <Label.Foreground>
+ <SolidColorBrush Color="{Binding LabelColor,Converter={StaticResource CompositeColorToColorConverter}}"></SolidColorBrush>
+ </Label.Foreground>
+ <Label.Style>
+ <Style TargetType="Label">
+ <Setter Property="Visibility" Value="Hidden"></Setter>
+ <Setter Property="Effect" Value="{x:Null}"></Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding LabelPosition}" Value="Top">
+ <Setter Property="Visibility" Value="Visible"></Setter>
+ </DataTrigger>
+ <DataTrigger Binding="{Binding LabelDropShadow}" Value="True">
+ <Setter Property="Effect">
+ <Setter.Value>
+ <DropShadowEffect BlurRadius="10"></DropShadowEffect>
+ </Setter.Value>
+ </Setter>
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </Label.Style>
+ <TextBlock TextAlignment="Center" Text="{Binding Label}" TextWrapping="Wrap"></TextBlock>
+ </Label>
+
+ <!--Right-->
+ <Label Margin="0 0 -305 0" VerticalAlignment="Center" HorizontalAlignment="Right" Width="300" Height="100" VerticalContentAlignment="Center" HorizontalContentAlignment="Left" Padding="0" IsHitTestVisible="False" DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem}" FontSize="{Binding LabelFontSize}" FontFamily="{Binding LabelFontFamily}">
+ <Label.Foreground>
+ <SolidColorBrush Color="{Binding LabelColor,Converter={StaticResource CompositeColorToColorConverter}}"></SolidColorBrush>
+ </Label.Foreground>
+ <Label.Style>
+ <Style TargetType="Label">
+ <Setter Property="Visibility" Value="Hidden"></Setter>
+ <Setter Property="Effect" Value="{x:Null}"></Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding LabelPosition}" Value="Right">
+ <Setter Property="Visibility" Value="Visible"></Setter>
+ </DataTrigger>
+ <DataTrigger Binding="{Binding LabelDropShadow}" Value="True">
+ <Setter Property="Effect">
+ <Setter.Value>
+ <DropShadowEffect BlurRadius="10"></DropShadowEffect>
+ </Setter.Value>
+ </Setter>
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </Label.Style>
+ <TextBlock TextAlignment="Left" Text="{Binding Label}" TextWrapping="Wrap"></TextBlock>
+ </Label>
+
+ <!--Left-->
+ <Label Margin="-305 0 0 0" VerticalAlignment="Center" HorizontalAlignment="Left" Width="300" Height="100" VerticalContentAlignment="Center" HorizontalContentAlignment="Right" Padding="0" IsHitTestVisible="False" DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem}" FontSize="{Binding LabelFontSize}" FontFamily="{Binding LabelFontFamily}">
+ <Label.Foreground>
+ <SolidColorBrush Color="{Binding LabelColor,Converter={StaticResource CompositeColorToColorConverter}}"></SolidColorBrush>
+ </Label.Foreground>
+ <Label.Style>
+ <Style TargetType="Label">
+ <Setter Property="Visibility" Value="Hidden"></Setter>
+ <Setter Property="Effect" Value="{x:Null}"></Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding LabelPosition}" Value="Left">
+ <Setter Property="Visibility" Value="Visible"></Setter>
+ </DataTrigger>
+ <DataTrigger Binding="{Binding LabelDropShadow}" Value="True">
+ <Setter Property="Effect">
+ <Setter.Value>
+ <DropShadowEffect BlurRadius="10"></DropShadowEffect>
+ </Setter.Value>
+ </Setter>
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </Label.Style>
+ <TextBlock TextAlignment="Right" Text="{Binding Label}" TextWrapping="Wrap"></TextBlock>
+ </Label>
+ </Grid>
+
+ <Grid Visibility="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem.EnableHtml,Converter={StaticResource BoolToVisibilityConverter}}">
+ <Viewbox HorizontalAlignment="Left" VerticalAlignment="Top" Stretch="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem.HtmlScalingMode}" Width="{Binding RelativeSource={RelativeSource AncestorType=Grid},Path=ActualWidth}" Height="{Binding RelativeSource={RelativeSource AncestorType=Grid},Path=ActualHeight}">
+ <html:HtmlPanel Background="Transparent" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Disabled" x:Name="html" Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=CanvasItem.HtmlContent}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Left" VerticalContentAlignment="Top"></html:HtmlPanel>
+ </Viewbox>
+ </Grid>
+
+ <Border BorderThickness="1" BorderBrush="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=BorderBrush,TargetNullValue={StaticResource BorderBrush},FallbackValue={StaticResource BorderBrush}}" Visibility="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=(local:ElementsEditor.IsSelected),Converter={StaticResource BoolToVisibilityConverter}}">
+ <Grid>
+ <ContentPresenter Content="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=InnerContent}"></ContentPresenter>
+
+ <Thumb Opacity="0" DragDelta="MoveDrag" DragStarted="DragStarted" DragCompleted="OnDragEnded"></Thumb>
+ <Thumb HorizontalAlignment="Left" Cursor="SizeWE" Opacity="0" DragDelta="DragLeft" DragStarted="DragStarted" DragCompleted="OnDragEnded"></Thumb>
+ <Thumb HorizontalAlignment="Right" Cursor="SizeWE" Opacity="0" DragDelta="DragRight" DragStarted="DragStarted" DragCompleted="OnDragEnded"></Thumb>
+ <Thumb VerticalAlignment="Top" Cursor="SizeNS" Opacity="0" DragDelta="DragTop" DragStarted="DragStarted" DragCompleted="OnDragEnded"></Thumb>
+ <Thumb VerticalAlignment="Bottom" Cursor="SizeNS" Opacity="0" DragDelta="DragBottom" DragStarted="DragStarted" DragCompleted="OnDragEnded"></Thumb>
+
+ <Grid ClipToBounds="False" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0 -20 0 0" Width="10" Height="10">
+ <Ellipse Stroke="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=CornersBrush,TargetNullValue={StaticResource CornersBrush},FallbackValue={StaticResource CornersBrush}}" StrokeThickness="2"></Ellipse>
+ <Rectangle HorizontalAlignment="Center" VerticalAlignment="Stretch" Margin="0 10 0 -8" StrokeThickness="1" Stroke="Red"></Rectangle>
+ <Thumb Opacity="0" DragDelta="DragAngle" DragStarted="DragStarted" Cursor="Arrow" DragCompleted="OnDragEnded"></Thumb>
+ </Grid>
+
+ <Grid Width="10" Height="10" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="-8 -8 0 0">
+ <Border BorderBrush="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=CornersBrush,TargetNullValue={StaticResource CornersBrush},FallbackValue={StaticResource CornersBrush}}" BorderThickness="2 2 0 0"></Border>
+ <Thumb Opacity="0" DragDelta="DragTopLeft" DragStarted="DragStarted" Cursor="SizeNWSE" DragCompleted="OnDragEnded"></Thumb>
+ </Grid>
+
+ <Grid Width="10" Height="10" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0 -8 -8 0">
+ <Border BorderBrush="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=CornersBrush,TargetNullValue={StaticResource CornersBrush},FallbackValue={StaticResource CornersBrush}}" BorderThickness="0 2 2 0"></Border>
+ <Thumb Opacity="0" DragDelta="DragTopRight" DragStarted="DragStarted" Cursor="SizeNESW" DragCompleted="OnDragEnded"></Thumb>
+ </Grid>
+
+ <Grid Width="10" Height="10" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0 0 -8 -8">
+ <Border BorderBrush="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=CornersBrush,TargetNullValue={StaticResource CornersBrush},FallbackValue={StaticResource CornersBrush}}" BorderThickness="0 0 2 2"></Border>
+ <Thumb Opacity="0" DragDelta="DragBottomRight" DragStarted="DragStarted" Cursor="SizeNWSE" DragCompleted="OnDragEnded"></Thumb>
+ </Grid>
+
+ <Grid Width="10" Height="10" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="-8 0 0 -8">
+ <Border BorderBrush="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=CornersBrush,TargetNullValue={StaticResource CornersBrush},FallbackValue={StaticResource CornersBrush}}" BorderThickness="2 0 0 2"></Border>
+ <Thumb Opacity="0" DragDelta="DragBottomLeft" DragStarted="DragStarted" Cursor="SizeNESW" DragCompleted="OnDragEnded"></Thumb>
+ </Grid>
+ </Grid>
+ </Border>
+ </Grid>
+</local:ElementEditor>
diff --git a/Software/Visual_Studio/Tango.Editors/CanvasItemEditor.xaml.cs b/Software/Visual_Studio/Tango.Editors/CanvasItemEditor.xaml.cs
new file mode 100644
index 000000000..628f541bd
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/CanvasItemEditor.xaml.cs
@@ -0,0 +1,240 @@
+using System;
+using System.Collections.Generic;
+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.Documents;
+using System.Windows.Input;
+using System.Windows.Markup;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using Tango.Editors;
+using Tango.Shared;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// <para><img class="classImage" src="../Media/CanvasItemEditor.png" /></para>
+ /// Represents a <see cref="FrameworkElement"/> editor with position, size and angle control.
+ /// </summary>
+ /// <example>
+ /// <para class="example-title">
+ /// <img class="exampleIcon" src="../Icons/CodeExample.png" />
+ /// <i>
+ /// The following example demonstrates a basic scenario of using <see cref="N:WpfVideoTools.Tiles"/> and <see cref="N:Tango.Editors"/> by creating a simple video projection application with the following features:
+ /// </i>
+ /// <list type="bullet">
+ /// <item>Use the mouse to draw any type of tile of the editor surface.</item>
+ /// <item>Apply any input shape on any tile.</item>
+ /// <item>Load any video and display it on any of the selected tile.</item>
+ /// <item>Built in support for undo, redo, cut, copy and paste, delete and select operations.</item>
+ /// <item>Built in support for saving/loading the editor state to memory or file.</item>
+ /// <item>Built in support for tile and editor properties adjustment using the <see cref="ParameterizedEditor"/>.</item>
+ /// </list>
+ /// </para>
+ /// <para><markup><video class="exampleVideo" autoplay="autoplay" loop="loop" controls="controls" src="../Media/EditorsExample.mp4"></video></markup></para>
+ /// <code lang="XAML" source="../FullAPIExamples/Examples/Editors/EditorsExample.xaml" title="Elements editor example." />
+ /// <i>Code-Behind.</i>
+ /// <code lang="C#" source="../FullAPIExamples/Examples/Editors/EditorsExample.xaml.cs" title="Elements editor example." />
+ /// </example>
+ /// <seealso cref="Tango.Editors.ElementEditor" />
+ [ContentProperty("InnerContent")]
+ public partial class CanvasItemEditor : ElementEditor
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CanvasItemEditor"/> class.
+ /// </summary>
+ public CanvasItemEditor()
+ : base()
+ {
+ InitializeComponent();
+ AdaptToCanasItemType();
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CanvasItemEditor"/> class.
+ /// </summary>
+ /// <param name="canvasItem">The canvas item.</param>
+ public CanvasItemEditor(CanvasItem canvasItem)
+ : this()
+ {
+ CanvasItem = canvasItem;
+
+ Left = canvasItem.Left;
+ Top = canvasItem.Top;
+ Width = canvasItem.Width;
+ Height = canvasItem.Height;
+ Angle = canvasItem.Angle;
+ AdaptToCanasItemType();
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CanvasItemEditor"/> class.
+ /// </summary>
+ /// <param name="canvasItem">The canvas item.</param>
+ /// <param name="bounds">The bounds.</param>
+ public CanvasItemEditor(CanvasItem canvasItem, Rect bounds)
+ : this(canvasItem)
+ {
+ Left = bounds.Left;
+ Top = bounds.Top;
+ Width = bounds.Width;
+ Height = bounds.Height;
+ AdaptToCanasItemType();
+ }
+
+ private void AdaptToCanasItemType()
+ {
+ if (CanvasItem != null && CanvasItem.CustomContent != null)
+ {
+ normalGrid.Visibility = Visibility.Hidden;
+ customContent.Content = CanvasItem.CustomContent;
+ ZIndex = CanvasItem.ZIndex;
+ }
+ }
+
+ public CanvasItem CanvasItem
+ {
+ get { return (CanvasItem)GetValue(CanvasItemProperty); }
+ set { SetValue(CanvasItemProperty, value); }
+ }
+ public static readonly DependencyProperty CanvasItemProperty =
+ DependencyProperty.Register("CanvasItem", typeof(CanvasItem), typeof(CanvasItemEditor), new PropertyMetadata(null, (d, e) => (d as CanvasItemEditor).AdaptToCanasItemType()));
+
+ /// <summary>
+ /// Clones this instance.
+ /// </summary>
+ /// <returns></returns>
+ public override IElementEditor Clone()
+ {
+ try
+ {
+ CanvasItemEditor cloned = new CanvasItemEditor();
+
+ cloned.CanvasItem = CanvasItem.Clone();
+ cloned.ElementDetached = ElementDetached;
+ cloned.Top = Top;
+ cloned.Left = Left;
+ cloned.Width = Width;
+ cloned.Height = Height;
+ cloned.Angle = Angle;
+ cloned.Background = Background;
+ cloned.Foreground = Foreground;
+ cloned.CornersBrush = CornersBrush;
+ return cloned;
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException("Could not clone this editor. You may have to create a custom editor and implement a custom Clone method.", ex);
+ }
+ }
+
+ /// <summary>
+ /// Gets the hosted element.
+ /// </summary>
+ [ParameterIgnore]
+ public override Object HostedElement
+ {
+ get { return CanvasItem; }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <returns></returns>
+ public override IConfiguration GetConfiguration()
+ {
+ //CanvasItemEditorConfiguration config = new CanvasItemEditorConfiguration(base.GetConfiguration() as ElementEditorConfiguration);
+
+ //try
+ //{
+ // config.ElementXaml = FrameworkElementCloner.Serialize(Element);
+ //}
+ //catch
+ //{
+ // Debug.WriteLine("Could not serialize framework element with ID " + ID);
+ //}
+
+ return null;
+ }
+
+ /// <summary>
+ /// Invoked when the attached editor has changed.
+ /// </summary>
+ protected override void OnAttachedEditorChanged()
+ {
+ base.OnAttachedEditorChanged();
+ }
+
+ /// <summary>
+ /// Applies the specified configuration with an optional animation if supported by the configurable type.
+ /// </summary>
+ /// <param name="configuration">The configuration.</param>
+ /// <param name="animation">The animation.</param>
+ public override void SetConfiguration(IConfiguration configuration, ConfigurationAnimation animation)
+ {
+ //if (!(configuration is CanvasItemEditorConfiguration))
+ //{
+ // throw new InvalidConfigurationException();
+ //}
+
+ //var config = configuration as CanvasItemEditorConfiguration;
+
+ //try
+ //{
+ // Element = FrameworkElementCloner.Deserialize(config.ElementXaml);
+ //}
+ //catch
+ //{
+ // Debug.WriteLine("Could not deserialize framework element with ID " + ID);
+ //}
+
+ base.SetConfiguration(configuration, animation);
+ }
+
+ /// <summary>
+ /// Called when the editor detached property has changed.
+ /// </summary>
+ protected override void OnElementDetachedChanged()
+ {
+ base.OnElementDetachedChanged();
+ }
+
+ protected override void OnAfterBoundsChange()
+ {
+ base.OnAfterBoundsChange();
+
+ CanvasItem.Angle = Angle;
+ CanvasItem.Width = Width;
+ CanvasItem.Height = Height;
+ CanvasItem.Top = Top;
+ CanvasItem.Left = Left;
+ }
+
+ public override int ZIndex
+ {
+ get
+ {
+ return Canvas.GetZIndex(this as UIElement);
+ }
+ set
+ {
+ _zIndex = value;
+ Canvas.SetZIndex(this as UIElement, value);
+
+ if (CanvasItem != null)
+ {
+ CanvasItem.ZIndex = value;
+ }
+ RaisePropertyChanged(nameof(ZIndex));
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ConfigurationAnimation.cs b/Software/Visual_Studio/Tango.Editors/ConfigurationAnimation.cs
new file mode 100644
index 000000000..fc6d75d6a
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ConfigurationAnimation.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media.Animation;
+using Tango.Editors;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents an animation configuration which can be applied to an <see cref="IConfigurable"/> object when invoking the <see cref="IConfigurable.SetConfiguration"/> method.
+ /// </summary>
+ /// <seealso cref="IConfiguration" />
+ /// <seealso cref="IConfigurable" />
+ public class ConfigurationAnimation
+ {
+ /// <summary>
+ /// Occurs when the animation is completed.
+ /// </summary>
+ public event EventHandler<Object> AnimationCompleted;
+
+ /// <summary>
+ /// Gets or sets the animation duration.
+ /// </summary>
+ public TimeSpan Duration { get; set; }
+
+ /// <summary>
+ /// Gets or sets the acceleration ratio.
+ /// </summary>
+ public double AccelerationRatio { get; set; }
+
+ /// <summary>
+ /// Gets or sets the deceleration ratio.
+ /// </summary>
+ public double DecelerationRatio { get; set; }
+
+ /// <summary>
+ /// Gets or sets the easing function.
+ /// </summary>
+ public IEasingFunction EasingFunction { get; set; }
+
+ /// <summary>
+ /// Raises the animation completed event.
+ /// </summary>
+ /// <param name="obj">The object.</param>
+ public void RaiseAnimationCompleted(Object obj)
+ {
+ if (AnimationCompleted != null) AnimationCompleted(this, obj);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ConfigurationAnimation"/> class.
+ /// </summary>
+ public ConfigurationAnimation()
+ {
+
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ConfigurationAnimation"/> class.
+ /// </summary>
+ /// <param name="duration">The animation duration.</param>
+ public ConfigurationAnimation(TimeSpan duration) : this()
+ {
+ Duration = duration;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Converters/BoolToVisibilityConverter.cs b/Software/Visual_Studio/Tango.Editors/Converters/BoolToVisibilityConverter.cs
new file mode 100644
index 000000000..d89177461
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Converters/BoolToVisibilityConverter.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Tango.Editors.Converters
+{
+ public class BoolToVisibilityConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return ((bool)value) ? Visibility.Visible : Visibility.Hidden;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Converters/BrushToColorConverter.cs b/Software/Visual_Studio/Tango.Editors/Converters/BrushToColorConverter.cs
new file mode 100644
index 000000000..1febc0364
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Converters/BrushToColorConverter.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace Tango.Editors.Converters
+{
+ internal class BrushToColorConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value != null && value is SolidColorBrush)
+ {
+ return (value as SolidColorBrush).Color;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value != null)
+ {
+ return new SolidColorBrush((Color)value);
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Converters/DetachedConverter.cs b/Software/Visual_Studio/Tango.Editors/Converters/DetachedConverter.cs
new file mode 100644
index 000000000..54bd889c4
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Converters/DetachedConverter.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace Tango.Editors.Converters
+{
+ internal class DetachedConverter : IMultiValueConverter
+ {
+ public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ if (!(bool)values[1])
+ {
+ return values[0];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Converters/DoubleToIntConverter.cs b/Software/Visual_Studio/Tango.Editors/Converters/DoubleToIntConverter.cs
new file mode 100644
index 000000000..b9df2ba5e
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Converters/DoubleToIntConverter.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace Tango.Editors.Converters
+{
+ internal class DoubleToIntConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return System.Convert.ToDouble(value);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return System.Convert.ToInt32(value);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Converters/EnumToBoolConverter.cs b/Software/Visual_Studio/Tango.Editors/Converters/EnumToBoolConverter.cs
new file mode 100644
index 000000000..8bc07d161
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Converters/EnumToBoolConverter.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace Tango.Editors.Converters
+{
+ internal class EnumToBoolConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return (value.ToString() == parameter.ToString());
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return Enum.Parse(targetType, parameter.ToString());
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Converters/HorizontalOffsetToMarginConverter.cs b/Software/Visual_Studio/Tango.Editors/Converters/HorizontalOffsetToMarginConverter.cs
new file mode 100644
index 000000000..3a5e074a4
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Converters/HorizontalOffsetToMarginConverter.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Tango.Editors.Converters
+{
+ internal class HorizontalOffsetToMarginConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (parameter.ToString() == "left")
+ {
+ return new Thickness(((double)value) * -1, 0, 0, 0);
+ }
+ else
+ {
+ return new Thickness(0, ((double)value) * -1, 0, 0);
+ }
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Converters/InverseBoolToVisibilityConverter.cs b/Software/Visual_Studio/Tango.Editors/Converters/InverseBoolToVisibilityConverter.cs
new file mode 100644
index 000000000..dbd9ec231
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Converters/InverseBoolToVisibilityConverter.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Tango.Editors.Converters
+{
+ internal class InverseBoolToVisibilityConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return ((bool)value) ? Visibility.Hidden : Visibility.Visible;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Converters/IsEnumConverter.cs b/Software/Visual_Studio/Tango.Editors/Converters/IsEnumConverter.cs
new file mode 100644
index 000000000..558e91e4c
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Converters/IsEnumConverter.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace Tango.Editors.Converters
+{
+ internal class IsEnumConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ Type t = value as Type;
+ return t.IsEnum;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Converters/IsNullConverter.cs b/Software/Visual_Studio/Tango.Editors/Converters/IsNullConverter.cs
new file mode 100644
index 000000000..fc366e3bb
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Converters/IsNullConverter.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace Tango.Editors.Converters
+{
+ internal class IsNullConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value == null;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Converters/StringFormatConverter.cs b/Software/Visual_Studio/Tango.Editors/Converters/StringFormatConverter.cs
new file mode 100644
index 000000000..62342d3ce
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Converters/StringFormatConverter.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace Tango.Editors.Converters
+{
+ internal class StringFormatConverter : IMultiValueConverter
+ {
+ public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+ {
+ String stringValue = values[0].ToString();
+ String format = values[1] != null ? values[1].ToString() : null;
+ var resultString = Regex.Match(stringValue, @"^-?[0-9]\d*(\.\d+)?$").Value;
+
+ if (String.IsNullOrWhiteSpace(resultString)) return "";
+
+ if (resultString == "-") return resultString;
+
+ return System.Convert.ToDouble(resultString).ToString(format != null ? format : "");
+ }
+
+ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+ {
+ String resultString;
+
+ if (value.ToString() == "-" || value.ToString() == ".")
+ {
+ resultString = value.ToString() + 0;
+ }
+ else
+ {
+ resultString = Regex.Match(value.ToString(), @"^-?[0-9]\d*(\.\d+)?$").Value;
+ }
+ return new object[] { System.Convert.ToDouble(String.IsNullOrWhiteSpace(resultString) ? "0" : resultString) };
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Converters/TransformPointToPointConverter.cs b/Software/Visual_Studio/Tango.Editors/Converters/TransformPointToPointConverter.cs
new file mode 100644
index 000000000..dad8d71e4
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Converters/TransformPointToPointConverter.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+
+namespace Tango.Editors.Converters
+{
+ internal class TransformPointToPointConverter : IMultiValueConverter
+ {
+ public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+ {
+ double value = (double)values[0];
+ double max = (double)values[1];
+
+ return max - value;
+ }
+
+ public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/CustomScrollViewer.cs b/Software/Visual_Studio/Tango.Editors/CustomScrollViewer.cs
new file mode 100644
index 000000000..e70c374ed
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/CustomScrollViewer.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace Tango.Editors
+{
+ internal class CustomScrollViewer : ScrollViewer
+ {
+ public event EventHandler<MouseWheelEventArgs> MouseZooming;
+
+ protected override void OnMouseWheel(MouseWheelEventArgs e)
+ {
+ if (Keyboard.IsKeyDown(Key.LeftCtrl))
+ {
+ e.Handled = true;
+ OnMouseZooming(e);
+ }
+ else
+ {
+ base.OnMouseWheel(e);
+ }
+ }
+
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ if (e.KeyboardDevice.Modifiers == ModifierKeys.Control)
+ {
+ if (e.Key == Key.Left || e.Key == Key.Right)
+ e.Handled = true;
+ return;
+ }
+ base.OnKeyDown(e);
+ }
+
+ protected override void OnPreviewKeyDown(KeyEventArgs e)
+ {
+ e.Handled = true;
+ }
+
+ protected virtual void OnMouseZooming(MouseWheelEventArgs e)
+ {
+ if (MouseZooming != null) MouseZooming(this, e);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/DependencyObjectExtensions.cs b/Software/Visual_Studio/Tango.Editors/DependencyObjectExtensions.cs
new file mode 100644
index 000000000..9947f8e0c
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/DependencyObjectExtensions.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+
+/// <exclude/>
+/// <summary>
+/// Contains a collection of DependencyObject extension methods.
+/// </summary>
+public static class DependencyObjectExtensions
+{
+ /// <summary>
+ /// Invokes the current dependency object dispatcher.
+ /// </summary>
+ /// <param name="dependencyObject">The dependency object.</param>
+ /// <param name="action">The action.</param>
+ internal static void BeginInvoke(this DependencyObject dependencyObject, Action action)
+ {
+ dependencyObject.Dispatcher.BeginInvoke(action);
+ }
+
+ /// <summary>
+ /// Invokes the specified action.
+ /// </summary>
+ /// <param name="dependencyObject">The dependency object.</param>
+ /// <param name="action">The action.</param>
+ internal static void Invoke(this DependencyObject dependencyObject, Action action)
+ {
+ dependencyObject.Dispatcher.Invoke(action);
+ }
+
+ /// <summary>
+ /// Determines whether this object is currently in design time mode.
+ /// </summary>
+ /// <param name="obj">The object.</param>
+ /// <returns></returns>
+ internal static bool IsInDesignMode(this DependencyObject obj)
+ {
+ return (DesignerProperties.GetIsInDesignMode(obj));
+ }
+
+
+ /// <summary>
+ /// Binds the specified dependency property to a another object property.
+ /// </summary>
+ /// <param name="target">The target dependency object.</param>
+ /// <param name="targetDP">The target dependency property.</param>
+ /// <param name="source">The source object.</param>
+ /// <param name="sourceDP">The source dependency property.</param>
+ /// <param name="mode">Binding mode.</param>
+ /// <param name="converter">Binding converter.</param>
+ /// <returns></returns>
+ public static Binding Bind(this DependencyObject target, DependencyProperty targetDP, DependencyObject source, DependencyProperty sourceDP, BindingMode mode, IValueConverter converter = null)
+ {
+ Binding binding = new Binding();
+ binding.Mode = mode;
+ binding.Source = source;
+ binding.Path = new PropertyPath(sourceDP);
+ binding.Converter = converter;
+ BindingOperations.SetBinding(target, targetDP, binding);
+ return binding;
+ }
+
+ /// <summary>
+ /// Binds the specified dependency property to a another object property.
+ /// </summary>
+ /// <param name="target">The target dependency object.</param>
+ /// <param name="targetDP">The target dependency property.</param>
+ /// <param name="source">The source object.</param>
+ /// <param name="sourceDP">The source dependency property.</param>
+ /// <param name="mode">Binding mode.</param>
+ /// <returns></returns>
+ public static Binding Bind(this DependencyObject target, DependencyProperty targetDP, DependencyObject source, DependencyProperty sourceDP, BindingMode mode = BindingMode.Default)
+ {
+ Binding binding = new Binding();
+ binding.Mode = mode;
+ binding.Source = source;
+ binding.Path = new PropertyPath(sourceDP);
+ BindingOperations.SetBinding(target, targetDP, binding);
+ return binding;
+ }
+
+ /// <summary>
+ /// Create asynchronous binding between target and source.
+ /// </summary>
+ /// <param name="target">The target dependency object.</param>
+ /// <param name="targetDP">The target dependency property.</param>
+ /// <param name="source">The source object.</param>
+ /// <param name="sourceDP">The source dependency property.</param>
+ /// <param name="mode">Binding mode.</param>
+ /// <returns></returns>
+ public static Binding BindAsync(this DependencyObject target, DependencyProperty targetDP, DependencyObject source, DependencyProperty sourceDP, BindingMode mode = BindingMode.Default)
+ {
+ Binding binding = new Binding();
+ binding.Mode = mode;
+ binding.Source = source;
+ binding.Path = new PropertyPath(sourceDP);
+ binding.IsAsync = true;
+ binding.BindsDirectlyToSource = true;
+ BindingOperations.SetBinding(target, targetDP, binding);
+ return binding;
+ }
+
+ /// <summary>
+ /// Binds the specified dependency property to a another object property.
+ /// </summary>
+ /// <param name="target">The target dependency object.</param>
+ /// <param name="targetDP">The target dependency property.</param>
+ /// <param name="source">The source object.</param>
+ /// <param name="sourceDP">The source dependency property name.</param>
+ /// <param name="mode">Binding mode.</param>
+ /// <returns></returns>
+ public static Binding Bind(this DependencyObject target, DependencyProperty targetDP, DependencyObject source, String sourceDP, BindingMode mode = BindingMode.Default)
+ {
+ Binding binding = new Binding();
+ binding.Mode = mode;
+ binding.Source = source;
+ binding.Path = new PropertyPath(sourceDP);
+ BindingOperations.SetBinding(target, targetDP, binding);
+ return binding;
+ }
+
+ /// <summary>
+ /// Clears bindings from the specified dependency property.
+ /// </summary>
+ /// <param name="target">The target dependency object.</param>
+ /// <param name="dependencyProperty">The dependency property.</param>
+ public static void Unbind(this DependencyObject target, DependencyProperty dependencyProperty)
+ {
+ BindingOperations.ClearBinding(target, dependencyProperty);
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ElementCreationEventArgs.cs b/Software/Visual_Studio/Tango.Editors/ElementCreationEventArgs.cs
new file mode 100644
index 000000000..b831caa0c
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ElementCreationEventArgs.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents an <see cref="ElementsEditor"/> element creation event arguments
+ /// </summary>
+ /// <seealso cref="System.EventArgs" />
+ public class ElementCreationEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Gets or sets the element bounds.
+ /// </summary>
+ public Rect Bounds { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to append an undo state after this event has been handled.
+ /// </summary>
+ public bool AppendUndoState { get; set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ElementCreationEventArgs"/> class.
+ /// </summary>
+ public ElementCreationEventArgs()
+ {
+
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ElementCreationEventArgs"/> class.
+ /// </summary>
+ /// <param name="left">The left.</param>
+ /// <param name="top">The top.</param>
+ /// <param name="width">The width.</param>
+ /// <param name="height">The height.</param>
+ public ElementCreationEventArgs(double left, double top, double width, double height) : this()
+ {
+ Bounds = new Rect(left, top, width, height);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ElementEditor.cs b/Software/Visual_Studio/Tango.Editors/ElementEditor.cs
new file mode 100644
index 000000000..c839639b9
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ElementEditor.cs
@@ -0,0 +1,686 @@
+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.Editors;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents a base class for all element editors.
+ /// </summary>
+ /// <seealso cref="Tango.Editors.HybridControl" />
+ /// <seealso cref="Tango.Editors.IElementEditor" />
+ 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
+
+ /// <summary>
+ /// Occurs when the editor is moving.
+ /// </summary>
+ public event EventHandler<DragDeltaEventArgs> Moving;
+
+ /// <summary>
+ /// Occurs before editor bounds change.
+ /// </summary>
+ public event EventHandler BeforeBoundsChange;
+
+ /// <summary>
+ /// Occurs after editor bounds change.
+ /// </summary>
+ public event EventHandler AfterBoundsChange;
+
+ #endregion
+
+ #region Properties
+
+ /// <summary>
+ /// Gets or sets the editor unique identifier.
+ /// </summary>
+ [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));
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the hosted element will be presented inside or outside the editor.
+ /// </summary>
+ [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()));
+
+ /// <summary>
+ /// Gets or sets the editor top position.
+ /// </summary>
+ [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); }));
+
+ /// <summary>
+ /// Gets or sets the editor left position.
+ /// </summary>
+ [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); }));
+
+ /// <summary>
+ /// Gets or sets the editor angle.
+ /// </summary>
+ [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));
+
+ /// <summary>
+ /// Gets or sets the corners brush.
+ /// </summary>
+ [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));
+
+ /// <summary>
+ /// Gets or sets the editor inner content.
+ /// </summary>
+ [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));
+
+ /// <summary>
+ /// Gets an bind-able observable collection of the component properties.
+ /// </summary>
+ [ParameterIgnore]
+ public ReadOnlyObservableCollection<ParameterItem> Parameters
+ {
+ get { return (ReadOnlyObservableCollection<ParameterItem>)GetValue(ParametersProperty); }
+ set { SetValue(ParametersProperty, value); }
+ }
+ public static readonly DependencyProperty ParametersProperty =
+ DependencyProperty.Register("Parameters", typeof(ReadOnlyObservableCollection<ParameterItem>), typeof(ElementEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets an optional attached element for editors mirroring mode.
+ /// </summary>
+ [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()));
+
+ /// <summary>
+ /// Gets the hosted element.
+ /// </summary>
+ [ParameterIgnore]
+ public abstract Object HostedElement { get; }
+
+ protected int _zIndex;
+ /// <summary>
+ /// Gets or sets the editor Z-index.
+ /// </summary>
+ 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
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ElementEditor"/> class.
+ /// </summary>
+ public ElementEditor()
+ {
+ ID = Guid.NewGuid().ToString();
+ Parameters = new ReadOnlyObservableCollection<ParameterItem>(GetEditorParameters());
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ /// <summary>
+ /// Gets the collection of <see cref="T:Tango.Editors.AnimationSetup" /> for the current editor and element properties.
+ /// </summary>
+ /// <param name="duration">The animation duration.</param>
+ /// <param name="animationSetupMode">The animation setup mode.</param>
+ /// <returns></returns>
+ /// <remarks>
+ /// The returned collection of animation setups can be inserted into a <see cref="T:WpfVideoTools.Timeline.TimelineControl" />.
+ /// </remarks>
+ public virtual List<AnimationSetup> GetAnimationSetups(TimeSpan duration, AnimationSetupMode animationSetupMode = AnimationSetupMode.Linear)
+ {
+ List<AnimationSetup> setups = new List<AnimationSetup>();
+
+ 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;
+ }
+
+ /// <summary>
+ /// Sets the editor bounds.
+ /// </summary>
+ /// <param name="bounds">The bounds.</param>
+ public virtual void SetBounds(Rect bounds)
+ {
+ Top = bounds.Top;
+ Left = bounds.Left;
+ Width = bounds.Width;
+ Height = bounds.Height;
+ }
+
+ #endregion
+
+ #region Protected Methods
+
+ /// <summary>
+ /// Determines whether the control key is down.
+ /// </summary>
+ protected bool IsCtrlDown()
+ {
+ return Keyboard.IsKeyDown(Key.LeftCtrl);
+ }
+
+ #endregion
+
+ #region Virtual Methods
+
+ /// <summary>
+ /// Invoked when the attached editor has changed.
+ /// </summary>
+ protected virtual void OnAttachedEditorChanged()
+ {
+
+ }
+
+ /// <summary>
+ /// Gets the editor parameters.
+ /// </summary>
+ /// <returns></returns>
+ protected virtual ObservableCollection<ParameterItem> 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;
+ }
+
+ /// <summary>
+ /// Gets the property animation setup.
+ /// </summary>
+ /// <param name="dependencyProperty">The dependency property.</param>
+ /// <param name="duration">The duration.</param>
+ /// <param name="animationSetupMode">The animation setup mode.</param>
+ /// <returns></returns>
+ protected virtual AnimationSetup GetPropertyAnimationSetup(DependencyProperty dependencyProperty, TimeSpan duration, AnimationSetupMode animationSetupMode)
+ {
+ return GetPropertyAnimationSetup(this, dependencyProperty, duration, animationSetupMode);
+ }
+
+ /// <summary>
+ /// Gets the property animation setup.
+ /// </summary>
+ /// <param name="dependencyObject">The dependency object.</param>
+ /// <param name="dependencyProperty">The dependency property.</param>
+ /// <param name="duration">The duration.</param>
+ /// <param name="animationSetupMode">The animation setup mode.</param>
+ /// <returns></returns>
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// Called when the position has changed.
+ /// </summary>
+ protected virtual void OnPositionChanged()
+ {
+ Canvas.SetTop(this, Top);
+ Canvas.SetLeft(this, Left);
+ }
+
+ /// <summary>
+ /// Drags the editor.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// Called when editor dragging has started.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="DragStartedEventArgs"/> instance containing the event data.</param>
+ 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();
+ }
+
+ /// <summary>
+ /// Called when the editor dragging has completed.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="DragCompletedEventArgs"/> instance containing the event data.</param>
+ protected virtual void OnDragEnded(object sender, DragCompletedEventArgs e)
+ {
+ OnAfterBoundsChange();
+ }
+
+ /// <summary>
+ /// Drags the editor left side.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// Drags the editor right side.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// Drags the editor top.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// Drags the editor bottom.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// Drags the top left corner.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ protected virtual void DragTopLeft(object sender, DragDeltaEventArgs e)
+ {
+ if (!IsCtrlDown())
+ {
+ DragLeft(sender, e);
+ DragTop(sender, e);
+ }
+ else
+ {
+ DragAngle(sender, e);
+ }
+ }
+
+ /// <summary>
+ /// Drags the top right corner.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ protected virtual void DragTopRight(object sender, DragDeltaEventArgs e)
+ {
+ if (!IsCtrlDown())
+ {
+ DragRight(sender, e);
+ DragTop(sender, e);
+ }
+ else
+ {
+ DragAngle(sender, e);
+ }
+ }
+
+ /// <summary>
+ /// Drags the bottom right corner.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ protected virtual void DragBottomRight(object sender, DragDeltaEventArgs e)
+ {
+ if (!IsCtrlDown())
+ {
+ DragRight(sender, e);
+ DragBottom(sender, e);
+ }
+ else
+ {
+ DragAngle(sender, e);
+ }
+ }
+
+ /// <summary>
+ /// Drags the bottom left corner.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ protected virtual void DragBottomLeft(object sender, DragDeltaEventArgs e)
+ {
+ if (!IsCtrlDown())
+ {
+ DragLeft(sender, e);
+ DragBottom(sender, e);
+ }
+ else
+ {
+ DragAngle(sender, e);
+ }
+ }
+
+ /// <summary>
+ /// Drags the editor angle.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ 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;
+ }
+
+ /// <summary>
+ /// Raises the <see cref="BeforeBoundsChange"/> event.
+ /// </summary>
+ protected virtual void OnBeforeBoundsChange()
+ {
+ if (BeforeBoundsChange != null) BeforeBoundsChange(this, new EventArgs());
+ }
+
+ /// <summary>
+ /// Raises the <see cref="AfterBoundsChange"/> event.
+ /// </summary>
+ protected virtual void OnAfterBoundsChange()
+ {
+ if (AfterBoundsChange != null) AfterBoundsChange(this, new EventArgs());
+ }
+
+ /// <summary>
+ /// Called before the top property changes.
+ /// </summary>
+ /// <param name="value">The value.</param>
+ /// <returns></returns>
+ protected virtual object OnCoerceTop(object value)
+ {
+ return value;
+ }
+
+ /// <summary>
+ /// Called before the left property changes.
+ /// </summary>
+ /// <param name="value">The value.</param>
+ /// <returns></returns>
+ protected virtual object OnCoerceLeft(object value)
+ {
+ return value;
+ }
+
+ /// <summary>
+ /// Called when the editor detached property has changed.
+ /// </summary>
+ protected virtual void OnElementDetachedChanged()
+ {
+ //Do Nothing ?
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ /// <summary>
+ /// Invoked when an unhandled <see cref="E:System.Windows.Input.Mouse.MouseDown" /> attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
+ /// </summary>
+ /// <param name="e">The <see cref="T:System.Windows.Input.MouseButtonEventArgs" /> that contains the event data. This event data reports details about the mouse button that was pressed and the handled state.</param>
+ protected override void OnMouseDown(MouseButtonEventArgs e)
+ {
+ if (!Keyboard.IsKeyDown(Key.LeftShift))
+ {
+ e.Handled = true;
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ /// <summary>
+ /// Moves the editor by the specified delta arguments.
+ /// </summary>
+ /// <param name="e">The <see cref="DragDeltaEventArgs" /> instance containing the event data.</param>
+ public void PushMove(DragDeltaEventArgs e)
+ {
+ _preventMovingEvent = true;
+ MoveDrag(this, e);
+ _preventMovingEvent = false;
+ }
+
+ /// <summary>
+ /// Clones this instance.
+ /// </summary>
+ /// <returns></returns>
+ public abstract IElementEditor Clone();
+
+ #endregion
+
+ #region IConfigurable Members
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <returns></returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Applies the specified configuration with an optional animation if supported by the configurable type.
+ /// </summary>
+ /// <param name="configuration">The configuration.</param>
+ /// <param name="animation">The animation.</param>
+ 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
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ElementEditorConfiguration.cs b/Software/Visual_Studio/Tango.Editors/ElementEditorConfiguration.cs
new file mode 100644
index 000000000..2ef9f1ff5
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ElementEditorConfiguration.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Media;
+using Tango.Editors;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// <para><img class="classImage" src="../Media/Configuration.png" /></para>
+ /// Represents an <see cref="ElementEditor"/> configuration which can be saved or load from a file or stream.
+ /// </summary>
+ /// <seealso cref="IConfiguration" />
+ [Serializable]
+ public class ElementEditorConfiguration : IConfiguration
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ElementEditorConfiguration"/> class.
+ /// </summary>
+ public ElementEditorConfiguration()
+ {
+ ConfigurableType = typeof(ElementEditor);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ElementEditorConfiguration"/> class.
+ /// </summary>
+ /// <param name="baseConfiguration">The base configuration.</param>
+ internal ElementEditorConfiguration(ElementEditorConfiguration baseConfiguration)
+ {
+ baseConfiguration.MapTo(this);
+ }
+
+ /// <summary>
+ /// Gets or sets the unique editor identifier.
+ /// </summary>
+ public String ID { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the hosted element will be displayed within the editor.
+ /// </summary>
+ public bool ElementDetached { get; set; }
+
+ /// <summary>
+ /// Gets or sets a string representation of the editor's background.
+ /// </summary>
+ public String BackgroundBrushString { get; set; }
+
+ /// <summary>
+ /// Gets or sets the left position.
+ /// </summary>
+ public double Left { get; set; }
+
+ /// <summary>
+ /// Gets or sets the top position.
+ /// </summary>
+ public double Top { get; set; }
+
+ /// <summary>
+ /// Gets or sets the width.
+ /// </summary>
+ public double Width { get; set; }
+
+ /// <summary>
+ /// Gets or sets the height.
+ /// </summary>
+ public double Height { get; set; }
+
+ /// <summary>
+ /// Gets or sets the angle.
+ /// </summary>
+ public double Angle { get; set; }
+
+ /// <summary>
+ /// Gets or sets the configurable type.
+ /// </summary>
+ public Type ConfigurableType { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ElementsEditor.xaml b/Software/Visual_Studio/Tango.Editors/ElementsEditor.xaml
new file mode 100644
index 000000000..19db3992d
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ElementsEditor.xaml
@@ -0,0 +1,221 @@
+<core:HybridControl x:Class="Tango.Editors.ElementsEditor"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:local="clr-namespace:Tango.Editors"
+ xmlns:converters="clr-namespace:Tango.Editors.Converters"
+ xmlns:core="clr-namespace:Tango.Editors"
+ mc:Ignorable="d"
+ x:Name="editor" Foreground="White" Background="#151515" FontSize="8" FocusVisualStyle="{x:Null}">
+
+ <UserControl.Resources>
+
+ <!--Converters-->
+ <converters:HorizontalOffsetToMarginConverter x:Key="HorizontalOffsetToMarginConverter"></converters:HorizontalOffsetToMarginConverter>
+
+ <!--Styles and Templates-->
+ <Style TargetType="{x:Type ListBoxItem}" x:Key="basicListBoxItem">
+ <Setter Property="Background" Value="Transparent"/>
+ <Setter Property="IsSelected" Value="{Binding IsSelected,Mode=TwoWay}"></Setter>
+ <Setter Property="BorderThickness" Value="0"></Setter>
+ <Setter Property="FocusVisualStyle" Value="{x:Null}"></Setter>
+ <Setter Property="Padding" Value="0"/>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type ListBoxItem}">
+ <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0" Background="{TemplateBinding Background}" Padding="0" SnapsToDevicePixels="true">
+ <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
+ </Border>
+ <ControlTemplate.Triggers>
+ <Trigger Property="IsSelected" Value="true">
+ <Setter Property="Background" TargetName="Bd" Value="Transparent"/>
+ </Trigger>
+ <MultiTrigger>
+ <MultiTrigger.Conditions>
+ <Condition Property="IsSelected" Value="true"/>
+ <Condition Property="Selector.IsSelectionActive" Value="false"/>
+ </MultiTrigger.Conditions>
+ <Setter Property="Background" TargetName="Bd" Value="Transparent"/>
+ </MultiTrigger>
+ <Trigger Property="IsEnabled" Value="false">
+ <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
+ </Trigger>
+ </ControlTemplate.Triggers>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+ <Style TargetType="ListBox" x:Key="basicList">
+ <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
+ <Setter Property="Background" Value="Transparent"></Setter>
+ <Setter Property="BorderThickness" Value="0"></Setter>
+ <Setter Property="Margin" Value="0"></Setter>
+ <Setter Property="Padding" Value="0"></Setter>
+ <Setter Property="FocusVisualStyle" Value="{x:Null}"></Setter>
+ <Setter Property="ItemContainerStyle">
+ <Setter.Value>
+ <Style TargetType="ListBoxItem" BasedOn="{StaticResource basicListBoxItem}">
+ <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
+ <Setter Property="Margin" Value="0"></Setter>
+ <Setter Property="Padding" Value="0"></Setter>
+ <Setter Property="BorderThickness" Value="0"></Setter>
+ <Style.Triggers>
+ <Trigger Property="IsMouseOver" Value="True">
+ <Setter Property="Background" Value="Transparent"></Setter>
+ </Trigger>
+ </Style.Triggers>
+ </Style>
+ </Setter.Value>
+ </Setter>
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type ListBox}">
+ <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0" Background="{TemplateBinding Background}" Padding="0" SnapsToDevicePixels="True">
+ <ScrollViewer Focusable="False" Padding="0">
+ <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
+ </ScrollViewer>
+ </Border>
+ <ControlTemplate.Triggers>
+ <Trigger Property="IsEnabled" Value="False">
+ <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
+ </Trigger>
+ <Trigger Property="IsGrouping" Value="True">
+ <Setter Property="ScrollViewer.CanContentScroll" Value="False"/>
+ </Trigger>
+ </ControlTemplate.Triggers>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+
+ <!--Themes-->
+ <SolidColorBrush x:Key="RulerBackground" Color="#151515"></SolidColorBrush>
+ <SolidColorBrush x:Key="EditorBackground" Color="#202020"></SolidColorBrush>
+ <SolidColorBrush x:Key="SelectionFillBrush" Color="#80FFFFFF"></SolidColorBrush>
+ <SolidColorBrush x:Key="SelectionStrokeBrush" Color="Gainsboro"></SolidColorBrush>
+
+ </UserControl.Resources>
+
+ <Grid>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition>
+ <ColumnDefinition.Style>
+ <Style TargetType="ColumnDefinition">
+ <Setter Property="Width" Value="{Binding ElementName=editor,Path=RulerHeight,FallbackValue=30,TargetNullValue=30}"></Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding ElementName=editor,Path=EditorMode}" Value="AdvancedCanvas">
+ <Setter Property="Width" Value="0"></Setter>
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </ColumnDefinition.Style>
+ </ColumnDefinition>
+ <ColumnDefinition Width="1*"/>
+ </Grid.ColumnDefinitions>
+ <Grid.RowDefinitions>
+ <RowDefinition>
+ <RowDefinition.Style>
+ <Style TargetType="RowDefinition">
+ <Setter Property="Height" Value="{Binding ElementName=editor,Path=RulerHeight,FallbackValue=30,TargetNullValue=30}"></Setter>
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding ElementName=editor,Path=EditorMode}" Value="AdvancedCanvas">
+ <Setter Property="Height" Value="0"></Setter>
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </RowDefinition.Style>
+ </RowDefinition>
+ <RowDefinition Height="1*"/>
+ </Grid.RowDefinitions>
+
+ <Grid Background="{Binding ElementName=editor,Path=EditorBackground,FallbackValue={StaticResource EditorBackground},TargetNullValue={StaticResource EditorBackground}}">
+ <Border BorderBrush="{Binding ElementName=editor,Path=Foreground}" BorderThickness="0 0 1 1" Background="Transparent" PreviewMouseUp="OnResetScaleFactor" Cursor="Hand" ToolTip="Reset">
+ <Viewbox Margin="5">
+ <Path Fill="{Binding ElementName=editor,Path=Foreground}" Data="M533.333 0v216.667L450 133.333l-100 100l-50-50l100-100L316.667 0H533.333z M233.333 350l-100 100l83.333 83.333H0 V316.667L83.333 400l100-100L233.333 350z"></Path>
+ </Viewbox>
+ </Border>
+ </Grid>
+
+ <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="1" ClipToBounds="True" Background="{Binding ElementName=editor,Path=RulerBackground,FallbackValue={StaticResource RulerBackground},TargetNullValue={StaticResource RulerBackground}}" BorderThickness="0 0 0 1" BorderBrush="{Binding ElementName=editor,Path=Foreground}">
+ <local:PixelRuler HorizontalAlignment="Left" Width="{Binding ElementName=gridInnerScrollViewer,Path=ActualWidth}" MinWidth="{Binding ElementName=scrollViewer,Path=ViewportWidth}" AutoSize="True" Marks="Down" Foreground="{Binding ElementName=editor,Path=Foreground}"
+ Zoom="{Binding RelativeSource={RelativeSource AncestorType=local:ElementsEditor},Path=ScaleFactor}"
+ Margin="{Binding ElementName=scrollViewer,Path=HorizontalOffset,Converter={StaticResource HorizontalOffsetToMarginConverter},ConverterParameter='left'}">
+ </local:PixelRuler>
+ </Border>
+ <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1" ClipToBounds="True" Background="{Binding ElementName=editor,Path=RulerBackground,FallbackValue={StaticResource RulerBackground},TargetNullValue={StaticResource RulerBackground}}" BorderThickness="0 0 1 0" BorderBrush="{Binding ElementName=editor,Path=Foreground}">
+ <local:PixelRuler VerticalAlignment="Top" Width="{Binding ElementName=gridInnerScrollViewer,Path=ActualHeight}" MinWidth="{Binding ElementName=scrollViewer,Path=ViewportHeight}" AutoSize="True" Orientation="Horizontal" Foreground="{Binding ElementName=editor,Path=Foreground}"
+ Zoom="{Binding RelativeSource={RelativeSource AncestorType=local:ElementsEditor},Path=ScaleFactor}"
+ Margin="{Binding ElementName=scrollViewer,Path=VerticalOffset,Converter={StaticResource HorizontalOffsetToMarginConverter},ConverterParameter='top'}">
+ <local:PixelRuler.LayoutTransform>
+ <TransformGroup>
+ <RotateTransform Angle="90"></RotateTransform>
+ </TransformGroup>
+ </local:PixelRuler.LayoutTransform>
+ </local:PixelRuler>
+ </Border>
+
+ <local:CustomScrollViewer x:Name="scrollViewer" FocusVisualStyle="{x:Null}" MouseZooming="OnMouseWheelZooming" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
+ <local:CustomScrollViewer.Resources>
+ <Style TargetType="{x:Type local:CustomScrollViewer}">
+ <Setter Property="Template">
+ <Setter.Value>
+ <ControlTemplate TargetType="{x:Type ScrollViewer}">
+ <Grid>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition/>
+ <ColumnDefinition Width="Auto"/>
+ </Grid.ColumnDefinitions>
+ <Grid.RowDefinitions>
+ <RowDefinition/>
+ <RowDefinition Height="Auto"/>
+ </Grid.RowDefinitions>
+ <ScrollContentPresenter Grid.Column="0" />
+ <ScrollBar x:Name="PART_VerticalScrollBar" Grid.Row="0" Grid.Column="1" Value="{TemplateBinding VerticalOffset}" Maximum="{TemplateBinding ScrollableHeight}" ViewportSize="{TemplateBinding ViewportHeight}" Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
+ <ScrollBar x:Name="PART_HorizontalScrollBar" Orientation="Horizontal" Grid.Row="1" Grid.Column="0" Value="{TemplateBinding HorizontalOffset}" Maximum="{TemplateBinding ScrollableWidth}" ViewportSize="{TemplateBinding ViewportWidth}" Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
+ <!--<Rectangle Grid.Row="1" Grid.Column="1" Fill="Red"/>-->
+ </Grid>
+ </ControlTemplate>
+ </Setter.Value>
+ </Setter>
+ </Style>
+ </local:CustomScrollViewer.Resources>
+ <Grid x:Name="gridInnerScrollViewer">
+ <Grid x:Name="gridCanvas" HorizontalAlignment="Left" VerticalAlignment="Top" Background="{Binding ElementName=editor,Path=EditorBackground,FallbackValue={StaticResource EditorBackground},TargetNullValue={StaticResource EditorBackground}}">
+ <Grid.Style>
+ <Style TargetType="Grid">
+ <Setter Property="Width" Value="{Binding RelativeSource={RelativeSource AncestorType=local:ElementsEditor},Path=EditorWidth,FallbackValue=1280}"></Setter>
+ <Setter Property="Height" Value="{Binding RelativeSource={RelativeSource AncestorType=local:ElementsEditor},Path=EditorHeight,FallbackValue=720}"></Setter>
+
+ <Style.Triggers>
+ <DataTrigger Binding="{Binding ElementName=editor,Path=EditorMode}" Value="AdvancedCanvas">
+ <Setter Property="Width" Value="{Binding ElementName=gridInnerScrollViewer,Path=ActualWidth}"></Setter>
+ <Setter Property="Height" Value="{Binding ElementName=gridInnerScrollViewer,Path=ActualHeight}"></Setter>
+ </DataTrigger>
+ </Style.Triggers>
+ </Style>
+ </Grid.Style>
+
+ <Grid.LayoutTransform>
+ <ScaleTransform
+ ScaleX="{Binding RelativeSource={RelativeSource AncestorType=local:ElementsEditor},Path=ScaleFactor}"
+ ScaleY="{Binding RelativeSource={RelativeSource AncestorType=local:ElementsEditor},Path=ScaleFactor}">
+ </ScaleTransform>
+ </Grid.LayoutTransform>
+
+ <Image IsHitTestVisible="False" Stretch="Fill" Width="{Binding ElementName=canvas,Path=ActualWidth}" Height="{Binding ElementName=canvas,Path=ActualHeight}" HorizontalAlignment="Left" VerticalAlignment="Top" Source="{Binding RelativeSource={RelativeSource AncestorType=local:ElementsEditor},Path=PreviewImage,Mode=OneWay,IsAsync=True,BindsDirectlyToSource=True}" Opacity="{Binding RelativeSource={RelativeSource AncestorType=local:ElementsEditor},Path=PreviewVisualSourceOpacity}"></Image>
+
+ <Grid MouseDown="OnCanvasMouseDown" MouseUp="OnCanvasMouseUp" MouseMove="OnCanvasMouseMove" Background="Transparent">
+ <Canvas x:Name="canvas" ClipToBounds="True">
+
+ </Canvas>
+ </Grid>
+
+ <Canvas x:Name="selectionCanvas" IsHitTestVisible="False" ClipToBounds="False" Visibility="Hidden">
+ <Rectangle x:Name="selectionRec" Stroke="{Binding ElementName=editor,Path=SelectionStrokeBrush,FallbackValue={StaticResource SelectionStrokeBrush},TargetNullValue={StaticResource SelectionStrokeBrush}}" Fill="{Binding ElementName=editor,Path=SelectionFillBrush,FallbackValue={StaticResource SelectionFillBrush},TargetNullValue={StaticResource SelectionFillBrush}}" IsHitTestVisible="False"></Rectangle>
+ </Canvas>
+ </Grid>
+ </Grid>
+ </local:CustomScrollViewer>
+ </Grid>
+</core:HybridControl>
diff --git a/Software/Visual_Studio/Tango.Editors/ElementsEditor.xaml.cs b/Software/Visual_Studio/Tango.Editors/ElementsEditor.xaml.cs
new file mode 100644
index 000000000..a0e92ccf6
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ElementsEditor.xaml.cs
@@ -0,0 +1,1476 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Imaging;
+using Tango.Editors;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// <para><img class="classImage" src="../Media/ElementsEditor.png" /></para>
+ /// Represents a <see cref="IElementEditor"/> collection container and editor. The editor supports Undo, Redo, Cut, Copy and Paste operations.
+ /// The whole editor state can easily be saved and load using the <see cref="GetConfiguration()"/> and <see cref="SetConfiguration"/> methods.
+ /// </summary>
+ /// <example>
+ /// <para class="example-title">
+ /// <img class="exampleIcon" src="../Icons/CodeExample.png" />
+ /// <i>
+ /// The following example demonstrates a basic scenario of using <see cref="N:WpfVideoTools.Tiles"/> and <see cref="N:Tango.Editors"/> by creating a simple video projection application with the following features:
+ /// </i>
+ /// <list type="bullet">
+ /// <item>Use the mouse to draw any type of tile of the editor surface.</item>
+ /// <item>Apply any input shape on any tile.</item>
+ /// <item>Load any video and display it on any of the selected tile.</item>
+ /// <item>Built in support for undo, redo, cut, copy and paste, delete and select operations.</item>
+ /// <item>Built in support for saving/loading the editor state to memory or file.</item>
+ /// <item>Built in support for tile and editor properties adjustment using the <see cref="ParameterizedEditor"/>.</item>
+ /// </list>
+ /// </para>
+ /// <para><markup><video class="exampleVideo" autoplay="autoplay" loop="loop" controls="controls" src="../Media/EditorsExample.mp4"></video></markup></para>
+ /// <code lang="XAML" source="../FullAPIExamples/Examples/Editors/EditorsExample.xaml" title="Elements editor example." />
+ /// <i>Code-Behind.</i>
+ /// <code lang="C#" source="../FullAPIExamples/Examples/Editors/EditorsExample.xaml.cs" title="Elements editor example." />
+ /// </example>
+ /// <seealso cref="Tango.Editors.HybridControl" />
+ /// <seealso cref="Tango.Editors.IConfigurable" />
+ /// <seealso cref="Tango.Editors.ISupportEditingOperations" />
+ /// <seealso cref="Tango.Editors.ISupportUndoRedoOperations" />
+ public partial class ElementsEditor : HybridControl, IConfigurable, ISupportEditingOperations, ISupportUndoRedoOperations
+ {
+ private List<IElementEditor> _copiedElements;
+ private bool _isSelectionMouseDown; //Determines whether the mouse is down for selection by the selection rectangle.
+ private Point _selectionMouseDownPoint; //Holds the originating point to perform the selection.
+ private bool _selectionPerformed; //Determines whether the selection rectangle has moved at least 1 pixel.
+
+ #region Events
+
+ /// <summary>
+ /// Occurs when attempting to create a new element using the selection rectangle and holding the shift key.
+ /// </summary>
+ public event EventHandler<ElementCreationEventArgs> ElementCreation;
+
+ /// <summary>
+ /// Occurs before removing selected elements using the DELETE key.
+ /// </summary>
+ public event EventHandler<ElementsEventArgs> RemovingElements;
+
+ /// <summary>
+ /// Occurs when elements selection has changed;.
+ /// </summary>
+ public event EventHandler<ElementsEventArgs> SelectionChanged;
+
+ /// <summary>
+ /// Occurs after pasting the currently copied elements. The collection of elements represents the new cloned elements.
+ /// </summary>
+ public event EventHandler<ElementsEventArgs> AfterPaste;
+
+ /// <summary>
+ /// Occurs when one or many elements were added.
+ /// </summary>
+ public event EventHandler<ElementsEventArgs> ElementsAdded;
+
+ /// <summary>
+ /// Occurs when one or many elements were removed.
+ /// </summary>
+ public event EventHandler<ElementsEventArgs> ElementsRemoved;
+
+ public event EventHandler<IElementEditor> ElementDoubleClicked;
+
+ #endregion
+
+ #region Constructors
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ElementsEditor"/> class.
+ /// </summary>
+ public ElementsEditor()
+ {
+ //Initialize Collections
+ Elements = new ObservableCollection<IElementEditor>();
+ SelectedElements = new ObservableCollection<IElementEditor>();
+
+ InitializeComponent();
+
+ //Initialize Default Undo/Redo States Provider.
+ UndoRedoStatesProvider = new ElementsEditorUndoRedoStatesProvider(this);
+
+ //Register Events.
+ Loaded += ElementsEditor_Loaded;
+
+ //Initialize Commands
+ CopyCommand = new RelayCommand(Copy, (x) => GetSelectedElements().Count > 0);
+ PasteCommand = new RelayCommand(Paste, (x) => _copiedElements != null && _copiedElements.Count > 0);
+ UndoCommand = new RelayCommand(Undo, (x) => UndoRedoStatesProvider.CanUndo);
+ RedoCommand = new RelayCommand(Redo, (x) => UndoRedoStatesProvider.CanRedo);
+ DeleteCommand = new RelayCommand(() => { RemoveSelectedElements(true); }, (x) => GetSelectedElements().Count > 0);
+ CutCommand = new RelayCommand(Cut, (x) => GetSelectedElements().Count > 0);
+ ZoomCommand = new RelayCommand<String>((str) => { Zoom(Convert.ToDouble(str)); });
+ ResetZoomCommand = new RelayCommand(() => { ScaleFactor = 1; });
+ SelectAllCommand = new RelayCommand(SelectAll);
+ BringToFrontCommand = new RelayCommand(BringToFront, (x) => GetSelectedElements().Count > 0);
+ SendToBackCommand = new RelayCommand(SendToBack, (x) => GetSelectedElements().Count > 0);
+ }
+
+ #endregion
+
+ #region Properties
+
+ public BitmapSource PreviewImage
+ {
+ get { return (BitmapSource)GetValue(PreviewImageProperty); }
+ set { SetValue(PreviewImageProperty, value); }
+ }
+ public static readonly DependencyProperty PreviewImageProperty =
+ DependencyProperty.Register("PreviewImage", typeof(BitmapSource), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to enable undo and redo operations using the keyboard.
+ /// </summary>
+ public bool EnableKeyboardUndoRedoOperations
+ {
+ get { return (bool)GetValue(EnableKeyboardUndoRedoOperationsProperty); }
+ set { SetValue(EnableKeyboardUndoRedoOperationsProperty, value); }
+ }
+ public static readonly DependencyProperty EnableKeyboardUndoRedoOperationsProperty =
+ DependencyProperty.Register("EnableKeyboardUndoRedoOperations", typeof(bool), typeof(ElementsEditor), new PropertyMetadata(true));
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to enable editing operations using the keyboard.
+ /// </summary>
+ public bool EnableKeyboardEditingOperations
+ {
+ get { return (bool)GetValue(EnableKeyboardEditingOperationsProperty); }
+ set { SetValue(EnableKeyboardEditingOperationsProperty, value); }
+ }
+ public static readonly DependencyProperty EnableKeyboardEditingOperationsProperty =
+ DependencyProperty.Register("EnableKeyboardEditingOperations", typeof(bool), typeof(ElementsEditor), new PropertyMetadata(true));
+
+ /// <summary>
+ /// Gets or sets the width of the editor.
+ /// </summary>
+ public double EditorWidth
+ {
+ get { return (double)GetValue(EditorWidthProperty); }
+ set { SetValue(EditorWidthProperty, value); }
+ }
+ public static readonly DependencyProperty EditorWidthProperty =
+ DependencyProperty.Register("EditorWidth", typeof(double), typeof(ElementsEditor), new PropertyMetadata(1280.0));
+
+ /// <summary>
+ /// Gets or sets the height of the editor.
+ /// </summary>
+ public double EditorHeight
+ {
+ get { return (double)GetValue(EditorHeightProperty); }
+ set { SetValue(EditorHeightProperty, value); }
+ }
+ public static readonly DependencyProperty EditorHeightProperty =
+ DependencyProperty.Register("EditorHeight", typeof(double), typeof(ElementsEditor), new PropertyMetadata(720.0));
+
+ /// <summary>
+ /// Gets or sets the editor scale factor.
+ /// </summary>
+ public double ScaleFactor
+ {
+ get { return (double)GetValue(ScaleFactorProperty); }
+ set { SetValue(ScaleFactorProperty, value); }
+ }
+ public static readonly DependencyProperty ScaleFactorProperty =
+ DependencyProperty.Register("ScaleFactor", typeof(double), typeof(ElementsEditor), new PropertyMetadata(1.0, null, (d, e) => { return (d as ElementsEditor).OnCoerceScaleFactor(e); }));
+
+ /// <summary>
+ /// Gets or sets the collection of <see cref="IElementEditor"/>.
+ /// </summary>
+ public ObservableCollection<IElementEditor> Elements
+ {
+ get { return (ObservableCollection<IElementEditor>)GetValue(ElementsProperty); }
+ set { SetValue(ElementsProperty, value); }
+ }
+ public static readonly DependencyProperty ElementsProperty =
+ DependencyProperty.Register("Elements", typeof(ObservableCollection<IElementEditor>), typeof(ElementsEditor), new PropertyMetadata(null, (d, e) => { (d as ElementsEditor).OnElementsChanged(); }));
+
+ /// <summary>
+ /// Gets or sets the selected element.
+ /// </summary>
+ public IElementEditor SelectedElement
+ {
+ get { return (IElementEditor)GetValue(SelectedElementProperty); }
+ set { SetValue(SelectedElementProperty, value); }
+ }
+ public static readonly DependencyProperty SelectedElementProperty =
+ DependencyProperty.Register("SelectedElement", typeof(IElementEditor), typeof(ElementsEditor), new PropertyMetadata(null, (d, e) => { (d as ElementsEditor).OnSelectedElementChanged(); }));
+
+ /// <summary>
+ /// Gets or sets the selected elements.
+ /// </summary>
+ public ObservableCollection<IElementEditor> SelectedElements
+ {
+ get { return (ObservableCollection<IElementEditor>)GetValue(SelectedElementsProperty); }
+ set { SetValue(SelectedElementsProperty, value); }
+ }
+ public static readonly DependencyProperty SelectedElementsProperty =
+ DependencyProperty.Register("SelectedElements", typeof(ObservableCollection<IElementEditor>), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the height of the ruler.
+ /// </summary>
+ public double RulerHeight
+ {
+ get { return (double)GetValue(RulerHeightProperty); }
+ set { SetValue(RulerHeightProperty, value); }
+ }
+ public static readonly DependencyProperty RulerHeightProperty =
+ DependencyProperty.Register("RulerHeight", typeof(double), typeof(ElementsEditor), new PropertyMetadata(22.0));
+
+ /// <summary>
+ /// Gets or sets the undo redo states provider.
+ /// </summary>
+ public IUndoRedoStatesProvider UndoRedoStatesProvider
+ {
+ get { return (IUndoRedoStatesProvider)GetValue(UndoRedoStatesProviderProperty); }
+ set { SetValue(UndoRedoStatesProviderProperty, value); }
+ }
+ public static readonly DependencyProperty UndoRedoStatesProviderProperty =
+ DependencyProperty.Register("UndoRedoStatesProvider", typeof(IUndoRedoStatesProvider), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to bring the selected element to the front z index.
+ /// </summary>
+ public bool BringToFrontOnSelect
+ {
+ get { return (bool)GetValue(BringToFrontOnSelectProperty); }
+ set { SetValue(BringToFrontOnSelectProperty, value); }
+ }
+ public static readonly DependencyProperty BringToFrontOnSelectProperty =
+ DependencyProperty.Register("BringToFrontOnSelect", typeof(bool), typeof(ElementsEditor), new PropertyMetadata(true));
+
+ /// <summary>
+ /// Gets or sets the editor mode.
+ /// </summary>
+ public ElementsEditorMode EditorMode
+ {
+ get { return (ElementsEditorMode)GetValue(EditorModeProperty); }
+ set { SetValue(EditorModeProperty, value); }
+ }
+ public static readonly DependencyProperty EditorModeProperty =
+ DependencyProperty.Register("EditorMode", typeof(ElementsEditorMode), typeof(ElementsEditor), new PropertyMetadata(ElementsEditorMode.Default));
+
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to enable the creation of new elements using the mouse and Shift key.
+ /// </summary>
+ public bool EnableElementCreation
+ {
+ get { return (bool)GetValue(EnableElementCreationProperty); }
+ set { SetValue(EnableElementCreationProperty, value); }
+ }
+ public static readonly DependencyProperty EnableElementCreationProperty =
+ DependencyProperty.Register("EnableElementCreation", typeof(bool), typeof(ElementsEditor), new PropertyMetadata(true));
+
+ /// <summary>
+ /// Gets or sets an optional attached elements editor for editors mirroring mode.
+ /// </summary>
+ public ElementsEditor AttachedEditor
+ {
+ get { return (ElementsEditor)GetValue(AttachedEditorProperty); }
+ set { SetValue(AttachedEditorProperty, value); }
+ }
+ public static readonly DependencyProperty AttachedEditorProperty =
+ DependencyProperty.Register("AttachedEditor", typeof(ElementsEditor), typeof(ElementsEditor), new PropertyMetadata(null, (d, e) => (d as ElementsEditor).OnAttachedEditorChanged()));
+
+
+ /// <summary>
+ /// Gets or sets the preview visual source opacity.
+ /// </summary>
+ public double PreviewVisualSourceOpacity
+ {
+ get { return (double)GetValue(PreviewVisualSourceOpacityProperty); }
+ set { SetValue(PreviewVisualSourceOpacityProperty, value); }
+ }
+ public static readonly DependencyProperty PreviewVisualSourceOpacityProperty =
+ DependencyProperty.Register("PreviewVisualSourceOpacity", typeof(double), typeof(ElementsEditor), new PropertyMetadata(1.0));
+
+
+ #endregion
+
+ #region Attached Properties
+
+ #region IsSelected
+
+ /// <summary>
+ /// Determines whether the element is currently selected.
+ /// </summary>
+ public static readonly DependencyProperty IsSelectedProperty =
+ DependencyProperty.RegisterAttached("IsSelected",
+ typeof(bool), typeof(ElementsEditor),
+ new FrameworkPropertyMetadata(false));
+
+ /// <summary>
+ /// Sets the IsSelected attached property.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <param name="value">if set to <c>true</c> selected.</param>
+ public static void SetIsSelected(IElementEditor element, bool value)
+ {
+ (element as DependencyObject).SetValue(IsSelectedProperty, value);
+ }
+
+ /// <summary>
+ /// Gets the is IsSelected attached property.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <returns></returns>
+ public static bool GetIsSelected(IElementEditor element)
+ {
+ return (bool)(element as DependencyObject).GetValue(IsSelectedProperty);
+ }
+
+ #endregion
+
+ #endregion
+
+ #region Theme Properties
+
+ /// <summary>
+ /// Gets or sets the ruler background.
+ /// </summary>
+ public Brush RulerBackground
+ {
+ get { return (Brush)GetValue(RulerBackgroundProperty); }
+ set { SetValue(RulerBackgroundProperty, value); }
+ }
+ public static readonly DependencyProperty RulerBackgroundProperty =
+ DependencyProperty.Register("RulerBackground", typeof(Brush), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the editor background.
+ /// </summary>
+ public Brush EditorBackground
+ {
+ get { return (Brush)GetValue(EditorBackgroundProperty); }
+ set { SetValue(EditorBackgroundProperty, value); }
+ }
+ public static readonly DependencyProperty EditorBackgroundProperty =
+ DependencyProperty.Register("EditorBackground", typeof(Brush), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the selection fill brush.
+ /// </summary>
+ public Brush SelectionFillBrush
+ {
+ get { return (Brush)GetValue(SelectionFillBrushProperty); }
+ set { SetValue(SelectionFillBrushProperty, value); }
+ }
+ public static readonly DependencyProperty SelectionFillBrushProperty =
+ DependencyProperty.Register("SelectionFillBrush", typeof(Brush), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the selection stroke brush.
+ /// </summary>
+ public Brush SelectionStrokeBrush
+ {
+ get { return (Brush)GetValue(SelectionStrokeBrushProperty); }
+ set { SetValue(SelectionStrokeBrushProperty, value); }
+ }
+ public static readonly DependencyProperty SelectionStrokeBrushProperty =
+ DependencyProperty.Register("SelectionStrokeBrush", typeof(Brush), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ #endregion
+
+ #region Virtual Methods
+
+ /// <summary>
+ /// Invoked when the attached editor has changed.
+ /// </summary>
+ protected virtual void OnAttachedEditorChanged()
+ {
+ if (AttachedEditor != null)
+ {
+ UndoRedoStatesProvider = new AttachedElementsEditorsUndoRedoStatesProvider(this, AttachedEditor);
+ AttachedEditor.UndoRedoStatesProvider = UndoRedoStatesProvider;
+ }
+ }
+
+ /// <summary>
+ /// Raises the <see cref="E:ElementsAdded" /> event.
+ /// </summary>
+ /// <param name="e">The <see cref="ElementsEventArgs"/> instance containing the event data.</param>
+ protected virtual void OnElementsAdded(ElementsEventArgs e)
+ {
+ if (ElementsAdded != null) ElementsAdded(this, e);
+ }
+
+ /// <summary>
+ /// Raises the <see cref="E:ElementsRemoved" /> event.
+ /// </summary>
+ /// <param name="e">The <see cref="ElementsEventArgs"/> instance containing the event data.</param>
+ protected virtual void OnElementsRemoved(ElementsEventArgs e)
+ {
+ if (ElementsRemoved != null) ElementsRemoved(this, e);
+ }
+
+ /// <summary>
+ /// Called when the reset scale factor button was clicked.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="MouseButtonEventArgs"/> instance containing the event data.</param>
+ protected virtual void OnResetScaleFactor(object sender, MouseButtonEventArgs e)
+ {
+ ScaleFactor = 1;
+ }
+
+ /// <summary>
+ /// Called when the selected element has changed.
+ /// </summary>
+ protected virtual void OnSelectedElementChanged()
+ {
+ if (SelectedElement != null)
+ {
+ if (BringToFrontOnSelect)
+ {
+ Canvas.SetZIndex(SelectedElement as UIElement, Elements.Max(x => Canvas.GetZIndex(x as UIElement) + 1));
+ }
+
+ SetElementSelection(SelectedElement, true);
+
+ if (AttachedEditor != null && AttachedEditor.SelectedElement != SelectedElement.AttachedEditor)
+ {
+ AttachedEditor.SelectedElement = SelectedElement.AttachedEditor;
+ }
+ }
+ else
+ {
+ if (AttachedEditor != null)
+ {
+ AttachedEditor.SelectedElement = null;
+ }
+ }
+
+ if (!IsCtrlDown())
+ {
+ foreach (var element in Elements.Where(x => x != SelectedElement))
+ {
+ SetElementSelection(element, false);
+ }
+ }
+
+ OnSelectionChanged();
+ InvalidateRelayCommands();
+ }
+
+ /// <summary>
+ /// Raises the <see cref="SelectionChanged"/> event.
+ /// </summary>
+ protected void OnSelectionChanged()
+ {
+ if (SelectionChanged != null) SelectionChanged(this, new ElementsEventArgs(GetSelectedElements()));
+ }
+
+ /// <summary>
+ /// Called when the elements collection has changed.
+ /// </summary>
+ protected virtual void OnElementsChanged()
+ {
+ if (Elements != null)
+ {
+ RegisterElementsEvents();
+ Elements.CollectionChanged -= Elements_CollectionChanged;
+ Elements.CollectionChanged += Elements_CollectionChanged;
+
+ SetCanvasElements();
+ }
+
+ InvalidateRelayCommands();
+ }
+
+ /// <summary>
+ /// Called when the hosting canvas has captured a mouse down event.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="MouseButtonEventArgs"/> instance containing the event data.</param>
+ protected virtual void OnCanvasMouseDown(object sender, MouseButtonEventArgs e)
+ {
+ DeselectElements();
+
+ _selectionMouseDownPoint = e.GetPosition(selectionCanvas);
+ _selectionPerformed = false;
+ _isSelectionMouseDown = true;
+ selectionRec.Width = 0;
+ selectionRec.Height = 0;
+ selectionCanvas.Visibility = System.Windows.Visibility.Visible;
+ }
+
+ /// <summary>
+ /// Handles the canvas mouse up event.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="MouseButtonEventArgs"/> instance containing the event data.</param>
+ protected virtual void OnCanvasMouseUp(object sender, MouseButtonEventArgs e)
+ {
+ if (_selectionPerformed)
+ {
+ _selectionPerformed = false;
+ selectionCanvas.Visibility = System.Windows.Visibility.Hidden;
+ e.Handled = true;
+
+ if (IsShiftDown() && EnableElementCreation)
+ {
+ OnElementCreation();
+ }
+ }
+
+ _isSelectionMouseDown = false;
+ }
+
+ /// <summary>
+ /// Raises the <see cref="ElementCreation"/> event.
+ /// </summary>
+ protected virtual void OnElementCreation()
+ {
+ PrepareUndoState();
+ var args = new ElementCreationEventArgs(Canvas.GetLeft(selectionRec), Canvas.GetTop(selectionRec), selectionRec.Width, selectionRec.Height);
+ if (ElementCreation != null) ElementCreation(this, args);
+ if (args.AppendUndoState)
+ {
+ CommitUndoState();
+ }
+ }
+
+ /// <summary>
+ /// Handles the canvas mouse move event.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
+ protected virtual void OnCanvasMouseMove(object sender, MouseEventArgs e)
+ {
+ if (_isSelectionMouseDown)
+ {
+ Point currentMousePoint = e.GetPosition(selectionCanvas);
+
+ _selectionPerformed = currentMousePoint.X != _selectionMouseDownPoint.X || currentMousePoint.Y != _selectionMouseDownPoint.Y;
+
+ Canvas.SetLeft(selectionRec, _selectionMouseDownPoint.X);
+ Canvas.SetTop(selectionRec, _selectionMouseDownPoint.Y);
+
+ if (currentMousePoint.X - _selectionMouseDownPoint.X > 1)
+ {
+ selectionRec.Width = currentMousePoint.X - _selectionMouseDownPoint.X;
+ }
+
+ if (currentMousePoint.Y - _selectionMouseDownPoint.Y > 1)
+ {
+ selectionRec.Height = currentMousePoint.Y - _selectionMouseDownPoint.Y;
+ }
+
+ if (currentMousePoint.X < _selectionMouseDownPoint.X)
+ {
+ Canvas.SetLeft(selectionRec, currentMousePoint.X);
+ selectionRec.Width = _selectionMouseDownPoint.X - currentMousePoint.X;
+ }
+
+ if (currentMousePoint.Y < _selectionMouseDownPoint.Y)
+ {
+ Canvas.SetTop(selectionRec, currentMousePoint.Y);
+ selectionRec.Height = _selectionMouseDownPoint.Y - currentMousePoint.Y;
+ }
+
+ if (!IsShiftDown())
+ {
+ //Select intersecting objects
+ Rect selectRect = new Rect(Canvas.GetLeft(selectionRec), Canvas.GetTop(selectionRec), selectionRec.Width, selectionRec.Height);
+
+ var allElements = GetAllElements();
+
+ foreach (var element in allElements)
+ {
+ SetElementSelection(element, GetElementBounds(element).IntersectsWith(selectRect));
+ }
+
+ var selectedElements = GetSelectedElements();
+
+ if (selectedElements.Count == 1)
+ {
+ SelectedElement = selectedElements.FirstOrDefault();
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Called when coercing the scale factor.
+ /// </summary>
+ /// <param name="value">The value.</param>
+ /// <returns></returns>
+ protected virtual object OnCoerceScaleFactor(object value)
+ {
+ if ((double)value < 0.1)
+ {
+ return 0.1;
+ }
+ else
+ {
+ return value;
+ }
+ }
+
+ /// <summary>
+ /// Called when zooming with mouse wheel.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="MouseWheelEventArgs"/> instance containing the event data.</param>
+ protected virtual void OnMouseWheelZooming(object sender, MouseWheelEventArgs e)
+ {
+ if (e.Delta > 0) //Ticks up
+ {
+ ScaleFactor += 0.1;
+ }
+ else //Ticks Down
+ {
+ ScaleFactor -= 0.1;
+ }
+
+ Point pointAbsolute = Mouse.GetPosition(gridCanvas);
+ Point pointRelative = Mouse.GetPosition(scrollViewer);
+
+ scrollViewer.ScrollToHorizontalOffset((pointAbsolute.X * ScaleFactor) - pointRelative.X);
+ scrollViewer.ScrollToVerticalOffset((pointAbsolute.Y * ScaleFactor) - pointRelative.Y);
+ }
+ #endregion
+
+ #region Event Handlers
+
+ /// <summary>
+ /// Handles the Loaded event of the ElementsEditor control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="RoutedEventArgs"/> instance containing the event data.</param>
+ private void ElementsEditor_Loaded(object sender, RoutedEventArgs e)
+ {
+ SetCanvasElements();
+ }
+
+ /// <summary>
+ /// Handles the CollectionChanged event of the Elements control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
+ private void Elements_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+ {
+ RegisterElementsEvents();
+ SetCanvasElements();
+
+ if (e.NewItems != null && e.NewItems.Count > 0)
+ {
+ OnElementsAdded(new ElementsEventArgs(e.NewItems.Cast<IElementEditor>().ToList()));
+ }
+
+ if (e.OldItems != null && e.OldItems.Count > 0)
+ {
+ OnElementsRemoved(new ElementsEventArgs(e.OldItems.Cast<IElementEditor>().ToList()));
+ }
+ }
+
+ /// <summary>
+ /// Handles the SelectionChanged event of the Element control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
+ /// <exception cref="System.NotImplementedException"></exception>
+ private void Element_SelectionChanged(object sender, EventArgs e)
+ {
+ IElementEditor element = sender as IElementEditor;
+ SelectedElement = element;
+ }
+
+ /// <summary>
+ /// Handles the Moving event of the Element control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="System.Windows.Controls.Primitives.DragDeltaEventArgs"/> instance containing the event data.</param>
+ /// <exception cref="System.NotImplementedException"></exception>
+ private void Element_Moving(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
+ {
+ if (IsCtrlDown())
+ {
+ GetSelectedElements().Where(x => x != sender).ToList().ForEach(x => x.PushMove(e));
+ }
+ }
+
+ /// <summary>
+ /// Handles the AfterBoundsChange event of the Element control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
+ private void Element_AfterBoundsChange(object sender, EventArgs e)
+ {
+ CommitUndoState();
+ }
+
+ /// <summary>
+ /// Handles the BeforeBoundsChange event of the Element control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
+ private void Element_BeforeBoundsChange(object sender, EventArgs e)
+ {
+ PrepareUndoState();
+ }
+
+ /// <summary>
+ /// Handles the PreviewMouseDown event of the Element control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="MouseButtonEventArgs"/> instance containing the event data.</param>
+ private void Element_PreviewMouseDown(object sender, MouseButtonEventArgs e)
+ {
+ if (e.ChangedButton == MouseButton.Left && !Keyboard.IsKeyDown(Key.LeftShift))
+ {
+ SelectedElement = sender as IElementEditor;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void SetCanvasElements()
+ {
+ if (!this.IsInDesignMode())
+ {
+ if (canvas != null && Elements != null)
+ {
+ foreach (UIElement element in Elements)
+ if (!canvas.Children.Contains(element))
+ canvas.Children.Add(element);
+
+ List<UIElement> removeList = new List<UIElement>();
+ foreach (UIElement element in canvas.Children)
+ if (!Elements.Contains(element as IElementEditor))
+ removeList.Add(element);
+
+ foreach (UIElement element in removeList)
+ canvas.Children.Remove(element);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Determines whether the control key is down.
+ /// </summary>
+ private bool IsCtrlDown()
+ {
+ return Keyboard.IsKeyDown(Key.LeftCtrl);
+ }
+
+ /// <summary>
+ /// Determines whether the shift control is down.
+ /// </summary>
+ private bool IsShiftDown()
+ {
+ return Keyboard.IsKeyDown(Key.LeftShift);
+ }
+
+ /// <summary>
+ /// Registers the elements events.
+ /// </summary>
+ private void RegisterElementsEvents()
+ {
+ foreach (var element in Elements)
+ {
+ element.Moving -= Element_Moving;
+ element.Moving += Element_Moving;
+ element.BeforeBoundsChange -= Element_BeforeBoundsChange;
+ element.BeforeBoundsChange += Element_BeforeBoundsChange;
+ element.AfterBoundsChange -= Element_AfterBoundsChange;
+ element.AfterBoundsChange += Element_AfterBoundsChange;
+
+ if (element is FrameworkElement)
+ {
+ (element as FrameworkElement).PreviewMouseDown -= Element_PreviewMouseDown;
+ (element as FrameworkElement).PreviewMouseDown += Element_PreviewMouseDown;
+ (element as FrameworkElement).PreviewMouseDown -= ElementsEditor_PreviewMouseDown;
+ (element as FrameworkElement).PreviewMouseDown += ElementsEditor_PreviewMouseDown;
+ }
+ }
+ }
+
+ private void ElementsEditor_PreviewMouseDown(object sender, MouseButtonEventArgs e)
+ {
+ if (e.ClickCount == 2)
+ {
+ ElementDoubleClicked?.Invoke(this, sender as IElementEditor);
+ }
+ }
+
+ /// <summary>
+ /// Prepares the state of the undo.
+ /// </summary>
+ private void PrepareUndoState()
+ {
+ UndoRedoStatesProvider.PrepareUndoState();
+ }
+
+ /// <summary>
+ /// Commits the state of the undo.
+ /// </summary>
+ private void CommitUndoState()
+ {
+ UndoRedoStatesProvider.CommitUndoState();
+ }
+
+ /// <summary>
+ /// Executes the undo/redo state.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ private void ExecuteUndoRedoState(List<AnimationSetup> state)
+ {
+ foreach (var setup in state)
+ {
+ setup.Animation.Completed += (x, y) =>
+ {
+ var aniValue = setup.DependencyObject.GetValue(setup.DependencyProperty);
+ (setup.DependencyObject as IAnimatable).BeginAnimation(setup.DependencyProperty, null);
+ setup.DependencyObject.SetCurrentValue(setup.DependencyProperty, aniValue);
+ };
+
+ (setup.DependencyObject as IAnimatable).BeginAnimation(setup.DependencyProperty, setup.Animation);
+ }
+ }
+
+ /// <summary>
+ /// Creates an undo/redo state.
+ /// </summary>
+ /// <returns></returns>
+ private List<AnimationSetup> CreateUndoRedoState()
+ {
+ List<AnimationSetup> all = new List<AnimationSetup>();
+
+ foreach (var element in Elements)
+ {
+ var setups = element.GetAnimationSetups(TimeSpan.FromSeconds(0), AnimationSetupMode.Discrete);
+ all.AddRange(setups);
+ }
+
+ return all;
+ }
+
+ /// <summary>
+ /// Gets the element bounds.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <returns></returns>
+ private Rect GetElementBounds(IElementEditor element)
+ {
+ var visual = element as FrameworkElement;
+
+ if (visual != null)
+ {
+ var position = visual.TranslatePoint(new Point(0, 0), selectionCanvas);
+ return new Rect(position.X, position.Y, visual.ActualWidth, visual.ActualHeight);
+ }
+ else
+ {
+ return new Rect();
+ }
+ }
+
+ /// <summary>
+ /// Gets the visual child.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="parent">The parent.</param>
+ /// <returns></returns>
+ private static T GetVisualChild<T>(DependencyObject parent) where T : Visual
+ {
+ T child = default(T);
+
+ int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
+ for (int i = 0; i < numVisuals; i++)
+ {
+ Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
+ child = v as T;
+ if (child == null)
+ {
+ child = GetVisualChild<T>(v);
+ }
+ if (child != null)
+ {
+ break;
+ }
+ }
+ return child;
+ }
+ #endregion
+
+ #region Public Methods
+
+ /// <summary>
+ /// De-selects all elements.
+ /// </summary>
+ public void DeselectElements()
+ {
+ Elements.ToList().ForEach(x => SetElementSelection(x, false));
+ SelectedElement = null;
+ OnSelectionChanged();
+ InvalidateRelayCommands();
+ }
+
+ /// <summary>
+ /// Selects all elements.
+ /// </summary>
+ public void SelectAll()
+ {
+ Elements.ToList().ForEach(x => SetElementSelection(x, true));
+ OnSelectionChanged();
+ InvalidateRelayCommands();
+ }
+
+ /// <summary>
+ /// Increase or decrease the <see cref="ScaleFactor"/> by the specified factor.
+ /// </summary>
+ /// <param name="factor">The factor (e.g 0.2 or -0.5).</param>
+ public void Zoom(double factor)
+ {
+ ScaleFactor += factor;
+ }
+
+ /// <summary>
+ /// Gets the selected elements.
+ /// </summary>
+ public List<IElementEditor> GetSelectedElements()
+ {
+ return GetAllElements().Where(x => GetIsSelected(x)).ToList();
+ }
+
+ /// <summary>
+ /// Gets all elements.
+ /// </summary>
+ /// <returns></returns>
+ public List<IElementEditor> GetAllElements()
+ {
+ return Elements.ToList();
+ }
+
+ /// <summary>
+ /// Removes the selected elements.
+ /// </summary>
+ public void RemoveSelectedElements(bool raiseRemoveEvent = false)
+ {
+ ElementsEventArgs args = new ElementsEventArgs();
+
+ if (raiseRemoveEvent)
+ {
+ args.Elements = GetSelectedElements();
+ if (RemovingElements != null) RemovingElements(this, args);
+ }
+
+ if (!raiseRemoveEvent || !args.Cancel)
+ {
+ PrepareUndoState();
+ var selectedElements = GetSelectedElements();
+ selectedElements.ForEach(x => RemoveElement(x));
+
+ if (AttachedEditor != null)
+ {
+ foreach (var element in selectedElements)
+ {
+ if (element.AttachedEditor != null)
+ {
+ AttachedEditor.RemoveElement(element.AttachedEditor);
+ }
+ }
+ }
+
+ CommitUndoState();
+ OnSelectionChanged();
+ InvalidateRelayCommands();
+ }
+ }
+
+ /// <summary>
+ /// Removes the element.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ public void RemoveElement(IElementEditor element)
+ {
+ SetElementSelection(element, false);
+ Elements.Remove(element);
+ if (SelectedElement == element) SelectedElement = null;
+ OnSelectionChanged();
+ InvalidateRelayCommands();
+ }
+
+ /// <summary>
+ /// Gets the element by the hosted element.
+ /// </summary>
+ /// <param name="hostedElement">The hosted element.</param>
+ public IElementEditor GetElementByHostedElement(object hostedElement)
+ {
+ return Elements.SingleOrDefault(x => x.HostedElement == hostedElement);
+ }
+
+ /// <summary>
+ /// Undoes the current state of the elements collection.
+ /// </summary>
+ public void Undo()
+ {
+ UndoRedoStatesProvider.Undo();
+ InvalidateRelayCommands();
+ }
+
+ /// <summary>
+ /// Redoes the current state of the elements collection.
+ /// </summary>
+ public void Redo()
+ {
+ UndoRedoStatesProvider.Redo();
+ InvalidateRelayCommands();
+ }
+
+ /// <summary>
+ /// Performs copy operation on the selected elements.
+ /// </summary>
+ public void Copy()
+ {
+ Copy(false);
+ }
+
+ private void Copy(bool fromAttached)
+ {
+ _copiedElements = GetSelectedElements();
+ InvalidateRelayCommands();
+
+ if (AttachedEditor != null && !fromAttached)
+ {
+ AttachedEditor.Copy(true);
+ }
+ }
+
+ /// <summary>
+ /// Performs a Cut operation over the selected elements.
+ /// </summary>
+ public void Cut()
+ {
+ Copy();
+ RemoveSelectedElements();
+ InvalidateRelayCommands();
+ }
+
+ /// <summary>
+ /// Pastes the last copied elements.
+ /// </summary>
+ public void Paste()
+ {
+ Paste(false, Mouse.GetPosition(gridCanvas), null);
+ }
+
+ private void Paste(bool fromAttached, Point point, List<IElementEditor> fromAttachedElements)
+ {
+ if (_copiedElements != null)
+ {
+ PrepareUndoState();
+
+ List<IElementEditor> elementsToAdd = new List<IElementEditor>();
+
+ var clonedElements = _copiedElements.Select(x => x.Clone()).ToList();
+
+ if (clonedElements.Count == 0) return;
+
+ var mostLeft = clonedElements.OrderBy(x => x.Left).First().Left;
+ var mostTop = clonedElements.OrderBy(x => x.Top).First().Top;
+
+ int count = 0;
+
+ foreach (var element in clonedElements)
+ {
+ element.Left += point.X - mostLeft;
+ element.Top += point.Y - mostTop;
+ elementsToAdd.Add(element);
+
+ if (fromAttachedElements != null && fromAttachedElements.Count > 0)
+ {
+ fromAttachedElements[count].AttachedEditor = element;
+ element.AttachedEditor = fromAttachedElements[count++];
+ }
+ }
+
+ if (AttachedEditor != null && !fromAttached)
+ {
+ AttachedEditor.Paste(true, point, clonedElements);
+ }
+
+ if (!fromAttached)
+ {
+ if (AfterPaste != null) AfterPaste(this, new ElementsEventArgs(clonedElements));
+ }
+
+ foreach (var element in elementsToAdd)
+ {
+ Elements.Add(element);
+ }
+
+ CommitUndoState();
+ }
+
+ InvalidateRelayCommands();
+ }
+
+ /// <summary>
+ /// Brings the new element Z-index in front of the old element.
+ /// </summary>
+ /// <param name="oldElement">The old element.</param>
+ /// <param name="newElement">The new element.</param>
+ public void BringInFront(IElementEditor oldElement, IElementEditor newElement)
+ {
+ Canvas.SetZIndex(newElement as UIElement, Canvas.GetZIndex(oldElement as UIElement) + 1);
+ }
+
+ /// <summary>
+ /// Brings the specified element to the Z-index front.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ public void BringToFront(IElementEditor element)
+ {
+ element.ZIndex = Elements.Max(x => Canvas.GetZIndex(x as UIElement) + 1);
+ }
+
+ /// <summary>
+ /// Brings all the selected items to the front.
+ /// </summary>
+ public void BringToFront()
+ {
+ foreach (var element in GetSelectedElements())
+ {
+ BringToFront(element);
+ }
+ }
+
+ /// <summary>
+ /// Sends all the selected items to the back.
+ /// </summary>
+ public void SendToBack()
+ {
+ foreach (var element in GetSelectedElements())
+ {
+ SendToBack(element);
+ }
+ }
+
+ /// <summary>
+ /// Sets the element Z-index.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <param name="index">The index.</param>
+ public void SetZIndex(IElementEditor element, int index)
+ {
+ Canvas.SetZIndex(element as UIElement, index);
+ }
+
+ /// <summary>
+ /// Gets the element Z-index.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <returns></returns>
+ public int GetZIndex(IElementEditor element)
+ {
+ return Canvas.GetZIndex(element as UIElement);
+ }
+
+ /// <summary>
+ /// Sends the specified element to the Z-index back.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ public void SendToBack(IElementEditor element)
+ {
+ element.ZIndex = Elements.Min(x => Canvas.GetZIndex(x as UIElement) - 1);
+ }
+
+ #endregion
+
+ #region Keyboard
+
+ /// <summary>
+ /// Invoked when an unhandled <see cref="E:System.Windows.Input.Keyboard.PreviewKeyDown" /> attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
+ /// </summary>
+ /// <param name="e">The <see cref="T:System.Windows.Input.KeyEventArgs" /> that contains the event data.</param>
+ protected override void OnPreviewKeyDown(KeyEventArgs e)
+ {
+ base.OnPreviewKeyDown(e);
+
+ if (e.Key == Key.Right)
+ {
+ GetSelectedElements().ForEach(x => x.PushMove(new DragDeltaEventArgs(1, 0)));
+ }
+ else if (e.Key == Key.Left)
+ {
+ GetSelectedElements().ForEach(x => x.PushMove(new DragDeltaEventArgs(-1, 0)));
+ }
+ else if (e.Key == Key.Up)
+ {
+ GetSelectedElements().ForEach(x => x.PushMove(new DragDeltaEventArgs(0, -1)));
+ }
+ else if (e.Key == Key.Down)
+ {
+ GetSelectedElements().ForEach(x => x.PushMove(new DragDeltaEventArgs(0, 1)));
+ }
+ else if (e.Key == Key.Z && IsCtrlDown() && EnableKeyboardUndoRedoOperations)
+ {
+ Undo();
+ }
+ else if (e.Key == Key.Y && IsCtrlDown() && EnableKeyboardUndoRedoOperations)
+ {
+ Redo();
+ }
+ else if (e.Key == Key.Delete)
+ {
+ RemoveSelectedElements(true);
+ }
+ else if (e.Key == Key.C && IsCtrlDown() && EnableKeyboardEditingOperations)
+ {
+ Copy();
+ }
+ else if (e.Key == Key.V && IsCtrlDown() && EnableKeyboardEditingOperations)
+ {
+ Paste();
+ }
+ else if (e.Key == Key.X && IsCtrlDown() && EnableKeyboardEditingOperations)
+ {
+ Cut();
+ }
+ else if (e.Key == Key.A && IsCtrlDown())
+ {
+ SelectAll();
+ }
+ else if (e.Key == Key.F && IsCtrlDown())
+ {
+ BringToFront();
+ }
+ else if (e.Key == Key.B && IsCtrlDown())
+ {
+ SendToBack();
+ }
+ }
+
+ #endregion
+
+ #region Override Methods
+
+ /// <summary>
+ /// Invoked when an unhandled <see cref="E:System.Windows.Input.Mouse.MouseEnter" /> attached event is raised on this element. Implement this method to add class handling for this event.
+ /// </summary>
+ /// <param name="e">The <see cref="T:System.Windows.Input.MouseEventArgs" /> that contains the event data.</param>
+ protected override void OnMouseEnter(MouseEventArgs e)
+ {
+ base.OnMouseEnter(e);
+ //scrollViewer.Focus();
+ }
+
+ #endregion
+
+ #region Commands
+
+ /// <summary>
+ /// Gets or sets the copy command.
+ /// </summary>
+ public RelayCommand CopyCommand
+ {
+ get { return (RelayCommand)GetValue(CopyCommandProperty); }
+ set { SetValue(CopyCommandProperty, value); }
+ }
+ public static readonly DependencyProperty CopyCommandProperty =
+ DependencyProperty.Register("CopyCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the paste command.
+ /// </summary>
+ public RelayCommand PasteCommand
+ {
+ get { return (RelayCommand)GetValue(PasteCommandProperty); }
+ set { SetValue(PasteCommandProperty, value); }
+ }
+ public static readonly DependencyProperty PasteCommandProperty =
+ DependencyProperty.Register("PasteCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the undo command.
+ /// </summary>
+ public RelayCommand UndoCommand
+ {
+ get { return (RelayCommand)GetValue(UndoCommandProperty); }
+ set { SetValue(UndoCommandProperty, value); }
+ }
+ public static readonly DependencyProperty UndoCommandProperty =
+ DependencyProperty.Register("UndoCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the redo command.
+ /// </summary>
+ public RelayCommand RedoCommand
+ {
+ get { return (RelayCommand)GetValue(RedoCommandProperty); }
+ set { SetValue(RedoCommandProperty, value); }
+ }
+ public static readonly DependencyProperty RedoCommandProperty =
+ DependencyProperty.Register("RedoCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the delete command.
+ /// </summary>
+ public RelayCommand DeleteCommand
+ {
+ get { return (RelayCommand)GetValue(DeleteCommandProperty); }
+ set { SetValue(DeleteCommandProperty, value); }
+ }
+ public static readonly DependencyProperty DeleteCommandProperty =
+ DependencyProperty.Register("DeleteCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the cut command.
+ /// </summary>
+ public RelayCommand CutCommand
+ {
+ get { return (RelayCommand)GetValue(CutCommandProperty); }
+ set { SetValue(CutCommandProperty, value); }
+ }
+ public static readonly DependencyProperty CutCommandProperty =
+ DependencyProperty.Register("CutCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the select all command.
+ /// </summary>
+ public RelayCommand SelectAllCommand
+ {
+ get { return (RelayCommand)GetValue(SelectAllCommandProperty); }
+ set { SetValue(SelectAllCommandProperty, value); }
+ }
+ public static readonly DependencyProperty SelectAllCommandProperty =
+ DependencyProperty.Register("SelectAllCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the zoom command.
+ /// </summary>
+ public RelayCommand<String> ZoomCommand
+ {
+ get { return (RelayCommand<String>)GetValue(ZoomCommandProperty); }
+ set { SetValue(ZoomCommandProperty, value); }
+ }
+ public static readonly DependencyProperty ZoomCommandProperty =
+ DependencyProperty.Register("ZoomCommand", typeof(RelayCommand<String>), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the reset zoom command.
+ /// </summary>
+ public RelayCommand ResetZoomCommand
+ {
+ get { return (RelayCommand)GetValue(ResetZoomCommandProperty); }
+ set { SetValue(ResetZoomCommandProperty, value); }
+ }
+ public static readonly DependencyProperty ResetZoomCommandProperty =
+ DependencyProperty.Register("ResetZoomCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Invokes bring to front on the selected item.
+ /// </summary>
+ public RelayCommand BringToFrontCommand
+ {
+ get { return (RelayCommand)GetValue(BringToFrontCommandProperty); }
+ set { SetValue(BringToFrontCommandProperty, value); }
+ }
+ public static readonly DependencyProperty BringToFrontCommandProperty =
+ DependencyProperty.Register("BringToFrontCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Invokes send to back on the selected item.
+ /// </summary>
+ public RelayCommand SendToBackCommand
+ {
+ get { return (RelayCommand)GetValue(SendToBackCommandProperty); }
+ set { SetValue(SendToBackCommandProperty, value); }
+ }
+ public static readonly DependencyProperty SendToBackCommandProperty =
+ DependencyProperty.Register("SendToBackCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null));
+
+ #endregion
+
+ #region IConfigurable Members
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <returns></returns>
+ public virtual IConfiguration GetConfiguration()
+ {
+ return GetConfiguration();
+ }
+
+ /// <summary>
+ /// Gets the configuration.
+ /// </summary>
+ /// <param name="configurationName">Name of the configuration.</param>
+ /// <returns></returns>
+ public virtual ElementsEditorConfiguration GetConfiguration(String configurationName)
+ {
+ ElementsEditorConfiguration config = new ElementsEditorConfiguration();
+ config.Name = configurationName;
+ config.Date = DateTime.Now;
+ config.ElementsConfigurations = new ObservableCollection<ElementEditorConfiguration>(Elements.Select(x => x.GetConfiguration() as ElementEditorConfiguration).ToList());
+ return config;
+ }
+
+ /// <summary>
+ /// Applies the specified configuration with an optional animation if supported by the configurable type.
+ /// </summary>
+ /// <param name="configuration">The configuration.</param>
+ /// <param name="animation">The animation.</param>
+ /// <exception cref="InvalidConfigurationException"></exception>
+ public virtual void SetConfiguration(IConfiguration configuration, ConfigurationAnimation animation)
+ {
+ if (!(configuration is ElementsEditorConfiguration))
+ {
+ throw new InvalidConfigurationException();
+ }
+
+ UndoRedoStatesProvider.Reset();
+
+ var config = configuration as ElementsEditorConfiguration;
+
+ foreach (var elementConfig in config.ElementsConfigurations)
+ {
+ var existingElement = Elements.SingleOrDefault(x => x.ID == elementConfig.ID);
+ if (existingElement != null)
+ {
+ existingElement.SetConfiguration(elementConfig, animation);
+ }
+ else
+ {
+ var newElement = elementConfig.CreateConfigurable<IElementEditor>();
+ Elements.Add(newElement);
+ }
+ }
+
+ var elementsToRemove = Elements.Where(x => !config.ElementsConfigurations.ToList().Exists(y => y.ID == x.ID)).ToList();
+ elementsToRemove.ForEach(x => RemoveElement(x));
+
+ InvalidateRelayCommands();
+ }
+
+ #endregion
+
+ private void SetElementSelection(IElementEditor element, bool selected)
+ {
+ SetIsSelected(element, selected);
+
+ if (element.AttachedEditor != null && GetIsSelected(element.AttachedEditor) != selected)
+ {
+ ElementsEditor.SetIsSelected(element.AttachedEditor, selected);
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ElementsEditorConfiguration.cs b/Software/Visual_Studio/Tango.Editors/ElementsEditorConfiguration.cs
new file mode 100644
index 000000000..e2c34dac3
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ElementsEditorConfiguration.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using Tango.Editors;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// <para><img class="classImage" src="../Media/Configuration.png" /></para>
+ /// Represents an <see cref="ElementsEditorConfiguration"/> configuration which can be saved or load from a file or stream.
+ /// </summary>
+ /// <seealso cref="IConfiguration" />
+ [Serializable]
+ public class ElementsEditorConfiguration : IConfiguration
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ElementsEditorConfiguration"/> class.
+ /// </summary>
+ public ElementsEditorConfiguration()
+ {
+ Date = DateTime.Now;
+ ElementsConfigurations = new ObservableCollection<ElementEditorConfiguration>();
+ ConfigurableType = typeof(ElementsEditor);
+ }
+
+ /// <summary>
+ /// Gets or sets the configuration name.
+ /// </summary>
+ public String Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the configuration creation date.
+ /// </summary>
+ public DateTime Date { get; set; }
+
+ /// <summary>
+ /// Gets or sets the elements configurations.
+ /// </summary>
+ public ObservableCollection<ElementEditorConfiguration> ElementsConfigurations { get; set; }
+
+ /// <summary>
+ /// Gets or sets the configurable type.
+ /// </summary>
+ public Type ConfigurableType { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ElementsEditorMode.cs b/Software/Visual_Studio/Tango.Editors/ElementsEditorMode.cs
new file mode 100644
index 000000000..eaba7354e
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ElementsEditorMode.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents different modes for the <see cref="ElementsEditor"/>.
+ /// </summary>
+ public enum ElementsEditorMode
+ {
+ /// <summary>
+ /// The editor will behave as normal.
+ /// </summary>
+ Default,
+ /// <summary>
+ /// Hides the rulers, auto editor size, disables zooming and panning.
+ /// </summary>
+ AdvancedCanvas,
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ElementsEditorUndoRedoExecutedEventArgs.cs b/Software/Visual_Studio/Tango.Editors/ElementsEditorUndoRedoExecutedEventArgs.cs
new file mode 100644
index 000000000..01c680c88
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ElementsEditorUndoRedoExecutedEventArgs.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Tango.Editors;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents a <see cref="ElementsEditorUndoRedoStatesProvider"/> event arguments
+ /// </summary>
+ /// <seealso cref="System.EventArgs" />
+ public class ElementsEditorUndoRedoExecutedEventArgs : UndoRedoStateExecutedEventArgs
+ {
+ /// <summary>
+ /// Gets or sets the restored elements.
+ /// </summary>
+ public List<IElementEditor> RestoredElements { get; set; }
+
+ /// <summary>
+ /// Gets or sets the removed elements.
+ /// </summary>
+ public List<IElementEditor> RemovedElements { get; set; }
+
+ /// <summary>
+ /// Gets or sets the modified elements.
+ /// </summary>
+ public List<IElementEditor> ModifiedElements { get; set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ElementsEditorUndoRedoExecutedEventArgs"/> class.
+ /// </summary>
+ public ElementsEditorUndoRedoExecutedEventArgs()
+ {
+ RestoredElements = new List<IElementEditor>();
+ RemovedElements = new List<IElementEditor>();
+ ModifiedElements = new List<IElementEditor>();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ElementsEditorUndoRedoState.cs b/Software/Visual_Studio/Tango.Editors/ElementsEditorUndoRedoState.cs
new file mode 100644
index 000000000..406ded024
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ElementsEditorUndoRedoState.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Tango.Editors;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents an <see cref="ElementsEditor"/> undo/redo state.
+ /// </summary>
+ /// <seealso cref="Tango.Editors.IUndoRedoState" />
+ public class ElementsEditorUndoRedoState : IUndoRedoState
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ElementsEditorUndoRedoState"/> class.
+ /// </summary>
+ public ElementsEditorUndoRedoState()
+ {
+ ElementsSetups = new List<KeyValuePair<IElementEditor, List<AnimationSetup>>>();
+ }
+
+ /// <summary>
+ /// Gets or sets the animation setups which will determine the <see cref="IElementEditor"/> configuration.
+ /// </summary>
+ public List<KeyValuePair<IElementEditor,List<AnimationSetup>>> ElementsSetups { get; set; }
+
+ /// <summary>
+ /// Gets or sets the undo/redo creation date time.
+ /// </summary>
+ public DateTime Date { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ElementsEditorUndoRedoStatesProvider.cs b/Software/Visual_Studio/Tango.Editors/ElementsEditorUndoRedoStatesProvider.cs
new file mode 100644
index 000000000..22a29d6c0
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ElementsEditorUndoRedoStatesProvider.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Media.Animation;
+using Tango.Editors;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents an <see cref="ElementsEditor"/> undo/redo states provider.
+ /// </summary>
+ /// <seealso cref="Tango.Editors.UndoRedoStatesProviderBase" />
+ public class ElementsEditorUndoRedoStatesProvider : UndoRedoStatesProviderBase
+ {
+ private ElementsEditorUndoRedoExecutedEventArgs args;
+
+ /// <summary>
+ /// Gets or sets the elements editor.
+ /// </summary>
+ public ElementsEditor ElementsEditor { get; private set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ElementsEditorUndoRedoStatesProvider"/> class.
+ /// </summary>
+ /// <param name="editor">The editor.</param>
+ public ElementsEditorUndoRedoStatesProvider(ElementsEditor editor)
+ {
+ ElementsEditor = editor;
+ }
+
+ /// <summary>
+ /// Creates the a new undo/redo state.
+ /// </summary>
+ /// <returns></returns>
+ public override IUndoRedoState CreateUndoRedoState()
+ {
+ ElementsEditorUndoRedoState state = new ElementsEditorUndoRedoState();
+
+ foreach (var element in ElementsEditor.Elements)
+ {
+ var setups = element.GetAnimationSetups(TimeSpan.FromSeconds(0), AnimationSetupMode.Discrete);
+ state.Date = DateTime.Now;
+ state.ElementsSetups.Add(new KeyValuePair<IElementEditor, List<AnimationSetup>>(element, setups));
+ }
+
+ return state;
+ }
+
+ /// <summary>
+ /// Executes the an undo/redo state.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ public override void ExecuteState(IUndoRedoState state)
+ {
+ ElementsEditorUndoRedoState elementsEditorState = state as ElementsEditorUndoRedoState;
+
+ args = new ElementsEditorUndoRedoExecutedEventArgs();
+ args.State = elementsEditorState;
+
+ foreach (var elementSetups in elementsEditorState.ElementsSetups)
+ {
+ var element = elementSetups.Key;
+ var setups = elementSetups.Value;
+
+ if (!ElementsEditor.Elements.Contains(element)) //Add elements missing on editor.
+ {
+ ElementsEditor.Elements.Add(element);
+ args.RestoredElements.Add(element);
+ }
+
+ Storyboard story = new Storyboard();
+
+ foreach (var setup in setups)
+ {
+ story.Children.Add(setup.Animation);
+ String name = UIHelper.GetRandomAnimationName();
+ ElementsEditor.RegisterName(name, setup.DependencyObject);
+ Storyboard.SetTargetName(setup.Animation, name);
+ Storyboard.SetTargetProperty(setup.Animation, new System.Windows.PropertyPath(setup.DependencyProperty));
+ }
+
+ story.Completed += (x, y) =>
+ {
+ List<KeyValuePair<AnimationSetup, object>> setupValues = new List<KeyValuePair<AnimationSetup, object>>();
+
+ foreach (var setup in setups)
+ {
+ KeyValuePair<AnimationSetup, object> setupvalue = new KeyValuePair<AnimationSetup, object>(setup, setup.DependencyObject.GetValue(setup.DependencyProperty));
+ setupValues.Add(setupvalue);
+ }
+
+ foreach (var setupValue in setupValues)
+ {
+ if (setupValue.Key.DependencyObject is IAnimatable)
+ {
+ (setupValue.Key.DependencyObject as IAnimatable).BeginAnimation(setupValue.Key.DependencyProperty, null);
+ }
+ setupValue.Key.DependencyObject.SetValue(setupValue.Key.DependencyProperty, setupValue.Value);
+ }
+
+ };
+
+ story.Duration = TimeSpan.FromMilliseconds(10);
+ story.Begin(ElementsEditor);
+ }
+
+ //Remove elements that does not exist on state.
+ var elementsToRemove = ElementsEditor.Elements.Where(x => !elementsEditorState.ElementsSetups.Select(y => y.Key).ToList().Contains(x)).ToList();
+ elementsToRemove.ForEach(x =>
+ {
+ ElementsEditor.RemoveElement(x);
+ });
+
+ args.RemovedElements = elementsToRemove;
+ args.ModifiedElements = elementsEditorState.ElementsSetups.
+ Select(x => x.Key).
+ ToList().
+ Where(x => !args.RestoredElements.Contains(x) && !args.RemovedElements.Contains(x)).ToList();
+ }
+
+ /// <summary>
+ /// Raises the <see cref="E:StateExecuted" /> event.
+ /// </summary>
+ /// <param name="e">The <see cref="T:Tango.Editors.UndoRedoStateExecutedEventArgs" /> instance containing the event data.</param>
+ public override void OnStateExecuted(UndoRedoStateExecutedEventArgs e)
+ {
+ args.Mode = e.Mode;
+ base.OnStateExecuted(args);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ElementsEventArgs.cs b/Software/Visual_Studio/Tango.Editors/ElementsEventArgs.cs
new file mode 100644
index 000000000..614712883
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ElementsEventArgs.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents a general <see cref="ElementsEditor"/> event arguments.
+ /// </summary>
+ /// <seealso cref="System.EventArgs" />
+ public class ElementsEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Gets or sets the elements associated with the event.
+ /// </summary>
+ public List<IElementEditor> Elements { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to cancel the operation if possible.
+ /// </summary>
+ public bool Cancel { get; set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ElementsEventArgs"/> class.
+ /// </summary>
+ public ElementsEventArgs()
+ {
+ Elements = new List<IElementEditor>();
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ElementsEventArgs"/> class.
+ /// </summary>
+ /// <param name="elements">The elements.</param>
+ public ElementsEventArgs(List<IElementEditor> elements) : this()
+ {
+ Elements = elements;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/EnumExtensions.cs b/Software/Visual_Studio/Tango.Editors/EnumExtensions.cs
new file mode 100644
index 000000000..106dd4f44
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/EnumExtensions.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+public static class EnumExtensions
+{
+ /// <summary>
+ /// Gets the Enum value description.
+ /// </summary>
+ /// <param name="value">The value.</param>
+ /// <returns></returns>
+ public static String ToDescription(this Enum value)
+ {
+ FieldInfo fi = value.GetType().GetField(value.ToString());
+
+ DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
+
+ if (attributes != null &&
+ attributes.Length > 0)
+ return attributes[0].Description;
+ else
+ return value.ToString();
+ }
+
+ public static int ToInt32(this Enum value)
+ {
+ return (int)((object)value);
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/FrameworkElementCloner.cs b/Software/Visual_Studio/Tango.Editors/FrameworkElementCloner.cs
new file mode 100644
index 000000000..74ffa3814
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/FrameworkElementCloner.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Markup;
+using System.Xml;
+
+namespace Tango.Editors
+{
+ internal static class FrameworkElementCloner
+ {
+ /// <summary>
+ /// Clones the specified element.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <returns></returns>
+ public static FrameworkElement Clone(FrameworkElement element)
+ {
+ var sb = new StringBuilder();
+ var writer = XmlWriter.Create(sb, new XmlWriterSettings
+ {
+ Indent = true,
+ ConformanceLevel = ConformanceLevel.Fragment,
+ OmitXmlDeclaration = true,
+ NamespaceHandling = NamespaceHandling.OmitDuplicates,
+ });
+ var mgr = new XamlDesignerSerializationManager(writer);
+
+ // HERE BE MAGIC!!!
+ mgr.XamlWriterMode = XamlWriterMode.Expression;
+ // THERE WERE MAGIC!!!
+
+ XamlWriter.Save(element, mgr);
+ String xaml = sb.ToString();
+
+ StringReader stringReader = new StringReader(xaml);
+ XmlReader xmlReader = XmlReader.Create(stringReader);
+ FrameworkElement cloned = (FrameworkElement)XamlReader.Load(xmlReader) as FrameworkElement;
+ return cloned;
+ }
+
+ public static String Serialize(FrameworkElement element)
+ {
+ var sb = new StringBuilder();
+ var writer = XmlWriter.Create(sb, new XmlWriterSettings
+ {
+ Indent = true,
+ ConformanceLevel = ConformanceLevel.Fragment,
+ OmitXmlDeclaration = true,
+ NamespaceHandling = NamespaceHandling.OmitDuplicates,
+ });
+ var mgr = new XamlDesignerSerializationManager(writer);
+
+ // HERE BE MAGIC!!!
+ mgr.XamlWriterMode = XamlWriterMode.Expression;
+ // THERE WERE MAGIC!!!
+
+ XamlWriter.Save(element, mgr);
+ String xaml = sb.ToString();
+
+ return xaml;
+ }
+
+ public static FrameworkElement Deserialize(String xaml)
+ {
+ StringReader stringReader = new StringReader(xaml);
+ XmlReader xmlReader = XmlReader.Create(stringReader);
+ FrameworkElement cloned = (FrameworkElement)XamlReader.Load(xmlReader) as FrameworkElement;
+ return cloned;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/FrameworkElementEditor.xaml b/Software/Visual_Studio/Tango.Editors/FrameworkElementEditor.xaml
new file mode 100644
index 000000000..b6b0fb40e
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/FrameworkElementEditor.xaml
@@ -0,0 +1,71 @@
+<local:ElementEditor x:Class="Tango.Editors.FrameworkElementEditor"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:converters="clr-namespace:Tango.Editors.Converters"
+ xmlns:local="clr-namespace:Tango.Editors"
+ mc:Ignorable="d"
+ d:DesignHeight="300" d:DesignWidth="300" Background="Transparent" ClipToBounds="False" BorderThickness="0" MinWidth="1" MinHeight="1" RenderTransformOrigin="0.5,0.5">
+
+ <UserControl.Resources>
+ <converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"></converters:BoolToVisibilityConverter>
+ <converters:DetachedConverter x:Key="DetachedConverter"></converters:DetachedConverter>
+
+ <!--Theme-->
+ <SolidColorBrush x:Key="BorderBrush" Color="Transparent"></SolidColorBrush>
+ <SolidColorBrush x:Key="CornersBrush" Color="Red"></SolidColorBrush>
+ </UserControl.Resources>
+
+ <UserControl.RenderTransform>
+ <RotateTransform Angle="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=Angle}"></RotateTransform>
+ </UserControl.RenderTransform>
+
+ <Grid>
+ <ContentPresenter>
+ <ContentPresenter.Content>
+ <MultiBinding Converter="{StaticResource DetachedConverter}">
+ <Binding RelativeSource="{RelativeSource AncestorType=local:FrameworkElementEditor}" Path="Element"></Binding>
+ <Binding RelativeSource="{RelativeSource AncestorType=local:FrameworkElementEditor}" Path="ElementDetached"></Binding>
+ </MultiBinding>
+ </ContentPresenter.Content>
+ </ContentPresenter>
+ <Border BorderThickness="1" BorderBrush="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=BorderBrush,TargetNullValue={StaticResource BorderBrush},FallbackValue={StaticResource BorderBrush}}" Visibility="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=(local:ElementsEditor.IsSelected),Converter={StaticResource BoolToVisibilityConverter}}">
+ <Grid>
+ <ContentPresenter Content="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=InnerContent}"></ContentPresenter>
+
+ <Thumb Opacity="0" DragDelta="MoveDrag" DragStarted="DragStarted" DragCompleted="OnDragEnded"></Thumb>
+ <Thumb HorizontalAlignment="Left" Cursor="SizeWE" Opacity="0" DragDelta="DragLeft" DragStarted="DragStarted" DragCompleted="OnDragEnded"></Thumb>
+ <Thumb HorizontalAlignment="Right" Cursor="SizeWE" Opacity="0" DragDelta="DragRight" DragStarted="DragStarted" DragCompleted="OnDragEnded"></Thumb>
+ <Thumb VerticalAlignment="Top" Cursor="SizeNS" Opacity="0" DragDelta="DragTop" DragStarted="DragStarted" DragCompleted="OnDragEnded"></Thumb>
+ <Thumb VerticalAlignment="Bottom" Cursor="SizeNS" Opacity="0" DragDelta="DragBottom" DragStarted="DragStarted" DragCompleted="OnDragEnded"></Thumb>
+
+ <Grid ClipToBounds="False" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0 -20 0 0" Width="10" Height="10">
+ <Ellipse Stroke="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=CornersBrush,TargetNullValue={StaticResource CornersBrush},FallbackValue={StaticResource CornersBrush}}" StrokeThickness="2"></Ellipse>
+ <Rectangle HorizontalAlignment="Center" VerticalAlignment="Stretch" Margin="0 10 0 -8" StrokeThickness="1" Stroke="Red"></Rectangle>
+ <Thumb Opacity="0" DragDelta="DragAngle" DragStarted="DragStarted" Cursor="Arrow" DragCompleted="OnDragEnded"></Thumb>
+ </Grid>
+
+ <Grid Width="10" Height="10" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="-8 -8 0 0">
+ <Border BorderBrush="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=CornersBrush,TargetNullValue={StaticResource CornersBrush},FallbackValue={StaticResource CornersBrush}}" BorderThickness="2 2 0 0"></Border>
+ <Thumb Opacity="0" DragDelta="DragTopLeft" DragStarted="DragStarted" Cursor="SizeNWSE" DragCompleted="OnDragEnded"></Thumb>
+ </Grid>
+
+ <Grid Width="10" Height="10" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0 -8 -8 0">
+ <Border BorderBrush="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=CornersBrush,TargetNullValue={StaticResource CornersBrush},FallbackValue={StaticResource CornersBrush}}" BorderThickness="0 2 2 0"></Border>
+ <Thumb Opacity="0" DragDelta="DragTopRight" DragStarted="DragStarted" Cursor="SizeNESW" DragCompleted="OnDragEnded"></Thumb>
+ </Grid>
+
+ <Grid Width="10" Height="10" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0 0 -8 -8">
+ <Border BorderBrush="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=CornersBrush,TargetNullValue={StaticResource CornersBrush},FallbackValue={StaticResource CornersBrush}}" BorderThickness="0 0 2 2"></Border>
+ <Thumb Opacity="0" DragDelta="DragBottomRight" DragStarted="DragStarted" Cursor="SizeNWSE" DragCompleted="OnDragEnded"></Thumb>
+ </Grid>
+
+ <Grid Width="10" Height="10" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="-8 0 0 -8">
+ <Border BorderBrush="{Binding RelativeSource={RelativeSource AncestorType=local:ElementEditor},Path=CornersBrush,TargetNullValue={StaticResource CornersBrush},FallbackValue={StaticResource CornersBrush}}" BorderThickness="2 0 0 2"></Border>
+ <Thumb Opacity="0" DragDelta="DragBottomLeft" DragStarted="DragStarted" Cursor="SizeNESW" DragCompleted="OnDragEnded"></Thumb>
+ </Grid>
+ </Grid>
+ </Border>
+ </Grid>
+</local:ElementEditor>
diff --git a/Software/Visual_Studio/Tango.Editors/FrameworkElementEditor.xaml.cs b/Software/Visual_Studio/Tango.Editors/FrameworkElementEditor.xaml.cs
new file mode 100644
index 000000000..655edb27c
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/FrameworkElementEditor.xaml.cs
@@ -0,0 +1,221 @@
+using System;
+using System.Collections.Generic;
+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.Documents;
+using System.Windows.Input;
+using System.Windows.Markup;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using Tango.Editors;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// <para><img class="classImage" src="../Media/FrameworkElementEditor.png" /></para>
+ /// Represents a <see cref="FrameworkElement"/> editor with position, size and angle control.
+ /// </summary>
+ /// <example>
+ /// <para class="example-title">
+ /// <img class="exampleIcon" src="../Icons/CodeExample.png" />
+ /// <i>
+ /// The following example demonstrates a basic scenario of using <see cref="N:WpfVideoTools.Tiles"/> and <see cref="N:Tango.Editors"/> by creating a simple video projection application with the following features:
+ /// </i>
+ /// <list type="bullet">
+ /// <item>Use the mouse to draw any type of tile of the editor surface.</item>
+ /// <item>Apply any input shape on any tile.</item>
+ /// <item>Load any video and display it on any of the selected tile.</item>
+ /// <item>Built in support for undo, redo, cut, copy and paste, delete and select operations.</item>
+ /// <item>Built in support for saving/loading the editor state to memory or file.</item>
+ /// <item>Built in support for tile and editor properties adjustment using the <see cref="ParameterizedEditor"/>.</item>
+ /// </list>
+ /// </para>
+ /// <para><markup><video class="exampleVideo" autoplay="autoplay" loop="loop" controls="controls" src="../Media/EditorsExample.mp4"></video></markup></para>
+ /// <code lang="XAML" source="../FullAPIExamples/Examples/Editors/EditorsExample.xaml" title="Elements editor example." />
+ /// <i>Code-Behind.</i>
+ /// <code lang="C#" source="../FullAPIExamples/Examples/Editors/EditorsExample.xaml.cs" title="Elements editor example." />
+ /// </example>
+ /// <seealso cref="Tango.Editors.ElementEditor" />
+ [ContentProperty("InnerContent")]
+ public partial class FrameworkElementEditor : ElementEditor
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FrameworkElementEditor"/> class.
+ /// </summary>
+ public FrameworkElementEditor()
+ : base()
+ {
+ InitializeComponent();
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FrameworkElementEditor"/> class.
+ /// </summary>
+ /// <param name="frameworkElement">The framework element.</param>
+ public FrameworkElementEditor(FrameworkElement frameworkElement)
+ : this()
+ {
+ Element = frameworkElement;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FrameworkElementEditor"/> class.
+ /// </summary>
+ /// <param name="frameworkElement">The framework element.</param>
+ /// <param name="bounds">The bounds.</param>
+ public FrameworkElementEditor(FrameworkElement frameworkElement, Rect bounds)
+ : this(frameworkElement)
+ {
+ Left = bounds.Left;
+ Top = bounds.Top;
+ Width = bounds.Width;
+ Height = bounds.Height;
+ }
+
+ /// <summary>
+ /// Gets or sets the framework element.
+ /// </summary>
+ public FrameworkElement Element
+ {
+ get { return (FrameworkElement)GetValue(ElementProperty); }
+ set { SetValue(ElementProperty, value); }
+ }
+ public static readonly DependencyProperty ElementProperty =
+ DependencyProperty.Register("Element", typeof(FrameworkElement), typeof(FrameworkElementEditor), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Clones this instance.
+ /// </summary>
+ /// <returns></returns>
+ public override IElementEditor Clone()
+ {
+ try
+ {
+ FrameworkElementEditor cloned = new FrameworkElementEditor();
+
+ if (Element is IConfigurable)
+ {
+ cloned.Element = (Element as IConfigurable).Clone() as FrameworkElement;
+ }
+ else
+ {
+ cloned.Element = FrameworkElementCloner.Clone(Element);
+ }
+
+ cloned.ElementDetached = ElementDetached;
+ cloned.Top = Top;
+ cloned.Left = Left;
+ cloned.Width = Width;
+ cloned.Height = Height;
+ cloned.Angle = Angle;
+ cloned.Background = Background;
+ cloned.Foreground = Foreground;
+ cloned.CornersBrush = CornersBrush;
+ return cloned;
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException("Could not clone this editor. You may have to create a custom editor and implement a custom Clone method.", ex);
+ }
+ }
+
+ /// <summary>
+ /// Gets the hosted element.
+ /// </summary>
+ [ParameterIgnore]
+ public override Object HostedElement
+ {
+ get { return Element; }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <returns></returns>
+ public override IConfiguration GetConfiguration()
+ {
+ FrameworkElementEditorConfiguration config = new FrameworkElementEditorConfiguration(base.GetConfiguration() as ElementEditorConfiguration);
+
+ try
+ {
+ config.ElementXaml = FrameworkElementCloner.Serialize(Element);
+ }
+ catch
+ {
+ Debug.WriteLine("Could not serialize framework element with ID " + ID);
+ }
+
+ return config;
+ }
+
+ /// <summary>
+ /// Invoked when the attached editor has changed.
+ /// </summary>
+ protected override void OnAttachedEditorChanged()
+ {
+ base.OnAttachedEditorChanged();
+ }
+
+ /// <summary>
+ /// Applies the specified configuration with an optional animation if supported by the configurable type.
+ /// </summary>
+ /// <param name="configuration">The configuration.</param>
+ /// <param name="animation">The animation.</param>
+ public override void SetConfiguration(IConfiguration configuration, ConfigurationAnimation animation)
+ {
+ if (!(configuration is FrameworkElementEditorConfiguration))
+ {
+ throw new InvalidConfigurationException();
+ }
+
+ var config = configuration as FrameworkElementEditorConfiguration;
+
+ try
+ {
+ Element = FrameworkElementCloner.Deserialize(config.ElementXaml);
+ }
+ catch
+ {
+ Debug.WriteLine("Could not deserialize framework element with ID " + ID);
+ }
+
+ base.SetConfiguration(configuration, animation);
+ }
+
+ /// <summary>
+ /// Called when the editor detached property has changed.
+ /// </summary>
+ protected override void OnElementDetachedChanged()
+ {
+ base.OnElementDetachedChanged();
+
+ if (ElementDetached)
+ {
+ Element.Bind(Canvas.LeftProperty, this, LeftProperty, BindingMode.TwoWay);
+ Element.Bind(Canvas.TopProperty, this, TopProperty, BindingMode.TwoWay);
+ Element.Bind(FrameworkElement.WidthProperty, this, WidthProperty, BindingMode.TwoWay);
+ Element.Bind(FrameworkElement.HeightProperty, this, HeightProperty, BindingMode.TwoWay);
+ Element.RenderTransformOrigin = new Point(0.5, 0.5);
+ RotateTransform rotate = new RotateTransform();
+ Element.RenderTransform = rotate;
+ rotate.Bind(RotateTransform.AngleProperty, this, AngleProperty, BindingMode.TwoWay);
+ }
+ else
+ {
+ Element.Unbind(Canvas.LeftProperty);
+ Element.Unbind(Canvas.TopProperty);
+ Element.Unbind(FrameworkElement.WidthProperty);
+ Element.Unbind(FrameworkElement.HeightProperty);
+ Element.RenderTransform = null;
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/FrameworkElementEditorConfiguration.cs b/Software/Visual_Studio/Tango.Editors/FrameworkElementEditorConfiguration.cs
new file mode 100644
index 000000000..1c2220e25
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/FrameworkElementEditorConfiguration.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// <para><img class="classImage" src="../Media/Configuration.png" /></para>
+ /// Represents a <see cref="FrameworkElementEditorConfiguration"/> configuration which can be saved or load from a file or stream.
+ /// </summary>
+ /// <seealso cref="ElementEditorConfiguration" />
+ [Serializable]
+ public class FrameworkElementEditorConfiguration : ElementEditorConfiguration
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FrameworkElementEditorConfiguration"/> class.
+ /// </summary>
+ public FrameworkElementEditorConfiguration()
+ {
+
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FrameworkElementEditorConfiguration"/> class.
+ /// </summary>
+ /// <param name="baseConfiguration">The base configuration.</param>
+ public FrameworkElementEditorConfiguration(ElementEditorConfiguration baseConfiguration) : base(baseConfiguration)
+ {
+
+ }
+
+ /// <summary>
+ /// Gets or sets the element xaml string.
+ /// </summary>
+ public String ElementXaml { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/FrameworkElementExtensions.cs b/Software/Visual_Studio/Tango.Editors/FrameworkElementExtensions.cs
new file mode 100644
index 000000000..fae9d7821
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/FrameworkElementExtensions.cs
@@ -0,0 +1,66 @@
+using Tango.Editors;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+/// <exclude/>
+/// <summary>
+/// A collection of <see cref="FrameworkElement"/> extension methods.
+/// </summary>
+public static class FrameworkElementExtensions
+{
+ /// <summary>
+ /// Renders the element to a bitmap source.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <param name="forceRedraw">if set to <c>true</c> forces the redraw of the element and prevents a freezing effect.</param>
+ /// <returns></returns>
+ public static BitmapSource TakeSnapshot(this FrameworkElement element, bool forceRedraw = true)
+ {
+ var bitmap = UIHelper.TakeSnapshot(element, new Size(element.ActualWidth, element.ActualHeight));
+
+ var parent = element.Parent;
+
+ if (parent != null && forceRedraw)
+ {
+ UIHelper.RemoveChild(parent, element);
+ element.UpdateLayout();
+ UIHelper.AddChild(parent, element);
+ element.UpdateLayout();
+ }
+
+ return bitmap;
+ }
+
+ /// <summary>
+ /// Renders the element to a bitmap source of the specified size.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <param name="width">The bitmap width.</param>
+ /// <param name="height">The bitmap height.</param>
+ /// <param name="forceRedraw">if set to <c>true</c> forces the redraw of the element and prevents a freezing effect.</param>
+ /// <returns></returns>
+ public static BitmapSource TakeSnapshot(this FrameworkElement element, int width, int height, bool forceRedraw = true)
+ {
+ var bitmap = UIHelper.TakeSnapshot(element, new Size(element.ActualWidth, element.ActualHeight));
+ TransformedBitmap resizedBitmap = new TransformedBitmap(bitmap, new ScaleTransform(width / bitmap.Width, height / bitmap.Height, 0, 0));
+
+ var parent = element.Parent;
+
+ if (parent != null && forceRedraw)
+ {
+ UIHelper.RemoveChild(parent, element);
+ element.UpdateLayout();
+ UIHelper.AddChild(parent, element);
+ element.UpdateLayout();
+ }
+
+ return resizedBitmap;
+ }
+}
+
diff --git a/Software/Visual_Studio/Tango.Editors/HybridControl.cs b/Software/Visual_Studio/Tango.Editors/HybridControl.cs
new file mode 100644
index 000000000..8b1da701e
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/HybridControl.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents abstract user control with some additional features.
+ /// </summary>
+ /// <seealso cref="System.Windows.Controls.UserControl" />
+ /// <seealso cref="System.ComponentModel.INotifyPropertyChanged" />
+ public class HybridControl : UserControl, INotifyPropertyChanged
+ {
+ /// <summary>
+ /// Raises the property changed event.
+ /// </summary>
+ /// <param name="propName">Name of the property.</param>
+ protected virtual void RaisePropertyChanged(String propName)
+ {
+ if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propName));
+ }
+
+ /// <summary>
+ /// Raises all relay commands CanExecute methods in the current instance.
+ /// </summary>
+ public virtual void InvalidateRelayCommands()
+ {
+ this.Dispatcher.BeginInvoke(new Action(() =>
+ {
+ foreach (var prop in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(RelayCommand)))
+ {
+ var value = prop.GetValue(this) as RelayCommand;
+
+ if (value != null)
+ {
+ value.RaiseCanExecuteChanged();
+ }
+ }
+ }));
+ }
+
+ /// <exclude/>
+ /// <summary>
+ /// Cast the instance of a dependency object to type T.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="d">The d.</param>
+ /// <returns></returns>
+ public static T GetInstance<T>(DependencyObject d) where T : DependencyObject
+ {
+ return d as T;
+ }
+
+ /// <summary>
+ /// Occurs when a property value has changed.
+ /// </summary>
+ public event PropertyChangedEventHandler PropertyChanged;
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/IConfigurable.cs b/Software/Visual_Studio/Tango.Editors/IConfigurable.cs
new file mode 100644
index 000000000..a30b9d8a9
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/IConfigurable.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents the base interface for all configurable types.
+ /// </summary>
+ public interface IConfigurable
+ {
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <returns></returns>
+ IConfiguration GetConfiguration();
+
+ /// <summary>
+ /// Applies the specified configuration with an optional animation if supported by the configurable type.
+ /// </summary>
+ /// <param name="configuration">The configuration.</param>
+ /// <param name="animation">The animation.</param>
+ void SetConfiguration(IConfiguration configuration, ConfigurationAnimation animation);
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/IConfigurableExtensions.cs b/Software/Visual_Studio/Tango.Editors/IConfigurableExtensions.cs
new file mode 100644
index 000000000..e7b44c55f
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/IConfigurableExtensions.cs
@@ -0,0 +1,92 @@
+using Tango.Editors;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Text;
+using System.Threading.Tasks;
+
+/// <exclude/>
+/// <summary>
+/// A collection of <see cref="IConfigurable"/> extension methods.
+/// </summary>
+public static class IConfigurableExtensions
+{
+ /// <summary>
+ /// Applies the specified configuration file with an optional animation if supported by the configurable type.
+ /// </summary>
+ /// <param name="filePath">The file path to where a configuration has been saved.</param>
+ /// <param name="animation">The animation.</param>
+ public static void SetConfiguration(this IConfigurable configurable, String filePath, ConfigurationAnimation animation)
+ {
+ IConfiguration config = null;
+
+ using (FileStream fs = new FileStream(filePath, FileMode.Open))
+ {
+ BinaryFormatter f = new BinaryFormatter();
+ config = f.Deserialize(fs) as IConfiguration;
+ }
+
+ if (config != null)
+ {
+ configurable.SetConfiguration(config, animation);
+ }
+ else
+ {
+ throw new ArgumentException("The specified file path does not contain any saved configuration.");
+ }
+ }
+
+ /// <summary>
+ /// Applies the specified configuration stream with an optional animation if supported by the configurable type.
+ /// </summary>
+ /// <param name="stream">The stream where a configuration has been saved.</param>
+ /// <param name="animation">The animation.</param>
+ public static void SetConfiguration(this IConfigurable configurable, Stream stream, ConfigurationAnimation animation)
+ {
+ IConfiguration config = null;
+
+ BinaryFormatter f = new BinaryFormatter();
+ config = f.Deserialize(stream) as IConfiguration;
+
+ if (config != null)
+ {
+ configurable.SetConfiguration(config, animation);
+ }
+ else
+ {
+ throw new ArgumentException("The specified stream does not contain any saved configuration.");
+ }
+ }
+
+ /// <summary>
+ /// Applies the specified configuration with no animation.
+ /// </summary>
+ /// <param name="configurable">The configurable.</param>
+ /// <param name="configuration">The configuration.</param>
+ public static void SetConfiguration(this IConfigurable configurable, IConfiguration configuration)
+ {
+ configurable.SetConfiguration(configuration, new ConfigurationAnimation());
+ }
+
+ /// <summary>
+ /// Clones this configurable.
+ /// </summary>
+ /// <param name="configurable">The configurable instance.</param>
+ /// <returns></returns>
+ public static IConfigurable Clone(this IConfigurable configurable)
+ {
+ IConfigurable cloned = Activator.CreateInstance(configurable.GetType()) as IConfigurable;
+ cloned.SetConfiguration(configurable.GetConfiguration());
+
+ try
+ {
+ configurable.GetType().GetProperty("ID").SetValue(cloned, Guid.NewGuid().ToString());
+ }
+ catch { }
+
+ return cloned;
+ }
+}
+
diff --git a/Software/Visual_Studio/Tango.Editors/IConfiguration.cs b/Software/Visual_Studio/Tango.Editors/IConfiguration.cs
new file mode 100644
index 000000000..5b87bacdc
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/IConfiguration.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents the base interface for all configurations.
+ /// </summary>
+ public interface IConfiguration
+ {
+ /// <summary>
+ /// Gets or sets the configurable type.
+ /// </summary>
+ Type ConfigurableType { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/IConfigurationExtensions.cs b/Software/Visual_Studio/Tango.Editors/IConfigurationExtensions.cs
new file mode 100644
index 000000000..fe1afcfdb
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/IConfigurationExtensions.cs
@@ -0,0 +1,114 @@
+using Tango.Editors;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Text;
+using System.Threading.Tasks;
+
+/// <exclude/>
+/// <summary>
+/// A collection of <see cref="IConfiguration"/> extension methods.
+/// </summary>
+public static class IConfigurationExtensions
+{
+ /// <summary>
+ /// Creates a new instance of the configurable.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="config">The configuration.</param>
+ /// <returns></returns>
+ public static T CreateConfigurable<T>(this IConfiguration config) where T : class
+ {
+ return CreateConfigurable<T>(config, new ConfigurationAnimation());
+ }
+
+ /// <summary>
+ /// Creates a new instance of the exact type provided and apply the configuration.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="config">The configuration.</param>
+ /// <returns></returns>
+ public static T CreateConfigurableForceType<T>(this IConfiguration config) where T : class
+ {
+ return CreateConfigurableForceType<T>(config, new ConfigurationAnimation());
+ }
+
+ /// <summary>
+ /// Creates a new instance of the configurable.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="config">The configuration.</param>
+ /// <param name="animation">The animation configuration.</param>
+ /// <returns></returns>
+ public static T CreateConfigurable<T>(this IConfiguration config, ConfigurationAnimation animation) where T : class
+ {
+ IConfigurable instance = Activator.CreateInstance(config.ConfigurableType) as IConfigurable;
+ instance.SetConfiguration(config, animation);
+ return instance as T;
+ }
+
+ /// <summary>
+ /// Creates a new instance of the configurable.
+ /// </summary>
+ /// <param name="config">The configuration.</param>
+ /// <returns></returns>
+ public static object CreateConfigurable(this IConfiguration config)
+ {
+ return CreateConfigurable(config, new ConfigurationAnimation());
+ }
+
+ /// <summary>
+ /// Creates a new instance of the configurable.
+ /// </summary>
+ /// <param name="config">The configuration.</param>
+ /// <param name="animation">The animation.</param>
+ /// <returns></returns>
+ public static object CreateConfigurable(this IConfiguration config, ConfigurationAnimation animation)
+ {
+ IConfigurable instance = Activator.CreateInstance(config.ConfigurableType) as IConfigurable;
+ instance.SetConfiguration(config, animation);
+ return instance;
+ }
+
+ /// <summary>
+ /// Creates a new instance of the exact type provided and apply the configuration.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="config">The configuration.</param>
+ /// <param name="animation">The animation.</param>
+ /// <returns></returns>
+ public static T CreateConfigurableForceType<T>(this IConfiguration config, ConfigurationAnimation animation) where T : class
+ {
+ IConfigurable instance = Activator.CreateInstance<T>() as IConfigurable;
+ instance.SetConfiguration(config, animation);
+ return instance as T;
+ }
+
+ /// <summary>
+ /// Saves the configuration as a file.
+ /// </summary>
+ /// <param name="config">The configuration.</param>
+ /// <param name="filePath">The file path.</param>
+ public static void SaveToFile(this IConfiguration config, String filePath)
+ {
+
+ using (FileStream fs = new FileStream(filePath, FileMode.Create))
+ {
+ BinaryFormatter f = new BinaryFormatter();
+ f.Serialize(fs, config);
+ }
+ }
+
+ /// <summary>
+ /// Saves to configuration to a stream.
+ /// </summary>
+ /// <param name="config">The configuration.</param>
+ /// <param name="stream">The stream.</param>
+ public static void SaveToStream(this IConfiguration config, Stream stream)
+ {
+ BinaryFormatter f = new BinaryFormatter();
+ f.Serialize(stream, config);
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/IElementEditor.cs b/Software/Visual_Studio/Tango.Editors/IElementEditor.cs
new file mode 100644
index 000000000..fc7c93396
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/IElementEditor.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls.Primitives;
+using Tango.Editors;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents the base interface for element editors.
+ /// </summary>
+ public interface IElementEditor : IConfigurable, IParameterized
+ {
+ /// <summary>
+ /// Gets the collection of <see cref="T:Tango.Editors.AnimationSetup"/> for the current editor and element properties.
+ /// </summary>
+ /// <param name="duration">The animation duration.</param>
+ /// <param name="animationSetupMode">The animation setup mode.</param>
+ /// <remarks>
+ /// The returned collection of animation setups can be inserted into a <see cref="T:WpfVideoTools.Timeline.TimelineControl"/>.
+ /// </remarks>
+ List<AnimationSetup> GetAnimationSetups(TimeSpan duration, AnimationSetupMode animationSetupMode = AnimationSetupMode.Linear);
+
+ /// <summary>
+ /// Gets or sets the editor unique identifier.
+ /// </summary>
+ String ID { get; set; }
+
+ /// <summary>
+ /// Gets or sets the editor top position.
+ /// </summary>
+ double Top { get; set; }
+
+ /// <summary>
+ /// Gets or sets the editor left position.
+ /// </summary>
+ double Left { get; set; }
+
+ /// <summary>
+ /// Gets or sets the editor width.
+ /// </summary>
+ double Width { get; set; }
+
+ /// <summary>
+ /// Gets or sets the editor height.
+ /// </summary>
+ double Height { get; set; }
+
+ /// <summary>
+ /// Gets or sets the editor angle.
+ /// </summary>
+ double Angle { get; set; }
+
+ /// <summary>
+ /// Gets or sets the editor Z-index.
+ /// </summary>
+ int ZIndex { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the hosted element will be presented inside or outside the editor.
+ /// </summary>
+ bool ElementDetached { get; set; }
+
+ /// <summary>
+ /// Occurs when the editor is moving.
+ /// </summary>
+ event EventHandler<DragDeltaEventArgs> Moving;
+
+ /// <summary>
+ /// Occurs before editor bounds change.
+ /// </summary>
+ event EventHandler BeforeBoundsChange;
+
+ /// <summary>
+ /// Occurs after editor bounds change.
+ /// </summary>
+ event EventHandler AfterBoundsChange;
+
+ /// <summary>
+ /// Moves the editor by the specified delta arguments.
+ /// </summary>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ void PushMove(DragDeltaEventArgs e);
+
+ /// <summary>
+ /// Clones this instance.
+ /// </summary>
+ /// <returns></returns>
+ IElementEditor Clone();
+
+ /// <summary>
+ /// Gets the hosted element.
+ /// </summary>
+ Object HostedElement { get; }
+
+ /// <summary>
+ /// Sets the editor bounds.
+ /// </summary>
+ /// <param name="bounds">The bounds.</param>
+ void SetBounds(Rect bounds);
+
+ /// <summary>
+ /// Gets or sets an optional attached element for editors mirroring mode.
+ /// </summary>
+ IElementEditor AttachedEditor { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/IParameterized.cs b/Software/Visual_Studio/Tango.Editors/IParameterized.cs
new file mode 100644
index 000000000..d0a32f209
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/IParameterized.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents a component that exposes some of it's properties as an observable collection of <see cref="ParameterItem"/> which can be bound to UI controls.
+ /// <para>
+ /// Use the <see cref="T:WpfVideoTools.Editors.ParameterizedEditor"/> to automatically display and edit the collection of parameters.
+ /// </para>
+ /// <para>
+ /// See
+ /// </para>
+ /// <list type="bullet">
+ /// <item><see cref="T:WpfVideoTools.Editors.ParameterizedEditor"/></item>
+ /// <item><see cref="T:WpfVideoTools.Editors.IParameterItemEditor"/></item>
+ /// </list>
+ /// </summary>
+ /// <example>
+ /// <para class="example-title">
+ /// <img class="exampleIcon" src="../Icons/CodeExample.png" />
+ /// <i>
+ /// The following example demonstrates how to implement the IParameterized interface, implement a custom parameter editor and use the ParameterizedEditor to display/edit the object.
+ /// </i>
+ /// </para>
+ /// <para><img class="exampleImage" src="../Media/ParameterizedEditorExample.png" /></para>
+ /// <i>Implement IParameterized.</i>
+ /// <code lang="C#" source="../FullAPIExamples/Examples/Core/IParameterizedExample.cs" title="Implement IParameterized interface." />
+ /// <i>Custom editor.</i>
+ /// <code lang="XAML" source="../FullAPIExamples/Examples/Core/CustomEditor.xaml" title="Implement custom editor." />
+ /// <i>Custom editor code behind.</i>
+ /// <code lang="C#" source="../FullAPIExamples/Examples/Core/CustomEditor.xaml.cs" title="Implement custom editor." />
+ /// <i>Use the ParameterizedEditor to display the parameterized object.</i>
+ /// <code lang="XAML" source="../FullAPIExamples/Examples/Core/ParameterizedEditorExample.xaml" title="Use ParameterizedEditor." />
+ /// <i>Code-Behind.</i>
+ /// <code lang="C#" source="../FullAPIExamples/Examples/Core/ParameterizedEditorExample.xaml.cs" title="Use ParameterizedEditor." />
+ /// </example>
+ /// <seealso cref="T:WpfVideoTools.Editors.ParameterizedEditor" />
+ /// <seealso cref="T:WpfVideoTools.Editors.IParameterItemEditor" />
+ public interface IParameterized
+ {
+ /// <summary>
+ /// Gets a bind-able observable collection of the component properties.
+ /// </summary>
+ [ParameterIgnore]
+ ReadOnlyObservableCollection<ParameterItem> Parameters { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/IParameterizedExtensions.cs b/Software/Visual_Studio/Tango.Editors/IParameterizedExtensions.cs
new file mode 100644
index 000000000..d238cd297
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/IParameterizedExtensions.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using Tango.Editors;
+
+/// <summary>
+/// Contains extension methods for <see cref="IParameterized"/>.
+/// </summary>
+public static class IParameterizedExtensions
+{
+ /// <summary>
+ /// Creates an observable collection of the parameterized object.
+ /// </summary>
+ /// <param name="instance">The instance.</param>
+ /// <param name="mode">The parameters update mode.</param>
+ /// <returns></returns>
+ public static ObservableCollection<ParameterItem> CreateParametersCollection(this IParameterized instance, ParameterItemMode mode)
+ {
+ var ps = new ObservableCollection<ParameterItem>();
+
+ int index = 0;
+
+ List<Type> types = new List<Type>();
+ Type currentType = instance.GetType();
+
+ while (true)
+ {
+ if (typeof(IParameterized).IsAssignableFrom(currentType) && currentType != typeof(IParameterized))
+ {
+ types.Add(currentType);
+ currentType = currentType.BaseType;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ List<PropertyInfo> properties = new List<PropertyInfo>();
+
+ foreach (var type in types)
+ {
+ foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
+ {
+ var paramAtt = prop.GetCustomAttributes(typeof(ParameterItemAttribute), false).Cast<ParameterItemAttribute>().FirstOrDefault();
+ var ignore = prop.GetCustomAttributes(typeof(ParameterIgnoreAttribute), false).Cast<ParameterIgnoreAttribute>().FirstOrDefault();
+
+ if (ignore == null && !properties.Exists(x => x.Name == prop.Name))
+ {
+ var item = instance.CreateParameterItem(prop, paramAtt, index++, mode);
+ ps.Add(item);
+ properties.Add(prop);
+ }
+ }
+ }
+
+ return ps;
+ }
+
+ /// <summary>
+ /// Creates the parameter item.
+ /// </summary>
+ /// <param name="instance">The instance.</param>
+ /// <param name="propertyInfo">The property information.</param>
+ /// <param name="attribute">The attribute.</param>
+ /// <param name="index">The index.</param>
+ /// <param name="mode">The mode.</param>
+ /// <returns></returns>
+ public static ParameterItem CreateParameterItem(this IParameterized instance, PropertyInfo propertyInfo, ParameterItemAttribute attribute, int index, ParameterItemMode mode)
+ {
+ ParameterItem item = new ParameterItem();
+ item.Name = propertyInfo.Name.ToTitle();
+ item.Index = index;
+ item.Type = propertyInfo.PropertyType;
+ item.Value = propertyInfo.GetValue(instance, null);
+
+ if (attribute != null)
+ {
+ item.Minimum = attribute.Minimum;
+ item.Maximum = attribute.Maximum;
+ item.CustomEditorTypeName = attribute.CustomEditorTypeName;
+ item.ExtraObject = attribute.ExtraObject;
+
+ if (attribute.Name != null)
+ {
+ item.Name = attribute.Name;
+ }
+ }
+
+ if (mode == ParameterItemMode.Event)
+ {
+ item.ParameterValueChanged += (sender, e) =>
+ {
+ propertyInfo.SetValue(instance, e.Value);
+ };
+ }
+ else if (mode == ParameterItemMode.Binding)
+ {
+ item.Bind(ParameterItem.ValueProperty, instance as DependencyObject, propertyInfo.Name, System.Windows.Data.BindingMode.TwoWay);
+ }
+
+ return item;
+ }
+
+ /// <summary>
+ /// Creates the parameter item.
+ /// </summary>
+ /// <param name="instance">The instance.</param>
+ /// <param name="propertyName">Name of the property.</param>
+ /// <param name="attribute">The attribute.</param>
+ /// <param name="index">The index.</param>
+ /// <param name="mode">The mode.</param>
+ /// <returns></returns>
+ public static ParameterItem CreateParameterItem(this IParameterized instance, String propertyName, ParameterItemAttribute attribute, int index, ParameterItemMode mode)
+ {
+ return instance.CreateParameterItem(instance.GetType().GetProperty(propertyName), attribute, index, mode);
+ }
+}
+
diff --git a/Software/Visual_Studio/Tango.Editors/ISupportEditingOperations.cs b/Software/Visual_Studio/Tango.Editors/ISupportEditingOperations.cs
new file mode 100644
index 000000000..539c1f5f9
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ISupportEditingOperations.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents a component which support Cut, Copy and Paste operations.
+ /// </summary>
+ public interface ISupportEditingOperations
+ {
+ /// <summary>
+ /// Gets or sets a value indicating whether to enable editing operations using the keyboard.
+ /// </summary>
+ bool EnableKeyboardEditingOperations { get; set; }
+
+ /// <summary>
+ /// Copies and deletes the selected objects.
+ /// </summary>
+ void Cut();
+
+ /// <summary>
+ /// Copies the selected objects.
+ /// </summary>
+ void Copy();
+
+ /// <summary>
+ /// Pastes the last copied objects.
+ /// </summary>
+ void Paste();
+
+ /// <summary>
+ /// Invokes the <see cref="Cut"/> method.
+ /// </summary>
+ RelayCommand CutCommand { get; set; }
+
+ /// <summary>
+ /// Invokes the <see cref="Copy"/> method.
+ /// </summary>
+ RelayCommand CopyCommand { get; set; }
+
+ /// <summary>
+ /// Invokes the <see cref="Paste"/> method.
+ /// </summary>
+ RelayCommand PasteCommand { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ISupportUndoRedoOperations.cs b/Software/Visual_Studio/Tango.Editors/ISupportUndoRedoOperations.cs
new file mode 100644
index 000000000..1e67b2fa5
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ISupportUndoRedoOperations.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents a component which supports Undo/Redo operations.
+ /// </summary>
+ public interface ISupportUndoRedoOperations
+ {
+ /// <summary>
+ /// Gets or sets a value indicating whether to enable undo and redo operations using the keyboard.
+ /// </summary>
+ bool EnableKeyboardUndoRedoOperations { get; set; }
+
+ /// <summary>
+ /// Gets or sets the undo redo states provider instance.
+ /// </summary>
+ IUndoRedoStatesProvider UndoRedoStatesProvider { get; set; }
+
+ /// <summary>
+ /// Performs undo operation.
+ /// </summary>
+ void Undo();
+
+ /// <summary>
+ /// Redoes the last undo operation.
+ /// </summary>
+ void Redo();
+
+ /// <summary>
+ /// Invokes the <see cref="Undo"/> method.
+ /// </summary>
+ RelayCommand UndoCommand { get; set; }
+
+ /// <summary>
+ /// Invokes the <see cref="Redo"/> method.
+ /// </summary>
+ RelayCommand RedoCommand { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/IUndoRedoState.cs b/Software/Visual_Studio/Tango.Editors/IUndoRedoState.cs
new file mode 100644
index 000000000..c4d455624
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/IUndoRedoState.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents an undo/redo state.
+ /// </summary>
+ public interface IUndoRedoState
+ {
+ /// <summary>
+ /// Gets or sets the undo/redo creation date time.
+ /// </summary>
+ DateTime Date { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/IUndoRedoStatesProvider.cs b/Software/Visual_Studio/Tango.Editors/IUndoRedoStatesProvider.cs
new file mode 100644
index 000000000..9735a94a8
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/IUndoRedoStatesProvider.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents an undo/redo state provider.
+ /// </summary>
+ public interface IUndoRedoStatesProvider
+ {
+ /// <summary>
+ /// Occurs when an Undo/Redo state was executed.
+ /// </summary>
+ event EventHandler<UndoRedoStateExecutedEventArgs> StateExecuted;
+
+ /// <summary>
+ /// Gets the undo states.
+ /// </summary>
+ Stack<IUndoRedoState> UndoStates { get; }
+
+ /// <summary>
+ /// Gets the redo states.
+ /// </summary>
+ Stack<IUndoRedoState> RedoStates { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance can undo.
+ /// </summary>
+ bool CanUndo { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance can redo.
+ /// </summary>
+ bool CanRedo { get; }
+
+ /// <summary>
+ /// Prepares the state of the undo.
+ /// </summary>
+ void PrepareUndoState();
+
+ /// <summary>
+ /// Creates the a new undo/redo state.
+ /// </summary>
+ /// <returns></returns>
+ IUndoRedoState CreateUndoRedoState();
+
+ /// <summary>
+ /// Executes the an undo/redo state.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ void ExecuteState(IUndoRedoState state);
+
+ /// <summary>
+ /// Commits the state created by <see cref="PrepareUndoState"/>.
+ /// </summary>
+ void CommitUndoState();
+
+ /// <summary>
+ /// Performs an undo operation.
+ /// </summary>
+ void Undo();
+
+ /// <summary>
+ /// Performs a redo operation.
+ /// </summary>
+ void Redo();
+
+ /// <summary>
+ /// Resets undo and redo states.
+ /// </summary>
+ void Reset();
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/InvalidConfigurationException.cs b/Software/Visual_Studio/Tango.Editors/InvalidConfigurationException.cs
new file mode 100644
index 000000000..52e6a3d9b
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/InvalidConfigurationException.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents an invalid configuration exception.
+ /// </summary>
+ /// <seealso cref="System.Exception" />
+ public class InvalidConfigurationException : Exception
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InvalidConfigurationException"/> class.
+ /// </summary>
+ public InvalidConfigurationException() : base("Invalid Configuration Object!")
+ {
+
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="InvalidConfigurationException"/> class.
+ /// </summary>
+ /// <param name="message">The message that describes the error.</param>
+ public InvalidConfigurationException(String message) : base(message)
+ {
+
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ObjectExtensions.cs b/Software/Visual_Studio/Tango.Editors/ObjectExtensions.cs
new file mode 100644
index 000000000..7f584a8f8
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ObjectExtensions.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+/// <exclude/>
+/// <summary>
+/// A collection of <see cref="Object"/> extension methods.
+/// </summary>
+internal static class ObjectExtensions
+{
+ /// <summary>
+ /// Tries to copy the specified source object properties to the target object properties.
+ /// </summary>
+ /// <param name="sourceObject">The source object.</param>
+ /// <param name="targetObject">The target object.</param>
+ internal static void MapTo(this object sourceObject,object targetObject)
+ {
+ foreach (PropertyInfo prop in sourceObject.GetType().GetProperties())
+ {
+ PropertyInfo prop2 = sourceObject.GetType().GetProperty(prop.Name);
+ prop2.SetValue(targetObject, prop.GetValue(sourceObject, null), null);
+ }
+ }
+
+ /// <summary>
+ /// Returns the name of the specified property.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <typeparam name="TT">The type of the t.</typeparam>
+ /// <param name="obj">The object.</param>
+ /// <param name="propertyAccessor">The property accessor.</param>
+ /// <returns></returns>
+ internal static String NameOf<T, TT>(this T obj, Expression<Func<T, TT>> propertyAccessor)
+ {
+ if (propertyAccessor.Body.NodeType == ExpressionType.MemberAccess)
+ {
+ var memberExpression = propertyAccessor.Body as MemberExpression;
+ if (memberExpression == null)
+ return null;
+ return memberExpression.Member.Name;
+ }
+ return null;
+ }
+
+ internal static string ToStringReflection(this object obj)
+ {
+ StringBuilder sb = new StringBuilder();
+ foreach (System.Reflection.PropertyInfo property in obj.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.DeclaredOnly))
+ {
+
+ sb.Append(property.Name);
+ sb.Append(": ");
+ if (property.GetIndexParameters().Length > 0)
+ {
+ sb.Append("Indexed Property cannot be used");
+ }
+ else
+ {
+ sb.Append(property.GetValue(obj, null));
+ }
+
+ sb.Append(System.Environment.NewLine);
+ }
+
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// Extension for 'Object' that copies the properties to a destination object.
+ /// </summary>
+ /// <param name="source">The source.</param>
+ /// <param name="destination">The destination.</param>
+ internal static void CopyProperties(this object source, object destination)
+ {
+ // If any this null throw an exception
+ if (source == null || destination == null)
+ throw new Exception("Source or/and Destination Objects are null");
+ // Getting the Types of the objects
+ Type typeDest = destination.GetType();
+ Type typeSrc = source.GetType();
+
+ // Iterate the Properties of the source instance and
+ // populate them from their desination counterparts
+ PropertyInfo[] srcProps = typeSrc.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.DeclaredOnly);
+ foreach (PropertyInfo srcProp in srcProps)
+ {
+ //if (!srcProp.CanRead)
+ //{
+ // continue;
+ //}
+ //PropertyInfo targetProperty = typeDest.GetProperty(srcProp.Name);
+ //if (targetProperty == null)
+ //{
+ // continue;
+ //}
+ //if (!targetProperty.CanWrite)
+ //{
+ // continue;
+ //}
+ //if (targetProperty.GetSetMethod(true) != null && targetProperty.GetSetMethod(true).IsPrivate)
+ //{
+ // continue;
+ //}
+ //if ((targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) != 0)
+ //{
+ // continue;
+ //}
+ //if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType))
+ //{
+ // continue;
+ //}
+ // Passed all tests, lets set the value
+ srcProp.SetValue(destination, srcProp.GetValue(source, null), null);
+ }
+ }
+}
+
diff --git a/Software/Visual_Studio/Tango.Editors/ParameterIgnoreAttribute.cs b/Software/Visual_Studio/Tango.Editors/ParameterIgnoreAttribute.cs
new file mode 100644
index 000000000..f94f7d354
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ParameterIgnoreAttribute.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents an attribute for specifying properties to ignore as parameters.
+ /// </summary>
+ /// <seealso cref="System.Attribute" />
+ public class ParameterIgnoreAttribute : Attribute
+ {
+
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ParameterItem.cs b/Software/Visual_Studio/Tango.Editors/ParameterItem.cs
new file mode 100644
index 000000000..4f08d25d6
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ParameterItem.cs
@@ -0,0 +1,152 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents a component parameter name and value which can be bound to UI elements.
+ /// </summary>
+ /// <seealso cref="System.Windows.DependencyObject" />
+ public class ParameterItem : DependencyObject
+ {
+ /// <summary>
+ /// Occurs when the parameter value has changed.
+ /// </summary>
+ public event EventHandler<ParameterItem> ParameterValueChanged;
+
+ /// <summary>
+ /// Gets or sets the parameter index.
+ /// </summary>
+ public int Index
+ {
+ get { return (int)GetValue(IndexProperty); }
+ set { SetValue(IndexProperty, value); }
+ }
+ public static readonly DependencyProperty IndexProperty =
+ DependencyProperty.Register("Index", typeof(int), typeof(ParameterItem), new PropertyMetadata(0));
+
+ /// <summary>
+ /// Gets or sets the parameter value type.
+ /// </summary>
+ public Type Type
+ {
+ get { return (Type)GetValue(TypeProperty); }
+ set { SetValue(TypeProperty, value); }
+ }
+ public static readonly DependencyProperty TypeProperty =
+ DependencyProperty.Register("Type", typeof(Type), typeof(ParameterItem), new PropertyMetadata(typeof(double)));
+
+
+ /// <summary>
+ /// Gets or sets the parameter name.
+ /// </summary>
+ public String Name
+ {
+ get { return (String)GetValue(NameProperty); }
+ set { SetValue(NameProperty, value); }
+ }
+ public static readonly DependencyProperty NameProperty =
+ DependencyProperty.Register("Name", typeof(String), typeof(ParameterItem), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets or sets the current parameter value.
+ /// </summary>
+ public object Value
+ {
+ get { return (object)GetValue(ValueProperty); }
+ set { SetValue(ValueProperty, value); }
+ }
+ public static readonly DependencyProperty ValueProperty =
+ DependencyProperty.Register("Value", typeof(object), typeof(ParameterItem), new PropertyMetadata(null, new PropertyChangedCallback(OnValueChanged)));
+
+
+ /// <summary>
+ /// Gets or sets the minimum parameter value.
+ /// </summary>
+ public object Minimum
+ {
+ get { return (object)GetValue(MinimumProperty); }
+ set { SetValue(MinimumProperty, value); }
+ }
+ public static readonly DependencyProperty MinimumProperty =
+ DependencyProperty.Register("Minimum", typeof(object), typeof(ParameterItem), new PropertyMetadata(0.0d));
+
+
+ /// <summary>
+ /// Gets or sets the maximum parameter value.
+ /// </summary>
+ public object Maximum
+ {
+ get { return (object)GetValue(MaximumProperty); }
+ set { SetValue(MaximumProperty, value); }
+ }
+ public static readonly DependencyProperty MaximumProperty =
+ DependencyProperty.Register("Maximum", typeof(object), typeof(ParameterItem), new PropertyMetadata(1.0d));
+
+
+ /// <summary>
+ /// Gets or sets an optional extra object.
+ /// </summary>
+ public object ExtraObject
+ {
+ get { return (object)GetValue(ExtraObjectProperty); }
+ set { SetValue(ExtraObjectProperty, value); }
+ }
+ public static readonly DependencyProperty ExtraObjectProperty =
+ DependencyProperty.Register("ExtraObject", typeof(object), typeof(ParameterItem), new PropertyMetadata(null));
+
+
+ /// <summary>
+ /// Gets or sets an optional custom editor.
+ /// </summary>
+ public String CustomEditorTypeName
+ {
+ get { return (String)GetValue(CustomEditorTypeNameProperty); }
+ set { SetValue(CustomEditorTypeNameProperty, value); }
+ }
+ public static readonly DependencyProperty CustomEditorTypeNameProperty =
+ DependencyProperty.Register("CustomEditorType", typeof(String), typeof(ParameterItem), new PropertyMetadata(null));
+
+
+ /// <summary>
+ /// Gets a value indicating whether this instance requires custom editor.
+ /// </summary>
+ public bool HasCustomEditor
+ {
+ get { return CustomEditorTypeName != null; }
+ }
+
+ /// <summary>
+ /// Called when value has changed.
+ /// </summary>
+ /// <param name="d">The d.</param>
+ /// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
+ private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ (d as ParameterItem).OnParameterChanged();
+ }
+
+ /// <summary>
+ /// Called when the parameter value has changed.
+ /// </summary>
+ protected virtual void OnParameterChanged()
+ {
+ if (ParameterValueChanged != null) ParameterValueChanged(this, this);
+ }
+
+ /// <summary>
+ /// Returns a <see cref="System.String" /> that represents this instance.
+ /// </summary>
+ /// <returns>
+ /// A <see cref="System.String" /> that represents this instance.
+ /// </returns>
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ParameterItemAttribute.cs b/Software/Visual_Studio/Tango.Editors/ParameterItemAttribute.cs
new file mode 100644
index 000000000..c914dd156
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ParameterItemAttribute.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents a parameter item attribute for used for default, min and max values definition of properties.
+ /// </summary>
+ /// <seealso cref="System.Attribute" />
+ public class ParameterItemAttribute : Attribute
+ {
+ /// <summary>
+ /// Gets or sets the custom parameter name.
+ /// </summary>
+ public String Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the default value.
+ /// </summary>
+ public object Default { get; set; }
+
+ /// <summary>
+ /// Gets or sets the minimum value.
+ /// </summary>
+ public object Minimum { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum value.
+ /// </summary>
+ public object Maximum { get; set; }
+
+ /// <summary>
+ /// Gets or sets an optional extra object.
+ /// </summary>
+ public object ExtraObject { get; set; }
+
+ /// <summary>
+ /// Gets or sets an optional custom editor type name.
+ /// </summary>
+ public String CustomEditorTypeName { get; set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ParameterItemAttribute"/> class.
+ /// </summary>
+ public ParameterItemAttribute()
+ {
+ Default = null;
+ Minimum = 0.0d;
+ Maximum = 1.0d;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ParameterItemAttribute"/> class.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="minimumValue">The minimum value.</param>
+ /// <param name="maximumValue">The maximum value.</param>
+ /// <param name="defaultValue">The default value.</param>
+ public ParameterItemAttribute(String name, object minimumValue, object maximumValue, object defaultValue) : this()
+ {
+ Name = name;
+ Default = defaultValue;
+ Minimum = minimumValue;
+ Maximum = maximumValue;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ParameterItemAttribute"/> class.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="minimumValue">The minimum value.</param>
+ /// <param name="maximumValue">The maximum value.</param>
+ public ParameterItemAttribute(String name, object minimumValue, double maximumValue) : this(name, minimumValue, maximumValue, null)
+ {
+
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ParameterItemAttribute"/> class.
+ /// </summary>
+ /// <param name="minimumValue">The minimum value.</param>
+ /// <param name="maximumValue">The maximum value.</param>
+ public ParameterItemAttribute(object minimumValue, object maximumValue) : this()
+ {
+ Minimum = minimumValue;
+ Maximum = maximumValue;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ParameterItemAttribute"/> class.
+ /// </summary>
+ /// <param name="customParameterEditorTypeName">Type of the custom parameter editor type name.</param>
+ public ParameterItemAttribute(String customParameterEditorTypeName) : this()
+ {
+ CustomEditorTypeName = customParameterEditorTypeName;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ParameterItemAttribute"/> class.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="customParameterEditorTypeName">Name of the custom parameter editor type.</param>
+ /// <param name="minimumValue">The minimum value.</param>
+ /// <param name="maximumValue">The maximum value.</param>
+ /// <param name="defaultValue">The default value.</param>
+ /// <param name="extraObject">The extra object.</param>
+ public ParameterItemAttribute(String name, String customParameterEditorTypeName, object minimumValue, object maximumValue, object defaultValue, object extraObject) : this()
+ {
+ Name = name;
+ Minimum = minimumValue;
+ Maximum = maximumValue;
+ Default = defaultValue;
+ CustomEditorTypeName = customParameterEditorTypeName;
+ ExtraObject = extraObject;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ParameterItemAttribute"/> class.
+ /// </summary>
+ /// <param name="name">The name.</param>
+ /// <param name="customParameterEditorTypeName">Custom parameter editor type.</param>
+ /// <param name="minimumValue">The minimum value.</param>
+ /// <param name="maximumValue">The maximum value.</param>
+ /// <param name="defaultValue">The default value.</param>
+ /// <param name="extraObject">The extra object.</param>
+ public ParameterItemAttribute(String name, Type customParameterEditorType, object minimumValue, object maximumValue, object defaultValue, object extraObject)
+ : this()
+ {
+ Name = name;
+ Minimum = minimumValue;
+ Maximum = maximumValue;
+ Default = defaultValue;
+ CustomEditorTypeName = customParameterEditorType.AssemblyQualifiedName;
+ ExtraObject = extraObject;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/ParameterItemMode.cs b/Software/Visual_Studio/Tango.Editors/ParameterItemMode.cs
new file mode 100644
index 000000000..2c3ed373c
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/ParameterItemMode.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents a <see cref="ParameterItem"/> update mode.
+ /// </summary>
+ public enum ParameterItemMode
+ {
+ /// <summary>
+ /// Update by binding.
+ /// </summary>
+ Binding,
+ /// <summary>
+ /// Update by event.
+ /// </summary>
+ Event
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/PixelRuler.cs b/Software/Visual_Studio/Tango.Editors/PixelRuler.cs
new file mode 100644
index 000000000..d63cb5878
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/PixelRuler.cs
@@ -0,0 +1,488 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// A ruler el which displays ruler in pixels.
+ /// In order to use it vertically, change the <see cref="Marks">Marks</see> property to <c>Up</c> and rotate it ninety degrees.
+ /// </summary>
+ /// <remarks>
+ /// Rewritten by: Sebestyen Murancsik
+ ///
+ /// Contributions from <see
+ /// cref="http://www.orbifold.net/default/?p=2295&amp;cpage=1#comment-61500">Raf
+ /// Lenfers</see>
+ /// <seealso>http://visualizationtools.net/default/wpf-ruler/</seealso>
+ /// </remarks>
+ internal class PixelRuler : Control
+ {
+ internal enum RulerOrientationEnum
+ {
+ Horizontal,
+ Vertical
+ }
+
+ #region Fields
+ private double SegmentHeight;
+ private Pen p = new Pen(Brushes.Orange, 1.0);
+ private Pen markerPen = new Pen(Brushes.Orange, 0.5);
+ private Pen BorderPen = new Pen(Brushes.Orange, 1.0);
+ private Pen RedPen = new Pen(Brushes.Red, 2.0);
+ #endregion
+
+ #region Properties
+
+
+
+ public RulerOrientationEnum Orientation
+ {
+ get { return (RulerOrientationEnum)GetValue(OrientationProperty); }
+ set { SetValue(OrientationProperty, value); }
+ }
+
+ // Using a DependencyProperty as the backing store for Orientation. This enables animation, styling, binding, etc...
+ public static readonly DependencyProperty OrientationProperty =
+ DependencyProperty.Register("Orientation", typeof(RulerOrientationEnum), typeof(PixelRuler), new PropertyMetadata(RulerOrientationEnum.Horizontal));
+
+
+
+ #region Length
+ /// <summary>
+ /// Gets or sets the length of the ruler. If the <see cref="AutoSize"/> property is set to false (default) this
+ /// is a fixed length. Otherwise the length is calculated based on the actual width of the ruler.
+ /// </summary>
+ public double Length
+ {
+ get
+ {
+ if (this.AutoSize)
+ {
+ if (Orientation == RulerOrientationEnum.Horizontal)
+ {
+ return ActualWidth / Zoom;
+ }
+ else
+ {
+ return ActualHeight / Zoom;
+ }
+ }
+ else
+ {
+ return (double)GetValue(LengthProperty);
+ }
+ }
+ set
+ {
+ SetValue(LengthProperty, value);
+ }
+ }
+
+ /// <summary>
+ /// Identifies the Length dependency property.
+ /// </summary>
+ public static readonly DependencyProperty LengthProperty =
+ DependencyProperty.Register(
+ "Length",
+ typeof(double),
+ typeof(PixelRuler),
+ new FrameworkPropertyMetadata(20D, FrameworkPropertyMetadataOptions.AffectsRender));
+ #endregion
+
+ #region AutoSize
+ /// <summary>
+ /// Gets or sets the AutoSize behavior of the ruler.
+ /// false (default): the lenght of the ruler results from the <see cref="Length"/> property. If the window size is changed, e.g. wider
+ /// than the rulers length, free space is shown at the end of the ruler. No rescaling is done.
+ /// true : the length of the ruler is always adjusted to its actual width. This ensures that the ruler is shown
+ /// for the actual width of the window.
+ /// </summary>
+ public bool AutoSize
+ {
+ get
+ {
+ return (bool)GetValue(AutoSizeProperty);
+ }
+ set
+ {
+ SetValue(AutoSizeProperty, value);
+ this.InvalidateVisual();
+ }
+ }
+
+ /// <summary>
+ /// Identifies the AutoSize dependency property.
+ /// </summary>
+ public static readonly DependencyProperty AutoSizeProperty =
+ DependencyProperty.Register(
+ "AutoSize",
+ typeof(bool),
+ typeof(PixelRuler),
+ new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
+ #endregion
+
+ #region Zoom
+ /// <summary>
+ /// Gets or sets the zoom factor for the ruler. The default value is 1.0.
+ /// </summary>
+ public double Zoom
+ {
+ get
+ {
+ return (double)GetValue(ZoomProperty);
+ }
+ set
+ {
+ SetValue(ZoomProperty, value);
+ this.InvalidateVisual();
+ }
+ }
+
+ /// <summary>
+ /// Identifies the Zoom dependency property.
+ /// </summary>
+ public static readonly DependencyProperty ZoomProperty =
+ DependencyProperty.Register("Zoom", typeof(double), typeof(PixelRuler),
+ new FrameworkPropertyMetadata((double)1.0,
+ FrameworkPropertyMetadataOptions.AffectsRender));
+
+ #endregion
+
+ #region SmallStep
+ /// <summary>
+ /// Gets or sets the small step for the ruler. The default value is 25.0.
+ /// </summary>
+ public double SmallStep
+ {
+ get
+ {
+ return (double)GetValue(SmallStepProperty);
+ }
+ set
+ {
+ SetValue(SmallStepProperty, value);
+ }
+ }
+
+ /// <summary>
+ /// Identifies the Zoom dependency property.
+ /// </summary>
+ public static readonly DependencyProperty SmallStepProperty =
+ DependencyProperty.Register("SmallStep", typeof(double), typeof(PixelRuler),
+ new FrameworkPropertyMetadata((double)25.0,
+ FrameworkPropertyMetadataOptions.AffectsRender));
+
+ #endregion
+
+ #region Step
+ /// <summary>
+ /// Gets or sets the step for the ruler. The default value is 100.0.
+ /// </summary>
+ public double Step
+ {
+ get
+ {
+ return (double)GetValue(StepProperty);
+ }
+ set
+ {
+ SetValue(StepProperty, value);
+ }
+ }
+
+ /// <summary>
+ /// Identifies the Zoom dependency property.
+ /// </summary>
+ public static readonly DependencyProperty StepProperty =
+ DependencyProperty.Register("Step", typeof(double), typeof(PixelRuler),
+ new FrameworkPropertyMetadata((double)100.0,
+ FrameworkPropertyMetadataOptions.AffectsRender));
+
+ #endregion
+
+ #region Chip
+
+ /// <summary>
+ /// Chip Dependency Property
+ /// </summary>
+ public static readonly DependencyProperty ChipProperty =
+ DependencyProperty.Register("Chip", typeof(double), typeof(PixelRuler),
+ new FrameworkPropertyMetadata((double)-1000,
+ FrameworkPropertyMetadataOptions.AffectsRender));
+
+ /// <summary>
+ /// Sets the location of the chip in the units of the ruler.
+ /// So, to set the chip to 100px units the chip needs to be set to 100.
+ /// Use the <see cref="DipHelper"/> class for conversions.
+ /// </summary>
+ public double Chip
+ {
+ get { return (double)GetValue(ChipProperty); }
+ set { SetValue(ChipProperty, value); }
+ }
+ #endregion
+
+ #region CountShift
+
+ /// <summary>
+ /// CountShift Dependency Property
+ /// </summary>
+ public static readonly DependencyProperty CountShiftProperty =
+ DependencyProperty.Register("CountShift", typeof(int), typeof(PixelRuler),
+ new FrameworkPropertyMetadata(0,
+ FrameworkPropertyMetadataOptions.AffectsRender));
+
+ /// <summary>
+ /// By default the counting of numbers starts at zero, this property allows you to shift
+ /// the counting.
+ /// </summary>
+ public int CountShift
+ {
+ get { return (int)GetValue(CountShiftProperty); }
+ set { SetValue(CountShiftProperty, value); }
+ }
+
+ #endregion
+
+ #region Marks
+
+ /// <summary>
+ /// Marks Dependency Property
+ /// </summary>
+ public static readonly DependencyProperty MarksProperty =
+ DependencyProperty.Register("Marks", typeof(MarksLocation), typeof(PixelRuler),
+ new FrameworkPropertyMetadata(MarksLocation.Up,
+ FrameworkPropertyMetadataOptions.AffectsRender));
+
+ /// <summary>
+ /// Gets or sets where the marks are shown in the ruler.
+ /// </summary>
+ public MarksLocation Marks
+ {
+ get { return (MarksLocation)GetValue(MarksProperty); }
+ set { SetValue(MarksProperty, value); }
+ }
+
+ #endregion
+
+ #endregion
+
+ #region Constructors
+
+ public PixelRuler()
+ {
+
+ }
+
+ #endregion
+
+ #region Methods
+
+ private void SetSteps()
+ {
+ var value = Zoom;
+
+ if (value >= 4.8)
+ {
+ SmallStep = 5;
+ Step = 10;
+ }
+ else if (value >= 4)
+ {
+ SmallStep = 5;
+ Step = 10;
+ }
+ else if (value >= 3)
+ {
+ SmallStep = 10;
+ Step = 20;
+ }
+ else if (value >= 2)
+ {
+ SmallStep = (12.5) / 2;
+ Step = 25;
+ }
+ else if (value >= 1.5)
+ {
+ SmallStep = 12.5;
+ Step = 50;
+ }
+ else if (value >= 1)
+ {
+ SmallStep = 25;
+ Step = 100;
+ }
+ else if (value <= 0.2)
+ {
+ SmallStep = 100;
+ Step = 400;
+ }
+ else if (value <= 0.5)
+ {
+ SmallStep = 50;
+ Step = 200;
+ }
+ }
+
+ /// <summary>
+ /// Participates in rendering operations.
+ /// </summary>
+ /// <param name="drawingContext">The drawing instructions for a specific element. This context is provided to the layout system.</param>
+ protected override void OnRender(DrawingContext drawingContext)
+ {
+ base.OnRender(drawingContext);
+
+ SetSteps();
+
+ var markersPen = new Pen(Foreground, 1);
+
+ SegmentHeight = ActualHeight - 10;
+
+ if (Orientation == RulerOrientationEnum.Horizontal)
+ {
+ double xDest = Length * Zoom;
+
+ //drawingContext.DrawRectangle(RulerBackground, BorderPen, new Rect(new Point(0.0, 0.0), new Point(xDest, Height)));
+ //drawingContext.DrawLine(RedPen, new Point(Chip, 0), new Point(Chip, Height));
+
+ for (double dUnit = 0; dUnit < Length; dUnit += SmallStep)
+ {
+ double d = dUnit * this.Zoom;
+
+ var startHeight = ActualHeight;
+ var endHeight = ((dUnit % Step == 0) ? (this.ActualHeight / 5) * 1.4 : this.ActualHeight / 5);
+
+ if (Marks == MarksLocation.Down)
+ {
+ drawingContext.DrawLine(markersPen, new Point(d, startHeight), new Point(d, (startHeight - endHeight)));
+ }
+ else
+ {
+ drawingContext.DrawLine(markersPen, new Point(d, 0), new Point(d, endHeight));
+ }
+
+ if ((dUnit != 0.0) && (dUnit % Step == 0) && (dUnit < Length))
+ {
+ FormattedText ft = new FormattedText((dUnit + CountShift).ToString(CultureInfo.CurrentCulture),
+ CultureInfo.CurrentCulture,
+ FlowDirection.LeftToRight,
+ new Typeface(FontFamily.ToString()),
+ FontSize,
+ Foreground);
+ ft.SetFontWeight(FontWeight);
+ ft.TextAlignment = TextAlignment.Center;
+ //drawingContext.PushTransform(new RotateTransform(180));
+
+ if (Marks == MarksLocation.Down)
+ {
+ drawingContext.DrawText(ft, new Point(d, (ActualHeight / 2) - (ft.Height / 2) - 2));
+ }
+ else
+ {
+ drawingContext.DrawText(ft, new Point(d, (ActualHeight / 2) - (ft.Height / 2) + 2));
+ }
+ //drawingContext.PushTransform(new RotateTransform(0));
+ }
+
+ }
+ }
+ else
+ {
+ double xDest = Length * Zoom;
+
+ //drawingContext.DrawRectangle(RulerBackground, BorderPen, new Rect(new Point(0.0, 0.0), new Point(Width, xDest)));
+ //drawingContext.DrawLine(RedPen, new Point(Chip, 0), new Point(Chip, ActualHeight));
+
+ for (double dUnit = 0; dUnit < Length; dUnit += SmallStep)
+ {
+ double d = dUnit * this.Zoom;
+
+ double startHeight;
+ double endHeight;
+ if (Marks == MarksLocation.Up)
+ {
+ startHeight = 0;
+ // Main step or small step?
+ endHeight = ((dUnit % Step == 0) ? SegmentHeight : SegmentHeight / 2);
+ }
+ else
+ {
+ startHeight = Height;
+ // Main step or small step?
+ endHeight = ((dUnit % Step == 0) ? SegmentHeight : SegmentHeight * 1.5);
+ }
+
+ drawingContext.DrawLine(markersPen, new Point(21, d), new Point(endHeight + 8, d));
+
+ if ((dUnit != 0.0) && (dUnit % Step == 0) && (dUnit < Length))
+ {
+ FormattedText ft = new FormattedText((dUnit + CountShift).ToString(CultureInfo.CurrentCulture),
+ CultureInfo.CurrentCulture,
+ FlowDirection.LeftToRight,
+ new Typeface(FontFamily.ToString()),
+ FontSize,
+ Foreground);
+ ft.SetFontWeight(FontWeight);
+ ft.TextAlignment = TextAlignment.Center;
+ drawingContext.DrawText(ft, new Point(d, (ActualHeight / 2) - (ft.Height / 2) - 2));
+ }
+
+ }
+ }
+ }
+
+ #endregion
+ }
+
+ internal enum MarksLocation
+ {
+ Up, Down
+ }
+
+ /// <summary>
+ /// A helper class for DIP (Device Independent Pixels) conversion and scaling operations.
+ /// </summary>
+ internal static class DipHelper
+ {
+ /// <summary>
+ /// Converts font points to DIP (Device Independant Pixels).
+ /// </summary>
+ /// <param name="pt">A font point value.</param>
+ /// <returns>A DIP value.</returns>
+ public static double PtToDip(double pt)
+ {
+ return (pt * 96.0 / 72.0);
+ }
+
+
+ /// <summary>
+ /// Gets the system DPI scale factor (compared to 96 dpi).
+ /// From http://blogs.msdn.com/jaimer/archive/2007/03/07/getting-system-dpi-in-wpf-app.aspx
+ /// Should not be called before the Loaded event (else XamlException mat throw)
+ /// </summary>
+ /// <returns>A Point object containing the X- and Y- scale factor.</returns>
+ private static Point GetSystemDpiFactor()
+ {
+ PresentationSource source = PresentationSource.FromVisual(Application.Current.MainWindow);
+ Matrix m = source.CompositionTarget.TransformToDevice;
+ return new Point(m.M11, m.M22);
+ }
+
+ private const double DpiBase = 96.0;
+
+ /// <summary>
+ /// Gets the system configured DPI.
+ /// </summary>
+ /// <returns>A Point object containing the X- and Y- DPI.</returns>
+ public static Point GetSystemDpi()
+ {
+ Point sysDpiFactor = GetSystemDpiFactor();
+ return new Point(
+ sysDpiFactor.X * DpiBase,
+ sysDpiFactor.Y * DpiBase);
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.Editors/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Tango.Editors/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..31b3938dc
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Properties/AssemblyInfo.cs
@@ -0,0 +1,19 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+[assembly: AssemblyTitle("Tango - Visual Diagram Editing Components")]
+[assembly: ComVisible(false)]
+
+
+[assembly:ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
diff --git a/Software/Visual_Studio/Tango.Editors/Properties/Resources.Designer.cs b/Software/Visual_Studio/Tango.Editors/Properties/Resources.Designer.cs
new file mode 100644
index 000000000..da196b9f6
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Tango.Editors.Properties {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Tango.Editors.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Properties/Resources.resx b/Software/Visual_Studio/Tango.Editors/Properties/Resources.resx
new file mode 100644
index 000000000..af7dbebba
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Properties/Resources.resx
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root> \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.Editors/Properties/Settings.Designer.cs b/Software/Visual_Studio/Tango.Editors/Properties/Settings.Designer.cs
new file mode 100644
index 000000000..fedd60c44
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Tango.Editors.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/Properties/Settings.settings b/Software/Visual_Studio/Tango.Editors/Properties/Settings.settings
new file mode 100644
index 000000000..033d7a5e9
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Properties/Settings.settings
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+ <Settings />
+</SettingsFile> \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.Editors/RelayCommand.cs b/Software/Visual_Studio/Tango.Editors/RelayCommand.cs
new file mode 100644
index 000000000..ce5a7d563
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/RelayCommand.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// RelayCommand is a very easy-to-use implementation of ICommand. You can use a RelayCommand to expose viewmodel functionality as a command, and
+ /// supply the condition that determines the command's availability. A control in the view bound to a command can execute an available command will</summary>
+ /// update its enabled state in response to the availability of the command.
+ /// <seealso cref="System.Windows.Input.ICommand" />
+ public sealed class RelayCommand : ICommand
+ {
+ #region fields
+ readonly Predicate<object> canExecute;
+ readonly Action<object> execute;
+ #endregion fields
+
+ #region constructors
+ public RelayCommand(Action<object> execute) : this(execute, null) { }
+
+ public RelayCommand(Action<object> execute, Predicate<object> canExecute)
+ {
+ this.execute = execute;
+ this.canExecute = canExecute;
+ }
+
+ public RelayCommand(Action execute)
+ : this((x) => execute())
+ {
+
+ }
+
+ public RelayCommand(Action execute, Predicate<object> canExecute)
+ : this((x) => execute(), canExecute)
+ {
+
+ }
+
+ #endregion constructors
+
+ #region methods
+ public void RaiseCanExecuteChanged()
+ {
+ if (this.CanExecuteChanged != null)
+ {
+ this.CanExecuteChanged(this, EventArgs.Empty);
+ }
+ }
+ #endregion methods
+
+ #region ICommand events
+ public event EventHandler CanExecuteChanged;
+ #endregion ICommand events
+
+ #region ICommand methods
+ public bool CanExecute(object parameter)
+ {
+ return this.canExecute != null ? this.canExecute(parameter) : true;
+ }
+
+ public void Execute(object parameter)
+ {
+ if (this.execute != null)
+ {
+ this.execute(parameter);
+ }
+ }
+ #endregion ICommand methods
+ }
+
+ public sealed class RelayCommand<T> : ICommand
+ {
+ #region fields
+ readonly Predicate<object> canExecute;
+ readonly Action<T> execute;
+ #endregion fields
+
+ #region constructors
+
+ public RelayCommand(Action<T> execute) : this(execute, null) { }
+
+ public RelayCommand(Action<T> execute, Predicate<object> canExecute)
+ {
+ this.execute = execute;
+ this.canExecute = canExecute;
+ }
+
+ public RelayCommand(Action execute)
+ : this((x) => execute())
+ {
+
+ }
+ #endregion constructors
+
+ #region methods
+ public void RaiseCanExecuteChanged()
+ {
+ if (this.CanExecuteChanged != null)
+ {
+ this.CanExecuteChanged(this, EventArgs.Empty);
+ }
+ }
+ #endregion methods
+
+ #region ICommand events
+ public event EventHandler CanExecuteChanged;
+ #endregion ICommand events
+
+ #region ICommand methods
+ public bool CanExecute(object parameter)
+ {
+ return this.canExecute != null ? this.canExecute(parameter) : true;
+ }
+
+ public void Execute(object parameter)
+ {
+ if (this.execute != null)
+ {
+ this.execute((T)parameter);
+ }
+ }
+ #endregion ICommand methods
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/StringExtensions.cs b/Software/Visual_Studio/Tango.Editors/StringExtensions.cs
new file mode 100644
index 000000000..0f5923512
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/StringExtensions.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+/// <exclude/>
+/// <summary>
+/// A collection of <see cref="String"/> extension methods.
+/// </summary>
+internal static class StringExtensions
+{
+ private static Regex titleRegEx;
+
+ /// <summary>
+ /// Initializes the <see cref="StringExtensions"/> class.
+ /// </summary>
+ static StringExtensions()
+ {
+ titleRegEx = new Regex(@"
+ (?<=[A-Z])(?=[A-Z][a-z]) |
+ (?<=[^A-Z])(?=[A-Z]) |
+ (?<=[A-Za-z])(?=[^A-Za-z])", RegexOptions.IgnorePatternWhitespace);
+ }
+
+ /// <summary>
+ /// Returns true if the string is not null or contains white spaces.
+ /// </summary>
+ /// <param name="str">The string.</param>
+ /// <returns></returns>
+ internal static bool IsValid(this String str)
+ {
+ return !String.IsNullOrWhiteSpace(str);
+ }
+
+ /// <summary>
+ /// Determines whether the string contains an existing file path.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ /// <returns></returns>
+ internal static bool IsFileExists(this String path)
+ {
+ return System.IO.File.Exists(path);
+ }
+
+ /// <summary>
+ /// Determines whether the string is valid as a file system path.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ /// <returns></returns>
+ internal static bool IsPathValid(this String path)
+ {
+ return path.IsValid() && path.IndexOfAny(System.IO.Path.GetInvalidPathChars()) == -1;
+ }
+
+ /// <summary>
+ /// Returns a friendly file size of the file path.
+ /// </summary>
+ /// <param name="filePath">The file path.</param>
+ /// <returns></returns>
+ internal static String GetFileSizeString(this String filePath)
+ {
+ FileInfo f = new FileInfo(filePath);
+ double length = f.Length;
+
+ string[] sizes = { "B", "KB", "MB", "GB" };
+ double len = length;
+ int order = 0;
+ while (len >= 1024 && order + 1 < sizes.Length)
+ {
+ order++;
+ len = len / 1024;
+ }
+
+ // Adjust the format string to your preferences. For example "{0:0.#}{1}" would
+ // show a single decimal place, and no space.
+ return String.Format("{0:0.##} {1}", len, sizes[order]);
+ }
+
+ /// <summary>
+ /// If string is a path, returns the file name.
+ /// </summary>
+ /// <param name="filePath">The file path.</param>
+ /// <returns></returns>
+ internal static String GetFileName(this String filePath)
+ {
+ return Path.GetFileName(filePath);
+ }
+
+ /// <summary>
+ /// If string is a path, returns the file extension.
+ /// </summary>
+ /// <param name="filePath">The file path.</param>
+ /// <returns></returns>
+ internal static String GetFileExtension(this String filePath)
+ {
+ return Path.GetExtension(filePath);
+ }
+
+ internal static String ToTitle(this String str)
+ {
+ return titleRegEx.Replace(str, " ");
+ }
+}
+
diff --git a/Software/Visual_Studio/Tango.Editors/Tango.Editors.csproj b/Software/Visual_Studio/Tango.Editors/Tango.Editors.csproj
new file mode 100644
index 000000000..9a4dddd56
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/Tango.Editors.csproj
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{DE2F2B86-025B-4F26-83A4-38BD48224ED5}</ProjectGuid>
+ <OutputType>library</OutputType>
+ <RootNamespace>Tango.Editors</RootNamespace>
+ <AssemblyName>Tango.Editors</AssemblyName>
+ <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <WarningLevel>4</WarningLevel>
+ <SccProjectName>SAK</SccProjectName>
+ <SccLocalPath>SAK</SccLocalPath>
+ <SccAuxPath>SAK</SccAuxPath>
+ <SccProvider>SAK</SccProvider>
+ <TargetFrameworkProfile />
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>..\Build\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="FontAwesome.WPF">
+ <HintPath>..\Referenced Assemblies\FontAwesome.WPF.dll</HintPath>
+ </Reference>
+ <Reference Include="HtmlRenderer">
+ <HintPath>..\Referenced Assemblies\HtmlRenderer.dll</HintPath>
+ </Reference>
+ <Reference Include="HtmlRenderer.WPF">
+ <HintPath>..\Referenced Assemblies\HtmlRenderer.WPF.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Xml" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xaml">
+ <RequiredTargetFramework>4.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="WindowsBase" />
+ <Reference Include="PresentationCore" />
+ <Reference Include="PresentationFramework" />
+ <Reference Include="WpfAnimatedGif, Version=1.4.14.0, Culture=neutral, processorArchitecture=MSIL">
+ <HintPath>..\packages\WpfAnimatedGif.1.4.14\lib\net\WpfAnimatedGif.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="..\Versioning\GlobalVersionInfo.cs">
+ <Link>GlobalVersionInfo.cs</Link>
+ </Compile>
+ <Compile Include="BrushExtensions.cs" />
+ <Compile Include="EnumExtensions.cs" />
+ <Compile Include="FrameworkElementCloner.cs" />
+ <None Include="CanvasItemEditor.xaml.cs">
+ <DependentUpon>CanvasItemEditor.xaml</DependentUpon>
+ </None>
+ <Compile Include="FrameworkElementExtensions.cs" />
+ <Compile Include="IParameterized.cs" />
+ <Compile Include="IParameterizedExtensions.cs" />
+ <Compile Include="ObjectExtensions.cs" />
+ <Compile Include="ParameterIgnoreAttribute.cs" />
+ <Compile Include="ParameterItem.cs" />
+ <Compile Include="ParameterItemAttribute.cs" />
+ <Compile Include="ParameterItemMode.cs" />
+ <Compile Include="StringExtensions.cs" />
+ <Compile Include="UIElementExtension.cs" />
+ <Compile Include="UIHelper.cs" />
+ <Compile Include="UndoRedoStatesProviderBase.cs" />
+ <Page Include="ElementsEditor.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <None Include="CanvasItemEditor.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </None>
+ <Page Include="FrameworkElementEditor.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Compile Include="AnimationSetup.cs" />
+ <Compile Include="AnimationSetupMode.cs" />
+ <Compile Include="AttachedElementsEditorsUndoRedoState.cs" />
+ <Compile Include="AttachedElementsEditorsUndoRedoStatesProvider.cs" />
+ <Compile Include="ConfigurationAnimation.cs" />
+ <Compile Include="Converters\BoolToVisibilityConverter.cs" />
+ <Compile Include="Converters\BrushToColorConverter.cs" />
+ <Compile Include="Converters\DetachedConverter.cs" />
+ <Compile Include="Converters\DoubleToIntConverter.cs" />
+ <Compile Include="Converters\EnumToBoolConverter.cs" />
+ <Compile Include="Converters\HorizontalOffsetToMarginConverter.cs" />
+ <Compile Include="Converters\InverseBoolToVisibilityConverter.cs" />
+ <Compile Include="Converters\IsEnumConverter.cs" />
+ <Compile Include="Converters\IsNullConverter.cs" />
+ <Compile Include="Converters\StringFormatConverter.cs" />
+ <Compile Include="Converters\TransformPointToPointConverter.cs" />
+ <Compile Include="CustomScrollViewer.cs" />
+ <Compile Include="DependencyObjectExtensions.cs" />
+ <Compile Include="ElementCreationEventArgs.cs" />
+ <Compile Include="ElementEditor.cs" />
+ <Compile Include="ElementEditorConfiguration.cs" />
+ <Compile Include="ElementsEditor.xaml.cs">
+ <DependentUpon>ElementsEditor.xaml</DependentUpon>
+ </Compile>
+ <Compile Include="ElementsEditorConfiguration.cs" />
+ <Compile Include="ElementsEditorMode.cs" />
+ <Compile Include="ElementsEditorUndoRedoExecutedEventArgs.cs" />
+ <Compile Include="ElementsEditorUndoRedoState.cs" />
+ <Compile Include="ElementsEditorUndoRedoStatesProvider.cs" />
+ <Compile Include="ElementsEventArgs.cs" />
+ <Compile Include="FrameworkElementEditor.xaml.cs">
+ <DependentUpon>FrameworkElementEditor.xaml</DependentUpon>
+ </Compile>
+ <Compile Include="FrameworkElementEditorConfiguration.cs" />
+ <Compile Include="HybridControl.cs" />
+ <Compile Include="IConfigurable.cs" />
+ <Compile Include="IConfigurableExtensions.cs" />
+ <Compile Include="IConfiguration.cs" />
+ <Compile Include="IConfigurationExtensions.cs" />
+ <Compile Include="IElementEditor.cs" />
+ <Compile Include="InvalidConfigurationException.cs" />
+ <Compile Include="ISupportEditingOperations.cs" />
+ <Compile Include="ISupportUndoRedoOperations.cs" />
+ <Compile Include="IUndoRedoState.cs" />
+ <Compile Include="IUndoRedoStatesProvider.cs" />
+ <Compile Include="PixelRuler.cs" />
+ <Compile Include="RelayCommand.cs" />
+ <Compile Include="UndoRedoStateExecutedEventArgs.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Properties\Resources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Resources.resx</DependentUpon>
+ </Compile>
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Settings.settings</DependentUpon>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ </Compile>
+ <EmbeddedResource Include="Properties\Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <None Include="app.config" />
+ <None Include="packages.config" />
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\AlphaMediaPresenter.Shared\AlphaMediaPresenter.Shared.csproj">
+ <Project>{43fae2d6-3b54-4b31-aee2-ced3b5fab656}</Project>
+ <Name>AlphaMediaPresenter.Shared</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.Editors/UIElementExtension.cs b/Software/Visual_Studio/Tango.Editors/UIElementExtension.cs
new file mode 100644
index 000000000..2320e6f97
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/UIElementExtension.cs
@@ -0,0 +1,302 @@
+using Tango.Editors;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Media3D;
+
+/// <exclude/>
+/// <summary>
+/// A collection of <see cref="UIElement"/> and <see cref="IAnimatable"/> extension methods.
+/// </summary>
+internal static class UIElementExtension
+{
+ /// <summary>
+ /// Starts the double animation.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <param name="property">The property.</param>
+ /// <param name="animation">The animation.</param>
+ /// <param name="to">To.</param>
+ /// <param name="from">From.</param>
+ /// <returns></returns>
+ internal static DoubleAnimation StartDoubleAnimation(this IAnimatable element, DependencyProperty property, ConfigurationAnimation animation, double to, double? from = null)
+ {
+ DoubleAnimation ani = new DoubleAnimation();
+ ani.AccelerationRatio = animation.AccelerationRatio;
+ ani.DecelerationRatio = animation.DecelerationRatio;
+
+ if (animation.EasingFunction != null)
+ {
+ ani.EasingFunction = animation.EasingFunction;
+ }
+
+ if (from != null && !double.IsNaN(from.Value))
+ {
+ ani.From = from.Value;
+ }
+
+ ani.Duration = new Duration(animation.Duration);
+
+ if (double.IsNaN((double)(element as DependencyObject).GetValue(property)) || animation.Duration == TimeSpan.FromSeconds(0))
+ {
+ element.BeginAnimation(property, null);
+ (element as DependencyObject).SetValue(property, to);
+ return ani;
+ }
+
+ ani.Completed += (x, y) =>
+ {
+ element.BeginAnimation(property, null);
+ (element as DependencyObject).SetValue(property, to);
+
+ animation.RaiseAnimationCompleted(element);
+ };
+
+ if (!double.IsNaN(to))
+ {
+ ani.To = to;
+ element.BeginAnimation(property, ani);
+ }
+ else
+ {
+ element.BeginAnimation(property, null, HandoffBehavior.Compose);
+ (element as DependencyObject).SetValue(property, to);
+ }
+
+ return ani;
+ }
+
+ internal static ColorAnimation StartColorAnimation(this IAnimatable element, DependencyProperty property, ConfigurationAnimation animation, Color to)
+ {
+ ColorAnimation ani = new ColorAnimation();
+ ani.AccelerationRatio = animation.AccelerationRatio;
+ ani.DecelerationRatio = animation.DecelerationRatio;
+
+ if (animation.EasingFunction != null)
+ {
+ ani.EasingFunction = animation.EasingFunction;
+ }
+
+ ani.Duration = new Duration(animation.Duration);
+
+ ani.Completed += (x, y) =>
+ {
+ element.BeginAnimation(property, null);
+ (element as DependencyObject).SetValue(property, to);
+
+ animation.RaiseAnimationCompleted(element);
+ };
+
+ ani.To = to;
+ element.BeginAnimation(property, ani);
+
+ return ani;
+ }
+
+ /// <summary>
+ /// Gets the double animation.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <param name="property">The property.</param>
+ /// <param name="animation">The animation.</param>
+ /// <param name="to">To.</param>
+ /// <param name="from">From.</param>
+ /// <returns></returns>
+ internal static AnimationSetup GetDoubleAnimation(this UIElement element, DependencyProperty property, ConfigurationAnimation animation, double to, double? from = null)
+ {
+ DoubleAnimation ani = new DoubleAnimation();
+ ani.AccelerationRatio = animation.AccelerationRatio;
+ ani.DecelerationRatio = animation.DecelerationRatio;
+
+ if (animation.EasingFunction != null)
+ {
+ ani.EasingFunction = animation.EasingFunction;
+ }
+
+ if (from != null && !double.IsNaN(from.Value))
+ {
+ ani.From = from.Value;
+ }
+
+ ani.Duration = new Duration(animation.Duration);
+
+ if (double.IsNaN((double)element.GetValue(property)) || animation.Duration == TimeSpan.FromSeconds(0))
+ {
+ //element.BeginAnimation(property, null);
+ //element.SetValue(property, to);
+ return new AnimationSetup(ani, element, property);
+ }
+
+ ani.Completed += (x, y) =>
+ {
+
+ Storyboard story = new Storyboard();
+ element.BeginAnimation(property, null);
+ element.SetValue(property, to);
+
+ animation.RaiseAnimationCompleted(element);
+ };
+
+ if (!double.IsNaN(to))
+ {
+ ani.To = to;
+ //element.BeginAnimation(property, ani);
+ }
+ else
+ {
+ //element.BeginAnimation(property, null, HandoffBehavior.Compose);
+ //element.SetValue(property, to);
+ }
+
+ return new AnimationSetup(ani, element, property);
+ }
+
+ /// <summary>
+ /// Starts the int32 animation.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <param name="property">The property.</param>
+ /// <param name="animation">The animation.</param>
+ /// <param name="to">To.</param>
+ /// <returns></returns>
+ internal static Int32Animation StartInt32Animation(this UIElement element, DependencyProperty property, ConfigurationAnimation animation, Int32 to)
+ {
+ Int32Animation ani = new Int32Animation();
+ ani.AccelerationRatio = animation.AccelerationRatio;
+ ani.DecelerationRatio = animation.DecelerationRatio;
+
+ if (animation.EasingFunction != null)
+ {
+ ani.EasingFunction = animation.EasingFunction;
+ }
+
+ ani.Duration = new Duration(animation.Duration);
+ ani.Completed += (x, y) =>
+ {
+ element.BeginAnimation(property, null);
+ element.SetValue(property, to);
+
+ animation.RaiseAnimationCompleted(element);
+ };
+
+ if (!double.IsNaN(to))
+ {
+ ani.To = to;
+ element.BeginAnimation(property, ani);
+ }
+ else
+ {
+ element.BeginAnimation(property, null, HandoffBehavior.Compose);
+ element.SetValue(property, to);
+ }
+
+ return ani;
+ }
+
+ /// <summary>
+ /// Starts the point animation.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <param name="property">The property.</param>
+ /// <param name="animation">The animation.</param>
+ /// <param name="to">To.</param>
+ /// <returns></returns>
+ internal static PointAnimation StartPointAnimation(this UIElement element, DependencyProperty property, ConfigurationAnimation animation, Point to)
+ {
+ PointAnimation ani = new PointAnimation();
+ ani.AccelerationRatio = animation.AccelerationRatio;
+ ani.DecelerationRatio = animation.DecelerationRatio;
+
+ if (animation.EasingFunction != null)
+ {
+ ani.EasingFunction = animation.EasingFunction;
+ }
+
+ ani.To = to;
+ ani.Duration = new Duration(animation.Duration);
+ ani.Completed += (x, y) =>
+ {
+ element.BeginAnimation(property, null);
+ element.SetValue(property, to);
+
+ animation.RaiseAnimationCompleted(element);
+ };
+
+ element.BeginAnimation(property, ani);
+
+ return ani;
+ }
+
+ /// <summary>
+ /// Starts the point3 d animation.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <param name="property">The property.</param>
+ /// <param name="animation">The animation.</param>
+ /// <param name="to">To.</param>
+ /// <returns></returns>
+ internal static Point3DAnimation StartPoint3DAnimation(this IAnimatable element, DependencyProperty property, ConfigurationAnimation animation, Point3D to)
+ {
+ Point3DAnimation ani = new Point3DAnimation();
+ ani.AccelerationRatio = animation.AccelerationRatio;
+ ani.DecelerationRatio = animation.DecelerationRatio;
+
+ if (animation.EasingFunction != null)
+ {
+ ani.EasingFunction = animation.EasingFunction;
+ }
+
+ ani.To = to;
+ ani.Duration = new Duration(animation.Duration);
+ ani.Completed += (x, y) =>
+ {
+ element.BeginAnimation(property, null);
+ (element as DependencyObject).SetValue(property, to);
+
+ animation.RaiseAnimationCompleted(element);
+ };
+
+ element.BeginAnimation(property, ani);
+
+ return ani;
+ }
+
+ /// <summary>
+ /// Starts the vector3 d animation.
+ /// </summary>
+ /// <param name="element">The element.</param>
+ /// <param name="property">The property.</param>
+ /// <param name="animation">The animation.</param>
+ /// <param name="to">To.</param>
+ /// <returns></returns>
+ internal static Vector3DAnimation StartVector3DAnimation(this IAnimatable element, DependencyProperty property, ConfigurationAnimation animation, Vector3D to)
+ {
+ Vector3DAnimation ani = new Vector3DAnimation();
+ ani.AccelerationRatio = animation.AccelerationRatio;
+ ani.DecelerationRatio = animation.DecelerationRatio;
+
+ if (animation.EasingFunction != null)
+ {
+ ani.EasingFunction = animation.EasingFunction;
+ }
+
+ ani.To = to;
+ ani.Duration = new Duration(animation.Duration);
+ ani.Completed += (x, y) =>
+ {
+ element.BeginAnimation(property, null);
+ (element as DependencyObject).SetValue(property, to);
+
+ animation.RaiseAnimationCompleted(element);
+ };
+
+ element.BeginAnimation(property, ani);
+
+ return ani;
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/UIHelper.cs b/Software/Visual_Studio/Tango.Editors/UIHelper.cs
new file mode 100644
index 000000000..d75e9bbdf
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/UIHelper.cs
@@ -0,0 +1,407 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Interop;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Threading;
+
+namespace Tango.Editors
+{
+ /// <exclude/>
+ /// <summary>
+ /// Contains some helper methods for UI interaction.
+ /// </summary>
+ internal static class UIHelper
+ {
+ #region Native Classes
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct RECT
+ {
+ private int _Left;
+ private int _Top;
+ private int _Right;
+ private int _Bottom;
+
+ public RECT(RECT Rectangle)
+ : this(Rectangle.Left, Rectangle.Top, Rectangle.Right, Rectangle.Bottom)
+ {
+ }
+ public RECT(int Left, int Top, int Right, int Bottom)
+ {
+ _Left = Left;
+ _Top = Top;
+ _Right = Right;
+ _Bottom = Bottom;
+ }
+
+ public int X
+ {
+ get { return _Left; }
+ set { _Left = value; }
+ }
+ public int Y
+ {
+ get { return _Top; }
+ set { _Top = value; }
+ }
+ public int Left
+ {
+ get { return _Left; }
+ set { _Left = value; }
+ }
+ public int Top
+ {
+ get { return _Top; }
+ set { _Top = value; }
+ }
+ public int Right
+ {
+ get { return _Right; }
+ set { _Right = value; }
+ }
+ public int Bottom
+ {
+ get { return _Bottom; }
+ set { _Bottom = value; }
+ }
+ public int Height
+ {
+ get { return _Bottom - _Top; }
+ set { _Bottom = value + _Top; }
+ }
+ public int Width
+ {
+ get { return _Right - _Left; }
+ set { _Right = value + _Left; }
+ }
+ public System.Drawing.Point Location
+ {
+ get { return new System.Drawing.Point(Left, Top); }
+ set
+ {
+ _Left = value.X;
+ _Top = value.Y;
+ }
+ }
+ public System.Drawing.Size Size
+ {
+ get { return new System.Drawing.Size(Width, Height); }
+ set
+ {
+ _Right = value.Width + _Left;
+ _Bottom = value.Height + _Top;
+ }
+ }
+
+ public static implicit operator System.Drawing.Rectangle(RECT Rectangle)
+ {
+ return new System.Drawing.Rectangle(Rectangle.Left, Rectangle.Top, Rectangle.Width, Rectangle.Height);
+ }
+ public static implicit operator RECT(System.Drawing.Rectangle Rectangle)
+ {
+ return new RECT(Rectangle.Left, Rectangle.Top, Rectangle.Right, Rectangle.Bottom);
+ }
+ public static bool operator ==(RECT Rectangle1, RECT Rectangle2)
+ {
+ return Rectangle1.Equals(Rectangle2);
+ }
+ public static bool operator !=(RECT Rectangle1, RECT Rectangle2)
+ {
+ return !Rectangle1.Equals(Rectangle2);
+ }
+
+ public override string ToString()
+ {
+ return "{Left: " + _Left + "; " + "Top: " + _Top + "; Right: " + _Right + "; Bottom: " + _Bottom + "}";
+ }
+
+ public override int GetHashCode()
+ {
+ return ToString().GetHashCode();
+ }
+
+ public bool Equals(RECT Rectangle)
+ {
+ return Rectangle.Left == _Left && Rectangle.Top == _Top && Rectangle.Right == _Right && Rectangle.Bottom == _Bottom;
+ }
+
+ public override bool Equals(object Object)
+ {
+ if (Object is RECT)
+ {
+ return Equals((RECT)Object);
+ }
+ else if (Object is System.Drawing.Rectangle)
+ {
+ return Equals(new RECT((System.Drawing.Rectangle)Object));
+ }
+
+ return false;
+ }
+ }
+
+ #endregion
+
+ [DllImport("user32.dll")]
+ internal static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
+ [DllImport("user32.dll")]
+ internal static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, int nFlags);
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ internal static extern IntPtr GetCapture();
+
+ internal static void InvokeUI(Action action)
+ {
+ EnsureDispatcher();
+
+
+ try
+ {
+ Application.Current.Dispatcher.BeginInvoke(action);
+ }
+ catch { }
+ }
+
+ internal static void InvokeUIUrgent(Action action)
+ {
+ EnsureDispatcher();
+ Application.Current.Dispatcher.Invoke(action, DispatcherPriority.Send);
+ }
+
+ internal static void EnsureDispatcher()
+ {
+ try
+ {
+ if (System.Windows.Application.Current == null) //Using this to enable processing outside a WPF application.
+ {
+ new System.Windows.Application { ShutdownMode = ShutdownMode.OnExplicitShutdown };
+ }
+ }
+ catch { }
+ }
+
+ internal static void DoEvents()
+ {
+ Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
+ new Action(delegate { }));
+ }
+
+ internal static void ReleaseMouseCapture()
+ {
+ IntPtr capturingHandle = GetCapture();
+ for (int i = 0; i < Application.Current.Windows.Count; i++)
+ {
+ if (new WindowInteropHelper(Application.Current.Windows[i]).Handle == capturingHandle)
+ {
+ Mouse.Capture(Application.Current.Windows[i], CaptureMode.Element);
+ Application.Current.Windows[i].ReleaseMouseCapture();
+ break;
+ }
+ }
+ }
+
+ internal static void RemoveChild(DependencyObject parent, UIElement child)
+ {
+ var panel = parent as Panel;
+ if (panel != null)
+ {
+ panel.Children.Remove(child);
+ return;
+ }
+
+ var decorator = parent as Decorator;
+ if (decorator != null)
+ {
+ if (decorator.Child == child)
+ {
+ decorator.Child = null;
+ }
+ return;
+ }
+
+ var contentPresenter = parent as ContentPresenter;
+ if (contentPresenter != null)
+ {
+ if (contentPresenter.Content == child)
+ {
+ contentPresenter.Content = null;
+ }
+ return;
+ }
+
+ var contentControl = parent as ContentControl;
+ if (contentControl != null)
+ {
+ if (contentControl.Content == child)
+ {
+ contentControl.Content = null;
+ }
+ return;
+ }
+ }
+
+ internal static void AddChild(DependencyObject parent, UIElement child)
+ {
+ var panel = parent as Panel;
+ if (panel != null)
+ {
+ panel.Children.Add(child);
+ return;
+ }
+
+ var decorator = parent as Decorator;
+ if (decorator != null)
+ {
+ decorator.Child = child;
+ return;
+ }
+
+ var contentPresenter = parent as ContentPresenter;
+ if (contentPresenter != null)
+ {
+ contentPresenter.Content = child;
+ return;
+ }
+
+ var contentControl = parent as ContentControl;
+ if (contentControl != null)
+ {
+ contentControl.Content = child;
+ return;
+ }
+ }
+
+ public static RenderTargetBitmap TakeSnapshot(Visual oVisual, Size oSize)
+ {
+
+ int nWidth = (int)Math.Ceiling(oSize.Width);
+ int nHeight = (int)Math.Ceiling(oSize.Height);
+
+ RenderTargetBitmap oTargetBitmap = new RenderTargetBitmap(
+ nWidth,
+ nHeight,
+ 96,
+ 96,
+ PixelFormats.Pbgra32
+ );
+
+ DrawingVisual oDrawingVisual = new DrawingVisual();
+
+ using (DrawingContext oDrawingContext = oDrawingVisual.RenderOpen())
+ {
+ VisualBrush oVisualBrush = new VisualBrush(oVisual) { Stretch = Stretch.Fill };
+
+ oDrawingContext.DrawRectangle(
+ oVisualBrush,
+ null,
+ new Rect(
+ new Point(),
+ new Size(nWidth, nHeight)
+ )
+ );
+
+ oDrawingContext.Close();
+ oTargetBitmap.Render(oDrawingVisual);
+ }
+
+ oTargetBitmap.Freeze();
+
+ return oTargetBitmap;
+ }
+
+ public static RenderTargetBitmap TakeSnapshot(FrameworkElement element)
+ {
+
+ int nWidth = (int)Math.Ceiling(element.ActualWidth);
+ int nHeight = (int)Math.Ceiling(element.ActualHeight);
+
+ RenderTargetBitmap oTargetBitmap = new RenderTargetBitmap(
+ nWidth,
+ nHeight,
+ 96,
+ 96,
+ PixelFormats.Pbgra32
+ );
+
+ DrawingVisual oDrawingVisual = new DrawingVisual();
+
+ using (DrawingContext oDrawingContext = oDrawingVisual.RenderOpen())
+ {
+ VisualBrush oVisualBrush = new VisualBrush(element) { Stretch = Stretch.Fill };
+
+ oDrawingContext.DrawRectangle(
+ oVisualBrush,
+ null,
+ new Rect(
+ new Point(),
+ new Size(nWidth, nHeight)
+ )
+ );
+
+ oDrawingContext.Close();
+ oTargetBitmap.Render(oDrawingVisual);
+ }
+
+ return oTargetBitmap;
+ }
+
+ public static System.Drawing.Bitmap TakeSnapshot(Window window)
+ {
+
+ var handle = new WindowInteropHelper(window).Handle;
+ RECT rc;
+ GetWindowRect(handle, out rc);
+
+ System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(rc.Width, rc.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
+ var gfxBmp = System.Drawing.Graphics.FromImage(bmp);
+ IntPtr hdcBitmap = gfxBmp.GetHdc();
+
+ PrintWindow(handle, hdcBitmap, 0);
+
+ gfxBmp.ReleaseHdc(hdcBitmap);
+ gfxBmp.Dispose();
+
+ return bmp;
+ }
+
+ internal static Stream GetApplicationResource(String resourcePath)
+ {
+ String callingAssembly = Assembly.GetCallingAssembly().GetName().Name;
+ Uri uri = new Uri("/" + callingAssembly + ";component/" + resourcePath, UriKind.Relative);
+ System.Windows.Resources.StreamResourceInfo info = Application.GetResourceStream(uri);
+ return info.Stream;
+ }
+
+ internal static BitmapSource GetImageFromApplicationResource(String resourcePath)
+ {
+ String callingAssembly = Assembly.GetCallingAssembly().GetName().Name;
+ Uri uri = new Uri("/" + callingAssembly + ";component/" + resourcePath, UriKind.Relative);
+ System.Windows.Resources.StreamResourceInfo info = Application.GetResourceStream(uri);
+
+ var bitmapImage = new BitmapImage();
+ bitmapImage.BeginInit();
+ bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
+ bitmapImage.StreamSource = info.Stream;
+ bitmapImage.EndInit();
+ return bitmapImage;
+ }
+
+ internal static String GetStartupPath()
+ {
+ return System.AppDomain.CurrentDomain.BaseDirectory;
+ }
+
+ internal static String GetRandomAnimationName()
+ {
+ return "ani" + Guid.NewGuid().ToString().Replace("-", "");
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/UndoRedoStateExecutedEventArgs.cs b/Software/Visual_Studio/Tango.Editors/UndoRedoStateExecutedEventArgs.cs
new file mode 100644
index 000000000..53486de3c
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/UndoRedoStateExecutedEventArgs.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents the <see cref="IUndoRedoStatesProvider.StateExecuted"/> event arguments.
+ /// </summary>
+ /// <seealso cref="System.EventArgs" />
+ public class UndoRedoStateExecutedEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Gets or sets the event arguments mode.
+ /// </summary>
+ public UndoRedoStateMode Mode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Undo/Redo state.
+ /// </summary>
+ public IUndoRedoState State { get; set; }
+ }
+
+ /// <summary>
+ /// Represents the <see cref="UndoRedoStateExecutedEventArgs"/> mode.
+ /// </summary>
+ public enum UndoRedoStateMode
+ {
+ /// <summary>
+ /// Undo was executed.
+ /// </summary>
+ Undo,
+ /// <summary>
+ /// Redo was executed.
+ /// </summary>
+ Redo,
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/UndoRedoStatesProviderBase.cs b/Software/Visual_Studio/Tango.Editors/UndoRedoStatesProviderBase.cs
new file mode 100644
index 000000000..94c6efb5a
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/UndoRedoStatesProviderBase.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.Editors
+{
+ /// <summary>
+ /// Represents an undo/redo states provider base class.
+ /// </summary>
+ /// <seealso cref="Tango.Editors.IUndoRedoStatesProvider" />
+ public abstract class UndoRedoStatesProviderBase : IUndoRedoStatesProvider
+ {
+ private IUndoRedoState _uncommittedState; //Holds the current undo state about to be committed.
+
+ /// <summary>
+ /// Occurs when an Undo/Redo state was executed.
+ /// </summary>
+ public event EventHandler<UndoRedoStateExecutedEventArgs> StateExecuted;
+
+ /// <summary>
+ /// Gets the undo states.
+ /// </summary>
+ public Stack<IUndoRedoState> UndoStates { get; protected set; }
+
+ /// <summary>
+ /// Gets the redo states.
+ /// </summary>
+ public Stack<IUndoRedoState> RedoStates { get; protected set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance can undo.
+ /// </summary>
+ public bool CanUndo
+ {
+ get
+ {
+ return UndoStates.Count > 0;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance can redo.
+ /// </summary>
+ public bool CanRedo
+ {
+ get
+ {
+ return RedoStates.Count > 0;
+ }
+ }
+
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UndoRedoStatesProviderBase"/> class.
+ /// </summary>
+ public UndoRedoStatesProviderBase()
+ {
+ UndoStates = new Stack<IUndoRedoState>();
+ RedoStates = new Stack<IUndoRedoState>();
+ }
+
+
+ /// <summary>
+ /// Prepares the state of the undo.
+ /// </summary>
+ public virtual void PrepareUndoState()
+ {
+ RedoStates.Clear();
+ _uncommittedState = CreateUndoRedoState();
+ }
+
+ /// <summary>
+ /// Commits the state created by <see cref="PrepareUndoState" />.
+ /// </summary>
+ public void CommitUndoState()
+ {
+ if (_uncommittedState != null)
+ {
+ UndoStates.Push(_uncommittedState);
+ _uncommittedState = null;
+ }
+ }
+
+ /// <summary>
+ /// Creates the a new undo/redo state.
+ /// </summary>
+ /// <returns></returns>
+ public abstract IUndoRedoState CreateUndoRedoState();
+
+ /// <summary>
+ /// Executes the an undo/redo state.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ public abstract void ExecuteState(IUndoRedoState state);
+
+ /// <summary>
+ /// Performs an undo operation.
+ /// </summary>
+ public virtual void Undo()
+ {
+ if (UndoStates.Count > 0)
+ {
+ var state = UndoStates.Pop();
+
+ var redoState = CreateUndoRedoState();
+
+ ExecuteState(state);
+
+ OnStateExecuted(new UndoRedoStateExecutedEventArgs() { Mode = UndoRedoStateMode.Undo, State = state });
+
+ RedoStates.Push(redoState);
+ }
+ }
+
+ /// <summary>
+ /// Performs a redo operation.
+ /// </summary>
+ public virtual void Redo()
+ {
+ if (RedoStates.Count > 0)
+ {
+ var state = RedoStates.Pop();
+
+ var undoState = CreateUndoRedoState();
+
+ ExecuteState(state);
+
+ OnStateExecuted(new UndoRedoStateExecutedEventArgs() { Mode = UndoRedoStateMode.Redo, State = state });
+
+ UndoStates.Push(undoState);
+ }
+ }
+
+ /// <summary>
+ /// Raises the <see cref="E:StateExecuted" /> event.
+ /// </summary>
+ /// <param name="e">The <see cref="UndoRedoStateExecutedEventArgs"/> instance containing the event data.</param>
+ public virtual void OnStateExecuted(UndoRedoStateExecutedEventArgs e)
+ {
+ if (StateExecuted != null) StateExecuted(this, e);
+ }
+
+ /// <summary>
+ /// Resets undo and redo states.
+ /// </summary>
+ public void Reset()
+ {
+ UndoStates.Clear();
+ RedoStates.Clear();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.Editors/app.config b/Software/Visual_Studio/Tango.Editors/app.config
new file mode 100644
index 000000000..34fff1c0a
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/app.config
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-1.2.1.0" newVersion="1.2.1.0"/>
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.IO.Compression" publicKeyToken="b77a5c561934e089" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-4.1.2.0" newVersion="4.1.2.0"/>
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.IO.FileSystem" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0"/>
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.IO.FileSystem.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0"/>
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.Security.Cryptography.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0"/>
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.Xml.XPath.XDocument" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0"/>
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.Console" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0"/>
+ </dependentAssembly>
+ <dependentAssembly>
+ <assemblyIdentity name="System.Diagnostics.StackTrace" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0"/>
+ </dependentAssembly>
+ </assemblyBinding>
+ </runtime>
+<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup></configuration>
diff --git a/Software/Visual_Studio/Tango.Editors/packages.config b/Software/Visual_Studio/Tango.Editors/packages.config
new file mode 100644
index 000000000..7154bc82a
--- /dev/null
+++ b/Software/Visual_Studio/Tango.Editors/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="WpfAnimatedGif" version="1.4.14" targetFramework="net45" />
+</packages> \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.sln b/Software/Visual_Studio/Tango.sln
index 23de2a413..7c1b522ee 100644
--- a/Software/Visual_Studio/Tango.sln
+++ b/Software/Visual_Studio/Tango.sln
@@ -121,6 +121,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.Video", "Tango.Video\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.UITests", "Utilities\Tango.UITests\Tango.UITests.csproj", "{5B954D98-4020-4AC6-939F-C52B5646E8E6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.Editors", "Tango.Editors\Tango.Editors.csproj", "{DE2F2B86-025B-4F26-83A4-38BD48224ED5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1561,6 +1563,36 @@ Global
{5B954D98-4020-4AC6-939F-C52B5646E8E6}.Release|x64.Build.0 = Release|Any CPU
{5B954D98-4020-4AC6-939F-C52B5646E8E6}.Release|x86.ActiveCfg = Release|Any CPU
{5B954D98-4020-4AC6-939F-C52B5646E8E6}.Release|x86.Build.0 = Release|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Debug|ARM.Build.0 = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Debug|x64.Build.0 = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Debug|x86.Build.0 = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.DefaultBuild|ARM.Build.0 = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.DefaultBuild|x64.Build.0 = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.DefaultBuild|x86.Build.0 = Debug|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Release|ARM.ActiveCfg = Release|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Release|ARM.Build.0 = Release|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Release|ARM64.Build.0 = Release|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Release|x64.ActiveCfg = Release|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Release|x64.Build.0 = Release|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Release|x86.ActiveCfg = Release|Any CPU
+ {DE2F2B86-025B-4F26-83A4-38BD48224ED5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Software/Visual_Studio/Utilities/Tango.UITests/MainWindow.xaml b/Software/Visual_Studio/Utilities/Tango.UITests/MainWindow.xaml
index aae846742..7c9b95a27 100644
--- a/Software/Visual_Studio/Utilities/Tango.UITests/MainWindow.xaml
+++ b/Software/Visual_Studio/Utilities/Tango.UITests/MainWindow.xaml
@@ -7,6 +7,7 @@
xmlns:controls="clr-namespace:Tango.SharedUI.Controls;assembly=Tango.SharedUI"
xmlns:common="clr-namespace:Tango.MachineStudio.Common.Controls;assembly=Tango.MachineStudio.Common"
xmlns:developer="clr-namespace:Tango.MachineStudio.Developer.Controls;assembly=Tango.MachineStudio.Developer"
+ xmlns:editors="clr-namespace:Tango.Editors;assembly=Tango.Editors"
mc:Ignorable="d"
Title="MainWindow" Height="324.369" Width="584.217">
@@ -15,8 +16,6 @@
</Window.Resources>
<Grid Margin="10">
- <Viewbox Stretch="Fill" Margin="171,73,217,110">
- <developer:IOMonitorControl FontFamily="{StaticResource digital-7}" Foreground="Gainsboro" />
- </Viewbox>
+ <editors:ElementsEditor Background="White" EditorBackground="White" RulerBackground="Gainsboro" Foreground="DimGray" SelectionFillBrush="#6E000000" />
</Grid>
</Window>
diff --git a/Software/Visual_Studio/Utilities/Tango.UITests/Tango.UITests.csproj b/Software/Visual_Studio/Utilities/Tango.UITests/Tango.UITests.csproj
index 3938ff915..987eaebd1 100644
--- a/Software/Visual_Studio/Utilities/Tango.UITests/Tango.UITests.csproj
+++ b/Software/Visual_Studio/Utilities/Tango.UITests/Tango.UITests.csproj
@@ -110,6 +110,10 @@
<Project>{a2f5af44-29ff-45d6-9d25-ecda5cce88b5}</Project>
<Name>Tango.ColorPicker</Name>
</ProjectReference>
+ <ProjectReference Include="..\..\Tango.Editors\Tango.Editors.csproj">
+ <Project>{de2f2b86-025b-4f26-83a4-38bd48224ed5}</Project>
+ <Name>Tango.Editors</Name>
+ </ProjectReference>
<ProjectReference Include="..\..\Tango.SharedUI\Tango.SharedUI.csproj">
<Project>{8491d07b-c1f6-4b62-a412-41b9fd2d6538}</Project>
<Name>Tango.SharedUI</Name>