diff options
| author | Roy Ben-Shabat <Roy@Twine-s.com> | 2018-02-08 11:52:30 +0200 |
|---|---|---|
| committer | Roy Ben-Shabat <Roy@Twine-s.com> | 2018-02-08 11:52:30 +0200 |
| commit | 0907f58b81e29d9c0f415e2c7660ad60787f281b (patch) | |
| tree | 4440c23e3805b5094dd939ff17c85656afc3298b /Software/Visual_Studio/Tango.Editors | |
| parent | 781e923508aafa6d39c3d38f1b1d4664dedbb83d (diff) | |
| download | Tango-0907f58b81e29d9c0f415e2c7660ad60787f281b.tar.gz Tango-0907f58b81e29d9c0f415e2c7660ad60787f281b.zip | |
Embedded My Diagram Editing Components !
Diffstat (limited to 'Software/Visual_Studio/Tango.Editors')
71 files changed, 8055 insertions, 0 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&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 |
