mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Added first visual-scripting draft [WIP]
This commit is contained in:
parent
719d95bd33
commit
a45dd094a0
26
src/Artemis.Core/VisualScripting/INode.cs
Normal file
26
src/Artemis.Core/VisualScripting/INode.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Artemis.Core.VisualScripting
|
||||
{
|
||||
public interface INode : INotifyPropertyChanged
|
||||
{
|
||||
string Name { get; }
|
||||
string Description { get; }
|
||||
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
|
||||
public IReadOnlyCollection<IPin> Pins { get; }
|
||||
public IReadOnlyCollection<IPinCollection> PinCollections { get; }
|
||||
|
||||
public object CustomView { get; }
|
||||
public object CustomViewModel { get; }
|
||||
|
||||
event EventHandler Resetting;
|
||||
|
||||
void Evaluate();
|
||||
void Reset();
|
||||
}
|
||||
}
|
||||
20
src/Artemis.Core/VisualScripting/IPin.cs
Normal file
20
src/Artemis.Core/VisualScripting/IPin.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.Core.VisualScripting
|
||||
{
|
||||
public interface IPin
|
||||
{
|
||||
INode Node { get; }
|
||||
|
||||
string Name { get; }
|
||||
PinDirection Direction { get; }
|
||||
Type Type { get; }
|
||||
object PinValue { get; }
|
||||
|
||||
bool IsEvaluated { get; set; }
|
||||
|
||||
void ConnectTo(IPin pin);
|
||||
void DisconnectFrom(IPin pin);
|
||||
}
|
||||
}
|
||||
16
src/Artemis.Core/VisualScripting/IPinCollection.cs
Normal file
16
src/Artemis.Core/VisualScripting/IPinCollection.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.Core.VisualScripting
|
||||
{
|
||||
public interface IPinCollection : IEnumerable<IPin>
|
||||
{
|
||||
string Name { get; }
|
||||
PinDirection Direction { get; }
|
||||
Type Type { get; }
|
||||
|
||||
IPin AddPin();
|
||||
bool Remove(IPin pin);
|
||||
}
|
||||
}
|
||||
25
src/Artemis.Core/VisualScripting/IScript.cs
Normal file
25
src/Artemis.Core/VisualScripting/IScript.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Artemis.Core.VisualScripting
|
||||
{
|
||||
public interface IScript : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
string Name { get; }
|
||||
string Description { get; }
|
||||
|
||||
IEnumerable<INode> Nodes { get; }
|
||||
|
||||
Type ResultType { get; }
|
||||
|
||||
void Run();
|
||||
void AddNode(INode node);
|
||||
void RemoveNode(INode node);
|
||||
}
|
||||
|
||||
public interface IScript<out T> : IScript
|
||||
{
|
||||
T Result { get; }
|
||||
}
|
||||
}
|
||||
8
src/Artemis.Core/VisualScripting/PinDirection.cs
Normal file
8
src/Artemis.Core/VisualScripting/PinDirection.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Artemis.VisualScripting.Model
|
||||
{
|
||||
public enum PinDirection
|
||||
{
|
||||
Input,
|
||||
Output
|
||||
}
|
||||
}
|
||||
@ -34,16 +34,19 @@
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignExtensions;component/Themes/MaterialDesignLightTheme.xaml" />
|
||||
|
||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Scrollbar.xaml" />
|
||||
|
||||
|
||||
<!-- Shared UI -->
|
||||
<ResourceDictionary Source="pack://application:,,,/Artemis.UI.Shared;component/Resources/ArtemisShared.xaml" />
|
||||
|
||||
<!-- Visual Scripting -->
|
||||
<ResourceDictionary Source="pack://application:,,,/Artemis.VisualScripting;component/Editor/EditorStyles.xaml" />
|
||||
|
||||
<ResourceDictionary Source="ResourceDictionaries/SideNavigation.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- Some general convertes etc. -->
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
|
||||
|
||||
<FontFamily x:Key="RobotoMono">pack://application:,,,/Resources/Fonts/#Roboto Mono</FontFamily>
|
||||
|
||||
<!-- Disable tab stop/focusable on all content controls -->
|
||||
|
||||
@ -82,6 +82,7 @@
|
||||
<ProjectReference Include="..\Artemis.UI.Shared\Artemis.UI.Shared.csproj">
|
||||
<Private>true</Private>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Artemis.VisualScripting\Artemis.VisualScripting.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Fonts\RobotoMono-Regular.ttf" />
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:displayConditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayConditions"
|
||||
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
||||
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls;assembly=Artemis.VisualScripting"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayConditions.DisplayConditionsView"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
@ -40,7 +41,9 @@
|
||||
<Separator Grid.Row="1" Grid.Column="0" Style="{StaticResource MaterialDesignDarkSeparator}" Margin="-2 0" />
|
||||
|
||||
<Grid Grid.Row="2" Grid.Column="0">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Background="{StaticResource MaterialDesignCardBackground}">
|
||||
<controls:VisualScriptEditor Script="{Binding Script}" AvailableNodes="{Binding AvailableNodes}" />
|
||||
|
||||
<!--<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Background="{StaticResource MaterialDesignCardBackground}">
|
||||
<ContentControl s:View.Model="{Binding ActiveItem}" />
|
||||
</ScrollViewer>
|
||||
|
||||
@ -65,7 +68,7 @@
|
||||
</materialDesign:PackIcon>
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center">Click the plus icon to start using display conditions!</TextBlock>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
</materialDesign:Card>-->
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="3" Visibility="{Binding IsEventCondition, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.ProfileEditor.Conditions;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.VisualScripting.Model;
|
||||
using Artemis.VisualScripting.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
||||
@ -24,6 +24,35 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
_dataModelConditionsVmFactory = dataModelConditionsVmFactory;
|
||||
|
||||
AvailableNodes = _nodeService.AvailableNodes;
|
||||
Script = new Script<bool>("Display Condition (TODO)", "TODO");
|
||||
}
|
||||
|
||||
#region TODO
|
||||
|
||||
private static NodeService _nodeService;
|
||||
|
||||
static DisplayConditionsViewModel()
|
||||
{
|
||||
_nodeService = new NodeService();
|
||||
_nodeService.InitializeNodes();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Script<bool> _script;
|
||||
public Script<bool> Script
|
||||
{
|
||||
get => _script;
|
||||
private set => SetAndNotify(ref _script, value);
|
||||
}
|
||||
|
||||
private IEnumerable<NodeData> _availableNodes;
|
||||
public IEnumerable<NodeData> AvailableNodes
|
||||
{
|
||||
get => _availableNodes;
|
||||
set => SetAndNotify(ref _availableNodes, value);
|
||||
}
|
||||
|
||||
public bool DisplayStartHint
|
||||
@ -110,8 +139,8 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
||||
{
|
||||
if (RenderProfileElement != null)
|
||||
{
|
||||
RenderProfileElement.DisplayCondition.ChildAdded -= DisplayConditionOnChildrenModified;
|
||||
RenderProfileElement.DisplayCondition.ChildRemoved -= DisplayConditionOnChildrenModified;
|
||||
//RenderProfileElement.DisplayCondition.ChildAdded -= DisplayConditionOnChildrenModified;
|
||||
//RenderProfileElement.DisplayCondition.ChildRemoved -= DisplayConditionOnChildrenModified;
|
||||
RenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
|
||||
}
|
||||
|
||||
@ -128,23 +157,26 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
||||
}
|
||||
|
||||
// Ensure the layer has a root display condition group
|
||||
//if (renderProfileElement.DisplayCondition == null)
|
||||
// renderProfileElement.DisplayCondition = new DataModelConditionGroup(null);
|
||||
|
||||
if (renderProfileElement.DisplayCondition == null)
|
||||
renderProfileElement.DisplayCondition = new DataModelConditionGroup(null);
|
||||
renderProfileElement.DisplayCondition = new Script<bool>("Display Condition (TODO)", "-");
|
||||
|
||||
List<Module> modules = new();
|
||||
if (_profileEditorService.SelectedProfileConfiguration?.Module != null)
|
||||
modules.Add(_profileEditorService.SelectedProfileConfiguration.Module);
|
||||
ActiveItem = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(renderProfileElement.DisplayCondition, ConditionGroupType.General, modules);
|
||||
ActiveItem.IsRootGroup = true;
|
||||
//List<Module> modules = new();
|
||||
//if (_profileEditorService.SelectedProfileConfiguration?.Module != null)
|
||||
// modules.Add(_profileEditorService.SelectedProfileConfiguration.Module);
|
||||
//ActiveItem = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(renderProfileElement.DisplayCondition, ConditionGroupType.General, modules);
|
||||
//ActiveItem.IsRootGroup = true;
|
||||
|
||||
DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any();
|
||||
IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent);
|
||||
//DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any();
|
||||
//IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent);
|
||||
|
||||
RenderProfileElement.DisplayCondition.ChildAdded += DisplayConditionOnChildrenModified;
|
||||
RenderProfileElement.DisplayCondition.ChildRemoved += DisplayConditionOnChildrenModified;
|
||||
//RenderProfileElement.DisplayCondition.ChildAdded += DisplayConditionOnChildrenModified;
|
||||
//RenderProfileElement.DisplayCondition.ChildRemoved += DisplayConditionOnChildrenModified;
|
||||
RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
|
||||
}
|
||||
|
||||
|
||||
private void TimelineOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
NotifyOfPropertyChange(nameof(DisplayContinuously));
|
||||
@ -154,8 +186,8 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
||||
|
||||
private void DisplayConditionOnChildrenModified(object sender, EventArgs e)
|
||||
{
|
||||
DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any();
|
||||
IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent);
|
||||
//DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any();
|
||||
//IsEventCondition = RenderProfileElement.DisplayCondition.Children.Any(c => c is DataModelConditionEvent);
|
||||
}
|
||||
|
||||
public void EventTriggerModeSelected()
|
||||
|
||||
@ -247,6 +247,11 @@
|
||||
"resolved": "2.1.0",
|
||||
"contentHash": "UTdxWvbgp2xzT1Ajaa2va+Qi3oNHJPasYmVhbKI2VVdu1VYP6yUG+RikhsHvpD7iM0S8e8UYb5Qm/LTWxx9QAA=="
|
||||
},
|
||||
"JetBrains.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2021.1.0",
|
||||
"contentHash": "n9JSw5Z+F+6gp9vSv4aLH6p/bx3GAYA6FZVq1wJq/TJySv/kPgFKLGFeS7A8Xa5X4/GWorh5gd43yjamUgnBNA=="
|
||||
},
|
||||
"LiteDB": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.10",
|
||||
@ -1472,6 +1477,13 @@
|
||||
"System.Buffers": "4.5.1",
|
||||
"System.Numerics.Vectors": "4.5.0"
|
||||
}
|
||||
},
|
||||
"artemis.visualscripting": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"Artemis.Core": "1.0.0",
|
||||
"JetBrains.Annotations": "2021.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
52
src/Artemis.VisualScripting/Artemis.VisualScripting.csproj
Normal file
52
src/Artemis.VisualScripting/Artemis.VisualScripting.csproj
Normal file
@ -0,0 +1,52 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<PreserveCompilationContext>false</PreserveCompilationContext>
|
||||
<ShouldIncludeNativeSkiaSharp>false</ShouldIncludeNativeSkiaSharp>
|
||||
<AssemblyTitle>Artemis.VisualScripting</AssemblyTitle>
|
||||
<Product>Artemis Visual-Scripting</Product>
|
||||
<Copyright>Copyright © Darth Affe - 2021</Copyright>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<Platforms>x64</Platforms>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Nullable>disable</Nullable>
|
||||
<AnalysisLevel>latest</AnalysisLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<DocumentationFile>bin\Artemis.VisualScripting.xml</DocumentationFile>
|
||||
<NoWarn></NoWarn>
|
||||
<WarningLevel>5</WarningLevel>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NrtRevisionFormat>1.0-{chash:6}</NrtRevisionFormat>
|
||||
<NrtResolveSimpleAttributes>true</NrtResolveSimpleAttributes>
|
||||
<NrtResolveInformationalAttribute>true</NrtResolveInformationalAttribute>
|
||||
<NrtResolveCopyright>true</NrtResolveCopyright>
|
||||
<NrtTagMatch>v[0-9]*</NrtTagMatch>
|
||||
<NrtRemoveTagV>true</NrtRemoveTagV>
|
||||
<NrtRequiredVcs>git</NrtRequiredVcs>
|
||||
<NrtShowRevision>true</NrtShowRevision>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DocumentationFile>bin\Artemis.VisualScripting.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Update="Editor\EditorStyles.xaml">
|
||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
37
src/Artemis.VisualScripting/Attributes/UIAttribute.cs
Normal file
37
src/Artemis.VisualScripting/Attributes/UIAttribute.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.VisualScripting.Attributes
|
||||
{
|
||||
public class UIAttribute : Attribute
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public string Name { get; }
|
||||
public string Description { get; set; }
|
||||
public string Category { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public UIAttribute(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
}
|
||||
|
||||
public UIAttribute(string name, string description)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
}
|
||||
|
||||
public UIAttribute(string name, string description, string category)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
this.Category = category;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Shapes;
|
||||
using Artemis.VisualScripting.Editor.Controls.Wrapper;
|
||||
|
||||
namespace Artemis.VisualScripting.Editor.Controls
|
||||
{
|
||||
[TemplatePart(Name = PART_PATH, Type = typeof(Path))]
|
||||
public class VisualScriptCablePresenter : Control
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string PART_PATH = "PART_Path";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties & Fields
|
||||
|
||||
private Path _path;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dependency Properties
|
||||
|
||||
public static readonly DependencyProperty CableProperty = DependencyProperty.Register(
|
||||
"Cable", typeof(VisualScriptCable), typeof(VisualScriptCablePresenter), new PropertyMetadata(default(VisualScriptCable)));
|
||||
|
||||
public VisualScriptCable Cable
|
||||
{
|
||||
get => (VisualScriptCable)GetValue(CableProperty);
|
||||
set => SetValue(CableProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ThicknessProperty = DependencyProperty.Register(
|
||||
"Thickness", typeof(double), typeof(VisualScriptCablePresenter), new PropertyMetadata(default(double)));
|
||||
|
||||
public double Thickness
|
||||
{
|
||||
get => (double)GetValue(ThicknessProperty);
|
||||
set => SetValue(ThicknessProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_path = GetTemplateChild(PART_PATH) as Path ?? throw new NullReferenceException($"The Path '{PART_PATH}' is missing.");
|
||||
_path.MouseDown += OnPathMouseDown;
|
||||
}
|
||||
|
||||
private void OnPathMouseDown(object sender, MouseButtonEventArgs args)
|
||||
{
|
||||
if ((args.ChangedButton == MouseButton.Left) && (args.LeftButton == MouseButtonState.Pressed) && (args.ClickCount == 2))
|
||||
{
|
||||
//TODO DarthAffe 17.06.2021: Should we add rerouting?
|
||||
//AddRerouteNode();
|
||||
}
|
||||
else if (args.ChangedButton == MouseButton.Middle)
|
||||
Cable.Disconnect();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Artemis.Core.VisualScripting;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.VisualScripting.Editor.Controls
|
||||
{
|
||||
public class VisualScriptEditor : Control
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dependency Properties
|
||||
|
||||
public static readonly DependencyProperty ScriptProperty = DependencyProperty.Register(
|
||||
"Script", typeof(IScript), typeof(VisualScriptEditor), new PropertyMetadata(default(IScript)));
|
||||
|
||||
public IScript Script
|
||||
{
|
||||
get => (IScript)GetValue(ScriptProperty);
|
||||
set => SetValue(ScriptProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AvailableNodesProperty = DependencyProperty.Register(
|
||||
"AvailableNodes", typeof(IEnumerable<NodeData>), typeof(VisualScriptEditor), new PropertyMetadata(default(IEnumerable<NodeData>)));
|
||||
|
||||
public IEnumerable<NodeData> AvailableNodes
|
||||
{
|
||||
get => (IEnumerable<NodeData>)GetValue(AvailableNodesProperty);
|
||||
set => SetValue(AvailableNodesProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.VisualScripting.Editor.Controls
|
||||
{
|
||||
[TemplatePart(Name = PART_SEARCHBOX, Type = typeof(TextBox))]
|
||||
[TemplatePart(Name = PART_CONTENT, Type = typeof(ListBox))]
|
||||
public class VisualScriptNodeCreationBox : Control
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string PART_SEARCHBOX = "PART_SearchBox";
|
||||
private const string PART_CONTENT = "PART_Content";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties & Fields
|
||||
|
||||
private TextBox _searchBox;
|
||||
private ListBox _contentList;
|
||||
|
||||
private CollectionViewSource _collectionViewSource;
|
||||
private ICollectionView _contentView;
|
||||
|
||||
#endregion
|
||||
|
||||
#region DependencyProperties
|
||||
|
||||
public static readonly DependencyProperty AvailableNodesProperty = DependencyProperty.Register(
|
||||
"AvailableNodes", typeof(IEnumerable), typeof(VisualScriptNodeCreationBox), new PropertyMetadata(default(IEnumerable), OnItemsSourceChanged));
|
||||
|
||||
public IEnumerable AvailableNodes
|
||||
{
|
||||
get => (IEnumerable)GetValue(AvailableNodesProperty);
|
||||
set => SetValue(AvailableNodesProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CreateNodeCommandProperty = DependencyProperty.Register(
|
||||
"CreateNodeCommand", typeof(ICommand), typeof(VisualScriptNodeCreationBox), new PropertyMetadata(default(ICommand)));
|
||||
|
||||
public ICommand CreateNodeCommand
|
||||
{
|
||||
get => (ICommand)GetValue(CreateNodeCommandProperty);
|
||||
set => SetValue(CreateNodeCommandProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_searchBox = GetTemplateChild(PART_SEARCHBOX) as TextBox ?? throw new NullReferenceException($"The Element '{PART_SEARCHBOX}' is missing.");
|
||||
_contentList = GetTemplateChild(PART_CONTENT) as ListBox ?? throw new NullReferenceException($"The Element '{PART_CONTENT}' is missing.");
|
||||
|
||||
_searchBox.TextChanged += OnSearchBoxTextChanged;
|
||||
_contentList.IsSynchronizedWithCurrentItem = false;
|
||||
_contentList.SelectionChanged += OnContentListSelectionChanged;
|
||||
_contentList.SelectionMode = SelectionMode.Single;
|
||||
|
||||
_searchBox.Focus();
|
||||
_contentView?.Refresh();
|
||||
ItemsSourceChanged();
|
||||
}
|
||||
|
||||
private void OnSearchBoxTextChanged(object sender, TextChangedEventArgs args)
|
||||
{
|
||||
_contentView?.Refresh();
|
||||
}
|
||||
|
||||
private void OnContentListSelectionChanged(object sender, SelectionChangedEventArgs args)
|
||||
{
|
||||
if ((args == null) || (_contentList?.SelectedItem == null)) return;
|
||||
|
||||
CreateNodeCommand?.Execute(_contentList.SelectedItem);
|
||||
}
|
||||
|
||||
private bool Filter(object o)
|
||||
{
|
||||
if (_searchBox == null) return false;
|
||||
if (o is not NodeData nodeData) return false;
|
||||
|
||||
return nodeData.Name.Contains(_searchBox.Text, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void ItemsSourceChanged()
|
||||
{
|
||||
if (_contentList == null) return;
|
||||
|
||||
if (AvailableNodes == null)
|
||||
{
|
||||
_contentView = null;
|
||||
_collectionViewSource = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_collectionViewSource = new CollectionViewSource { Source = AvailableNodes, SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending)}};
|
||||
_contentView = _collectionViewSource.View;
|
||||
_contentView.Filter += Filter;
|
||||
}
|
||||
|
||||
_contentList.ItemsSource = _contentView;
|
||||
}
|
||||
|
||||
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs args) => (d as VisualScriptNodeCreationBox)?.ItemsSourceChanged();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.VisualScripting.Editor.Controls.Wrapper;
|
||||
|
||||
namespace Artemis.VisualScripting.Editor.Controls
|
||||
{
|
||||
public class VisualScriptNodePresenter : Control
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private bool _isDragging;
|
||||
private bool _isDragged;
|
||||
private bool _isDeselected;
|
||||
private Point _dragStartPosition;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dependency Properties
|
||||
|
||||
public static readonly DependencyProperty NodeProperty = DependencyProperty.Register(
|
||||
"Node", typeof(VisualScriptNode), typeof(VisualScriptNodePresenter), new PropertyMetadata(default(VisualScriptNode)));
|
||||
|
||||
public VisualScriptNode Node
|
||||
{
|
||||
get => (VisualScriptNode)GetValue(NodeProperty);
|
||||
set => SetValue(NodeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TitleBrushProperty = DependencyProperty.Register(
|
||||
"TitleBrush", typeof(Brush), typeof(VisualScriptNodePresenter), new PropertyMetadata(default(Brush)));
|
||||
|
||||
public Brush TitleBrush
|
||||
{
|
||||
get => (Brush)GetValue(TitleBrushProperty);
|
||||
set => SetValue(TitleBrushProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
protected override Size MeasureOverride(Size constraint)
|
||||
{
|
||||
int SnapToGridSize(int value)
|
||||
{
|
||||
int mod = value % Node.Script.GridSize;
|
||||
return mod switch
|
||||
{
|
||||
< 0 => Node.Script.GridSize,
|
||||
> 0 => value + (Node.Script.GridSize - mod),
|
||||
_ => value
|
||||
};
|
||||
}
|
||||
|
||||
Size neededSize = base.MeasureOverride(constraint);
|
||||
int width = (int)Math.Ceiling(neededSize.Width);
|
||||
int height = (int)Math.Ceiling(neededSize.Height);
|
||||
|
||||
return new Size(SnapToGridSize(width), SnapToGridSize(height));
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs args)
|
||||
{
|
||||
base.OnMouseLeftButtonDown(args);
|
||||
|
||||
_isDragged = false;
|
||||
_isDragging = true;
|
||||
_isDeselected = false;
|
||||
|
||||
bool isShiftDown = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
|
||||
if (!Node.IsSelected)
|
||||
Node.Select(isShiftDown);
|
||||
else if (isShiftDown)
|
||||
{
|
||||
_isDeselected = true;
|
||||
Node.Deselect(true);
|
||||
}
|
||||
|
||||
Node.DragStart();
|
||||
|
||||
_dragStartPosition = PointToScreen(args.GetPosition(this));
|
||||
|
||||
CaptureMouse();
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs args)
|
||||
{
|
||||
base.OnMouseLeftButtonUp(args);
|
||||
|
||||
if (_isDragging)
|
||||
{
|
||||
_isDragging = false;
|
||||
Node.DragEnd();
|
||||
|
||||
ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
if (!_isDragged && !_isDeselected)
|
||||
Node.Select(Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift));
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs args)
|
||||
{
|
||||
base.OnMouseMove(args);
|
||||
|
||||
if (!_isDragging) return;
|
||||
|
||||
Point mousePosition = PointToScreen(args.GetPosition(this));
|
||||
Vector offset = mousePosition - _dragStartPosition;
|
||||
|
||||
if (args.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
_dragStartPosition = mousePosition;
|
||||
Node.DragMove(offset.X, offset.Y);
|
||||
|
||||
if ((offset.X != 0) && (offset.Y != 0))
|
||||
_isDragged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isDragging = false;
|
||||
Node.DragEnd();
|
||||
ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,184 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.VisualScripting.Editor.Controls.Wrapper;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.VisualScripting.Editor.Controls
|
||||
{
|
||||
[TemplatePart(Name = PART_DOT, Type = typeof(FrameworkElement))]
|
||||
public class VisualScriptPinPresenter : Control
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string PART_DOT = "PART_Dot";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties & Fields
|
||||
|
||||
private VisualScriptNodePresenter _nodePresenter;
|
||||
private FrameworkElement _dot;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dependency Properties
|
||||
|
||||
public static readonly DependencyProperty PinProperty = DependencyProperty.Register(
|
||||
"Pin", typeof(VisualScriptPin), typeof(VisualScriptPinPresenter), new PropertyMetadata(default(VisualScriptPin), PinChanged));
|
||||
|
||||
public VisualScriptPin Pin
|
||||
{
|
||||
get => (VisualScriptPin)GetValue(PinProperty);
|
||||
set => SetValue(PinProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public VisualScriptPinPresenter()
|
||||
{
|
||||
Loaded += OnLoaded;
|
||||
Unloaded += OnUnloaded;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs args)
|
||||
{
|
||||
DependencyObject parent = this;
|
||||
while ((parent = VisualTreeHelper.GetParent(parent)) != null)
|
||||
{
|
||||
if (parent is VisualScriptNodePresenter nodePresenter)
|
||||
{
|
||||
_nodePresenter = nodePresenter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LayoutUpdated += OnLayoutUpdated;
|
||||
|
||||
UpdateAbsoluteLocation();
|
||||
}
|
||||
|
||||
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LayoutUpdated -= OnLayoutUpdated;
|
||||
_nodePresenter = null;
|
||||
}
|
||||
|
||||
private void OnNodePropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
UpdateAbsoluteLocation();
|
||||
}
|
||||
|
||||
private void OnLayoutUpdated(object sender, EventArgs args)
|
||||
{
|
||||
_dot = GetTemplateChild(PART_DOT) as FrameworkElement ?? throw new NullReferenceException($"The Element '{PART_DOT}' is missing.");
|
||||
|
||||
_dot.AllowDrop = true;
|
||||
|
||||
_dot.MouseDown += OnDotMouseDown;
|
||||
_dot.MouseMove += OnDotMouseMove;
|
||||
_dot.Drop += OnDotDrop;
|
||||
_dot.DragEnter += OnDotDrag;
|
||||
_dot.DragOver += OnDotDrag;
|
||||
|
||||
UpdateAbsoluteLocation();
|
||||
}
|
||||
|
||||
private static void PinChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (d is not VisualScriptPinPresenter presenter) return;
|
||||
|
||||
presenter.PinChanged(args);
|
||||
}
|
||||
|
||||
private void PinChanged(DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (args.OldValue is VisualScriptPin oldPin)
|
||||
oldPin.Node.Node.PropertyChanged -= OnNodePropertyChanged;
|
||||
|
||||
if (args.NewValue is VisualScriptPin newPin)
|
||||
newPin.Node.Node.PropertyChanged += OnNodePropertyChanged;
|
||||
|
||||
UpdateAbsoluteLocation();
|
||||
}
|
||||
|
||||
private void UpdateAbsoluteLocation()
|
||||
{
|
||||
if ((Pin == null) || (_nodePresenter == null)) return;
|
||||
|
||||
try
|
||||
{
|
||||
double circleRadius = ActualHeight / 2.0;
|
||||
double xOffset = Pin.Pin.Direction == PinDirection.Input ? circleRadius : ActualWidth - circleRadius;
|
||||
Point relativePosition = this.TransformToVisual(_nodePresenter).Transform(new Point(xOffset, circleRadius));
|
||||
Pin.AbsolutePosition = new Point(Pin.Node.X + relativePosition.X, Pin.Node.Y + relativePosition.Y);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Pin.AbsolutePosition = new Point(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDotMouseDown(object sender, MouseButtonEventArgs args)
|
||||
{
|
||||
if (args.ChangedButton == MouseButton.Middle)
|
||||
Pin.DisconnectAll();
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDotMouseMove(object sender, MouseEventArgs args)
|
||||
{
|
||||
if (args.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Pin.SetConnecting(true);
|
||||
DragDrop.DoDragDrop(this, Pin, DragDropEffects.Link);
|
||||
Pin.SetConnecting(false);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDotDrag(object sender, DragEventArgs args)
|
||||
{
|
||||
if (!args.Data.GetDataPresent(typeof(VisualScriptPin)))
|
||||
args.Effects = DragDropEffects.None;
|
||||
else
|
||||
{
|
||||
VisualScriptPin sourcePin = (VisualScriptPin)args.Data.GetData(typeof(VisualScriptPin));
|
||||
if (sourcePin == null)
|
||||
args.Effects = DragDropEffects.None;
|
||||
else
|
||||
args.Effects = ((sourcePin.Pin.Direction != Pin.Pin.Direction) && (sourcePin.Pin.Node != Pin.Pin.Node) && IsTypeCompatible(sourcePin.Pin.Type)) ? DragDropEffects.Link : DragDropEffects.None;
|
||||
}
|
||||
|
||||
if (args.Effects == DragDropEffects.None)
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnDotDrop(object sender, DragEventArgs args)
|
||||
{
|
||||
if (!args.Data.GetDataPresent(typeof(VisualScriptPin))) return;
|
||||
|
||||
VisualScriptPin sourcePin = (VisualScriptPin)args.Data.GetData(typeof(VisualScriptPin));
|
||||
if ((sourcePin == null) || !IsTypeCompatible(sourcePin.Pin.Type)) return;
|
||||
|
||||
try { new VisualScriptCable(Pin, sourcePin); } catch { /**/ }
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private bool IsTypeCompatible(Type type) => ((Pin.Pin.Direction == PinDirection.Input) && (Pin.Pin.Type == typeof(object))) || (Pin.Pin.Type == type);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,437 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core.VisualScripting;
|
||||
using Artemis.VisualScripting.Editor.Controls.Wrapper;
|
||||
using Artemis.VisualScripting.Model;
|
||||
using Artemis.VisualScripting.ViewModel;
|
||||
|
||||
namespace Artemis.VisualScripting.Editor.Controls
|
||||
{
|
||||
[TemplatePart(Name = PART_CANVAS, Type = typeof(Canvas))]
|
||||
[TemplatePart(Name = PART_NODELIST, Type = typeof(ItemsControl))]
|
||||
[TemplatePart(Name = PART_CABLELIST, Type = typeof(ItemsControl))]
|
||||
[TemplatePart(Name = PART_SELECTION_BORDER, Type = typeof(Border))]
|
||||
[TemplatePart(Name = PART_CREATION_BOX_PARENT, Type = typeof(Panel))]
|
||||
public class VisualScriptPresenter : Control
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string PART_CANVAS = "PART_Canvas";
|
||||
private const string PART_NODELIST = "PART_NodeList";
|
||||
private const string PART_CABLELIST = "PART_CableList";
|
||||
private const string PART_SELECTION_BORDER = "PART_SelectionBorder";
|
||||
private const string PART_CREATION_BOX_PARENT = "PART_CreationBoxParent";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties & Fields
|
||||
|
||||
private Canvas _canvas;
|
||||
private ItemsControl _nodeList;
|
||||
private ItemsControl _cableList;
|
||||
private Border _selectionBorder;
|
||||
private TranslateTransform _canvasViewPortTransform;
|
||||
private Panel _creationBoxParent;
|
||||
|
||||
private Vector _viewportCenter = new(0, 0);
|
||||
|
||||
private bool _dragCanvas = false;
|
||||
private Point _dragCanvasStartLocation;
|
||||
private Vector _dragCanvasStartOffset;
|
||||
private bool _movedDuringDrag = false;
|
||||
|
||||
private bool _boxSelect = false;
|
||||
private Point _boxSelectStartPoint;
|
||||
|
||||
private Point _lastRightClickLocation;
|
||||
|
||||
internal VisualScript VisualScript { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dependency Properties
|
||||
|
||||
public static readonly DependencyProperty ScriptProperty = DependencyProperty.Register(
|
||||
"Script", typeof(IScript), typeof(VisualScriptPresenter), new PropertyMetadata(default(IScript), ScriptChanged));
|
||||
|
||||
public IScript Script
|
||||
{
|
||||
get => (IScript)GetValue(ScriptProperty);
|
||||
set => SetValue(ScriptProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ScaleProperty = DependencyProperty.Register(
|
||||
"Scale", typeof(double), typeof(VisualScriptPresenter), new PropertyMetadata(1.0, ScaleChanged));
|
||||
|
||||
public double Scale
|
||||
{
|
||||
get => (double)GetValue(ScaleProperty);
|
||||
set => SetValue(ScaleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty MinScaleProperty = DependencyProperty.Register(
|
||||
"MinScale", typeof(double), typeof(VisualScriptPresenter), new PropertyMetadata(0.15));
|
||||
|
||||
public double MinScale
|
||||
{
|
||||
get => (double)GetValue(MinScaleProperty);
|
||||
set => SetValue(MinScaleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty MaxScaleProperty = DependencyProperty.Register(
|
||||
"MaxScale", typeof(double), typeof(VisualScriptPresenter), new PropertyMetadata(1.0));
|
||||
|
||||
public double MaxScale
|
||||
{
|
||||
get => (double)GetValue(MaxScaleProperty);
|
||||
set => SetValue(MaxScaleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ScaleFactorProperty = DependencyProperty.Register(
|
||||
"ScaleFactor", typeof(double), typeof(VisualScriptPresenter), new PropertyMetadata(1.3));
|
||||
|
||||
public double ScaleFactor
|
||||
{
|
||||
get => (double)GetValue(ScaleFactorProperty);
|
||||
set => SetValue(ScaleFactorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AvailableNodesProperty = DependencyProperty.Register(
|
||||
"AvailableNodes", typeof(IEnumerable<NodeData>), typeof(VisualScriptPresenter), new PropertyMetadata(default(IEnumerable<NodeData>)));
|
||||
|
||||
public IEnumerable<NodeData> AvailableNodes
|
||||
{
|
||||
get => (IEnumerable<NodeData>)GetValue(AvailableNodesProperty);
|
||||
set => SetValue(AvailableNodesProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CreateNodeCommandProperty = DependencyProperty.Register(
|
||||
"CreateNodeCommand", typeof(ICommand), typeof(VisualScriptPresenter), new PropertyMetadata(default(ICommand)));
|
||||
|
||||
public ICommand CreateNodeCommand
|
||||
{
|
||||
get => (ICommand)GetValue(CreateNodeCommandProperty);
|
||||
private set => SetValue(CreateNodeCommandProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty GridSizeProperty = DependencyProperty.Register(
|
||||
"GridSize", typeof(int), typeof(VisualScriptPresenter), new PropertyMetadata(24));
|
||||
|
||||
public int GridSize
|
||||
{
|
||||
get => (int)GetValue(GridSizeProperty);
|
||||
set => SetValue(GridSizeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SurfaceSizeProperty = DependencyProperty.Register(
|
||||
"SurfaceSize", typeof(int), typeof(VisualScriptPresenter), new PropertyMetadata(16384));
|
||||
|
||||
public int SurfaceSize
|
||||
{
|
||||
get => (int)GetValue(SurfaceSizeProperty);
|
||||
set => SetValue(SurfaceSizeProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public VisualScriptPresenter()
|
||||
{
|
||||
CreateNodeCommand = new ActionCommand<NodeData>(CreateNode);
|
||||
|
||||
this.SizeChanged += OnSizeChanged;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_canvas = GetTemplateChild(PART_CANVAS) as Canvas ?? throw new NullReferenceException($"The Canvas '{PART_CANVAS}' is missing.");
|
||||
_selectionBorder = GetTemplateChild(PART_SELECTION_BORDER) as Border ?? throw new NullReferenceException($"The Border '{PART_SELECTION_BORDER}' is missing.");
|
||||
_nodeList = GetTemplateChild(PART_NODELIST) as ItemsControl ?? throw new NullReferenceException($"The ItemsControl '{PART_NODELIST}' is missing.");
|
||||
_cableList = GetTemplateChild(PART_CABLELIST) as ItemsControl ?? throw new NullReferenceException($"The ItemsControl '{PART_CABLELIST}' is missing.");
|
||||
_creationBoxParent = GetTemplateChild(PART_CREATION_BOX_PARENT) as Panel ?? throw new NullReferenceException($"The Panel '{PART_CREATION_BOX_PARENT}' is missing.");
|
||||
|
||||
_canvas.AllowDrop = true;
|
||||
|
||||
_canvas.RenderTransform = _canvasViewPortTransform = new TranslateTransform(0, 0);
|
||||
_canvas.MouseLeftButtonDown += OnCanvasMouseLeftButtonDown;
|
||||
_canvas.MouseLeftButtonUp += OnCanvasMouseLeftButtonUp;
|
||||
_canvas.MouseRightButtonDown += OnCanvasMouseRightButtonDown;
|
||||
_canvas.MouseRightButtonUp += OnCanvasMouseRightButtonUp;
|
||||
_canvas.PreviewMouseRightButtonDown += OnCanvasPreviewMouseRightButtonDown;
|
||||
_canvas.MouseMove += OnCanvasMouseMove;
|
||||
_canvas.MouseWheel += OnCanvasMouseWheel;
|
||||
_canvas.DragOver += OnCanvasDragOver;
|
||||
|
||||
_nodeList.ItemsSource = VisualScript?.Nodes;
|
||||
_cableList.ItemsSource = VisualScript?.Cables;
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object sender, SizeChangedEventArgs args)
|
||||
{
|
||||
if (sender is not VisualScriptPresenter scriptPresenter) return;
|
||||
|
||||
scriptPresenter.UpdatePanning();
|
||||
}
|
||||
|
||||
private static void ScriptChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (d is not VisualScriptPresenter scriptPresenter) return;
|
||||
|
||||
scriptPresenter.ScriptChanged(args.NewValue is not Script script ? null : new VisualScript(script, scriptPresenter.SurfaceSize, scriptPresenter.GridSize));
|
||||
}
|
||||
|
||||
private void ScriptChanged(VisualScript newScript)
|
||||
{
|
||||
if (VisualScript != null)
|
||||
VisualScript.PropertyChanged -= OnVisualScriptPropertyChanged;
|
||||
|
||||
VisualScript = newScript;
|
||||
|
||||
if (VisualScript != null)
|
||||
{
|
||||
VisualScript.PropertyChanged += OnVisualScriptPropertyChanged;
|
||||
|
||||
if (_nodeList != null)
|
||||
_nodeList.ItemsSource = VisualScript?.Nodes;
|
||||
|
||||
if (_cableList != null)
|
||||
_cableList.ItemsSource = VisualScript?.Cables;
|
||||
|
||||
VisualScript.Nodes.Clear();
|
||||
foreach (INode node in VisualScript.Script.Nodes)
|
||||
InitializeNode(node);
|
||||
}
|
||||
|
||||
CenterAt(new Vector(0, 0));
|
||||
}
|
||||
|
||||
private void OnVisualScriptPropertyChanged(object sender, PropertyChangedEventArgs args)
|
||||
{
|
||||
if (args.PropertyName == nameof(VisualScript.Cables))
|
||||
_cableList.ItemsSource = VisualScript.Cables;
|
||||
}
|
||||
|
||||
private void OnCanvasPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs args)
|
||||
{
|
||||
_lastRightClickLocation = args.GetPosition(_canvas);
|
||||
}
|
||||
|
||||
private void OnCanvasMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
|
||||
{
|
||||
VisualScript.DeselectAllNodes();
|
||||
|
||||
_boxSelect = true;
|
||||
_boxSelectStartPoint = args.GetPosition(_canvas);
|
||||
Canvas.SetLeft(_selectionBorder, _boxSelectStartPoint.X);
|
||||
Canvas.SetTop(_selectionBorder, _boxSelectStartPoint.Y);
|
||||
_selectionBorder.Width = 0;
|
||||
_selectionBorder.Height = 0;
|
||||
_selectionBorder.Visibility = Visibility.Visible;
|
||||
_canvas.CaptureMouse();
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnCanvasMouseLeftButtonUp(object sender, MouseButtonEventArgs args)
|
||||
{
|
||||
if (_boxSelect)
|
||||
{
|
||||
_boxSelect = false;
|
||||
_canvas.ReleaseMouseCapture();
|
||||
Mouse.OverrideCursor = null;
|
||||
_selectionBorder.Visibility = Visibility.Hidden;
|
||||
SelectWithinRectangle(new Rect(Canvas.GetLeft(_selectionBorder), Canvas.GetTop(_selectionBorder), _selectionBorder.Width, _selectionBorder.Height));
|
||||
args.Handled = _movedDuringDrag;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCanvasMouseRightButtonDown(object sender, MouseButtonEventArgs args)
|
||||
{
|
||||
_dragCanvas = true;
|
||||
_dragCanvasStartLocation = args.GetPosition(this);
|
||||
_dragCanvasStartOffset = _viewportCenter;
|
||||
|
||||
_movedDuringDrag = false;
|
||||
|
||||
_canvas.CaptureMouse();
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnCanvasMouseRightButtonUp(object sender, MouseButtonEventArgs args)
|
||||
{
|
||||
if (_dragCanvas)
|
||||
{
|
||||
_dragCanvas = false;
|
||||
_canvas.ReleaseMouseCapture();
|
||||
Mouse.OverrideCursor = null;
|
||||
args.Handled = _movedDuringDrag;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCanvasMouseMove(object sender, MouseEventArgs args)
|
||||
{
|
||||
if (_dragCanvas)
|
||||
{
|
||||
if (args.RightButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Vector newLocation = _dragCanvasStartOffset + (((args.GetPosition(this) - _dragCanvasStartLocation)) * (1.0 / Scale));
|
||||
CenterAt(newLocation);
|
||||
|
||||
_movedDuringDrag = true;
|
||||
Mouse.OverrideCursor = Cursors.ScrollAll;
|
||||
}
|
||||
else
|
||||
_dragCanvas = false;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
else if (_boxSelect)
|
||||
{
|
||||
if (args.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
double x = _boxSelectStartPoint.X;
|
||||
double y = _boxSelectStartPoint.Y;
|
||||
Point mousePosition = args.GetPosition(_canvas);
|
||||
|
||||
double rectX = mousePosition.X > x ? x : mousePosition.X;
|
||||
double rectY = mousePosition.Y > y ? y : mousePosition.Y;
|
||||
double rectWidth = Math.Abs(x - mousePosition.X);
|
||||
double rectHeight = Math.Abs(y - mousePosition.Y);
|
||||
|
||||
Canvas.SetLeft(_selectionBorder, rectX);
|
||||
Canvas.SetTop(_selectionBorder, rectY);
|
||||
_selectionBorder.Width = rectWidth;
|
||||
_selectionBorder.Height = rectHeight;
|
||||
}
|
||||
else
|
||||
_boxSelect = false;
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCanvasDragOver(object sender, DragEventArgs args)
|
||||
{
|
||||
if (VisualScript == null) return;
|
||||
|
||||
if (VisualScript.IsConnecting)
|
||||
VisualScript.OnDragOver(args.GetPosition(_canvas));
|
||||
}
|
||||
|
||||
private void OnCanvasMouseWheel(object sender, MouseWheelEventArgs args)
|
||||
{
|
||||
if (args.Delta < 0)
|
||||
Scale /= ScaleFactor;
|
||||
else
|
||||
Scale *= ScaleFactor;
|
||||
|
||||
Scale = Clamp(Scale, MinScale, MaxScale);
|
||||
|
||||
_canvas.LayoutTransform = new ScaleTransform(Scale, Scale);
|
||||
|
||||
UpdatePanning();
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void SelectWithinRectangle(Rect rectangle)
|
||||
{
|
||||
if (Script == null) return;
|
||||
|
||||
VisualScript.DeselectAllNodes();
|
||||
|
||||
for (int i = 0; i < _nodeList.Items.Count; i++)
|
||||
{
|
||||
ContentPresenter nodeControl = (ContentPresenter)_nodeList.ItemContainerGenerator.ContainerFromIndex(i);
|
||||
VisualScriptNode node = (VisualScriptNode)nodeControl.Content;
|
||||
|
||||
double nodeWidth = nodeControl.ActualWidth;
|
||||
double nodeHeight = nodeControl.ActualHeight;
|
||||
|
||||
if (rectangle.IntersectsWith(new Rect(node.X, node.Y, nodeWidth, nodeHeight)))
|
||||
node.Select(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ScaleChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
|
||||
{
|
||||
if (d is not VisualScriptPresenter presenter) return;
|
||||
if (presenter.VisualScript == null) return;
|
||||
|
||||
presenter.VisualScript.NodeDragScale = 1.0 / presenter.Scale;
|
||||
}
|
||||
|
||||
private void CreateNode(NodeData nodeData)
|
||||
{
|
||||
if (nodeData == null) return;
|
||||
|
||||
if (_creationBoxParent.ContextMenu != null)
|
||||
_creationBoxParent.ContextMenu.IsOpen = false;
|
||||
|
||||
INode node = nodeData.CreateNode();
|
||||
Script.AddNode(node);
|
||||
|
||||
InitializeNode(node, _lastRightClickLocation);
|
||||
}
|
||||
|
||||
private void InitializeNode(INode node, Point? initialLocation = null)
|
||||
{
|
||||
VisualScriptNode visualScriptNode = new(VisualScript, node);
|
||||
if (initialLocation != null)
|
||||
{
|
||||
visualScriptNode.X = initialLocation.Value.X;
|
||||
visualScriptNode.Y = initialLocation.Value.Y;
|
||||
}
|
||||
visualScriptNode.SnapNodeToGrid();
|
||||
VisualScript.Nodes.Add(visualScriptNode);
|
||||
}
|
||||
|
||||
private void CenterAt(Vector vector)
|
||||
{
|
||||
double halfSurface = (SurfaceSize / 2.0);
|
||||
_viewportCenter = Clamp(vector, new Vector(-halfSurface, -halfSurface), new Vector(halfSurface, halfSurface));
|
||||
UpdatePanning();
|
||||
}
|
||||
|
||||
private void UpdatePanning()
|
||||
{
|
||||
if (_canvasViewPortTransform == null) return;
|
||||
|
||||
double surfaceOffset = (SurfaceSize / 2.0) * Scale;
|
||||
_canvasViewPortTransform.X = (((_viewportCenter.X * Scale) + (ActualWidth / 2.0))) - surfaceOffset;
|
||||
_canvasViewPortTransform.Y = (((_viewportCenter.Y * Scale) + (ActualHeight / 2.0))) - surfaceOffset;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double Clamp(double value, double min, double max)
|
||||
{
|
||||
// ReSharper disable ConvertIfStatementToReturnStatement - I'm not sure why, but inlining this statement reduces performance by ~10%
|
||||
if (value < min) return min;
|
||||
if (value > max) return max;
|
||||
return value;
|
||||
// ReSharper restore ConvertIfStatementToReturnStatement
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector Clamp(Vector value, Vector min, Vector max)
|
||||
{
|
||||
double x = Clamp(value.X, min.X, max.X);
|
||||
double y = Clamp(value.Y, min.Y, max.Y);
|
||||
return new Vector(x, y);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using Artemis.Core.VisualScripting;
|
||||
using Artemis.VisualScripting.Events;
|
||||
using Artemis.VisualScripting.ViewModel;
|
||||
|
||||
namespace Artemis.VisualScripting.Editor.Controls.Wrapper
|
||||
{
|
||||
public class VisualScript : AbstractBindable
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly HashSet<VisualScriptNode> _selectedNodes = new();
|
||||
private readonly Dictionary<VisualScriptNode, (double X, double Y)> _nodeStartPositions = new();
|
||||
private double _nodeDragAccumulationX;
|
||||
private double _nodeDragAccumulationY;
|
||||
|
||||
public IScript Script { get; }
|
||||
|
||||
public int SurfaceSize { get; }
|
||||
public int GridSize { get; }
|
||||
|
||||
private double _nodeDragScale = 1;
|
||||
public double NodeDragScale
|
||||
{
|
||||
get => _nodeDragScale;
|
||||
set => SetProperty(ref _nodeDragScale, value);
|
||||
}
|
||||
|
||||
private VisualScriptPin _isConnectingPin;
|
||||
private VisualScriptPin IsConnectingPin
|
||||
{
|
||||
get => _isConnectingPin;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isConnectingPin, value))
|
||||
OnPropertyChanged(nameof(IsConnecting));
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<VisualScriptNode> Nodes { get; } = new();
|
||||
|
||||
public IEnumerable<VisualScriptCable> Cables => Nodes.SelectMany(n => n.InputPins.SelectMany(p => p.Connections))
|
||||
.Concat(Nodes.SelectMany(n => n.OutputPins.SelectMany(p => p.Connections)))
|
||||
.Concat(Nodes.SelectMany(n => n.InputPinCollections.SelectMany(p => p.Pins).SelectMany(p => p.Connections)))
|
||||
.Concat(Nodes.SelectMany(n => n.OutputPinCollections.SelectMany(p => p.Pins).SelectMany(p => p.Connections)))
|
||||
.Distinct();
|
||||
|
||||
public bool IsConnecting => IsConnectingPin != null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public VisualScript(IScript script, int surfaceSize, int gridSize)
|
||||
{
|
||||
this.Script = script;
|
||||
this.SurfaceSize = surfaceSize;
|
||||
this.GridSize = gridSize;
|
||||
|
||||
Nodes.CollectionChanged += OnNodeCollectionChanged;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void DeselectAllNodes(VisualScriptNode except = null)
|
||||
{
|
||||
List<VisualScriptNode> selectedNodes = _selectedNodes.ToList();
|
||||
foreach (VisualScriptNode node in selectedNodes.Where(n => n != except))
|
||||
node.Deselect();
|
||||
}
|
||||
|
||||
private void OnNodeCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
|
||||
{
|
||||
if (args.OldItems != null)
|
||||
UnregisterNodes(args.OldItems.Cast<VisualScriptNode>());
|
||||
|
||||
if (args.NewItems != null)
|
||||
RegisterNodes(args.NewItems.Cast<VisualScriptNode>());
|
||||
}
|
||||
|
||||
private void RegisterNodes(IEnumerable<VisualScriptNode> nodes)
|
||||
{
|
||||
foreach (VisualScriptNode node in nodes)
|
||||
{
|
||||
node.IsSelectedChanged += OnNodeIsSelectedChanged;
|
||||
node.DragStarting += OnNodeDragStarting;
|
||||
node.DragEnding += OnNodeDragEnding;
|
||||
node.DragMoving += OnNodeDragMoving;
|
||||
|
||||
if (node.IsSelected)
|
||||
_selectedNodes.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterNodes(IEnumerable<VisualScriptNode> nodes)
|
||||
{
|
||||
foreach (VisualScriptNode node in nodes)
|
||||
{
|
||||
node.IsSelectedChanged -= OnNodeIsSelectedChanged;
|
||||
node.DragStarting -= OnNodeDragStarting;
|
||||
node.DragEnding -= OnNodeDragEnding;
|
||||
node.DragMoving -= OnNodeDragMoving;
|
||||
|
||||
_selectedNodes.Remove(node);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNodeIsSelectedChanged(object sender, VisualScriptNodeIsSelectedChangedEventArgs args)
|
||||
{
|
||||
if (sender is not VisualScriptNode node) return;
|
||||
|
||||
if (args.IsSelected)
|
||||
{
|
||||
if (!args.AlterSelection)
|
||||
DeselectAllNodes(node);
|
||||
|
||||
_selectedNodes.Add(node);
|
||||
}
|
||||
else
|
||||
_selectedNodes.Remove(node);
|
||||
}
|
||||
|
||||
private void OnNodeDragStarting(object sender, EventArgs args)
|
||||
{
|
||||
_nodeDragAccumulationX = 0;
|
||||
_nodeDragAccumulationY = 0;
|
||||
_nodeStartPositions.Clear();
|
||||
|
||||
foreach (VisualScriptNode node in _selectedNodes)
|
||||
_nodeStartPositions.Add(node, (node.X, node.Y));
|
||||
}
|
||||
|
||||
private void OnNodeDragEnding(object sender, EventArgs args)
|
||||
{
|
||||
foreach (VisualScriptNode node in _selectedNodes)
|
||||
node.SnapNodeToGrid();
|
||||
}
|
||||
|
||||
private void OnNodeDragMoving(object sender, VisualScriptNodeDragMovingEventArgs args)
|
||||
{
|
||||
_nodeDragAccumulationX += args.DX * NodeDragScale;
|
||||
_nodeDragAccumulationY += args.DY * NodeDragScale;
|
||||
|
||||
foreach (VisualScriptNode node in _selectedNodes)
|
||||
{
|
||||
node.X = _nodeStartPositions[node].X + _nodeDragAccumulationX;
|
||||
node.Y = _nodeStartPositions[node].Y + _nodeDragAccumulationY;
|
||||
node.SnapNodeToGrid();
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnDragOver(Point position)
|
||||
{
|
||||
if (IsConnectingPin == null) return;
|
||||
|
||||
IsConnectingPin.AbsolutePosition = position;
|
||||
}
|
||||
|
||||
internal void OnIsConnectingPinChanged(VisualScriptPin isConnectingPin)
|
||||
{
|
||||
IsConnectingPin = isConnectingPin;
|
||||
}
|
||||
|
||||
internal void OnPinConnected(PinConnectedEventArgs args)
|
||||
{
|
||||
OnPropertyChanged(nameof(Cables));
|
||||
}
|
||||
|
||||
internal void OnPinDisconnected(PinDisconnectedEventArgs args)
|
||||
{
|
||||
OnPropertyChanged(nameof(Cables));
|
||||
}
|
||||
|
||||
public void RemoveNode(VisualScriptNode node)
|
||||
{
|
||||
Nodes.Remove(node);
|
||||
Script.RemoveNode(node.Node);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using Artemis.Core.VisualScripting;
|
||||
using Artemis.VisualScripting.Model;
|
||||
using Artemis.VisualScripting.ViewModel;
|
||||
|
||||
namespace Artemis.VisualScripting.Editor.Controls.Wrapper
|
||||
{
|
||||
public class VisualScriptCable : AbstractBindable
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private VisualScriptPin _from;
|
||||
public VisualScriptPin From
|
||||
{
|
||||
get => _from;
|
||||
private set => SetProperty(ref _from, value);
|
||||
}
|
||||
|
||||
private VisualScriptPin _to;
|
||||
public VisualScriptPin To
|
||||
{
|
||||
get => _to;
|
||||
private set => SetProperty(ref _to, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public VisualScriptCable(VisualScriptPin pin1, VisualScriptPin pin2)
|
||||
{
|
||||
if ((pin1.Pin.Direction == PinDirection.Input) && (pin2.Pin.Direction == PinDirection.Input))
|
||||
throw new ArgumentException("Can't connect two input pins.");
|
||||
|
||||
if ((pin1.Pin.Direction == PinDirection.Output) && (pin2.Pin.Direction == PinDirection.Output))
|
||||
throw new ArgumentException("Can't connect two output pins.");
|
||||
|
||||
From = pin1.Pin.Direction == PinDirection.Output ? pin1 : pin2;
|
||||
To = pin1.Pin.Direction == PinDirection.Input ? pin1 : pin2;
|
||||
|
||||
From.ConnectTo(this);
|
||||
To.ConnectTo(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
internal void Disconnect()
|
||||
{
|
||||
From?.Disconnect(this);
|
||||
To?.Disconnect(this);
|
||||
|
||||
From = null;
|
||||
To = null;
|
||||
}
|
||||
|
||||
internal IPin GetConnectedPin(IPin pin)
|
||||
{
|
||||
if (From.Pin == pin) return To.Pin;
|
||||
if (To.Pin == pin) return From.Pin;
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Core.VisualScripting;
|
||||
using Artemis.VisualScripting.Events;
|
||||
using Artemis.VisualScripting.Internal;
|
||||
using Artemis.VisualScripting.Model;
|
||||
using Artemis.VisualScripting.ViewModel;
|
||||
|
||||
namespace Artemis.VisualScripting.Editor.Controls.Wrapper
|
||||
{
|
||||
public class VisualScriptNode : AbstractBindable
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private double _locationOffset;
|
||||
|
||||
public VisualScript Script { get; }
|
||||
public INode Node { get; }
|
||||
|
||||
private bool _isSelected;
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
private set => SetProperty(ref _isSelected, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<VisualScriptPin> _inputPins = new();
|
||||
public ObservableCollection<VisualScriptPin> InputPins
|
||||
{
|
||||
get => _inputPins;
|
||||
private set => SetProperty(ref _inputPins, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<VisualScriptPin> _outputPins = new();
|
||||
public ObservableCollection<VisualScriptPin> OutputPins
|
||||
{
|
||||
get => _outputPins;
|
||||
private set => SetProperty(ref _outputPins, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<VisualScriptPinCollection> _inputPinCollections = new();
|
||||
public ObservableCollection<VisualScriptPinCollection> InputPinCollections
|
||||
{
|
||||
get => _inputPinCollections;
|
||||
private set => SetProperty(ref _inputPinCollections, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<VisualScriptPinCollection> _outputPinCollections = new();
|
||||
public ObservableCollection<VisualScriptPinCollection> OutputPinCollections
|
||||
{
|
||||
get => _outputPinCollections;
|
||||
private set => SetProperty(ref _outputPinCollections, value);
|
||||
}
|
||||
|
||||
public double X
|
||||
{
|
||||
get => Node.X + _locationOffset;
|
||||
set
|
||||
{
|
||||
Node.X = value - _locationOffset;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public double Y
|
||||
{
|
||||
get => Node.Y + _locationOffset;
|
||||
set
|
||||
{
|
||||
Node.Y = value - _locationOffset;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
private ActionCommand _removeCommand;
|
||||
public ActionCommand RemoveCommand => _removeCommand ??= new ActionCommand(Remove, RemoveCanExecute);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler<VisualScriptNodeIsSelectedChangedEventArgs> IsSelectedChanged;
|
||||
public event EventHandler DragStarting;
|
||||
public event EventHandler DragEnding;
|
||||
public event EventHandler<VisualScriptNodeDragMovingEventArgs> DragMoving;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public VisualScriptNode(VisualScript script, INode node)
|
||||
{
|
||||
this.Script = script;
|
||||
this.Node = node;
|
||||
|
||||
_locationOffset = script.SurfaceSize / 2.0;
|
||||
|
||||
foreach (IPin pin in node.Pins)
|
||||
{
|
||||
if (pin.Direction == PinDirection.Input)
|
||||
InputPins.Add(new VisualScriptPin(this, pin));
|
||||
else
|
||||
OutputPins.Add(new VisualScriptPin(this, pin));
|
||||
}
|
||||
|
||||
foreach (IPinCollection pinCollection in node.PinCollections)
|
||||
{
|
||||
if (pinCollection.Direction == PinDirection.Input)
|
||||
InputPinCollections.Add(new VisualScriptPinCollection(this, pinCollection));
|
||||
else
|
||||
OutputPinCollections.Add(new VisualScriptPinCollection(this, pinCollection));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void SnapNodeToGrid()
|
||||
{
|
||||
X -= X % Script.GridSize;
|
||||
Y -= Y % Script.GridSize;
|
||||
}
|
||||
|
||||
public void Select(bool alterSelection = false)
|
||||
{
|
||||
IsSelected = true;
|
||||
OnIsSelectedChanged(IsSelected, alterSelection);
|
||||
}
|
||||
|
||||
public void Deselect(bool alterSelection = false)
|
||||
{
|
||||
IsSelected = false;
|
||||
OnIsSelectedChanged(IsSelected, alterSelection);
|
||||
}
|
||||
|
||||
public void DragStart() => DragStarting?.Invoke(this, new EventArgs());
|
||||
public void DragEnd() => DragEnding?.Invoke(this, new EventArgs());
|
||||
public void DragMove(double dx, double dy) => DragMoving?.Invoke(this, new VisualScriptNodeDragMovingEventArgs(dx, dy));
|
||||
|
||||
private void OnIsSelectedChanged(bool isSelected, bool alterSelection) => IsSelectedChanged?.Invoke(this, new VisualScriptNodeIsSelectedChangedEventArgs(isSelected, alterSelection));
|
||||
|
||||
internal void OnPinConnected(PinConnectedEventArgs args) => Script.OnPinConnected(args);
|
||||
internal void OnPinDisconnected(PinDisconnectedEventArgs args) => Script.OnPinDisconnected(args);
|
||||
|
||||
internal void OnIsConnectingPinChanged(VisualScriptPin isConnectingPin) => Script.OnIsConnectingPinChanged(isConnectingPin);
|
||||
|
||||
private void DisconnectAllPins()
|
||||
{
|
||||
foreach (VisualScriptPin pin in InputPins)
|
||||
pin.DisconnectAll();
|
||||
|
||||
foreach (VisualScriptPin pin in OutputPins)
|
||||
pin.DisconnectAll();
|
||||
|
||||
foreach (VisualScriptPinCollection pinCollection in InputPinCollections)
|
||||
foreach (VisualScriptPin pin in pinCollection.Pins)
|
||||
pin.DisconnectAll();
|
||||
|
||||
foreach (VisualScriptPinCollection pinCollection in OutputPinCollections)
|
||||
foreach (VisualScriptPin pin in pinCollection.Pins)
|
||||
pin.DisconnectAll();
|
||||
}
|
||||
|
||||
private void Remove()
|
||||
{
|
||||
DisconnectAllPins();
|
||||
Script.RemoveNode(this);
|
||||
}
|
||||
|
||||
private bool RemoveCanExecute() => Node is not IExitNode;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,112 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using Artemis.Core.VisualScripting;
|
||||
using Artemis.VisualScripting.Events;
|
||||
using Artemis.VisualScripting.Internal;
|
||||
using Artemis.VisualScripting.Model;
|
||||
using Artemis.VisualScripting.ViewModel;
|
||||
|
||||
namespace Artemis.VisualScripting.Editor.Controls.Wrapper
|
||||
{
|
||||
public class VisualScriptPin : AbstractBindable
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const double CABLE_OFFSET = 24 * 4;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties & Fields
|
||||
|
||||
private VisualScriptPin _isConnectingPin;
|
||||
private VisualScriptCable _isConnectingCable;
|
||||
|
||||
public VisualScriptNode Node { get; }
|
||||
public IPin Pin { get; }
|
||||
|
||||
public ObservableCollection<VisualScriptCable> Connections { get; } = new();
|
||||
|
||||
private Point _absolutePosition;
|
||||
public Point AbsolutePosition
|
||||
{
|
||||
get => _absolutePosition;
|
||||
internal set
|
||||
{
|
||||
if (SetProperty(ref _absolutePosition, value))
|
||||
OnPropertyChanged(nameof(AbsoluteCableTargetPosition));
|
||||
}
|
||||
}
|
||||
|
||||
public Point AbsoluteCableTargetPosition => Pin.Direction == PinDirection.Input ? new Point(AbsolutePosition.X - CABLE_OFFSET, AbsolutePosition.Y) : new Point(AbsolutePosition.X + CABLE_OFFSET, AbsolutePosition.Y);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public VisualScriptPin(VisualScriptNode node, IPin pin)
|
||||
{
|
||||
this.Node = node;
|
||||
this.Pin = pin;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void SetConnecting(bool isConnecting)
|
||||
{
|
||||
if (isConnecting)
|
||||
{
|
||||
if (_isConnectingCable != null)
|
||||
SetConnecting(false);
|
||||
|
||||
_isConnectingPin = new VisualScriptPin(null, new IsConnectingPin(Pin.Direction == PinDirection.Input ? PinDirection.Output : PinDirection.Input)) { AbsolutePosition = AbsolutePosition };
|
||||
_isConnectingCable = new VisualScriptCable(this, _isConnectingPin);
|
||||
Node.OnIsConnectingPinChanged(_isConnectingPin);
|
||||
}
|
||||
else
|
||||
{
|
||||
_isConnectingCable.Disconnect();
|
||||
_isConnectingCable = null;
|
||||
_isConnectingPin = null;
|
||||
Node.OnIsConnectingPinChanged(_isConnectingPin);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ConnectTo(VisualScriptCable cable)
|
||||
{
|
||||
if (Connections.Contains(cable)) return;
|
||||
|
||||
if (Pin.Direction == PinDirection.Input)
|
||||
{
|
||||
List<VisualScriptCable> cables = Connections.ToList();
|
||||
foreach (VisualScriptCable c in cables)
|
||||
c.Disconnect();
|
||||
}
|
||||
|
||||
Connections.Add(cable);
|
||||
Pin.ConnectTo(cable.GetConnectedPin(Pin));
|
||||
|
||||
Node?.OnPinConnected(new PinConnectedEventArgs(this, cable));
|
||||
}
|
||||
|
||||
public void DisconnectAll()
|
||||
{
|
||||
List<VisualScriptCable> cables = Connections.ToList();
|
||||
foreach (VisualScriptCable cable in cables)
|
||||
cable.Disconnect();
|
||||
}
|
||||
|
||||
internal void Disconnect(VisualScriptCable cable)
|
||||
{
|
||||
Connections.Remove(cable);
|
||||
Pin.DisconnectFrom(cable.GetConnectedPin(Pin));
|
||||
|
||||
Node?.OnPinDisconnected(new PinDisconnectedEventArgs(this, cable));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Core.VisualScripting;
|
||||
using Artemis.VisualScripting.ViewModel;
|
||||
|
||||
namespace Artemis.VisualScripting.Editor.Controls.Wrapper
|
||||
{
|
||||
public class VisualScriptPinCollection : AbstractBindable
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public VisualScriptNode Node { get; }
|
||||
public IPinCollection PinCollection { get; }
|
||||
|
||||
public ObservableCollection<VisualScriptPin> Pins { get; } = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
private ActionCommand _addPinCommand;
|
||||
public ActionCommand AddPinCommand => _addPinCommand ??= new ActionCommand(AddPin);
|
||||
|
||||
private ActionCommand<VisualScriptPin> _removePinCommand;
|
||||
public ActionCommand<VisualScriptPin> RemovePinCommand => _removePinCommand ??= new ActionCommand<VisualScriptPin>(RemovePin);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public VisualScriptPinCollection(VisualScriptNode node, IPinCollection pinCollection)
|
||||
{
|
||||
this.Node = node;
|
||||
this.PinCollection = pinCollection;
|
||||
|
||||
foreach (IPin pin in PinCollection)
|
||||
Pins.Add(new VisualScriptPin(node, pin));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void AddPin()
|
||||
{
|
||||
IPin pin = PinCollection.AddPin();
|
||||
Pins.Add(new VisualScriptPin(Node, pin));
|
||||
}
|
||||
|
||||
public void RemovePin(VisualScriptPin pin)
|
||||
{
|
||||
pin.DisconnectAll();
|
||||
PinCollection.Remove(pin.Pin);
|
||||
Pins.Remove(pin);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
13
src/Artemis.VisualScripting/Editor/EditorStyles.xaml
Normal file
13
src/Artemis.VisualScripting/Editor/EditorStyles.xaml
Normal file
@ -0,0 +1,13 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Styles/VisualScriptCablePresenter.xaml" />
|
||||
<ResourceDictionary Source="Styles/VisualScriptEditor.xaml" />
|
||||
<ResourceDictionary Source="Styles/VisualScriptNodeCreationBox.xaml" />
|
||||
<ResourceDictionary Source="Styles/VisualScriptNodePresenter.xaml" />
|
||||
<ResourceDictionary Source="Styles/VisualScriptPinPresenter.xaml" />
|
||||
<ResourceDictionary Source="Styles/VisualScriptPresenter.xaml" />
|
||||
|
||||
<!-- TODO: Load external -->
|
||||
<ResourceDictionary Source="../Nodes/Styles/StaticValueNodes.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
@ -0,0 +1,68 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls">
|
||||
|
||||
<ControlTemplate x:Key="TemplateVisualScriptCablePresenter"
|
||||
TargetType="{x:Type controls:VisualScriptCablePresenter}">
|
||||
<Path x:Name="PART_Path"
|
||||
Stroke="{TemplateBinding BorderBrush}"
|
||||
StrokeThickness="{TemplateBinding Thickness}"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round">
|
||||
<Path.Data>
|
||||
<PathGeometry>
|
||||
<PathGeometry.Figures>
|
||||
<PathFigureCollection>
|
||||
<PathFigure StartPoint="{Binding Cable.From.AbsolutePosition, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<PathFigure.Segments>
|
||||
<PathSegmentCollection>
|
||||
<BezierSegment Point1="{Binding Cable.From.AbsoluteCableTargetPosition, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Point2="{Binding Cable.To.AbsoluteCableTargetPosition, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Point3="{Binding Cable.To.AbsolutePosition, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
</PathSegmentCollection>
|
||||
</PathFigure.Segments>
|
||||
</PathFigure>
|
||||
</PathFigureCollection>
|
||||
</PathGeometry.Figures>
|
||||
</PathGeometry>
|
||||
</Path.Data>
|
||||
</Path>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="StyleVisualScriptCablePresenter"
|
||||
TargetType="{x:Type controls:VisualScriptCablePresenter}">
|
||||
|
||||
<Setter Property="BorderBrush" Value="#FFFFFFFF" />
|
||||
<Setter Property="Thickness" Value="4" />
|
||||
<Setter Property="ToolTip" Value="{Binding Cable.From.Pin.PinValue, TargetNullValue=-, RelativeSource={RelativeSource Self}}" />
|
||||
|
||||
<Setter Property="Template" Value="{StaticResource TemplateVisualScriptCablePresenter}" />
|
||||
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Thickness" Value="6" />
|
||||
</Trigger>
|
||||
|
||||
<DataTrigger Binding="{Binding Cable.From.Pin.Type, RelativeSource={RelativeSource Self}}" Value="{x:Type system:Boolean}">
|
||||
<Setter Property="BorderBrush" Value="#FFCD3232" />
|
||||
</DataTrigger>
|
||||
|
||||
<DataTrigger Binding="{Binding Cable.From.Pin.Type, RelativeSource={RelativeSource Self}}" Value="{x:Type system:Int32}">
|
||||
<Setter Property="BorderBrush" Value="LimeGreen" />
|
||||
</DataTrigger>
|
||||
|
||||
<DataTrigger Binding="{Binding Cable.From.Pin.Type, RelativeSource={RelativeSource Self}}" Value="{x:Type system:Double}">
|
||||
<Setter Property="BorderBrush" Value="DodgerBlue" />
|
||||
</DataTrigger>
|
||||
|
||||
<DataTrigger Binding="{Binding Cable.From.Pin.Type, RelativeSource={RelativeSource Self}}" Value="{x:Type system:String}">
|
||||
<Setter Property="BorderBrush" Value="Gold" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource StyleVisualScriptCablePresenter}"
|
||||
TargetType="{x:Type controls:VisualScriptCablePresenter}" />
|
||||
|
||||
</ResourceDictionary>
|
||||
@ -0,0 +1,28 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls">
|
||||
|
||||
<ControlTemplate x:Key="TemplateVisualScriptEditor"
|
||||
TargetType="{x:Type controls:VisualScriptEditor}">
|
||||
<Border BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}">
|
||||
<controls:VisualScriptPresenter Script="{TemplateBinding Script}"
|
||||
AvailableNodes="{TemplateBinding AvailableNodes}" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="StyleVisualScriptEditor"
|
||||
TargetType="{x:Type controls:VisualScriptEditor}">
|
||||
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="BorderBrush" Value="{x:Null}"/>
|
||||
|
||||
<Setter Property="Template" Value="{StaticResource TemplateVisualScriptEditor}" />
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource StyleVisualScriptEditor}"
|
||||
TargetType="{x:Type controls:VisualScriptEditor}" />
|
||||
|
||||
</ResourceDictionary>
|
||||
@ -0,0 +1,54 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls"
|
||||
xmlns:model="clr-namespace:Artemis.VisualScripting.Model">
|
||||
|
||||
<DataTemplate x:Key="TemplateNodeItem"
|
||||
DataType="{x:Type model:NodeData}">
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</DataTemplate>
|
||||
|
||||
<ControlTemplate x:Key="TemplateVisualScriptNodeCreationBox"
|
||||
TargetType="{x:Type controls:VisualScriptNodeCreationBox}">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Padding="2"
|
||||
BorderThickness="1"
|
||||
BorderBrush="#A0FFFFFF">
|
||||
<TextBox x:Name="PART_SearchBox"
|
||||
Background="Transparent"
|
||||
Foreground="#FFFFFFFF"
|
||||
CaretBrush="#FFFFFFFF"
|
||||
BorderThickness="0" />
|
||||
</Border>
|
||||
|
||||
<ListBox x:Name="PART_Content"
|
||||
Grid.Row="1"
|
||||
Background="Transparent"
|
||||
Foreground="#FFFFFFFF"
|
||||
BorderThickness="0"
|
||||
ItemTemplate="{StaticResource TemplateNodeItem}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="StyleVisualScriptNodeCreationBox"
|
||||
TargetType="{x:Type controls:VisualScriptNodeCreationBox}">
|
||||
<Setter Property="Background" Value="#DD333333" />
|
||||
<Setter Property="BorderBrush" Value="#FF222222" />
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
|
||||
<Setter Property="Template" Value="{StaticResource TemplateVisualScriptNodeCreationBox}" />
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource StyleVisualScriptNodeCreationBox}"
|
||||
TargetType="{x:Type controls:VisualScriptNodeCreationBox}" />
|
||||
|
||||
</ResourceDictionary>
|
||||
@ -0,0 +1,262 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls"
|
||||
xmlns:wrapper="clr-namespace:Artemis.VisualScripting.Editor.Controls.Wrapper"
|
||||
xmlns:model="clr-namespace:Artemis.VisualScripting.Model">
|
||||
|
||||
<Style x:Key="StyleButtonPinsModify"
|
||||
TargetType="Button">
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="Width" Value="16" />
|
||||
<Setter Property="Height" Value="16" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="Padding" Value="2" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="#FFA0A0A0" />
|
||||
<Setter Property="FontFamily" Value="Webdings" />
|
||||
<Setter Property="Content" Value="r" />
|
||||
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Padding="{TemplateBinding Padding}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}">
|
||||
<TextBlock Text="{TemplateBinding Content}" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<Style.Triggers>
|
||||
<Trigger Property="HorizontalAlignment" Value="Left">
|
||||
<Setter Property="RenderTransformOrigin" Value="0.5,0.5" />
|
||||
<Setter Property="RenderTransform">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle="45" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
|
||||
<Trigger Property="HorizontalAlignment" Value="Right">
|
||||
<Setter Property="RenderTransformOrigin" Value="0.5,0.5" />
|
||||
<Setter Property="RenderTransform">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle="45" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF" />
|
||||
</Trigger>
|
||||
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Foreground" Value="#FFFFA0A0" />
|
||||
</Trigger>
|
||||
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter Property="Visibility" Value="Hidden" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="StylePinListInput"
|
||||
TargetType="ItemsControl">
|
||||
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
|
||||
<Setter Property="ItemTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type model:Pin}">
|
||||
<controls:VisualScriptPinPresenter Margin="0,5,0,3"
|
||||
Pin="{Binding .}" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="StylePinListOutput"
|
||||
TargetType="ItemsControl">
|
||||
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
|
||||
<Setter Property="ItemTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type model:Pin}">
|
||||
<controls:VisualScriptPinPresenter Margin="0,5,0,3"
|
||||
Pin="{Binding .}" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="StylePinCollectionListInput"
|
||||
TargetType="ItemsControl">
|
||||
|
||||
<Setter Property="ItemTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type wrapper:VisualScriptPinCollection}">
|
||||
<StackPanel Margin="0,4" Orientation="Vertical">
|
||||
<TextBlock Text="{Binding PinCollection.Name}" Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType={x:Type controls:VisualScriptNodePresenter}}}" />
|
||||
<ItemsControl Margin="0,4"
|
||||
Style="{StaticResource StylePinListInput}"
|
||||
ItemsSource="{Binding Pins}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type model:Pin}">
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
<controls:VisualScriptPinPresenter Margin="0,5,-3,3"
|
||||
Pin="{Binding .}" />
|
||||
<Button Style="{StaticResource StyleButtonPinsModify}"
|
||||
Command="{Binding DataContext.RemovePinCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
|
||||
CommandParameter="{Binding .}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Button HorizontalAlignment="Left"
|
||||
Margin="0,0,0,0"
|
||||
Style="{StaticResource StyleButtonPinsModify}"
|
||||
Command="{Binding AddPinCommand}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="StylePinCollectionListOutput"
|
||||
TargetType="ItemsControl">
|
||||
|
||||
<Setter Property="ItemTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type wrapper:VisualScriptPinCollection}">
|
||||
<StackPanel Margin="0,4" Orientation="Vertical">
|
||||
<TextBlock Text="{Binding PinCollection.Name}" Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType={x:Type controls:VisualScriptNodePresenter}}}" />
|
||||
<ItemsControl Margin="0,4"
|
||||
Style="{StaticResource StylePinListOutput}"
|
||||
ItemsSource="{Binding Pins}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type model:Pin}">
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||
<Button Style="{StaticResource StyleButtonPinsModify}"
|
||||
Command="{Binding DataContext.RemovePinCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
|
||||
CommandParameter="{Binding .}"/>
|
||||
<controls:VisualScriptPinPresenter Margin="-3,5,0,3"
|
||||
Pin="{Binding .}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Button HorizontalAlignment="Right"
|
||||
Margin="0,0,0,0"
|
||||
Style="{StaticResource StyleButtonPinsModify}"
|
||||
Command="{Binding AddPinCommand}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<ControlTemplate x:Key="TemplateVisualScriptNodePresenter"
|
||||
TargetType="{x:Type controls:VisualScriptNodePresenter}">
|
||||
<Border BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}">
|
||||
<StackPanel>
|
||||
<Border x:Name="BrdHeader"
|
||||
VerticalAlignment="Stretch"
|
||||
Height="24"
|
||||
Background="{TemplateBinding TitleBrush}">
|
||||
<DockPanel>
|
||||
<Border DockPanel.Dock="Right" Width="24">
|
||||
<Button Style="{StaticResource StyleButtonPinsModify}"
|
||||
Command="{Binding Node.RemoveCommand, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
</Border>
|
||||
<Border DockPanel.Dock="Left" Width="24">
|
||||
<!-- Placeholder -->
|
||||
</Border>
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
FontFamily="{TemplateBinding FontFamily}"
|
||||
FontSize="14" FontWeight="Bold"
|
||||
Text="{Binding Node.Node.Name, RelativeSource={RelativeSource TemplatedParent}}"/>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<Border x:Name="BrdContent"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" MinWidth="8" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border x:Name="BrdInputPins" Grid.Column="0">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<ItemsControl Style="{StaticResource StylePinListInput}"
|
||||
ItemsSource="{Binding Node.InputPins, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
|
||||
<ItemsControl Style="{StaticResource StylePinCollectionListInput}"
|
||||
ItemsSource="{Binding Node.InputPinCollections, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border x:Name="BrdCustomView" Grid.Column="1">
|
||||
<ContentControl Content="{Binding Node.Node.CustomViewModel, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
ContentTemplate="{Binding Node.Node.CustomView, RelativeSource={RelativeSource TemplatedParent}}"/>
|
||||
</Border>
|
||||
|
||||
<Border x:Name="BrdOutputPins" Grid.Column="2">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<ItemsControl Style="{StaticResource StylePinListOutput}"
|
||||
ItemsSource="{Binding Node.OutputPins, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
|
||||
<ItemsControl Style="{StaticResource StylePinCollectionListOutput}"
|
||||
ItemsSource="{Binding Node.OutputPinCollections, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="StyleVisualScriptNodePresenter"
|
||||
TargetType="{x:Type controls:VisualScriptNodePresenter}">
|
||||
|
||||
<Setter Property="Padding" Value="8,10" />
|
||||
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF"/>
|
||||
|
||||
<Setter Property="Background" Value="#80101010" />
|
||||
<Setter Property="TitleBrush" Value="#FF444444" />
|
||||
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="#FF000000" />
|
||||
|
||||
<Setter Property="Template" Value="{StaticResource TemplateVisualScriptNodePresenter}" />
|
||||
|
||||
<Setter Property="RenderTransform">
|
||||
<Setter.Value>
|
||||
<TranslateTransform X="{Binding Node.X, RelativeSource={RelativeSource AncestorType={x:Type controls:VisualScriptNodePresenter}}}"
|
||||
Y="{Binding Node.Y, RelativeSource={RelativeSource AncestorType={x:Type controls:VisualScriptNodePresenter}}}" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Node.IsSelected, RelativeSource={RelativeSource Self}}" Value="True">
|
||||
<Setter Property="BorderBrush" Value="#FFFFFFFF" />
|
||||
<Setter Property="Background" Value="#A0101010" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource StyleVisualScriptNodePresenter}"
|
||||
TargetType="{x:Type controls:VisualScriptNodePresenter}" />
|
||||
|
||||
</ResourceDictionary>
|
||||
@ -0,0 +1,113 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls"
|
||||
xmlns:model="clr-namespace:Artemis.VisualScripting.Model;assembly=Artemis.Core">
|
||||
|
||||
<ControlTemplate x:Key="TemplateVisualScriptPinPresenterInput"
|
||||
TargetType="{x:Type controls:VisualScriptPinPresenter}">
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal"
|
||||
Background="Transparent">
|
||||
<Border x:Name="PART_Dot"
|
||||
VerticalAlignment="Stretch"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
Width="{Binding ActualHeight, ElementName=PART_Dot}"
|
||||
CornerRadius="{Binding ActualHeight, ElementName=PART_Dot}"
|
||||
Background="Transparent">
|
||||
<Border x:Name="VisualDot" VerticalAlignment="Stretch"
|
||||
Margin="2"
|
||||
Width="{Binding ActualHeight, ElementName=VisualDot}"
|
||||
CornerRadius="{Binding ActualHeight, ElementName=VisualDot}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}"/>
|
||||
</Border>
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
FontFamily="{TemplateBinding FontFamily}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
FontStretch="{TemplateBinding FontSize}"
|
||||
FontStyle="{TemplateBinding FontStyle}"
|
||||
FontWeight="{TemplateBinding FontWeight}"
|
||||
Text="{Binding Pin.Pin.Name, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="TemplateVisualScriptPinPresenterOutput"
|
||||
TargetType="{x:Type controls:VisualScriptPinPresenter}">
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal"
|
||||
Background="Transparent">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
FontFamily="{TemplateBinding FontFamily}"
|
||||
FontSize="{TemplateBinding FontSize}"
|
||||
FontStretch="{TemplateBinding FontSize}"
|
||||
FontStyle="{TemplateBinding FontStyle}"
|
||||
FontWeight="{TemplateBinding FontWeight}"
|
||||
Text="{Binding Pin.Pin.Name, RelativeSource={RelativeSource TemplatedParent}}" />
|
||||
|
||||
<Border x:Name="PART_Dot"
|
||||
VerticalAlignment="Stretch"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
Width="{Binding ActualHeight, ElementName=PART_Dot}"
|
||||
CornerRadius="{Binding ActualHeight, ElementName=PART_Dot}"
|
||||
Background="Transparent">
|
||||
<Border x:Name="VisualDot" VerticalAlignment="Stretch"
|
||||
Margin="2"
|
||||
Width="{Binding ActualHeight, ElementName=VisualDot}"
|
||||
CornerRadius="{Binding ActualHeight, ElementName=VisualDot}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="StyleVisualScriptPinPresenter"
|
||||
TargetType="{x:Type controls:VisualScriptPinPresenter}">
|
||||
|
||||
<Setter Property="Height" Value="16" />
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
|
||||
<Setter Property="Foreground" Value="#FFFFFFFF" />
|
||||
<Setter Property="Background" Value="#A0000000" />
|
||||
<Setter Property="BorderBrush" Value="#FFFFFFFF" />
|
||||
|
||||
<Setter Property="Padding" Value="0,0,6,0" />
|
||||
<Setter Property="Template" Value="{StaticResource TemplateVisualScriptPinPresenterInput}" />
|
||||
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Pin.Pin.Direction, RelativeSource={RelativeSource Self}}" Value="{x:Static model:PinDirection.Output}">
|
||||
<Setter Property="Padding" Value="6,0,0,0" />
|
||||
<Setter Property="Template" Value="{StaticResource TemplateVisualScriptPinPresenterOutput}" />
|
||||
</DataTrigger>
|
||||
|
||||
<DataTrigger Binding="{Binding Pin.Pin.Type, RelativeSource={RelativeSource Self}}" Value="{x:Type system:Object}">
|
||||
<Setter Property="Background" Value="#FFA0A0A0" />
|
||||
<Setter Property="BorderBrush" Value="White" />
|
||||
</DataTrigger>
|
||||
|
||||
<DataTrigger Binding="{Binding Pin.Pin.Type, RelativeSource={RelativeSource Self}}" Value="{x:Type system:Boolean}">
|
||||
<Setter Property="Background" Value="DarkRed" />
|
||||
<Setter Property="BorderBrush" Value="#FFCD3232" />
|
||||
</DataTrigger>
|
||||
|
||||
<DataTrigger Binding="{Binding Pin.Pin.Type, RelativeSource={RelativeSource Self}}" Value="{x:Type system:Int32}">
|
||||
<Setter Property="Background" Value="DarkGreen" />
|
||||
<Setter Property="BorderBrush" Value="LimeGreen" />
|
||||
</DataTrigger>
|
||||
|
||||
<DataTrigger Binding="{Binding Pin.Pin.Type, RelativeSource={RelativeSource Self}}" Value="{x:Type system:Double}">
|
||||
<Setter Property="Background" Value="Blue" />
|
||||
<Setter Property="BorderBrush" Value="DodgerBlue" />
|
||||
</DataTrigger>
|
||||
|
||||
<DataTrigger Binding="{Binding Pin.Pin.Type, RelativeSource={RelativeSource Self}}" Value="{x:Type system:String}">
|
||||
<Setter Property="Background" Value="DarkGoldenrod" />
|
||||
<Setter Property="BorderBrush" Value="Gold" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource StyleVisualScriptPinPresenter}"
|
||||
TargetType="{x:Type controls:VisualScriptPinPresenter}" />
|
||||
|
||||
</ResourceDictionary>
|
||||
@ -0,0 +1,111 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls">
|
||||
|
||||
<ControlTemplate x:Key="TemplateVisualScriptPresenter"
|
||||
TargetType="{x:Type controls:VisualScriptPresenter}">
|
||||
<Border BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<Grid x:Name="PART_CreationBoxParent"
|
||||
ClipToBounds="True"
|
||||
Tag="{Binding RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<Grid.ContextMenu>
|
||||
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
|
||||
<ContextMenu.Template>
|
||||
<ControlTemplate>
|
||||
<Border Width="700" Height="300">
|
||||
<controls:VisualScriptNodeCreationBox AvailableNodes="{Binding AvailableNodes}"
|
||||
CreateNodeCommand="{Binding CreateNodeCommand}" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</ContextMenu.Template>
|
||||
</ContextMenu>
|
||||
</Grid.ContextMenu>
|
||||
|
||||
<Canvas x:Name="PART_Canvas"
|
||||
Width="{Binding SurfaceSize, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Height="{Binding SurfaceSize, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Background="{TemplateBinding Background}">
|
||||
|
||||
<Border x:Name="PART_SelectionBorder"
|
||||
Background="#3300FF00"
|
||||
BorderThickness="1"
|
||||
BorderBrush="#FF00CC00"
|
||||
CornerRadius="1"
|
||||
Visibility="Hidden" />
|
||||
|
||||
<ItemsControl x:Name="PART_NodeList"
|
||||
Panel.ZIndex="0">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:VisualScriptNodePresenter Node="{Binding .}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="ContentPresenter">
|
||||
<Setter Property="Panel.ZIndex" Value="0"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<Setter Property="Panel.ZIndex" Value="1"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
</ItemsControl>
|
||||
|
||||
<ItemsControl x:Name="PART_CableList"
|
||||
Panel.ZIndex="-1">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:VisualScriptCablePresenter Cable="{Binding .}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Canvas>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style x:Key="StyleVisualScriptPresenter"
|
||||
TargetType="{x:Type controls:VisualScriptPresenter}">
|
||||
<Setter Property="Template" Value="{StaticResource TemplateVisualScriptPresenter}" />
|
||||
|
||||
<Setter Property="SurfaceSize" Value="16384" />
|
||||
|
||||
<Setter Property="Background">
|
||||
<Setter.Value>
|
||||
<DrawingBrush TileMode="Tile" Viewport="0,0,24,24" ViewportUnits="Absolute">
|
||||
<DrawingBrush.Drawing>
|
||||
<GeometryDrawing>
|
||||
<GeometryDrawing.Geometry>
|
||||
<RectangleGeometry Rect="0,0,24,24"/>
|
||||
</GeometryDrawing.Geometry>
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Brush="#FF808080" Thickness="0.5"/>
|
||||
</GeometryDrawing.Pen>
|
||||
</GeometryDrawing>
|
||||
</DrawingBrush.Drawing>
|
||||
</DrawingBrush>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
|
||||
<Style BasedOn="{StaticResource StyleVisualScriptPresenter}"
|
||||
TargetType="{x:Type controls:VisualScriptPresenter}" />
|
||||
|
||||
</ResourceDictionary>
|
||||
25
src/Artemis.VisualScripting/Events/PinConnectedEventArgs.cs
Normal file
25
src/Artemis.VisualScripting/Events/PinConnectedEventArgs.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using Artemis.VisualScripting.Editor.Controls.Wrapper;
|
||||
|
||||
namespace Artemis.VisualScripting.Events
|
||||
{
|
||||
public class PinConnectedEventArgs : EventArgs
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public VisualScriptPin Pin { get; }
|
||||
public VisualScriptCable Cable { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public PinConnectedEventArgs(VisualScriptPin pin, VisualScriptCable cable)
|
||||
{
|
||||
this.Pin = pin;
|
||||
this.Cable = cable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using Artemis.VisualScripting.Editor.Controls.Wrapper;
|
||||
|
||||
namespace Artemis.VisualScripting.Events
|
||||
{
|
||||
public class PinDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public VisualScriptPin Pin { get; }
|
||||
public VisualScriptCable Cable { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public PinDisconnectedEventArgs(VisualScriptPin pin, VisualScriptCable cable)
|
||||
{
|
||||
this.Pin = pin;
|
||||
this.Cable = cable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.VisualScripting.Events
|
||||
{
|
||||
public class VisualScriptNodeDragMovingEventArgs : EventArgs
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public double DX { get; }
|
||||
public double DY { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public VisualScriptNodeDragMovingEventArgs(double dx, double dy)
|
||||
{
|
||||
this.DX = dx;
|
||||
this.DY = dy;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.VisualScripting.Events
|
||||
{
|
||||
public class VisualScriptNodeIsSelectedChangedEventArgs : EventArgs
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public bool IsSelected { get; }
|
||||
public bool AlterSelection { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public VisualScriptNodeIsSelectedChangedEventArgs(bool isSelected, bool alterSelection)
|
||||
{
|
||||
this.IsSelected = isSelected;
|
||||
this.AlterSelection = alterSelection;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
40
src/Artemis.VisualScripting/Internal/ExitNode.cs
Normal file
40
src/Artemis.VisualScripting/Internal/ExitNode.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Artemis.Core.VisualScripting;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.VisualScripting.Internal
|
||||
{
|
||||
internal interface IExitNode : INode
|
||||
{ }
|
||||
|
||||
internal class ExitNode<T> : Node, IExitNode
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public InputPin<T> Input { get; }
|
||||
|
||||
public T Value { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ExitNode(string name, string description = "")
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
|
||||
Input = CreateInputPin<T>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
Value = Input.Value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
26
src/Artemis.VisualScripting/Internal/IsConnectingPin.cs
Normal file
26
src/Artemis.VisualScripting/Internal/IsConnectingPin.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.VisualScripting.Internal
|
||||
{
|
||||
internal class IsConnectingPin : Pin
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public override PinDirection Direction { get; }
|
||||
public override Type Type { get; } = typeof(object);
|
||||
public override object PinValue => null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public IsConnectingPin(PinDirection direction)
|
||||
: base(null, null)
|
||||
{
|
||||
this.Direction = direction;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
51
src/Artemis.VisualScripting/Model/InputPin.cs
Normal file
51
src/Artemis.VisualScripting/Model/InputPin.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using Artemis.Core.VisualScripting;
|
||||
|
||||
namespace Artemis.VisualScripting.Model
|
||||
{
|
||||
public sealed class InputPin<T> : Pin
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public override Type Type { get; } = typeof(T);
|
||||
public override object PinValue => Value;
|
||||
public override PinDirection Direction => PinDirection.Input;
|
||||
|
||||
private T _value;
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsEvaluated)
|
||||
Evaluate();
|
||||
|
||||
return _value;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
_value = value;
|
||||
IsEvaluated = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
internal InputPin(INode node, string name)
|
||||
: base(node, name)
|
||||
{ }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
private void Evaluate()
|
||||
{
|
||||
Value = ConnectedTo.Count > 0 ? (T)ConnectedTo[0].PinValue : default;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
35
src/Artemis.VisualScripting/Model/InputPinCollection.cs
Normal file
35
src/Artemis.VisualScripting/Model/InputPinCollection.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core.VisualScripting;
|
||||
|
||||
namespace Artemis.VisualScripting.Model
|
||||
{
|
||||
public sealed class InputPinCollection<T> : PinCollection
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public override PinDirection Direction => PinDirection.Input;
|
||||
public override Type Type => typeof(T);
|
||||
|
||||
public new IEnumerable<InputPin<T>> Pins => base.Pins.Cast<InputPin<T>>();
|
||||
|
||||
public IEnumerable<T> Values => Pins.Select(p => p.Value);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
internal InputPinCollection(INode node, string name, int initialCount)
|
||||
: base(node, name, initialCount)
|
||||
{ }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
protected override IPin CreatePin() => new InputPin<T>(Node, string.Empty);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
117
src/Artemis.VisualScripting/Model/Node.cs
Normal file
117
src/Artemis.VisualScripting/Model/Node.cs
Normal file
@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using Artemis.Core.VisualScripting;
|
||||
using Artemis.VisualScripting.ViewModel;
|
||||
|
||||
namespace Artemis.VisualScripting.Model
|
||||
{
|
||||
public abstract class Node : AbstractBindable, INode
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private string _name;
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
protected set => SetProperty(ref _name, value);
|
||||
}
|
||||
|
||||
private string _description;
|
||||
public string Description
|
||||
{
|
||||
get => _description;
|
||||
protected set => SetProperty(ref _description, value);
|
||||
}
|
||||
|
||||
private double _x;
|
||||
public double X
|
||||
{
|
||||
get => _x;
|
||||
set => SetProperty(ref _x, value);
|
||||
}
|
||||
|
||||
private double _y;
|
||||
public double Y
|
||||
{
|
||||
get => _y;
|
||||
set => SetProperty(ref _y, value);
|
||||
}
|
||||
|
||||
private readonly List<IPin> _pins = new();
|
||||
public IReadOnlyCollection<IPin> Pins => new ReadOnlyCollection<IPin>(_pins);
|
||||
|
||||
private readonly List<IPinCollection> _pinCollections = new();
|
||||
public IReadOnlyCollection<IPinCollection> PinCollections => new ReadOnlyCollection<IPinCollection>(_pinCollections);
|
||||
|
||||
public object CustomView { get; private set; }
|
||||
public object CustomViewModel { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler Resetting;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construtors
|
||||
|
||||
protected Node()
|
||||
{ }
|
||||
|
||||
protected Node(string name, string description)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
protected InputPin<T> CreateInputPin<T>(string name = "")
|
||||
{
|
||||
InputPin<T> pin = new(this, name);
|
||||
_pins.Add(pin);
|
||||
return pin;
|
||||
}
|
||||
|
||||
protected OutputPin<T> CreateOutputPin<T>(string name = "")
|
||||
{
|
||||
OutputPin<T> pin = new(this, name);
|
||||
_pins.Add(pin);
|
||||
return pin;
|
||||
}
|
||||
|
||||
protected InputPinCollection<T> CreateInputPinCollection<T>(string name = "", int initialCount = 1)
|
||||
{
|
||||
InputPinCollection<T> pin = new(this, name, initialCount);
|
||||
_pinCollections.Add(pin);
|
||||
return pin;
|
||||
}
|
||||
|
||||
protected OutputPinCollection<T> CreateOutputPinCollection<T>(string name = "", int initialCount = 1)
|
||||
{
|
||||
OutputPinCollection<T> pin = new(this, name, initialCount);
|
||||
_pinCollections.Add(pin);
|
||||
return pin;
|
||||
}
|
||||
|
||||
protected void RegisterCustomView(DataTemplate view, object viewModel)
|
||||
{
|
||||
CustomView = view;
|
||||
CustomViewModel = viewModel;
|
||||
}
|
||||
|
||||
public abstract void Evaluate();
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
Resetting?.Invoke(this, new EventArgs());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
38
src/Artemis.VisualScripting/Model/NodeData.cs
Normal file
38
src/Artemis.VisualScripting/Model/NodeData.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Artemis.Core.VisualScripting;
|
||||
|
||||
namespace Artemis.VisualScripting.Model
|
||||
{
|
||||
public class NodeData
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public Type Type { get; }
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public string Category { get; }
|
||||
|
||||
private Func<INode> _create;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public NodeData(Type type, string name, string description, string category, Func<INode> create)
|
||||
{
|
||||
this.Type = type;
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
this.Category = category;
|
||||
this._create = create;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public INode CreateNode() => _create();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
41
src/Artemis.VisualScripting/Model/OutputPin.cs
Normal file
41
src/Artemis.VisualScripting/Model/OutputPin.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using Artemis.Core.VisualScripting;
|
||||
|
||||
namespace Artemis.VisualScripting.Model
|
||||
{
|
||||
public sealed class OutputPin<T> : Pin
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public override Type Type { get; } = typeof(T);
|
||||
public override object PinValue => Value;
|
||||
public override PinDirection Direction => PinDirection.Output;
|
||||
|
||||
private T _value;
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsEvaluated)
|
||||
Node?.Evaluate();
|
||||
|
||||
return _value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_value = value;
|
||||
IsEvaluated = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
internal OutputPin(INode node, string name)
|
||||
: base(node, name)
|
||||
{ }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
29
src/Artemis.VisualScripting/Model/OutputPinCollection.cs
Normal file
29
src/Artemis.VisualScripting/Model/OutputPinCollection.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using Artemis.Core.VisualScripting;
|
||||
|
||||
namespace Artemis.VisualScripting.Model
|
||||
{
|
||||
public sealed class OutputPinCollection<T> : PinCollection
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public override PinDirection Direction => PinDirection.Output;
|
||||
public override Type Type => typeof(T);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
internal OutputPinCollection(INode node, string name, int initialCount)
|
||||
: base(node, name, initialCount)
|
||||
{ }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
protected override IPin CreatePin() => new OutputPin<T>(Node, string.Empty);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
64
src/Artemis.VisualScripting/Model/Pin.cs
Normal file
64
src/Artemis.VisualScripting/Model/Pin.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Core.VisualScripting;
|
||||
using Artemis.VisualScripting.ViewModel;
|
||||
|
||||
namespace Artemis.VisualScripting.Model
|
||||
{
|
||||
public abstract class Pin : AbstractBindable, IPin
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public INode Node { get; }
|
||||
public string Name { get; }
|
||||
|
||||
private bool _isEvaluated;
|
||||
public bool IsEvaluated
|
||||
{
|
||||
get => _isEvaluated;
|
||||
set => SetProperty(ref _isEvaluated, value);
|
||||
}
|
||||
|
||||
private readonly List<IPin> _connectedTo = new();
|
||||
public IReadOnlyList<IPin> ConnectedTo => new ReadOnlyCollection<IPin>(_connectedTo);
|
||||
|
||||
public abstract PinDirection Direction { get; }
|
||||
public abstract Type Type { get; }
|
||||
public abstract object PinValue { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
protected Pin(INode node, string name = "")
|
||||
{
|
||||
this.Node = node;
|
||||
this.Name = name;
|
||||
|
||||
if (Node != null)
|
||||
Node.Resetting += OnNodeResetting;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void ConnectTo(IPin pin)
|
||||
{
|
||||
_connectedTo.Add(pin);
|
||||
}
|
||||
|
||||
public void DisconnectFrom(IPin pin)
|
||||
{
|
||||
_connectedTo.Remove(pin);
|
||||
}
|
||||
|
||||
private void OnNodeResetting(object sender, EventArgs e)
|
||||
{
|
||||
IsEvaluated = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
55
src/Artemis.VisualScripting/Model/PinCollection.cs
Normal file
55
src/Artemis.VisualScripting/Model/PinCollection.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Core.VisualScripting;
|
||||
|
||||
namespace Artemis.VisualScripting.Model
|
||||
{
|
||||
public abstract class PinCollection : IPinCollection
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public INode Node { get; }
|
||||
public string Name { get; }
|
||||
|
||||
public abstract PinDirection Direction { get; }
|
||||
public abstract Type Type { get; }
|
||||
|
||||
private readonly ObservableCollection<IPin> _pins = new();
|
||||
public ReadOnlyObservableCollection<IPin> Pins => new(_pins);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
protected PinCollection(INode node, string name, int initialCount)
|
||||
{
|
||||
this.Node = node;
|
||||
this.Name = name;
|
||||
|
||||
for (int i = 0; i < initialCount; i++)
|
||||
AddPin();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public IPin AddPin()
|
||||
{
|
||||
IPin pin = CreatePin();
|
||||
_pins.Add(pin);
|
||||
return pin;
|
||||
}
|
||||
|
||||
public bool Remove(IPin pin) => _pins.Remove(pin);
|
||||
|
||||
protected abstract IPin CreatePin();
|
||||
|
||||
public IEnumerator<IPin> GetEnumerator() => Pins.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
91
src/Artemis.VisualScripting/Model/Script.cs
Normal file
91
src/Artemis.VisualScripting/Model/Script.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Core.VisualScripting;
|
||||
using Artemis.VisualScripting.Internal;
|
||||
using Artemis.VisualScripting.ViewModel;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Artemis.VisualScripting.Model
|
||||
{
|
||||
public abstract class Script : AbstractBindable, IScript
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
|
||||
private readonly List<INode> _nodes = new();
|
||||
public IEnumerable<INode> Nodes => new ReadOnlyCollection<INode>(_nodes);
|
||||
|
||||
protected INode ExitNode { get; set; }
|
||||
public abstract Type ResultType { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public Script(string name, string description)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void Run()
|
||||
{
|
||||
foreach (INode node in Nodes)
|
||||
node.Reset();
|
||||
|
||||
ExitNode.Evaluate();
|
||||
}
|
||||
|
||||
public void AddNode(INode node)
|
||||
{
|
||||
_nodes.Add(node);
|
||||
}
|
||||
|
||||
public void RemoveNode(INode node)
|
||||
{
|
||||
_nodes.Remove(node);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{ }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class Script<T> : Script, IScript<T>
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public T Result => ((ExitNode<T>)ExitNode).Value;
|
||||
|
||||
public override Type ResultType => typeof(T);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public Script(string name, string description)
|
||||
: base(name, description)
|
||||
{
|
||||
ExitNode = new ExitNode<T>(name, description);
|
||||
AddNode(ExitNode);
|
||||
}
|
||||
|
||||
[UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
|
||||
private Script(string name, string description, INode exitNode)
|
||||
: base(name, description)
|
||||
{
|
||||
ExitNode = exitNode;
|
||||
AddNode(ExitNode);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
160
src/Artemis.VisualScripting/Nodes/BoolOperations.cs
Normal file
160
src/Artemis.VisualScripting/Nodes/BoolOperations.cs
Normal file
@ -0,0 +1,160 @@
|
||||
using System.Collections;
|
||||
using Artemis.VisualScripting.Attributes;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes
|
||||
{
|
||||
[UI("Greater than", "Checks if the first input is greater than the second.")]
|
||||
public class GreaterThanNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public InputPin<object> Input1 { get; }
|
||||
public InputPin<object> Input2 { get; }
|
||||
|
||||
public OutputPin<bool> Result { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public GreaterThanNode()
|
||||
: base("Greater than", "Checks if the first input is greater than the second.")
|
||||
{
|
||||
Input1 = CreateInputPin<object>();
|
||||
Input2 = CreateInputPin<object>();
|
||||
Result = CreateOutputPin<bool>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
try
|
||||
{
|
||||
Result.Value = Comparer.DefaultInvariant.Compare(Input1.Value, Input2.Value) == 1;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Result.Value = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[UI("Less than", "Checks if the first input is less than the second.")]
|
||||
public class LessThanNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public InputPin<object> Input1 { get; }
|
||||
public InputPin<object> Input2 { get; }
|
||||
|
||||
public OutputPin<bool> Result { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public LessThanNode()
|
||||
: base("Less than", "Checks if the first input is less than the second.")
|
||||
{
|
||||
Input1 = CreateInputPin<object>();
|
||||
Input2 = CreateInputPin<object>();
|
||||
Result = CreateOutputPin<bool>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
try
|
||||
{
|
||||
Result.Value = Comparer.DefaultInvariant.Compare(Input1.Value, Input2.Value) == -1;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Result.Value = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[UI("Equals", "Checks if the two inputs are equals.")]
|
||||
public class EqualsNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public InputPin<object> Input1 { get; }
|
||||
public InputPin<object> Input2 { get; }
|
||||
|
||||
public OutputPin<bool> Result { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public EqualsNode()
|
||||
: base("Equals", "Checks if the two inputs are equals.")
|
||||
{
|
||||
Input1 = CreateInputPin<object>();
|
||||
Input2 = CreateInputPin<object>();
|
||||
Result = CreateOutputPin<bool>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
try
|
||||
{
|
||||
Result.Value = Equals(Input1.Value, Input2.Value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Result.Value = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[UI("Negate", "Negates the boolean.")]
|
||||
public class NegateNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public InputPin<bool> Input { get; }
|
||||
public OutputPin<bool> Output { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public NegateNode()
|
||||
: base("Negate", "Negates the boolean.")
|
||||
{
|
||||
Input = CreateInputPin<bool>();
|
||||
Output = CreateOutputPin<bool>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
Output.Value = !Input.Value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
107
src/Artemis.VisualScripting/Nodes/ConvertNodes.cs
Normal file
107
src/Artemis.VisualScripting/Nodes/ConvertNodes.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using Artemis.VisualScripting.Attributes;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes
|
||||
{
|
||||
[UI("To String", "Converts the input to a string.")]
|
||||
public class ConvertToStringNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public InputPin<object> Input { get; }
|
||||
|
||||
public OutputPin<string> String { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ConvertToStringNode()
|
||||
: base("To String", "Converts the input to a string.")
|
||||
{
|
||||
Input = CreateInputPin<object>();
|
||||
String = CreateOutputPin<string>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
String.Value = Input.Value?.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[UI("To Integer", "Converts the input to an integer.")]
|
||||
public class ConvertToIntegerNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public InputPin<object> Input { get; }
|
||||
|
||||
public OutputPin<int> Integer { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ConvertToIntegerNode()
|
||||
: base("To Integer", "Converts the input to an integer.")
|
||||
{
|
||||
Input = CreateInputPin<object>();
|
||||
Integer = CreateOutputPin<int>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
if (!int.TryParse(Input.Value?.ToString(), out int value))
|
||||
value = 0;
|
||||
|
||||
Integer.Value = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[UI("To Double", "Converts the input to a double.")]
|
||||
public class ConvertToDoubleNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public InputPin<object> Input { get; }
|
||||
|
||||
public OutputPin<double> Double { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ConvertToDoubleNode()
|
||||
: base("To Double", "Converts the input to a double.")
|
||||
{
|
||||
Input = CreateInputPin<object>();
|
||||
Double = CreateOutputPin<double>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
if (!double.TryParse(Input.Value?.ToString(), out double value))
|
||||
value = 0;
|
||||
|
||||
Double.Value = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
121
src/Artemis.VisualScripting/Nodes/StaticValueNodes.cs
Normal file
121
src/Artemis.VisualScripting/Nodes/StaticValueNodes.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using System.Windows;
|
||||
using Artemis.VisualScripting.Attributes;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes
|
||||
{
|
||||
[UI("Integer-Value", "Outputs an configurable integer value.")]
|
||||
public class StaticIntegerValueNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private StaticIntegerValueNodeCustomViewModel _customViewModel = new();
|
||||
|
||||
public OutputPin<int> Output { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public StaticIntegerValueNode()
|
||||
: base("Integer", "Outputs an configurable integer value.")
|
||||
{
|
||||
Output = CreateOutputPin<int>();
|
||||
RegisterCustomView(Application.Current.FindResource("StaticValueCustomViewTemplate") as DataTemplate, _customViewModel);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
Output.Value = _customViewModel.Input;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[UI("Double-Value", "Outputs a configurable double value.")]
|
||||
public class StaticDoubleValueNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private StaticDoubleValueNodeCustomViewModel _customViewModel = new();
|
||||
|
||||
public OutputPin<double> Output { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public StaticDoubleValueNode()
|
||||
: base("Double", "Outputs a configurable double value.")
|
||||
{
|
||||
Output = CreateOutputPin<double>();
|
||||
RegisterCustomView(Application.Current.FindResource("StaticValueCustomViewTemplate") as DataTemplate, _customViewModel);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
Output.Value = _customViewModel.Input;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[UI("String-Value", "Outputs a configurable string value.")]
|
||||
public class StaticStringValueNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private StaticStringValueNodeCustomViewModel _customViewModel = new();
|
||||
|
||||
public OutputPin<string> Output { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public StaticStringValueNode()
|
||||
: base("String", "Outputs a configurable string value.")
|
||||
{
|
||||
Output = CreateOutputPin<string>();
|
||||
RegisterCustomView(Application.Current.FindResource("StaticValueCustomViewTemplate") as DataTemplate, _customViewModel);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
Output.Value = _customViewModel.Input;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region CustomViewModels
|
||||
|
||||
public class StaticIntegerValueNodeCustomViewModel
|
||||
{
|
||||
public int Input { get; set; }
|
||||
}
|
||||
|
||||
public class StaticDoubleValueNodeCustomViewModel
|
||||
{
|
||||
public double Input { get; set; }
|
||||
}
|
||||
|
||||
public class StaticStringValueNodeCustomViewModel
|
||||
{
|
||||
public string Input { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
40
src/Artemis.VisualScripting/Nodes/StringFormatNode.cs
Normal file
40
src/Artemis.VisualScripting/Nodes/StringFormatNode.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System.Linq;
|
||||
using Artemis.VisualScripting.Attributes;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes
|
||||
{
|
||||
[UI("Format", "Formats the input string.")]
|
||||
public class StringFormatNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public InputPin<string> Format { get; }
|
||||
public InputPinCollection<object> Values { get; }
|
||||
|
||||
public OutputPin<string> Output { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public StringFormatNode()
|
||||
: base("Format", "Formats the input string.")
|
||||
{
|
||||
Format = CreateInputPin<string>("Format");
|
||||
Values = CreateInputPinCollection<object>("Values", 1);
|
||||
Output = CreateOutputPin<string>("Result");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
Output.Value = string.Format(Format.Value ?? string.Empty, Values.Values.ToArray());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:nodes="clr-namespace:Artemis.VisualScripting.Nodes">
|
||||
|
||||
<DataTemplate x:Key="StaticValueCustomViewTemplate"
|
||||
DataType="{x:Type nodes:StaticIntegerValueNodeCustomViewModel}">
|
||||
<TextBox VerticalAlignment="Center"
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding Input}" />
|
||||
</DataTemplate>
|
||||
|
||||
</ResourceDictionary>
|
||||
70
src/Artemis.VisualScripting/Nodes/SumNode.cs
Normal file
70
src/Artemis.VisualScripting/Nodes/SumNode.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using System.Linq;
|
||||
using Artemis.VisualScripting.Attributes;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes
|
||||
{
|
||||
[UI("Sum (Integer)", "Sums the connected integer values.")]
|
||||
public class SumIntegersNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public InputPinCollection<int> Values { get; }
|
||||
|
||||
public OutputPin<int> Sum { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public SumIntegersNode()
|
||||
: base("Sum", "Sums the connected integer values.")
|
||||
{
|
||||
Values = CreateInputPinCollection<int>("Values", 2);
|
||||
Sum = CreateOutputPin<int>("Sum");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
Sum.Value = Values.Values.Sum();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[UI("Sum (Double)", "Sums the connected double values.")]
|
||||
public class SumDoublesNode : Node
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
public InputPinCollection<double> Values { get; }
|
||||
|
||||
public OutputPin<double> Sum { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public SumDoublesNode()
|
||||
: base("Sum", "Sums the connected double values.")
|
||||
{
|
||||
Values = CreateInputPinCollection<double>("Values", 2);
|
||||
Sum = CreateOutputPin<double>("Sum");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
Sum.Value = Values.Values.Sum();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
59
src/Artemis.VisualScripting/Services/NodeService.cs
Normal file
59
src/Artemis.VisualScripting/Services/NodeService.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.VisualScripting;
|
||||
using Artemis.VisualScripting.Attributes;
|
||||
using Artemis.VisualScripting.Model;
|
||||
|
||||
namespace Artemis.VisualScripting.Services
|
||||
{
|
||||
//TODO DarthAffe 09.07.2021: Make this Static?
|
||||
public class NodeService
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private static readonly Type TYPE_NODE = typeof(INode);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties & Fields
|
||||
|
||||
private Dictionary<Type, NodeData> _nodeData { get; } = new();
|
||||
public IReadOnlyDictionary<Type, NodeData> NodeData => new ReadOnlyDictionary<Type, NodeData>(_nodeData);
|
||||
public IEnumerable<NodeData> AvailableNodes => _nodeData.Values;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public void InitializeNodes()
|
||||
{
|
||||
foreach (Type nodeType in typeof(NodeService).Assembly.GetTypes().Where(t => typeof(INode).IsAssignableFrom(t) && t.IsPublic && !t.IsAbstract && !t.IsInterface))
|
||||
InitializeNode(nodeType);
|
||||
}
|
||||
|
||||
public void InitializeNode<T>()
|
||||
where T : INode
|
||||
=> InitializeNode(typeof(T));
|
||||
|
||||
public void InitializeNode(Type nodeType)
|
||||
{
|
||||
if (!TYPE_NODE.IsAssignableFrom(nodeType)) throw new ArgumentException("Node has to be a base type of the Node-Type.", nameof(nodeType));
|
||||
if (_nodeData.ContainsKey(nodeType)) return;
|
||||
|
||||
UIAttribute uiAttribute = nodeType.GetCustomAttribute<UIAttribute>();
|
||||
string name = uiAttribute?.Name ?? nodeType.Name;
|
||||
string description = uiAttribute?.Description ?? string.Empty;
|
||||
string category = uiAttribute?.Category ?? string.Empty;
|
||||
|
||||
NodeData nodeData = new(nodeType, name, description, category, () => CreateNode(nodeType));
|
||||
_nodeData.Add(nodeType, nodeData);
|
||||
}
|
||||
|
||||
private INode CreateNode(Type nodeType) => Activator.CreateInstance(nodeType) as INode;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
64
src/Artemis.VisualScripting/ViewModel/AbstractBindable.cs
Normal file
64
src/Artemis.VisualScripting/ViewModel/AbstractBindable.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Artemis.VisualScripting.ViewModel
|
||||
{
|
||||
//TODO DarthAffe 13.07.2021: What is Artemis using here?
|
||||
public abstract class AbstractBindable : INotifyPropertyChanged
|
||||
{
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the property already matches the desirec value or needs to be updated.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the property.</typeparam>
|
||||
/// <param name="storage">Reference to the backing-filed.</param>
|
||||
/// <param name="value">Result to apply.</param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected virtual bool RequiresUpdate<T>(ref T storage, T value)
|
||||
{
|
||||
return !Equals(storage, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the property already matches the desired value and updates it if not.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the property.</typeparam>
|
||||
/// <param name="storage">Reference to the backing-filed.</param>
|
||||
/// <param name="value">Result to apply.</param>
|
||||
/// <param name="propertyName">Name of the property used to notify listeners. This value is optional
|
||||
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute"/>.</param>
|
||||
/// <returns><c>true</c> if the value was changed, <c>false</c> if the existing value matched the desired value.</returns>
|
||||
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
|
||||
{
|
||||
if (!this.RequiresUpdate(ref storage, value)) return false;
|
||||
|
||||
storage = value;
|
||||
// ReSharper disable once ExplicitCallerInfoArgument
|
||||
this.OnPropertyChanged(propertyName);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the <see cref="PropertyChanged"/>-event when a a property value has changed.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property used to notify listeners. This value is optional
|
||||
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute"/>.</param>
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
77
src/Artemis.VisualScripting/ViewModel/ActionCommand.cs
Normal file
77
src/Artemis.VisualScripting/ViewModel/ActionCommand.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Artemis.VisualScripting.ViewModel
|
||||
{
|
||||
public class ActionCommand : ICommand
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly Func<bool> _canExecute;
|
||||
private readonly Action _command;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler CanExecuteChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ActionCommand(Action command, Func<bool> canExecute = null)
|
||||
{
|
||||
this._command = command;
|
||||
this._canExecute = canExecute;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
|
||||
|
||||
public void Execute(object parameter) => _command?.Invoke();
|
||||
|
||||
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, new EventArgs());
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class ActionCommand<T> : ICommand
|
||||
{
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly Func<T, bool> _canExecute;
|
||||
private readonly Action<T> _command;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
public event EventHandler CanExecuteChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
public ActionCommand(Action<T> command, Func<T, bool> canExecute = null)
|
||||
{
|
||||
this._command = command;
|
||||
this._canExecute = canExecute;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public bool CanExecute(object parameter) => _canExecute?.Invoke((T)parameter) ?? true;
|
||||
|
||||
public void Execute(object parameter) => _command?.Invoke((T)parameter);
|
||||
|
||||
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, new EventArgs());
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
1246
src/Artemis.VisualScripting/packages.lock.json
Normal file
1246
src/Artemis.VisualScripting/packages.lock.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Shared", "Artemi
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Console", "Artemis.ConsoleUI\Artemis.UI.Console.csproj", "{ACCC50FD-611A-41C4-B58F-DDC80B47D0CF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.VisualScripting", "Artemis.VisualScripting\Artemis.VisualScripting.csproj", "{CF125C61-FD85-47EE-AF64-38B8F90DD50C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
@ -39,6 +41,10 @@ Global
|
||||
{ACCC50FD-611A-41C4-B58F-DDC80B47D0CF}.Debug|x64.Build.0 = Debug|x64
|
||||
{ACCC50FD-611A-41C4-B58F-DDC80B47D0CF}.Release|x64.ActiveCfg = Release|x64
|
||||
{ACCC50FD-611A-41C4-B58F-DDC80B47D0CF}.Release|x64.Build.0 = Release|x64
|
||||
{CF125C61-FD85-47EE-AF64-38B8F90DD50C}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{CF125C61-FD85-47EE-AF64-38B8F90DD50C}.Debug|x64.Build.0 = Debug|x64
|
||||
{CF125C61-FD85-47EE-AF64-38B8F90DD50C}.Release|x64.ActiveCfg = Release|x64
|
||||
{CF125C61-FD85-47EE-AF64-38B8F90DD50C}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user