diff --git a/src/Artemis.Core/Extensions/TypeExtensions.cs b/src/Artemis.Core/Extensions/TypeExtensions.cs
index 9a148caef..e8c25e05a 100644
--- a/src/Artemis.Core/Extensions/TypeExtensions.cs
+++ b/src/Artemis.Core/Extensions/TypeExtensions.cs
@@ -192,6 +192,52 @@ namespace Artemis.Core
return enumerableType?.GenericTypeArguments[0];
}
+ ///
+ /// Determines if the is of a certain .
+ ///
+ /// The type to check.
+ /// The generic type it should be or implement
+ public static bool IsOfGenericType(this Type typeToCheck, Type genericType)
+ {
+ return typeToCheck.IsOfGenericType(genericType, out Type _);
+ }
+
+ private static bool IsOfGenericType(this Type typeToCheck, Type genericType, out Type concreteGenericType)
+ {
+ while (true)
+ {
+ concreteGenericType = null;
+
+ if (genericType == null)
+ throw new ArgumentNullException(nameof(genericType));
+
+ if (!genericType.IsGenericTypeDefinition)
+ throw new ArgumentException("The definition needs to be a GenericTypeDefinition", nameof(genericType));
+
+ if (typeToCheck == null || typeToCheck == typeof(object))
+ return false;
+
+ if (typeToCheck == genericType)
+ {
+ concreteGenericType = typeToCheck;
+ return true;
+ }
+
+ if ((typeToCheck.IsGenericType ? typeToCheck.GetGenericTypeDefinition() : typeToCheck) == genericType)
+ {
+ concreteGenericType = typeToCheck;
+ return true;
+ }
+
+ if (genericType.IsInterface)
+ foreach (var i in typeToCheck.GetInterfaces())
+ if (i.IsOfGenericType(genericType, out concreteGenericType))
+ return true;
+
+ typeToCheck = typeToCheck.BaseType;
+ }
+ }
+
///
/// Determines a display name for the given type
///
diff --git a/src/Artemis.Core/VisualScripting/NodeData.cs b/src/Artemis.Core/VisualScripting/NodeData.cs
index f6e2a1ca3..6259f6ca6 100644
--- a/src/Artemis.Core/VisualScripting/NodeData.cs
+++ b/src/Artemis.Core/VisualScripting/NodeData.cs
@@ -1,83 +1,117 @@
using System;
using Artemis.Storage.Entities.Profile.Nodes;
-namespace Artemis.Core
+namespace Artemis.Core;
+
+///
+/// Represents node data describing a certain
+///
+public class NodeData
{
- ///
- /// Represents node data describing a certain
- ///
- public class NodeData
+ #region Constructors
+
+ internal NodeData(Plugin plugin, Type type, string name, string description, string category, Type? inputType, Type? outputType, Func create)
{
- #region Constructors
-
- internal NodeData(Plugin plugin, Type type, string name, string description, string category, Type? inputType, Type? outputType, Func create)
- {
- Plugin = plugin;
- Type = type;
- Name = name;
- Description = description;
- Category = category;
- InputType = inputType;
- OutputType = outputType;
- _create = create;
- }
-
- #endregion
-
- #region Methods
-
- ///
- /// Creates a new instance of the node this data represents
- ///
- /// The script to create the node for
- /// An optional storage entity to apply to the node
- /// The returning node of type
- public INode CreateNode(INodeScript script, NodeEntity? entity)
- {
- return _create(script, entity);
- }
-
- #endregion
-
- #region Properties & Fields
-
- ///
- /// Gets the plugin that provided this node data
- ///
- public Plugin Plugin { get; }
-
- ///
- /// Gets the type of this data represents
- ///
- public Type Type { get; }
-
- ///
- /// Gets the name of the node this data represents
- ///
- public string Name { get; }
-
- ///
- /// Gets the description of the node this data represents
- ///
- public string Description { get; }
-
- ///
- /// Gets the category of the node this data represents
- ///
- public string Category { get; }
-
- ///
- /// Gets the primary input type of the node this data represents
- ///
- public Type? InputType { get; }
-
- ///
- /// Gets the primary output of the node this data represents
- ///
- public Type? OutputType { get; }
-
- private readonly Func _create;
-
- #endregion
+ Plugin = plugin;
+ Type = type;
+ Name = name;
+ Description = description;
+ Category = category;
+ InputType = inputType;
+ OutputType = outputType;
+ _create = create;
}
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Creates a new instance of the node this data represents
+ ///
+ /// The script to create the node for
+ /// An optional storage entity to apply to the node
+ /// The returning node of type
+ public INode CreateNode(INodeScript script, NodeEntity? entity)
+ {
+ return _create(script, entity);
+ }
+
+ #endregion
+
+ ///
+ /// Determines whether the given pin is compatible with this node data's node.
+ ///
+ ///
+ /// The pin to check compatibility with, if then the node data is always
+ /// considered compatible.
+ ///
+ ///
+ /// if the pin is compatible with this node data's node; otherwise .
+ ///
+ public bool IsCompatibleWithPin(IPin? pin)
+ {
+ if (pin == null)
+ return true;
+
+ if (pin.Direction == PinDirection.Input)
+ return OutputType != null && pin.IsTypeCompatible(OutputType);
+ return InputType != null && pin.IsTypeCompatible(InputType);
+ }
+
+ ///
+ /// Determines whether the given text matches this node data for a search query.
+ ///
+ /// The text to search for.
+ ///
+ /// if the node matches; otherwise .
+ ///
+ public bool MatchesSearch(string text)
+ {
+ text = text.Trim();
+ return Name.Contains(text, StringComparison.InvariantCultureIgnoreCase) ||
+ Description.Contains(text, StringComparison.InvariantCultureIgnoreCase) ||
+ Category.Contains(text, StringComparison.InvariantCultureIgnoreCase);
+ }
+
+ #region Properties & Fields
+
+ ///
+ /// Gets the plugin that provided this node data
+ ///
+ public Plugin Plugin { get; }
+
+ ///
+ /// Gets the type of this data represents
+ ///
+ public Type Type { get; }
+
+ ///
+ /// Gets the name of the node this data represents
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the description of the node this data represents
+ ///
+ public string Description { get; }
+
+ ///
+ /// Gets the category of the node this data represents
+ ///
+ public string Category { get; }
+
+ ///
+ /// Gets the primary input type of the node this data represents
+ ///
+ public Type? InputType { get; }
+
+ ///
+ /// Gets the primary output of the node this data represents
+ ///
+ public Type? OutputType { get; }
+
+ private readonly Func _create;
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs
index 56ae49395..9c611d37f 100644
--- a/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs
+++ b/src/Artemis.VisualScripting/Editor/Controls/VisualScriptNodePresenter.cs
@@ -158,7 +158,7 @@ namespace Artemis.VisualScripting.Editor.Controls
if (Node?.Node is Node customViewModelNode)
{
- CustomViewModel = customViewModelNode.GetCustomViewModel();
+ CustomViewModel = customViewModelNode.GetCustomViewModel((NodeScript) Node.Script.Script);
// CustomViewModel?.OnActivate();
}
else
diff --git a/src/Artemis.sln.DotSettings b/src/Artemis.sln.DotSettings
index 7af04acad..6943df083 100644
--- a/src/Artemis.sln.DotSettings
+++ b/src/Artemis.sln.DotSettings
@@ -242,6 +242,7 @@
True
True
True
+ True
True
True
True
diff --git a/src/Avalonia/Artemis.UI.Linux/packages.lock.json b/src/Avalonia/Artemis.UI.Linux/packages.lock.json
index 748e8244f..66c4330a9 100644
--- a/src/Avalonia/Artemis.UI.Linux/packages.lock.json
+++ b/src/Avalonia/Artemis.UI.Linux/packages.lock.json
@@ -157,29 +157,29 @@
},
"Avalonia.Xaml.Behaviors": {
"type": "Transitive",
- "resolved": "0.10.12.2",
- "contentHash": "EqfzwstvqQcWnTJnaBvezxKwBSddozXpkFi5WrzVe976zedE+A1NruFgnC19aG7Vvy0mTQdlWFTtbAInv6IQyg==",
+ "resolved": "0.10.13.2",
+ "contentHash": "sZlq6FFzNNzYmHK+vARWFpxtDY4XUdnU6q6zVIm4l1iQ3/ZXor4SeUnYDdd3lFZtoJ9yc8K2g4X7d/lVEgV9tA==",
"dependencies": {
- "Avalonia": "0.10.12",
- "Avalonia.Xaml.Interactions": "0.10.12.2",
- "Avalonia.Xaml.Interactivity": "0.10.12.2"
+ "Avalonia": "0.10.13",
+ "Avalonia.Xaml.Interactions": "0.10.13.2",
+ "Avalonia.Xaml.Interactivity": "0.10.13.2"
}
},
"Avalonia.Xaml.Interactions": {
"type": "Transitive",
- "resolved": "0.10.12.2",
- "contentHash": "01NGXHMbvpg1JcZ4tFAZXD6i55vHIQnJl3+HFi7RSP1jevkjkSaVM8qjwLsTSfREsJ2OoiWxx2LcyUQJvO5Kjw==",
+ "resolved": "0.10.13.2",
+ "contentHash": "bMgr5NtEjJ/qvf+1JD4T4rRt9AbZNnJdYCx5cBfGyXHETbeliTJAt07mqTahcoPY1G2FskF1OSIW5ytljbviLw==",
"dependencies": {
- "Avalonia": "0.10.12",
- "Avalonia.Xaml.Interactivity": "0.10.12.2"
+ "Avalonia": "0.10.13",
+ "Avalonia.Xaml.Interactivity": "0.10.13.2"
}
},
"Avalonia.Xaml.Interactivity": {
"type": "Transitive",
- "resolved": "0.10.12.2",
- "contentHash": "AGAbT1I6XW1+9tweLHDMGX8+SijE111vNNIQy2gI3bpbLfPYTirLPyK0do2s9V6l7hHfQnNmiX2NA6JHC4WG4Q==",
+ "resolved": "0.10.13.2",
+ "contentHash": "OIjK5XCsUrBCqog8lxI/DEbubaNQRwy8e8Px4i3dvllomU28EYsJm4XtrPVakY7MC+we825uXY47tsO/benLug==",
"dependencies": {
- "Avalonia": "0.10.12"
+ "Avalonia": "0.10.13"
}
},
"Castle.Core": {
@@ -1778,9 +1778,7 @@
"Avalonia.Diagnostics": "0.10.13",
"Avalonia.ReactiveUI": "0.10.13",
"Avalonia.Svg.Skia": "0.10.12",
- "Avalonia.Xaml.Behaviors": "0.10.12.2",
- "Avalonia.Xaml.Interactions": "0.10.12.2",
- "Avalonia.Xaml.Interactivity": "0.10.12.2",
+ "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0",
"Flurl.Http": "3.2.0",
@@ -1801,6 +1799,7 @@
"Avalonia": "0.10.13",
"Avalonia.ReactiveUI": "0.10.13",
"Avalonia.Svg.Skia": "0.10.12",
+ "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0",
"Material.Icons.Avalonia": "1.0.2",
@@ -1817,6 +1816,7 @@
"Artemis.UI.Shared": "1.0.0",
"Avalonia": "0.10.13",
"Avalonia.ReactiveUI": "0.10.13",
+ "Avalonia.Xaml.Behaviors": "0.10.13.2",
"Ninject": "3.3.4",
"NoStringEvaluating": "2.2.2",
"ReactiveUI": "17.1.50",
diff --git a/src/Avalonia/Artemis.UI.MacOS/packages.lock.json b/src/Avalonia/Artemis.UI.MacOS/packages.lock.json
index 748e8244f..66c4330a9 100644
--- a/src/Avalonia/Artemis.UI.MacOS/packages.lock.json
+++ b/src/Avalonia/Artemis.UI.MacOS/packages.lock.json
@@ -157,29 +157,29 @@
},
"Avalonia.Xaml.Behaviors": {
"type": "Transitive",
- "resolved": "0.10.12.2",
- "contentHash": "EqfzwstvqQcWnTJnaBvezxKwBSddozXpkFi5WrzVe976zedE+A1NruFgnC19aG7Vvy0mTQdlWFTtbAInv6IQyg==",
+ "resolved": "0.10.13.2",
+ "contentHash": "sZlq6FFzNNzYmHK+vARWFpxtDY4XUdnU6q6zVIm4l1iQ3/ZXor4SeUnYDdd3lFZtoJ9yc8K2g4X7d/lVEgV9tA==",
"dependencies": {
- "Avalonia": "0.10.12",
- "Avalonia.Xaml.Interactions": "0.10.12.2",
- "Avalonia.Xaml.Interactivity": "0.10.12.2"
+ "Avalonia": "0.10.13",
+ "Avalonia.Xaml.Interactions": "0.10.13.2",
+ "Avalonia.Xaml.Interactivity": "0.10.13.2"
}
},
"Avalonia.Xaml.Interactions": {
"type": "Transitive",
- "resolved": "0.10.12.2",
- "contentHash": "01NGXHMbvpg1JcZ4tFAZXD6i55vHIQnJl3+HFi7RSP1jevkjkSaVM8qjwLsTSfREsJ2OoiWxx2LcyUQJvO5Kjw==",
+ "resolved": "0.10.13.2",
+ "contentHash": "bMgr5NtEjJ/qvf+1JD4T4rRt9AbZNnJdYCx5cBfGyXHETbeliTJAt07mqTahcoPY1G2FskF1OSIW5ytljbviLw==",
"dependencies": {
- "Avalonia": "0.10.12",
- "Avalonia.Xaml.Interactivity": "0.10.12.2"
+ "Avalonia": "0.10.13",
+ "Avalonia.Xaml.Interactivity": "0.10.13.2"
}
},
"Avalonia.Xaml.Interactivity": {
"type": "Transitive",
- "resolved": "0.10.12.2",
- "contentHash": "AGAbT1I6XW1+9tweLHDMGX8+SijE111vNNIQy2gI3bpbLfPYTirLPyK0do2s9V6l7hHfQnNmiX2NA6JHC4WG4Q==",
+ "resolved": "0.10.13.2",
+ "contentHash": "OIjK5XCsUrBCqog8lxI/DEbubaNQRwy8e8Px4i3dvllomU28EYsJm4XtrPVakY7MC+we825uXY47tsO/benLug==",
"dependencies": {
- "Avalonia": "0.10.12"
+ "Avalonia": "0.10.13"
}
},
"Castle.Core": {
@@ -1778,9 +1778,7 @@
"Avalonia.Diagnostics": "0.10.13",
"Avalonia.ReactiveUI": "0.10.13",
"Avalonia.Svg.Skia": "0.10.12",
- "Avalonia.Xaml.Behaviors": "0.10.12.2",
- "Avalonia.Xaml.Interactions": "0.10.12.2",
- "Avalonia.Xaml.Interactivity": "0.10.12.2",
+ "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0",
"Flurl.Http": "3.2.0",
@@ -1801,6 +1799,7 @@
"Avalonia": "0.10.13",
"Avalonia.ReactiveUI": "0.10.13",
"Avalonia.Svg.Skia": "0.10.12",
+ "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0",
"Material.Icons.Avalonia": "1.0.2",
@@ -1817,6 +1816,7 @@
"Artemis.UI.Shared": "1.0.0",
"Avalonia": "0.10.13",
"Avalonia.ReactiveUI": "0.10.13",
+ "Avalonia.Xaml.Behaviors": "0.10.13.2",
"Ninject": "3.3.4",
"NoStringEvaluating": "2.2.2",
"ReactiveUI": "17.1.50",
diff --git a/src/Avalonia/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Avalonia/Artemis.UI.Shared/Artemis.UI.Shared.csproj
index e9e9bd769..18db8283c 100644
--- a/src/Avalonia/Artemis.UI.Shared/Artemis.UI.Shared.csproj
+++ b/src/Avalonia/Artemis.UI.Shared/Artemis.UI.Shared.csproj
@@ -20,6 +20,7 @@
+
diff --git a/src/Avalonia/Artemis.UI.Shared/Behaviors/LostFocusNumberBoxBindingBehavior.cs b/src/Avalonia/Artemis.UI.Shared/Behaviors/LostFocusNumberBoxBindingBehavior.cs
new file mode 100644
index 000000000..cf004cf16
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Behaviors/LostFocusNumberBoxBindingBehavior.cs
@@ -0,0 +1,56 @@
+using System;
+using Avalonia;
+using Avalonia.Data;
+using Avalonia.Interactivity;
+using Avalonia.Xaml.Interactivity;
+using FluentAvalonia.UI.Controls;
+
+namespace Artemis.UI.Shared.Behaviors;
+
+///
+/// Represents a behavior that can be used to make a text box only update it's binding on focus loss.
+///
+public class LostFocusNumberBoxBindingBehavior : Behavior
+{
+ public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register(
+ nameof(Value), defaultBindingMode: BindingMode.TwoWay);
+
+ static LostFocusNumberBoxBindingBehavior()
+ {
+ ValueProperty.Changed.Subscribe(e => ((LostFocusNumberBoxBindingBehavior) e.Sender).OnBindingValueChanged());
+ }
+
+ public double Value
+ {
+ get => GetValue(ValueProperty);
+ set => SetValue(ValueProperty, value);
+ }
+
+ ///
+ protected override void OnAttached()
+ {
+ if (AssociatedObject != null)
+ AssociatedObject.LostFocus += OnLostFocus;
+ base.OnAttached();
+ }
+
+ ///
+ protected override void OnDetaching()
+ {
+ if (AssociatedObject != null)
+ AssociatedObject.LostFocus -= OnLostFocus;
+ base.OnDetaching();
+ }
+
+ private void OnLostFocus(object? sender, RoutedEventArgs e)
+ {
+ if (AssociatedObject != null)
+ Value = AssociatedObject.Value;
+ }
+
+ private void OnBindingValueChanged()
+ {
+ if (AssociatedObject != null)
+ AssociatedObject.Value = Value;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Behaviors/LostFocusTextBoxBindingBehavior.cs b/src/Avalonia/Artemis.UI.Shared/Behaviors/LostFocusTextBoxBindingBehavior.cs
new file mode 100644
index 000000000..961ea308f
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Behaviors/LostFocusTextBoxBindingBehavior.cs
@@ -0,0 +1,56 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Data;
+using Avalonia.Interactivity;
+using Avalonia.Xaml.Interactivity;
+
+namespace Artemis.UI.Shared.Behaviors;
+
+///
+/// Represents a behavior that can be used to make a text box only update it's binding on focus loss.
+///
+public class LostFocusTextBoxBindingBehavior : Behavior
+{
+ public static readonly StyledProperty TextProperty = AvaloniaProperty.Register(
+ "Text", defaultBindingMode: BindingMode.TwoWay);
+
+ static LostFocusTextBoxBindingBehavior()
+ {
+ TextProperty.Changed.Subscribe(e => ((LostFocusTextBoxBindingBehavior) e.Sender).OnBindingValueChanged());
+ }
+
+ public string Text
+ {
+ get => GetValue(TextProperty);
+ set => SetValue(TextProperty, value);
+ }
+
+ ///
+ protected override void OnAttached()
+ {
+ if (AssociatedObject != null)
+ AssociatedObject.LostFocus += OnLostFocus;
+ base.OnAttached();
+ }
+
+ ///
+ protected override void OnDetaching()
+ {
+ if (AssociatedObject != null)
+ AssociatedObject.LostFocus -= OnLostFocus;
+ base.OnDetaching();
+ }
+
+ private void OnLostFocus(object? sender, RoutedEventArgs e)
+ {
+ if (AssociatedObject != null)
+ Text = AssociatedObject.Text;
+ }
+
+ private void OnBindingValueChanged()
+ {
+ if (AssociatedObject != null)
+ AssociatedObject.Text = Text;
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs b/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs
index 0a9f50f3e..644388738 100644
--- a/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs
@@ -13,10 +13,8 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
-using Material.Icons;
using Material.Icons.Avalonia;
using ReactiveUI;
-using SkiaSharp;
namespace Artemis.UI.Shared.Controls.DataModelPicker;
diff --git a/src/Avalonia/Artemis.UI.Shared/packages.lock.json b/src/Avalonia/Artemis.UI.Shared/packages.lock.json
index cb93e25fb..aaa59d395 100644
--- a/src/Avalonia/Artemis.UI.Shared/packages.lock.json
+++ b/src/Avalonia/Artemis.UI.Shared/packages.lock.json
@@ -40,6 +40,17 @@
"Svg.Skia": "0.5.12"
}
},
+ "Avalonia.Xaml.Behaviors": {
+ "type": "Direct",
+ "requested": "[0.10.13.2, )",
+ "resolved": "0.10.13.2",
+ "contentHash": "sZlq6FFzNNzYmHK+vARWFpxtDY4XUdnU6q6zVIm4l1iQ3/ZXor4SeUnYDdd3lFZtoJ9yc8K2g4X7d/lVEgV9tA==",
+ "dependencies": {
+ "Avalonia": "0.10.13",
+ "Avalonia.Xaml.Interactions": "0.10.13.2",
+ "Avalonia.Xaml.Interactivity": "0.10.13.2"
+ }
+ },
"DynamicData": {
"type": "Direct",
"requested": "[7.5.4, )",
@@ -204,6 +215,23 @@
"Avalonia.Skia": "0.10.13"
}
},
+ "Avalonia.Xaml.Interactions": {
+ "type": "Transitive",
+ "resolved": "0.10.13.2",
+ "contentHash": "bMgr5NtEjJ/qvf+1JD4T4rRt9AbZNnJdYCx5cBfGyXHETbeliTJAt07mqTahcoPY1G2FskF1OSIW5ytljbviLw==",
+ "dependencies": {
+ "Avalonia": "0.10.13",
+ "Avalonia.Xaml.Interactivity": "0.10.13.2"
+ }
+ },
+ "Avalonia.Xaml.Interactivity": {
+ "type": "Transitive",
+ "resolved": "0.10.13.2",
+ "contentHash": "OIjK5XCsUrBCqog8lxI/DEbubaNQRwy8e8Px4i3dvllomU28EYsJm4XtrPVakY7MC+we825uXY47tsO/benLug==",
+ "dependencies": {
+ "Avalonia": "0.10.13"
+ }
+ },
"Castle.Core": {
"type": "Transitive",
"resolved": "4.2.0",
diff --git a/src/Avalonia/Artemis.UI.Windows/packages.lock.json b/src/Avalonia/Artemis.UI.Windows/packages.lock.json
index c8195bec5..f78508371 100644
--- a/src/Avalonia/Artemis.UI.Windows/packages.lock.json
+++ b/src/Avalonia/Artemis.UI.Windows/packages.lock.json
@@ -173,29 +173,29 @@
},
"Avalonia.Xaml.Behaviors": {
"type": "Transitive",
- "resolved": "0.10.12.2",
- "contentHash": "EqfzwstvqQcWnTJnaBvezxKwBSddozXpkFi5WrzVe976zedE+A1NruFgnC19aG7Vvy0mTQdlWFTtbAInv6IQyg==",
+ "resolved": "0.10.13.2",
+ "contentHash": "sZlq6FFzNNzYmHK+vARWFpxtDY4XUdnU6q6zVIm4l1iQ3/ZXor4SeUnYDdd3lFZtoJ9yc8K2g4X7d/lVEgV9tA==",
"dependencies": {
- "Avalonia": "0.10.12",
- "Avalonia.Xaml.Interactions": "0.10.12.2",
- "Avalonia.Xaml.Interactivity": "0.10.12.2"
+ "Avalonia": "0.10.13",
+ "Avalonia.Xaml.Interactions": "0.10.13.2",
+ "Avalonia.Xaml.Interactivity": "0.10.13.2"
}
},
"Avalonia.Xaml.Interactions": {
"type": "Transitive",
- "resolved": "0.10.12.2",
- "contentHash": "01NGXHMbvpg1JcZ4tFAZXD6i55vHIQnJl3+HFi7RSP1jevkjkSaVM8qjwLsTSfREsJ2OoiWxx2LcyUQJvO5Kjw==",
+ "resolved": "0.10.13.2",
+ "contentHash": "bMgr5NtEjJ/qvf+1JD4T4rRt9AbZNnJdYCx5cBfGyXHETbeliTJAt07mqTahcoPY1G2FskF1OSIW5ytljbviLw==",
"dependencies": {
- "Avalonia": "0.10.12",
- "Avalonia.Xaml.Interactivity": "0.10.12.2"
+ "Avalonia": "0.10.13",
+ "Avalonia.Xaml.Interactivity": "0.10.13.2"
}
},
"Avalonia.Xaml.Interactivity": {
"type": "Transitive",
- "resolved": "0.10.12.2",
- "contentHash": "AGAbT1I6XW1+9tweLHDMGX8+SijE111vNNIQy2gI3bpbLfPYTirLPyK0do2s9V6l7hHfQnNmiX2NA6JHC4WG4Q==",
+ "resolved": "0.10.13.2",
+ "contentHash": "OIjK5XCsUrBCqog8lxI/DEbubaNQRwy8e8Px4i3dvllomU28EYsJm4XtrPVakY7MC+we825uXY47tsO/benLug==",
"dependencies": {
- "Avalonia": "0.10.12"
+ "Avalonia": "0.10.13"
}
},
"Castle.Core": {
@@ -1794,9 +1794,7 @@
"Avalonia.Diagnostics": "0.10.13",
"Avalonia.ReactiveUI": "0.10.13",
"Avalonia.Svg.Skia": "0.10.12",
- "Avalonia.Xaml.Behaviors": "0.10.12.2",
- "Avalonia.Xaml.Interactions": "0.10.12.2",
- "Avalonia.Xaml.Interactivity": "0.10.12.2",
+ "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0",
"Flurl.Http": "3.2.0",
@@ -1817,6 +1815,7 @@
"Avalonia": "0.10.13",
"Avalonia.ReactiveUI": "0.10.13",
"Avalonia.Svg.Skia": "0.10.12",
+ "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0",
"Material.Icons.Avalonia": "1.0.2",
@@ -1833,6 +1832,7 @@
"Artemis.UI.Shared": "1.0.0",
"Avalonia": "0.10.13",
"Avalonia.ReactiveUI": "0.10.13",
+ "Avalonia.Xaml.Behaviors": "0.10.13.2",
"Ninject": "3.3.4",
"NoStringEvaluating": "2.2.2",
"ReactiveUI": "17.1.50",
diff --git a/src/Avalonia/Artemis.UI/Artemis.UI.csproj b/src/Avalonia/Artemis.UI/Artemis.UI.csproj
index 16018ec21..1c0c569b1 100644
--- a/src/Avalonia/Artemis.UI/Artemis.UI.csproj
+++ b/src/Avalonia/Artemis.UI/Artemis.UI.csproj
@@ -21,9 +21,7 @@
-
-
-
+
diff --git a/src/Avalonia/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.axaml.cs
index 0024f1448..b759491c9 100644
--- a/src/Avalonia/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.axaml.cs
+++ b/src/Avalonia/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.axaml.cs
@@ -1,9 +1,10 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Sidebar
{
- public class SidebarProfileConfigurationView : UserControl
+ public class SidebarProfileConfigurationView : ReactiveUserControl
{
public SidebarProfileConfigurationView()
{
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml
index bb7c03328..9025e35c8 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml
@@ -30,24 +30,33 @@
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
None of the nodes match your search
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml.cs
index e6ddc86fc..27e0f04f7 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml.cs
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml.cs
@@ -1,33 +1,28 @@
using System;
using System.Reactive.Linq;
-using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Mixins;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
-using Avalonia.VisualTree;
using ReactiveUI;
-namespace Artemis.UI.Screens.VisualScripting
-{
- public partial class NodePickerView : ReactiveUserControl
- {
- public NodePickerView()
- {
- InitializeComponent();
- this.WhenActivated(
- d => ViewModel
- .WhenAnyValue(vm => vm.IsVisible)
- .Where(visible => !visible)
- .Subscribe(_ => this.FindLogicalAncestorOfType()?.ContextFlyout?.Hide())
- .DisposeWith(d)
- );
- }
+namespace Artemis.UI.Screens.VisualScripting;
- private void InitializeComponent()
+public class NodePickerView : ReactiveUserControl
+{
+ public NodePickerView()
+ {
+ InitializeComponent();
+ this.WhenActivated(d =>
{
- AvaloniaXamlLoader.Load(this);
- }
+ ViewModel?.WhenAnyValue(vm => vm.IsVisible).Where(visible => !visible).Subscribe(_ => this.FindLogicalAncestorOfType()?.ContextFlyout?.Hide()).DisposeWith(d);
+ this.Get("SearchBox").SelectAll();
+ });
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs
index 9e66457c8..b19f4e559 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.ObjectModel;
+using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Artemis.Core;
@@ -8,6 +9,7 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Avalonia;
+using DynamicData;
using ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting;
@@ -16,36 +18,63 @@ public class NodePickerViewModel : ActivatableViewModelBase
{
private readonly INodeEditorService _nodeEditorService;
private readonly NodeScript _nodeScript;
- private readonly INodeService _nodeService;
private bool _isVisible;
private Point _position;
+ private DateTime _closed;
private string? _searchText;
- private NodeData? _selectedNode;
+ private object? _selectedNode;
+ private IPin? _targetPin;
public NodePickerViewModel(NodeScript nodeScript, INodeService nodeService, INodeEditorService nodeEditorService)
{
_nodeScript = nodeScript;
- _nodeService = nodeService;
_nodeEditorService = nodeEditorService;
- Nodes = new ObservableCollection(_nodeService.AvailableNodes);
+ SourceList nodeSourceList = new();
+ IObservable> nodeFilter = this.WhenAnyValue(vm => vm.SearchText, vm => vm.TargetPin).Select(v => CreatePredicate(v.Item1, v.Item2));
+
+ nodeSourceList.Connect()
+ .Filter(nodeFilter)
+ .GroupWithImmutableState(n => n.Category)
+ .Bind(out ReadOnlyObservableCollection> categories)
+ .Subscribe();
+ Categories = categories;
this.WhenActivated(d =>
{
+ if (DateTime.Now - _closed > TimeSpan.FromSeconds(10))
+ SearchText = null;
+ TargetPin = null;
+
+ nodeSourceList.Edit(list =>
+ {
+ list.Clear();
+ list.AddRange(nodeService.AvailableNodes);
+ });
+
IsVisible = true;
- Disposable.Create(() => IsVisible = false).DisposeWith(d);
+
+ Disposable.Create(() =>
+ {
+ _closed = DateTime.Now;
+ IsVisible = false;
+ }).DisposeWith(d);
});
- this.WhenAnyValue(vm => vm.SelectedNode).WhereNotNull().Throttle(TimeSpan.FromMilliseconds(200), RxApp.MainThreadScheduler).Subscribe(data =>
- {
- CreateNode(data);
- Hide();
- SelectedNode = null;
- });
+ this.WhenAnyValue(vm => vm.SelectedNode)
+ .WhereNotNull()
+ .Where(o => o is NodeData)
+ .Throttle(TimeSpan.FromMilliseconds(200), RxApp.MainThreadScheduler)
+ .Subscribe(data =>
+ {
+ CreateNode((NodeData) data);
+ IsVisible = false;
+ SelectedNode = null;
+ });
}
- public ObservableCollection Nodes { get; }
+ public ReadOnlyObservableCollection> Categories { get; }
public bool IsVisible
{
@@ -65,29 +94,47 @@ public class NodePickerViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _searchText, value);
}
- public NodeData? SelectedNode
+ public IPin? TargetPin
+ {
+ get => _targetPin;
+ set => RaiseAndSetIfChanged(ref _targetPin, value);
+ }
+
+ public object? SelectedNode
{
get => _selectedNode;
set => RaiseAndSetIfChanged(ref _selectedNode, value);
}
- public void Show(Point position)
- {
- IsVisible = true;
- Position = position;
- }
-
- public void Hide()
- {
- IsVisible = false;
- }
-
- private void CreateNode(NodeData data)
+ public void CreateNode(NodeData data)
{
INode node = data.CreateNode(_nodeScript, null);
- node.X = Position.X;
- node.Y = Position.Y;
+ node.X = Math.Round(Position.X / 10d, 0, MidpointRounding.AwayFromZero) * 10d;
+ node.Y = Math.Round(Position.Y / 10d, 0, MidpointRounding.AwayFromZero) * 10d;
- _nodeEditorService.ExecuteCommand(_nodeScript, new AddNode(_nodeScript, node));
+ if (TargetPin != null)
+ {
+ using (_nodeEditorService.CreateCommandScope(_nodeScript, "Create node for pin"))
+ {
+ _nodeEditorService.ExecuteCommand(_nodeScript, new AddNode(_nodeScript, node));
+
+ // Find the first compatible source pin for the target pin
+ IPin? source = TargetPin.Direction == PinDirection.Output
+ ? node.Pins.Concat(node.PinCollections.SelectMany(c => c)).Where(p => p.Direction == PinDirection.Input).FirstOrDefault(p => TargetPin.IsTypeCompatible(p.Type))
+ : node.Pins.Concat(node.PinCollections.SelectMany(c => c)).Where(p => p.Direction == PinDirection.Output).FirstOrDefault(p => TargetPin.IsTypeCompatible(p.Type));
+
+ if (source != null)
+ _nodeEditorService.ExecuteCommand(_nodeScript, new ConnectPins(source, TargetPin));
+ }
+ }
+ else
+ _nodeEditorService.ExecuteCommand(_nodeScript, new AddNode(_nodeScript, node));
+ }
+
+ private Func CreatePredicate(string? text, IPin? targetPin)
+ {
+ if (string.IsNullOrWhiteSpace(text))
+ return data => data.IsCompatibleWithPin(targetPin);
+ return data => data.IsCompatibleWithPin(targetPin) && data.MatchesSearch(text);
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs
index cf4a8e15b..3a3d84fa4 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.UI.Shared.Controls;
@@ -11,6 +12,7 @@ using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.ReactiveUI;
+using ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting;
@@ -33,6 +35,11 @@ public class NodeScriptView : ReactiveUserControl
UpdateZoomBorderBackground();
_grid.AddHandler(PointerReleasedEvent, CanvasOnPointerReleased, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
+ this.WhenActivated(_ => ViewModel?.PickerPositionSubject.Subscribe(p =>
+ {
+ ViewModel.NodePickerViewModel.Position = p;
+ _grid?.ContextFlyout?.ShowAt(_grid, true);
+ }));
}
private void CanvasOnPointerReleased(object? sender, PointerReleasedEventArgs e)
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs
index 9dbe70cd0..9bdf0eb50 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs
@@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
+using System.Reactive.Subjects;
using Artemis.Core;
using Artemis.Core.Events;
+using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.VisualScripting.Pins;
using Artemis.UI.Shared;
@@ -23,21 +25,27 @@ public class NodeScriptViewModel : ActivatableViewModelBase
{
private readonly INodeEditorService _nodeEditorService;
private readonly INotificationService _notificationService;
- private readonly SourceList _nodeViewModels;
private readonly INodeVmFactory _nodeVmFactory;
+ private readonly INodeService _nodeService;
+ private readonly SourceList _nodeViewModels;
+ private readonly Subject _requestedPickerPositionSubject;
+
private DragCableViewModel? _dragViewModel;
private List? _initialNodeSelection;
- public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService, INotificationService notificationService)
+ public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeService nodeService, INodeEditorService nodeEditorService, INotificationService notificationService)
{
_nodeVmFactory = nodeVmFactory;
+ _nodeService = nodeService;
_nodeEditorService = nodeEditorService;
_notificationService = notificationService;
_nodeViewModels = new SourceList();
+ _requestedPickerPositionSubject = new Subject();
NodeScript = nodeScript;
NodePickerViewModel = _nodeVmFactory.NodePickerViewModel(nodeScript);
History = nodeEditorService.GetHistory(NodeScript);
+ PickerPositionSubject = _requestedPickerPositionSubject.AsObservable();
this.WhenActivated(d =>
{
@@ -87,6 +95,7 @@ public class NodeScriptViewModel : ActivatableViewModelBase
public ReadOnlyObservableCollection CableViewModels { get; }
public NodePickerViewModel NodePickerViewModel { get; }
public NodeEditorHistory History { get; }
+ public IObservable PickerPositionSubject { get; }
public DragCableViewModel? DragViewModel
{
@@ -165,7 +174,7 @@ public class NodeScriptViewModel : ActivatableViewModelBase
return targetPinVmModel == null || targetPinVmModel.IsCompatibleWith(sourcePinViewModel);
}
- public void FinishPinDrag(PinViewModel sourcePinViewModel, PinViewModel? targetPinVmModel)
+ public void FinishPinDrag(PinViewModel sourcePinViewModel, PinViewModel? targetPinVmModel, Point position)
{
if (DragViewModel == null)
return;
@@ -175,6 +184,24 @@ public class NodeScriptViewModel : ActivatableViewModelBase
// If dropped on top of a compatible pin, connect to it
if (targetPinVmModel != null && targetPinVmModel.IsCompatibleWith(sourcePinViewModel))
_nodeEditorService.ExecuteCommand(NodeScript, new ConnectPins(sourcePinViewModel.Pin, targetPinVmModel.Pin));
+ // If not dropped on a pin allow the user to create a new node
+ else if (targetPinVmModel == null)
+ {
+ // If there is only one, spawn that straight away
+ List singleCompatibleNode = _nodeService.AvailableNodes.Where(n => n.IsCompatibleWithPin(sourcePinViewModel.Pin)).ToList();
+ if (singleCompatibleNode.Count == 1)
+ {
+ // Borrow the node picker to spawn the node in, even if it's never shown
+ NodePickerViewModel.TargetPin = sourcePinViewModel.Pin;
+ NodePickerViewModel.CreateNode(singleCompatibleNode.First());
+ }
+ // Otherwise show the user the picker by requesting it at the drop position
+ else
+ {
+ _requestedPickerPositionSubject.OnNext(position);
+ NodePickerViewModel.TargetPin = sourcePinViewModel.Pin;
+ }
+ }
}
private void HandleNodeAdded(SingleValueEventArgs eventArgs)
diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinView.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinView.cs
index 5d7cb3bb5..34d252d65 100644
--- a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinView.cs
+++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinView.cs
@@ -55,7 +55,7 @@ public class PinView : ReactiveUserControl
if (targetPin == ViewModel)
targetPin = null;
- this.FindAncestorOfType()?.ViewModel?.FinishPinDrag(ViewModel, targetPin);
+ this.FindAncestorOfType()?.ViewModel?.FinishPinDrag(ViewModel, targetPin, point.Position);
_pinPoint.Cursor = new Cursor(StandardCursorType.Hand);
e.Handled = true;
}
diff --git a/src/Avalonia/Artemis.UI/ViewLocator.cs b/src/Avalonia/Artemis.UI/ViewLocator.cs
index c0c797ed7..09a87cddc 100644
--- a/src/Avalonia/Artemis.UI/ViewLocator.cs
+++ b/src/Avalonia/Artemis.UI/ViewLocator.cs
@@ -1,28 +1,33 @@
using System;
+using Artemis.Core;
+using Artemis.UI.Exceptions;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
+using Avalonia.ReactiveUI;
using ReactiveUI;
-namespace Artemis.UI
+namespace Artemis.UI;
+
+public class ViewLocator : IDataTemplate
{
- public class ViewLocator : IDataTemplate
+ public IControl Build(object data)
{
- public bool SupportsRecycling => false;
+ Type dataType = data.GetType();
+ string name = dataType.FullName!.Split('`')[0].Replace("ViewModel", "View");
+ Type? type = dataType.Assembly.GetType(name);
- public IControl Build(object data)
- {
- Type dataType = data.GetType();
- string name = dataType.FullName!.Split('`')[0].Replace("ViewModel", "View");
- Type? type = dataType.Assembly.GetType(name);
+ // This isn't strictly required but it's super confusing (and happens to me all the time) if you implement IActivatableViewModel but forget to make your user control reactive.
+ // When this happens your OnActivated never gets called and it's easy to miss.
+ if (data is IActivatableViewModel && type != null && !type.IsOfGenericType(typeof(ReactiveUserControl<>)))
+ throw new ArtemisUIException($"The views of activatable view models should inherit ReactiveUserControl, in this case ReactiveUserControl<{data.GetType().Name}>.");
- if (type != null)
- return (Control) Activator.CreateInstance(type)!;
- return new TextBlock {Text = "Not Found: " + name};
- }
+ if (type != null)
+ return (Control) Activator.CreateInstance(type)!;
+ return new TextBlock {Text = "Not Found: " + name};
+ }
- public bool Match(object data)
- {
- return data is ReactiveObject;
- }
+ public bool Match(object data)
+ {
+ return data is ReactiveObject;
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI/packages.lock.json b/src/Avalonia/Artemis.UI/packages.lock.json
index 1e975be58..a3900f74e 100644
--- a/src/Avalonia/Artemis.UI/packages.lock.json
+++ b/src/Avalonia/Artemis.UI/packages.lock.json
@@ -76,32 +76,13 @@
},
"Avalonia.Xaml.Behaviors": {
"type": "Direct",
- "requested": "[0.10.12.2, )",
- "resolved": "0.10.12.2",
- "contentHash": "EqfzwstvqQcWnTJnaBvezxKwBSddozXpkFi5WrzVe976zedE+A1NruFgnC19aG7Vvy0mTQdlWFTtbAInv6IQyg==",
+ "requested": "[0.10.13.2, )",
+ "resolved": "0.10.13.2",
+ "contentHash": "sZlq6FFzNNzYmHK+vARWFpxtDY4XUdnU6q6zVIm4l1iQ3/ZXor4SeUnYDdd3lFZtoJ9yc8K2g4X7d/lVEgV9tA==",
"dependencies": {
- "Avalonia": "0.10.12",
- "Avalonia.Xaml.Interactions": "0.10.12.2",
- "Avalonia.Xaml.Interactivity": "0.10.12.2"
- }
- },
- "Avalonia.Xaml.Interactions": {
- "type": "Direct",
- "requested": "[0.10.12.2, )",
- "resolved": "0.10.12.2",
- "contentHash": "01NGXHMbvpg1JcZ4tFAZXD6i55vHIQnJl3+HFi7RSP1jevkjkSaVM8qjwLsTSfREsJ2OoiWxx2LcyUQJvO5Kjw==",
- "dependencies": {
- "Avalonia": "0.10.12",
- "Avalonia.Xaml.Interactivity": "0.10.12.2"
- }
- },
- "Avalonia.Xaml.Interactivity": {
- "type": "Direct",
- "requested": "[0.10.12.2, )",
- "resolved": "0.10.12.2",
- "contentHash": "AGAbT1I6XW1+9tweLHDMGX8+SijE111vNNIQy2gI3bpbLfPYTirLPyK0do2s9V6l7hHfQnNmiX2NA6JHC4WG4Q==",
- "dependencies": {
- "Avalonia": "0.10.12"
+ "Avalonia": "0.10.13",
+ "Avalonia.Xaml.Interactions": "0.10.13.2",
+ "Avalonia.Xaml.Interactivity": "0.10.13.2"
}
},
"DynamicData": {
@@ -284,6 +265,23 @@
"Avalonia.Skia": "0.10.13"
}
},
+ "Avalonia.Xaml.Interactions": {
+ "type": "Transitive",
+ "resolved": "0.10.13.2",
+ "contentHash": "bMgr5NtEjJ/qvf+1JD4T4rRt9AbZNnJdYCx5cBfGyXHETbeliTJAt07mqTahcoPY1G2FskF1OSIW5ytljbviLw==",
+ "dependencies": {
+ "Avalonia": "0.10.13",
+ "Avalonia.Xaml.Interactivity": "0.10.13.2"
+ }
+ },
+ "Avalonia.Xaml.Interactivity": {
+ "type": "Transitive",
+ "resolved": "0.10.13.2",
+ "contentHash": "OIjK5XCsUrBCqog8lxI/DEbubaNQRwy8e8Px4i3dvllomU28EYsJm4XtrPVakY7MC+we825uXY47tsO/benLug==",
+ "dependencies": {
+ "Avalonia": "0.10.13"
+ }
+ },
"Castle.Core": {
"type": "Transitive",
"resolved": "4.2.0",
@@ -1788,6 +1786,7 @@
"Avalonia": "0.10.13",
"Avalonia.ReactiveUI": "0.10.13",
"Avalonia.Svg.Skia": "0.10.12",
+ "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0",
"Material.Icons.Avalonia": "1.0.2",
@@ -1804,6 +1803,7 @@
"Artemis.UI.Shared": "1.0.0",
"Avalonia": "0.10.13",
"Avalonia.ReactiveUI": "0.10.13",
+ "Avalonia.Xaml.Behaviors": "0.10.13.2",
"Ninject": "3.3.4",
"NoStringEvaluating": "2.2.2",
"ReactiveUI": "17.1.50",
diff --git a/src/Avalonia/Artemis.VisualScripting/Artemis.VisualScripting.csproj b/src/Avalonia/Artemis.VisualScripting/Artemis.VisualScripting.csproj
index a4c935bf5..31b30959d 100644
--- a/src/Avalonia/Artemis.VisualScripting/Artemis.VisualScripting.csproj
+++ b/src/Avalonia/Artemis.VisualScripting/Artemis.VisualScripting.csproj
@@ -21,6 +21,7 @@
+
diff --git a/src/Avalonia/Artemis.VisualScripting/Converters/NumericConverter.cs b/src/Avalonia/Artemis.VisualScripting/Converters/NumericConverter.cs
index 4673f2912..320250b7e 100644
--- a/src/Avalonia/Artemis.VisualScripting/Converters/NumericConverter.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Converters/NumericConverter.cs
@@ -12,18 +12,15 @@ public class NumericConverter : IValueConverter
///
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
- if (targetType == typeof(Numeric))
- return new Numeric(value);
+ if (value is not Numeric numeric)
+ return value;
- return value;
+ return Numeric.IsTypeCompatible(targetType) ? numeric.ToType(targetType, NumberFormatInfo.InvariantInfo) : value;
}
///
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
- if (targetType == typeof(Numeric))
- return new Numeric(value);
-
- return value;
+ return new Numeric(value);
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs
index 256681acb..312f9bf2a 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViewModels/StaticValueNodeViewModels.cs
@@ -1,18 +1,47 @@
using Artemis.Core;
+using Artemis.UI.Shared.Services.NodeEditor;
+using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Artemis.UI.Shared.VisualScripting;
+using ReactiveUI;
namespace Artemis.VisualScripting.Nodes.CustomViewModels;
public class StaticNumericValueNodeCustomViewModel : CustomNodeViewModel
{
- public StaticNumericValueNodeCustomViewModel(INode node, INodeScript script) : base(node, script)
+ private readonly StaticNumericValueNode _node;
+ private readonly INodeEditorService _nodeEditorService;
+
+ public StaticNumericValueNodeCustomViewModel(StaticNumericValueNode node, INodeScript script, INodeEditorService nodeEditorService) : base(node, script)
{
+ _node = node;
+ _nodeEditorService = nodeEditorService;
+
+ NodeModified += (_, _) => this.RaisePropertyChanged(nameof(CurrentValue));
+ }
+
+ public Numeric? CurrentValue
+ {
+ get => _node.Storage;
+ set => _nodeEditorService.ExecuteCommand(Script, new UpdateStorage(_node, value ?? new Numeric()));
}
}
public class StaticStringValueNodeCustomViewModel : CustomNodeViewModel
{
- public StaticStringValueNodeCustomViewModel(INode node, INodeScript script) : base(node, script)
+ private readonly StaticStringValueNode _node;
+ private readonly INodeEditorService _nodeEditorService;
+
+ public StaticStringValueNodeCustomViewModel(StaticStringValueNode node, INodeScript script, INodeEditorService nodeEditorService) : base(node, script)
{
+ _node = node;
+ _nodeEditorService = nodeEditorService;
+
+ NodeModified += (_, _) => this.RaisePropertyChanged(nameof(CurrentValue));
+ }
+
+ public string? CurrentValue
+ {
+ get => _node.Storage;
+ set => _nodeEditorService.ExecuteCommand(Script, new UpdateStorage(_node, value));
}
}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticNumericValueNodeCustomView.axaml b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticNumericValueNodeCustomView.axaml
index f4ef79c1a..5ea33c945 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticNumericValueNodeCustomView.axaml
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticNumericValueNodeCustomView.axaml
@@ -5,15 +5,16 @@
xmlns:customViewModels="clr-namespace:Artemis.VisualScripting.Nodes.CustomViewModels"
xmlns:converters="clr-namespace:Artemis.VisualScripting.Converters"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
+ xmlns:behaviors="clr-namespace:Artemis.UI.Shared.Behaviors;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.VisualScripting.Nodes.CustomViews.StaticNumericValueNodeCustomView"
x:DataType="customViewModels:StaticNumericValueNodeCustomViewModel">
-
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticNumericValueNodeCustomView.axaml.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticNumericValueNodeCustomView.axaml.cs
index ea3efe812..118464c0f 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticNumericValueNodeCustomView.axaml.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticNumericValueNodeCustomView.axaml.cs
@@ -1,9 +1,11 @@
+using Artemis.VisualScripting.Nodes.CustomViewModels;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
namespace Artemis.VisualScripting.Nodes.CustomViews
{
- public partial class StaticNumericValueNodeCustomView : UserControl
+ public partial class StaticNumericValueNodeCustomView : ReactiveUserControl
{
public StaticNumericValueNodeCustomView()
{
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticStringValueNodeCustomView.axaml b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticStringValueNodeCustomView.axaml
index 966921331..8748f8b6a 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticStringValueNodeCustomView.axaml
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticStringValueNodeCustomView.axaml
@@ -3,8 +3,13 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:customViewModels="clr-namespace:Artemis.VisualScripting.Nodes.CustomViewModels"
+ xmlns:behaviors="clr-namespace:Artemis.UI.Shared.Behaviors;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.VisualScripting.Nodes.CustomViews.StaticStringValueNodeCustomView"
x:DataType="customViewModels:StaticStringValueNodeCustomViewModel">
-
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticStringValueNodeCustomView.axaml.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticStringValueNodeCustomView.axaml.cs
index c619b4b29..c53bc453c 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticStringValueNodeCustomView.axaml.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/CustomViews/StaticStringValueNodeCustomView.axaml.cs
@@ -1,9 +1,11 @@
+using Artemis.VisualScripting.Nodes.CustomViewModels;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
namespace Artemis.VisualScripting.Nodes.CustomViews
{
- public partial class StaticStringValueNodeCustomView : UserControl
+ public partial class StaticStringValueNodeCustomView : ReactiveUserControl
{
public StaticStringValueNodeCustomView()
{
diff --git a/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViews/EasingTypeNodeCustomView.axaml.cs b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViews/EasingTypeNodeCustomView.axaml.cs
index 7a699a2a5..7a038e3ce 100644
--- a/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViews/EasingTypeNodeCustomView.axaml.cs
+++ b/src/Avalonia/Artemis.VisualScripting/Nodes/Easing/CustomViews/EasingTypeNodeCustomView.axaml.cs
@@ -1,10 +1,12 @@
+using Artemis.VisualScripting.Nodes.Easing.CustomViewModels;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
namespace Artemis.VisualScripting.Nodes.Easing.CustomViews
{
- public partial class EasingTypeNodeCustomView : UserControl
+ public partial class EasingTypeNodeCustomView : ReactiveUserControl
{
public EasingTypeNodeCustomView()
{
diff --git a/src/Avalonia/Artemis.VisualScripting/packages.lock.json b/src/Avalonia/Artemis.VisualScripting/packages.lock.json
index 39d2fbeb4..beb46fa29 100644
--- a/src/Avalonia/Artemis.VisualScripting/packages.lock.json
+++ b/src/Avalonia/Artemis.VisualScripting/packages.lock.json
@@ -28,6 +28,17 @@
"System.Reactive": "5.0.0"
}
},
+ "Avalonia.Xaml.Behaviors": {
+ "type": "Direct",
+ "requested": "[0.10.13.2, )",
+ "resolved": "0.10.13.2",
+ "contentHash": "sZlq6FFzNNzYmHK+vARWFpxtDY4XUdnU6q6zVIm4l1iQ3/ZXor4SeUnYDdd3lFZtoJ9yc8K2g4X7d/lVEgV9tA==",
+ "dependencies": {
+ "Avalonia": "0.10.13",
+ "Avalonia.Xaml.Interactions": "0.10.13.2",
+ "Avalonia.Xaml.Interactivity": "0.10.13.2"
+ }
+ },
"Ninject": {
"type": "Direct",
"requested": "[3.3.4, )",
@@ -175,6 +186,23 @@
"Avalonia.Skia": "0.10.13"
}
},
+ "Avalonia.Xaml.Interactions": {
+ "type": "Transitive",
+ "resolved": "0.10.13.2",
+ "contentHash": "bMgr5NtEjJ/qvf+1JD4T4rRt9AbZNnJdYCx5cBfGyXHETbeliTJAt07mqTahcoPY1G2FskF1OSIW5ytljbviLw==",
+ "dependencies": {
+ "Avalonia": "0.10.13",
+ "Avalonia.Xaml.Interactivity": "0.10.13.2"
+ }
+ },
+ "Avalonia.Xaml.Interactivity": {
+ "type": "Transitive",
+ "resolved": "0.10.13.2",
+ "contentHash": "OIjK5XCsUrBCqog8lxI/DEbubaNQRwy8e8Px4i3dvllomU28EYsJm4XtrPVakY7MC+we825uXY47tsO/benLug==",
+ "dependencies": {
+ "Avalonia": "0.10.13"
+ }
+ },
"Castle.Core": {
"type": "Transitive",
"resolved": "4.2.0",
@@ -1707,6 +1735,7 @@
"Avalonia": "0.10.13",
"Avalonia.ReactiveUI": "0.10.13",
"Avalonia.Svg.Skia": "0.10.12",
+ "Avalonia.Xaml.Behaviors": "0.10.13.2",
"DynamicData": "7.5.4",
"FluentAvaloniaUI": "1.3.0",
"Material.Icons.Avalonia": "1.0.2",