mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Node editor - Added node creation by dropping cables in empty space
Node editor - Added undo/redo to static value nodes UI - Throw exception when binding a non-reactive view to an activatable view model
This commit is contained in:
parent
06ab2c5bb6
commit
2ae1f5f56c
@ -192,6 +192,52 @@ namespace Artemis.Core
|
|||||||
return enumerableType?.GenericTypeArguments[0];
|
return enumerableType?.GenericTypeArguments[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the <paramref name="typeToCheck"></paramref> is of a certain <paramref name="genericType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="typeToCheck">The type to check.</param>
|
||||||
|
/// <param name="genericType">The generic type it should be or implement</param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines a display name for the given type
|
/// Determines a display name for the given type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -1,83 +1,117 @@
|
|||||||
using System;
|
using System;
|
||||||
using Artemis.Storage.Entities.Profile.Nodes;
|
using Artemis.Storage.Entities.Profile.Nodes;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents node data describing a certain <see cref="INode" />
|
||||||
|
/// </summary>
|
||||||
|
public class NodeData
|
||||||
{
|
{
|
||||||
/// <summary>
|
#region Constructors
|
||||||
/// Represents node data describing a certain <see cref="INode" />
|
|
||||||
/// </summary>
|
internal NodeData(Plugin plugin, Type type, string name, string description, string category, Type? inputType, Type? outputType, Func<INodeScript, NodeEntity?, INode> create)
|
||||||
public class NodeData
|
|
||||||
{
|
{
|
||||||
#region Constructors
|
Plugin = plugin;
|
||||||
|
Type = type;
|
||||||
internal NodeData(Plugin plugin, Type type, string name, string description, string category, Type? inputType, Type? outputType, Func<INodeScript, NodeEntity?, INode> create)
|
Name = name;
|
||||||
{
|
Description = description;
|
||||||
Plugin = plugin;
|
Category = category;
|
||||||
Type = type;
|
InputType = inputType;
|
||||||
Name = name;
|
OutputType = outputType;
|
||||||
Description = description;
|
_create = create;
|
||||||
Category = category;
|
|
||||||
InputType = inputType;
|
|
||||||
OutputType = outputType;
|
|
||||||
_create = create;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Methods
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the node this data represents
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="script">The script to create the node for</param>
|
|
||||||
/// <param name="entity">An optional storage entity to apply to the node</param>
|
|
||||||
/// <returns>The returning node of type <see cref="Type" /></returns>
|
|
||||||
public INode CreateNode(INodeScript script, NodeEntity? entity)
|
|
||||||
{
|
|
||||||
return _create(script, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Properties & Fields
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the plugin that provided this node data
|
|
||||||
/// </summary>
|
|
||||||
public Plugin Plugin { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the type of <see cref="INode" /> this data represents
|
|
||||||
/// </summary>
|
|
||||||
public Type Type { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the node this data represents
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the description of the node this data represents
|
|
||||||
/// </summary>
|
|
||||||
public string Description { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the category of the node this data represents
|
|
||||||
/// </summary>
|
|
||||||
public string Category { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the primary input type of the node this data represents
|
|
||||||
/// </summary>
|
|
||||||
public Type? InputType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the primary output of the node this data represents
|
|
||||||
/// </summary>
|
|
||||||
public Type? OutputType { get; }
|
|
||||||
|
|
||||||
private readonly Func<INodeScript, NodeEntity?, INode> _create;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the node this data represents
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="script">The script to create the node for</param>
|
||||||
|
/// <param name="entity">An optional storage entity to apply to the node</param>
|
||||||
|
/// <returns>The returning node of type <see cref="Type" /></returns>
|
||||||
|
public INode CreateNode(INodeScript script, NodeEntity? entity)
|
||||||
|
{
|
||||||
|
return _create(script, entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the given pin is compatible with this node data's node.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pin">
|
||||||
|
/// The pin to check compatibility with, if <see langword="null" /> then the node data is always
|
||||||
|
/// considered compatible.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if the pin is compatible with this node data's node; otherwise <see langword="false" />.
|
||||||
|
/// </returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the given text matches this node data for a search query.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The text to search for.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if the node matches; otherwise <see langword="false" />.
|
||||||
|
/// </returns>
|
||||||
|
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
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the plugin that provided this node data
|
||||||
|
/// </summary>
|
||||||
|
public Plugin Plugin { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type of <see cref="INode" /> this data represents
|
||||||
|
/// </summary>
|
||||||
|
public Type Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the node this data represents
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the description of the node this data represents
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the category of the node this data represents
|
||||||
|
/// </summary>
|
||||||
|
public string Category { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the primary input type of the node this data represents
|
||||||
|
/// </summary>
|
||||||
|
public Type? InputType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the primary output of the node this data represents
|
||||||
|
/// </summary>
|
||||||
|
public Type? OutputType { get; }
|
||||||
|
|
||||||
|
private readonly Func<INodeScript, NodeEntity?, INode> _create;
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@ -158,7 +158,7 @@ namespace Artemis.VisualScripting.Editor.Controls
|
|||||||
|
|
||||||
if (Node?.Node is Node customViewModelNode)
|
if (Node?.Node is Node customViewModelNode)
|
||||||
{
|
{
|
||||||
CustomViewModel = customViewModelNode.GetCustomViewModel();
|
CustomViewModel = customViewModelNode.GetCustomViewModel((NodeScript) Node.Script.Script);
|
||||||
// CustomViewModel?.OnActivate();
|
// CustomViewModel?.OnActivate();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@ -242,6 +242,7 @@
|
|||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=activatable/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Hotkey/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Hotkey/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=luma/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=luma/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=pixmap/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=pixmap/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|||||||
@ -157,29 +157,29 @@
|
|||||||
},
|
},
|
||||||
"Avalonia.Xaml.Behaviors": {
|
"Avalonia.Xaml.Behaviors": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "0.10.12.2",
|
"resolved": "0.10.13.2",
|
||||||
"contentHash": "EqfzwstvqQcWnTJnaBvezxKwBSddozXpkFi5WrzVe976zedE+A1NruFgnC19aG7Vvy0mTQdlWFTtbAInv6IQyg==",
|
"contentHash": "sZlq6FFzNNzYmHK+vARWFpxtDY4XUdnU6q6zVIm4l1iQ3/ZXor4SeUnYDdd3lFZtoJ9yc8K2g4X7d/lVEgV9tA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Avalonia": "0.10.12",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.Xaml.Interactions": "0.10.12.2",
|
"Avalonia.Xaml.Interactions": "0.10.13.2",
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.12.2"
|
"Avalonia.Xaml.Interactivity": "0.10.13.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Avalonia.Xaml.Interactions": {
|
"Avalonia.Xaml.Interactions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "0.10.12.2",
|
"resolved": "0.10.13.2",
|
||||||
"contentHash": "01NGXHMbvpg1JcZ4tFAZXD6i55vHIQnJl3+HFi7RSP1jevkjkSaVM8qjwLsTSfREsJ2OoiWxx2LcyUQJvO5Kjw==",
|
"contentHash": "bMgr5NtEjJ/qvf+1JD4T4rRt9AbZNnJdYCx5cBfGyXHETbeliTJAt07mqTahcoPY1G2FskF1OSIW5ytljbviLw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Avalonia": "0.10.12",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.12.2"
|
"Avalonia.Xaml.Interactivity": "0.10.13.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Avalonia.Xaml.Interactivity": {
|
"Avalonia.Xaml.Interactivity": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "0.10.12.2",
|
"resolved": "0.10.13.2",
|
||||||
"contentHash": "AGAbT1I6XW1+9tweLHDMGX8+SijE111vNNIQy2gI3bpbLfPYTirLPyK0do2s9V6l7hHfQnNmiX2NA6JHC4WG4Q==",
|
"contentHash": "OIjK5XCsUrBCqog8lxI/DEbubaNQRwy8e8Px4i3dvllomU28EYsJm4XtrPVakY7MC+we825uXY47tsO/benLug==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Avalonia": "0.10.12"
|
"Avalonia": "0.10.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Castle.Core": {
|
"Castle.Core": {
|
||||||
@ -1778,9 +1778,7 @@
|
|||||||
"Avalonia.Diagnostics": "0.10.13",
|
"Avalonia.Diagnostics": "0.10.13",
|
||||||
"Avalonia.ReactiveUI": "0.10.13",
|
"Avalonia.ReactiveUI": "0.10.13",
|
||||||
"Avalonia.Svg.Skia": "0.10.12",
|
"Avalonia.Svg.Skia": "0.10.12",
|
||||||
"Avalonia.Xaml.Behaviors": "0.10.12.2",
|
"Avalonia.Xaml.Behaviors": "0.10.13.2",
|
||||||
"Avalonia.Xaml.Interactions": "0.10.12.2",
|
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.12.2",
|
|
||||||
"DynamicData": "7.5.4",
|
"DynamicData": "7.5.4",
|
||||||
"FluentAvaloniaUI": "1.3.0",
|
"FluentAvaloniaUI": "1.3.0",
|
||||||
"Flurl.Http": "3.2.0",
|
"Flurl.Http": "3.2.0",
|
||||||
@ -1801,6 +1799,7 @@
|
|||||||
"Avalonia": "0.10.13",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.ReactiveUI": "0.10.13",
|
"Avalonia.ReactiveUI": "0.10.13",
|
||||||
"Avalonia.Svg.Skia": "0.10.12",
|
"Avalonia.Svg.Skia": "0.10.12",
|
||||||
|
"Avalonia.Xaml.Behaviors": "0.10.13.2",
|
||||||
"DynamicData": "7.5.4",
|
"DynamicData": "7.5.4",
|
||||||
"FluentAvaloniaUI": "1.3.0",
|
"FluentAvaloniaUI": "1.3.0",
|
||||||
"Material.Icons.Avalonia": "1.0.2",
|
"Material.Icons.Avalonia": "1.0.2",
|
||||||
@ -1817,6 +1816,7 @@
|
|||||||
"Artemis.UI.Shared": "1.0.0",
|
"Artemis.UI.Shared": "1.0.0",
|
||||||
"Avalonia": "0.10.13",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.ReactiveUI": "0.10.13",
|
"Avalonia.ReactiveUI": "0.10.13",
|
||||||
|
"Avalonia.Xaml.Behaviors": "0.10.13.2",
|
||||||
"Ninject": "3.3.4",
|
"Ninject": "3.3.4",
|
||||||
"NoStringEvaluating": "2.2.2",
|
"NoStringEvaluating": "2.2.2",
|
||||||
"ReactiveUI": "17.1.50",
|
"ReactiveUI": "17.1.50",
|
||||||
|
|||||||
@ -157,29 +157,29 @@
|
|||||||
},
|
},
|
||||||
"Avalonia.Xaml.Behaviors": {
|
"Avalonia.Xaml.Behaviors": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "0.10.12.2",
|
"resolved": "0.10.13.2",
|
||||||
"contentHash": "EqfzwstvqQcWnTJnaBvezxKwBSddozXpkFi5WrzVe976zedE+A1NruFgnC19aG7Vvy0mTQdlWFTtbAInv6IQyg==",
|
"contentHash": "sZlq6FFzNNzYmHK+vARWFpxtDY4XUdnU6q6zVIm4l1iQ3/ZXor4SeUnYDdd3lFZtoJ9yc8K2g4X7d/lVEgV9tA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Avalonia": "0.10.12",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.Xaml.Interactions": "0.10.12.2",
|
"Avalonia.Xaml.Interactions": "0.10.13.2",
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.12.2"
|
"Avalonia.Xaml.Interactivity": "0.10.13.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Avalonia.Xaml.Interactions": {
|
"Avalonia.Xaml.Interactions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "0.10.12.2",
|
"resolved": "0.10.13.2",
|
||||||
"contentHash": "01NGXHMbvpg1JcZ4tFAZXD6i55vHIQnJl3+HFi7RSP1jevkjkSaVM8qjwLsTSfREsJ2OoiWxx2LcyUQJvO5Kjw==",
|
"contentHash": "bMgr5NtEjJ/qvf+1JD4T4rRt9AbZNnJdYCx5cBfGyXHETbeliTJAt07mqTahcoPY1G2FskF1OSIW5ytljbviLw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Avalonia": "0.10.12",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.12.2"
|
"Avalonia.Xaml.Interactivity": "0.10.13.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Avalonia.Xaml.Interactivity": {
|
"Avalonia.Xaml.Interactivity": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "0.10.12.2",
|
"resolved": "0.10.13.2",
|
||||||
"contentHash": "AGAbT1I6XW1+9tweLHDMGX8+SijE111vNNIQy2gI3bpbLfPYTirLPyK0do2s9V6l7hHfQnNmiX2NA6JHC4WG4Q==",
|
"contentHash": "OIjK5XCsUrBCqog8lxI/DEbubaNQRwy8e8Px4i3dvllomU28EYsJm4XtrPVakY7MC+we825uXY47tsO/benLug==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Avalonia": "0.10.12"
|
"Avalonia": "0.10.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Castle.Core": {
|
"Castle.Core": {
|
||||||
@ -1778,9 +1778,7 @@
|
|||||||
"Avalonia.Diagnostics": "0.10.13",
|
"Avalonia.Diagnostics": "0.10.13",
|
||||||
"Avalonia.ReactiveUI": "0.10.13",
|
"Avalonia.ReactiveUI": "0.10.13",
|
||||||
"Avalonia.Svg.Skia": "0.10.12",
|
"Avalonia.Svg.Skia": "0.10.12",
|
||||||
"Avalonia.Xaml.Behaviors": "0.10.12.2",
|
"Avalonia.Xaml.Behaviors": "0.10.13.2",
|
||||||
"Avalonia.Xaml.Interactions": "0.10.12.2",
|
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.12.2",
|
|
||||||
"DynamicData": "7.5.4",
|
"DynamicData": "7.5.4",
|
||||||
"FluentAvaloniaUI": "1.3.0",
|
"FluentAvaloniaUI": "1.3.0",
|
||||||
"Flurl.Http": "3.2.0",
|
"Flurl.Http": "3.2.0",
|
||||||
@ -1801,6 +1799,7 @@
|
|||||||
"Avalonia": "0.10.13",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.ReactiveUI": "0.10.13",
|
"Avalonia.ReactiveUI": "0.10.13",
|
||||||
"Avalonia.Svg.Skia": "0.10.12",
|
"Avalonia.Svg.Skia": "0.10.12",
|
||||||
|
"Avalonia.Xaml.Behaviors": "0.10.13.2",
|
||||||
"DynamicData": "7.5.4",
|
"DynamicData": "7.5.4",
|
||||||
"FluentAvaloniaUI": "1.3.0",
|
"FluentAvaloniaUI": "1.3.0",
|
||||||
"Material.Icons.Avalonia": "1.0.2",
|
"Material.Icons.Avalonia": "1.0.2",
|
||||||
@ -1817,6 +1816,7 @@
|
|||||||
"Artemis.UI.Shared": "1.0.0",
|
"Artemis.UI.Shared": "1.0.0",
|
||||||
"Avalonia": "0.10.13",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.ReactiveUI": "0.10.13",
|
"Avalonia.ReactiveUI": "0.10.13",
|
||||||
|
"Avalonia.Xaml.Behaviors": "0.10.13.2",
|
||||||
"Ninject": "3.3.4",
|
"Ninject": "3.3.4",
|
||||||
"NoStringEvaluating": "2.2.2",
|
"NoStringEvaluating": "2.2.2",
|
||||||
"ReactiveUI": "17.1.50",
|
"ReactiveUI": "17.1.50",
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
<PackageReference Include="Avalonia" Version="0.10.13" />
|
<PackageReference Include="Avalonia" Version="0.10.13" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
|
||||||
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" />
|
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" />
|
||||||
|
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.13.2" />
|
||||||
<PackageReference Include="DynamicData" Version="7.5.4" />
|
<PackageReference Include="DynamicData" Version="7.5.4" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" Version="1.3.0" />
|
<PackageReference Include="FluentAvaloniaUI" Version="1.3.0" />
|
||||||
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
|
<PackageReference Include="Material.Icons.Avalonia" Version="1.0.2" />
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a behavior that can be used to make a text box only update it's binding on focus loss.
|
||||||
|
/// </summary>
|
||||||
|
public class LostFocusNumberBoxBindingBehavior : Behavior<NumberBox>
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<double> ValueProperty = AvaloniaProperty.Register<LostFocusTextBoxBindingBehavior, double>(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnAttached()
|
||||||
|
{
|
||||||
|
if (AssociatedObject != null)
|
||||||
|
AssociatedObject.LostFocus += OnLostFocus;
|
||||||
|
base.OnAttached();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a behavior that can be used to make a text box only update it's binding on focus loss.
|
||||||
|
/// </summary>
|
||||||
|
public class LostFocusTextBoxBindingBehavior : Behavior<TextBox>
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<LostFocusTextBoxBindingBehavior, string>(
|
||||||
|
"Text", defaultBindingMode: BindingMode.TwoWay);
|
||||||
|
|
||||||
|
static LostFocusTextBoxBindingBehavior()
|
||||||
|
{
|
||||||
|
TextProperty.Changed.Subscribe(e => ((LostFocusTextBoxBindingBehavior) e.Sender).OnBindingValueChanged());
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get => GetValue(TextProperty);
|
||||||
|
set => SetValue(TextProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnAttached()
|
||||||
|
{
|
||||||
|
if (AssociatedObject != null)
|
||||||
|
AssociatedObject.LostFocus += OnLostFocus;
|
||||||
|
base.OnAttached();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,10 +13,8 @@ using Avalonia;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Data;
|
using Avalonia.Data;
|
||||||
using Material.Icons;
|
|
||||||
using Material.Icons.Avalonia;
|
using Material.Icons.Avalonia;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Controls.DataModelPicker;
|
namespace Artemis.UI.Shared.Controls.DataModelPicker;
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,17 @@
|
|||||||
"Svg.Skia": "0.5.12"
|
"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": {
|
"DynamicData": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[7.5.4, )",
|
"requested": "[7.5.4, )",
|
||||||
@ -204,6 +215,23 @@
|
|||||||
"Avalonia.Skia": "0.10.13"
|
"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": {
|
"Castle.Core": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "4.2.0",
|
"resolved": "4.2.0",
|
||||||
|
|||||||
@ -173,29 +173,29 @@
|
|||||||
},
|
},
|
||||||
"Avalonia.Xaml.Behaviors": {
|
"Avalonia.Xaml.Behaviors": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "0.10.12.2",
|
"resolved": "0.10.13.2",
|
||||||
"contentHash": "EqfzwstvqQcWnTJnaBvezxKwBSddozXpkFi5WrzVe976zedE+A1NruFgnC19aG7Vvy0mTQdlWFTtbAInv6IQyg==",
|
"contentHash": "sZlq6FFzNNzYmHK+vARWFpxtDY4XUdnU6q6zVIm4l1iQ3/ZXor4SeUnYDdd3lFZtoJ9yc8K2g4X7d/lVEgV9tA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Avalonia": "0.10.12",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.Xaml.Interactions": "0.10.12.2",
|
"Avalonia.Xaml.Interactions": "0.10.13.2",
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.12.2"
|
"Avalonia.Xaml.Interactivity": "0.10.13.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Avalonia.Xaml.Interactions": {
|
"Avalonia.Xaml.Interactions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "0.10.12.2",
|
"resolved": "0.10.13.2",
|
||||||
"contentHash": "01NGXHMbvpg1JcZ4tFAZXD6i55vHIQnJl3+HFi7RSP1jevkjkSaVM8qjwLsTSfREsJ2OoiWxx2LcyUQJvO5Kjw==",
|
"contentHash": "bMgr5NtEjJ/qvf+1JD4T4rRt9AbZNnJdYCx5cBfGyXHETbeliTJAt07mqTahcoPY1G2FskF1OSIW5ytljbviLw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Avalonia": "0.10.12",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.12.2"
|
"Avalonia.Xaml.Interactivity": "0.10.13.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Avalonia.Xaml.Interactivity": {
|
"Avalonia.Xaml.Interactivity": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "0.10.12.2",
|
"resolved": "0.10.13.2",
|
||||||
"contentHash": "AGAbT1I6XW1+9tweLHDMGX8+SijE111vNNIQy2gI3bpbLfPYTirLPyK0do2s9V6l7hHfQnNmiX2NA6JHC4WG4Q==",
|
"contentHash": "OIjK5XCsUrBCqog8lxI/DEbubaNQRwy8e8Px4i3dvllomU28EYsJm4XtrPVakY7MC+we825uXY47tsO/benLug==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Avalonia": "0.10.12"
|
"Avalonia": "0.10.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Castle.Core": {
|
"Castle.Core": {
|
||||||
@ -1794,9 +1794,7 @@
|
|||||||
"Avalonia.Diagnostics": "0.10.13",
|
"Avalonia.Diagnostics": "0.10.13",
|
||||||
"Avalonia.ReactiveUI": "0.10.13",
|
"Avalonia.ReactiveUI": "0.10.13",
|
||||||
"Avalonia.Svg.Skia": "0.10.12",
|
"Avalonia.Svg.Skia": "0.10.12",
|
||||||
"Avalonia.Xaml.Behaviors": "0.10.12.2",
|
"Avalonia.Xaml.Behaviors": "0.10.13.2",
|
||||||
"Avalonia.Xaml.Interactions": "0.10.12.2",
|
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.12.2",
|
|
||||||
"DynamicData": "7.5.4",
|
"DynamicData": "7.5.4",
|
||||||
"FluentAvaloniaUI": "1.3.0",
|
"FluentAvaloniaUI": "1.3.0",
|
||||||
"Flurl.Http": "3.2.0",
|
"Flurl.Http": "3.2.0",
|
||||||
@ -1817,6 +1815,7 @@
|
|||||||
"Avalonia": "0.10.13",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.ReactiveUI": "0.10.13",
|
"Avalonia.ReactiveUI": "0.10.13",
|
||||||
"Avalonia.Svg.Skia": "0.10.12",
|
"Avalonia.Svg.Skia": "0.10.12",
|
||||||
|
"Avalonia.Xaml.Behaviors": "0.10.13.2",
|
||||||
"DynamicData": "7.5.4",
|
"DynamicData": "7.5.4",
|
||||||
"FluentAvaloniaUI": "1.3.0",
|
"FluentAvaloniaUI": "1.3.0",
|
||||||
"Material.Icons.Avalonia": "1.0.2",
|
"Material.Icons.Avalonia": "1.0.2",
|
||||||
@ -1833,6 +1832,7 @@
|
|||||||
"Artemis.UI.Shared": "1.0.0",
|
"Artemis.UI.Shared": "1.0.0",
|
||||||
"Avalonia": "0.10.13",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.ReactiveUI": "0.10.13",
|
"Avalonia.ReactiveUI": "0.10.13",
|
||||||
|
"Avalonia.Xaml.Behaviors": "0.10.13.2",
|
||||||
"Ninject": "3.3.4",
|
"Ninject": "3.3.4",
|
||||||
"NoStringEvaluating": "2.2.2",
|
"NoStringEvaluating": "2.2.2",
|
||||||
"ReactiveUI": "17.1.50",
|
"ReactiveUI": "17.1.50",
|
||||||
|
|||||||
@ -21,9 +21,7 @@
|
|||||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.13" />
|
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.13" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
|
||||||
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" />
|
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.12" />
|
||||||
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.12.2" />
|
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.13.2" />
|
||||||
<PackageReference Include="Avalonia.Xaml.Interactions" Version="0.10.12.2" />
|
|
||||||
<PackageReference Include="Avalonia.Xaml.Interactivity" Version="0.10.12.2" />
|
|
||||||
<PackageReference Include="DynamicData" Version="7.5.4" />
|
<PackageReference Include="DynamicData" Version="7.5.4" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" Version="1.3.0" />
|
<PackageReference Include="FluentAvaloniaUI" Version="1.3.0" />
|
||||||
<PackageReference Include="Flurl.Http" Version="3.2.0" />
|
<PackageReference Include="Flurl.Http" Version="3.2.0" />
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Sidebar
|
namespace Artemis.UI.Screens.Sidebar
|
||||||
{
|
{
|
||||||
public class SidebarProfileConfigurationView : UserControl
|
public class SidebarProfileConfigurationView : ReactiveUserControl<SidebarProfileConfigurationViewModel>
|
||||||
{
|
{
|
||||||
public SidebarProfileConfigurationView()
|
public SidebarProfileConfigurationView()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -30,24 +30,33 @@
|
|||||||
</Template>
|
</Template>
|
||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TreeView#NodeTree">
|
||||||
|
|
||||||
|
</Style>
|
||||||
</UserControl.Styles>
|
</UserControl.Styles>
|
||||||
<Border Classes="picker-container">
|
<Border Classes="picker-container">
|
||||||
<Grid RowDefinitions="Auto,*">
|
<Grid RowDefinitions="Auto,*">
|
||||||
<TextBox Name="SearchBox" Text="{CompiledBinding SearchText}" Margin="0 0 0 15"></TextBox>
|
<TextBox Name="SearchBox" Text="{CompiledBinding SearchText}" Margin="0 0 0 15" Watermark="Search"></TextBox>
|
||||||
<ListBox Grid.Row="1"
|
<TreeView Name="NodeTree" Grid.Row="1" Items="{CompiledBinding Categories}" IsVisible="{CompiledBinding Categories.Count}" SelectedItem="{CompiledBinding SelectedNode}">
|
||||||
Items="{CompiledBinding Nodes}"
|
<TreeView.Styles>
|
||||||
SelectedItem="{CompiledBinding SelectedNode}"
|
<Style Selector="TreeViewItem">
|
||||||
IsVisible="{CompiledBinding Nodes.Count}">
|
<Setter Property="IsExpanded" Value="True" />
|
||||||
<ListBox.ItemTemplate>
|
</Style>
|
||||||
<DataTemplate DataType="core:NodeData">
|
</TreeView.Styles>
|
||||||
<StackPanel>
|
<TreeView.DataTemplates>
|
||||||
<TextBlock Text="{CompiledBinding Name}" FontWeight="Bold"></TextBlock>
|
<TreeDataTemplate DataType="{x:Type core:NodeData}">
|
||||||
<TextBlock Text="{CompiledBinding Description}"></TextBlock>
|
<StackPanel Margin="-15 1 0 1">
|
||||||
|
<TextBlock Classes="BodyStrongTextBlockStyle" Text="{Binding Name}"></TextBlock>
|
||||||
|
<TextBlock Foreground="{DynamicResource TextFillColorSecondary}" Text="{Binding Description}"></TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</TreeDataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
<TreeDataTemplate ItemsSource="{Binding Items}">
|
||||||
</ListBox>
|
<TextBlock Text="{Binding Key}"></TextBlock>
|
||||||
<StackPanel Grid.Row="1" VerticalAlignment="Center" Spacing="20" IsVisible="{CompiledBinding !Nodes.Count}">
|
</TreeDataTemplate>
|
||||||
|
</TreeView.DataTemplates>
|
||||||
|
</TreeView>
|
||||||
|
<StackPanel Grid.Row="1" VerticalAlignment="Center" Spacing="20" IsVisible="{CompiledBinding !Categories.Count}">
|
||||||
<avalonia:MaterialIcon Kind="CloseCircle" Width="64" Height="64"></avalonia:MaterialIcon>
|
<avalonia:MaterialIcon Kind="CloseCircle" Width="64" Height="64"></avalonia:MaterialIcon>
|
||||||
<TextBlock Classes="h4" TextAlignment="Center">None of the nodes match your search</TextBlock>
|
<TextBlock Classes="h4" TextAlignment="Center">None of the nodes match your search</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@ -1,33 +1,28 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Mixins;
|
using Avalonia.Controls.Mixins;
|
||||||
using Avalonia.LogicalTree;
|
using Avalonia.LogicalTree;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using Avalonia.VisualTree;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting
|
namespace Artemis.UI.Screens.VisualScripting;
|
||||||
{
|
|
||||||
public partial class NodePickerView : ReactiveUserControl<NodePickerViewModel>
|
|
||||||
{
|
|
||||||
public NodePickerView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
this.WhenActivated(
|
|
||||||
d => ViewModel
|
|
||||||
.WhenAnyValue(vm => vm.IsVisible)
|
|
||||||
.Where(visible => !visible)
|
|
||||||
.Subscribe(_ => this.FindLogicalAncestorOfType<Grid>()?.ContextFlyout?.Hide())
|
|
||||||
.DisposeWith(d)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
public class NodePickerView : ReactiveUserControl<NodePickerViewModel>
|
||||||
|
{
|
||||||
|
public NodePickerView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
ViewModel?.WhenAnyValue(vm => vm.IsVisible).Where(visible => !visible).Subscribe(_ => this.FindLogicalAncestorOfType<Grid>()?.ContextFlyout?.Hide()).DisposeWith(d);
|
||||||
}
|
this.Get<TextBox>("SearchBox").SelectAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
using System.Reactive.Disposables;
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
@ -8,6 +9,7 @@ using Artemis.UI.Shared;
|
|||||||
using Artemis.UI.Shared.Services.NodeEditor;
|
using Artemis.UI.Shared.Services.NodeEditor;
|
||||||
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using DynamicData;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting;
|
namespace Artemis.UI.Screens.VisualScripting;
|
||||||
@ -16,36 +18,63 @@ public class NodePickerViewModel : ActivatableViewModelBase
|
|||||||
{
|
{
|
||||||
private readonly INodeEditorService _nodeEditorService;
|
private readonly INodeEditorService _nodeEditorService;
|
||||||
private readonly NodeScript _nodeScript;
|
private readonly NodeScript _nodeScript;
|
||||||
private readonly INodeService _nodeService;
|
|
||||||
|
|
||||||
private bool _isVisible;
|
private bool _isVisible;
|
||||||
private Point _position;
|
private Point _position;
|
||||||
|
private DateTime _closed;
|
||||||
private string? _searchText;
|
private string? _searchText;
|
||||||
private NodeData? _selectedNode;
|
private object? _selectedNode;
|
||||||
|
private IPin? _targetPin;
|
||||||
|
|
||||||
public NodePickerViewModel(NodeScript nodeScript, INodeService nodeService, INodeEditorService nodeEditorService)
|
public NodePickerViewModel(NodeScript nodeScript, INodeService nodeService, INodeEditorService nodeEditorService)
|
||||||
{
|
{
|
||||||
_nodeScript = nodeScript;
|
_nodeScript = nodeScript;
|
||||||
_nodeService = nodeService;
|
|
||||||
_nodeEditorService = nodeEditorService;
|
_nodeEditorService = nodeEditorService;
|
||||||
|
|
||||||
Nodes = new ObservableCollection<NodeData>(_nodeService.AvailableNodes);
|
SourceList<NodeData> nodeSourceList = new();
|
||||||
|
IObservable<Func<NodeData, bool>> 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<DynamicData.List.IGrouping<NodeData, string>> categories)
|
||||||
|
.Subscribe();
|
||||||
|
Categories = categories;
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
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;
|
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 =>
|
this.WhenAnyValue(vm => vm.SelectedNode)
|
||||||
{
|
.WhereNotNull()
|
||||||
CreateNode(data);
|
.Where(o => o is NodeData)
|
||||||
Hide();
|
.Throttle(TimeSpan.FromMilliseconds(200), RxApp.MainThreadScheduler)
|
||||||
SelectedNode = null;
|
.Subscribe(data =>
|
||||||
});
|
{
|
||||||
|
CreateNode((NodeData) data);
|
||||||
|
IsVisible = false;
|
||||||
|
SelectedNode = null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableCollection<NodeData> Nodes { get; }
|
public ReadOnlyObservableCollection<DynamicData.List.IGrouping<NodeData, string>> Categories { get; }
|
||||||
|
|
||||||
public bool IsVisible
|
public bool IsVisible
|
||||||
{
|
{
|
||||||
@ -65,29 +94,47 @@ public class NodePickerViewModel : ActivatableViewModelBase
|
|||||||
set => RaiseAndSetIfChanged(ref _searchText, value);
|
set => RaiseAndSetIfChanged(ref _searchText, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NodeData? SelectedNode
|
public IPin? TargetPin
|
||||||
|
{
|
||||||
|
get => _targetPin;
|
||||||
|
set => RaiseAndSetIfChanged(ref _targetPin, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? SelectedNode
|
||||||
{
|
{
|
||||||
get => _selectedNode;
|
get => _selectedNode;
|
||||||
set => RaiseAndSetIfChanged(ref _selectedNode, value);
|
set => RaiseAndSetIfChanged(ref _selectedNode, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Show(Point position)
|
public void CreateNode(NodeData data)
|
||||||
{
|
|
||||||
IsVisible = true;
|
|
||||||
Position = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Hide()
|
|
||||||
{
|
|
||||||
IsVisible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateNode(NodeData data)
|
|
||||||
{
|
{
|
||||||
INode node = data.CreateNode(_nodeScript, null);
|
INode node = data.CreateNode(_nodeScript, null);
|
||||||
node.X = Position.X;
|
node.X = Math.Round(Position.X / 10d, 0, MidpointRounding.AwayFromZero) * 10d;
|
||||||
node.Y = Position.Y;
|
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<NodeData, bool> CreatePredicate(string? text, IPin? targetPin)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
|
return data => data.IsCompatibleWithPin(targetPin);
|
||||||
|
return data => data.IsCompatibleWithPin(targetPin) && data.MatchesSearch(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Artemis.UI.Shared.Controls;
|
using Artemis.UI.Shared.Controls;
|
||||||
@ -11,6 +12,7 @@ using Avalonia.Interactivity;
|
|||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.VisualScripting;
|
namespace Artemis.UI.Screens.VisualScripting;
|
||||||
|
|
||||||
@ -33,6 +35,11 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
|
|||||||
UpdateZoomBorderBackground();
|
UpdateZoomBorderBackground();
|
||||||
|
|
||||||
_grid.AddHandler(PointerReleasedEvent, CanvasOnPointerReleased, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
|
_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)
|
private void CanvasOnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||||
|
|||||||
@ -3,8 +3,10 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
|
using System.Reactive.Subjects;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.Core.Events;
|
using Artemis.Core.Events;
|
||||||
|
using Artemis.Core.Services;
|
||||||
using Artemis.UI.Ninject.Factories;
|
using Artemis.UI.Ninject.Factories;
|
||||||
using Artemis.UI.Screens.VisualScripting.Pins;
|
using Artemis.UI.Screens.VisualScripting.Pins;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
@ -23,21 +25,27 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
|||||||
{
|
{
|
||||||
private readonly INodeEditorService _nodeEditorService;
|
private readonly INodeEditorService _nodeEditorService;
|
||||||
private readonly INotificationService _notificationService;
|
private readonly INotificationService _notificationService;
|
||||||
private readonly SourceList<NodeViewModel> _nodeViewModels;
|
|
||||||
private readonly INodeVmFactory _nodeVmFactory;
|
private readonly INodeVmFactory _nodeVmFactory;
|
||||||
|
private readonly INodeService _nodeService;
|
||||||
|
private readonly SourceList<NodeViewModel> _nodeViewModels;
|
||||||
|
private readonly Subject<Point> _requestedPickerPositionSubject;
|
||||||
|
|
||||||
private DragCableViewModel? _dragViewModel;
|
private DragCableViewModel? _dragViewModel;
|
||||||
private List<NodeViewModel>? _initialNodeSelection;
|
private List<NodeViewModel>? _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;
|
_nodeVmFactory = nodeVmFactory;
|
||||||
|
_nodeService = nodeService;
|
||||||
_nodeEditorService = nodeEditorService;
|
_nodeEditorService = nodeEditorService;
|
||||||
_notificationService = notificationService;
|
_notificationService = notificationService;
|
||||||
_nodeViewModels = new SourceList<NodeViewModel>();
|
_nodeViewModels = new SourceList<NodeViewModel>();
|
||||||
|
_requestedPickerPositionSubject = new Subject<Point>();
|
||||||
|
|
||||||
NodeScript = nodeScript;
|
NodeScript = nodeScript;
|
||||||
NodePickerViewModel = _nodeVmFactory.NodePickerViewModel(nodeScript);
|
NodePickerViewModel = _nodeVmFactory.NodePickerViewModel(nodeScript);
|
||||||
History = nodeEditorService.GetHistory(NodeScript);
|
History = nodeEditorService.GetHistory(NodeScript);
|
||||||
|
PickerPositionSubject = _requestedPickerPositionSubject.AsObservable();
|
||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
@ -87,6 +95,7 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
|||||||
public ReadOnlyObservableCollection<CableViewModel> CableViewModels { get; }
|
public ReadOnlyObservableCollection<CableViewModel> CableViewModels { get; }
|
||||||
public NodePickerViewModel NodePickerViewModel { get; }
|
public NodePickerViewModel NodePickerViewModel { get; }
|
||||||
public NodeEditorHistory History { get; }
|
public NodeEditorHistory History { get; }
|
||||||
|
public IObservable<Point> PickerPositionSubject { get; }
|
||||||
|
|
||||||
public DragCableViewModel? DragViewModel
|
public DragCableViewModel? DragViewModel
|
||||||
{
|
{
|
||||||
@ -165,7 +174,7 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
|||||||
return targetPinVmModel == null || targetPinVmModel.IsCompatibleWith(sourcePinViewModel);
|
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)
|
if (DragViewModel == null)
|
||||||
return;
|
return;
|
||||||
@ -175,6 +184,24 @@ public class NodeScriptViewModel : ActivatableViewModelBase
|
|||||||
// If dropped on top of a compatible pin, connect to it
|
// If dropped on top of a compatible pin, connect to it
|
||||||
if (targetPinVmModel != null && targetPinVmModel.IsCompatibleWith(sourcePinViewModel))
|
if (targetPinVmModel != null && targetPinVmModel.IsCompatibleWith(sourcePinViewModel))
|
||||||
_nodeEditorService.ExecuteCommand(NodeScript, new ConnectPins(sourcePinViewModel.Pin, targetPinVmModel.Pin));
|
_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<NodeData> 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<INode> eventArgs)
|
private void HandleNodeAdded(SingleValueEventArgs<INode> eventArgs)
|
||||||
|
|||||||
@ -55,7 +55,7 @@ public class PinView : ReactiveUserControl<PinViewModel>
|
|||||||
if (targetPin == ViewModel)
|
if (targetPin == ViewModel)
|
||||||
targetPin = null;
|
targetPin = null;
|
||||||
|
|
||||||
this.FindAncestorOfType<NodeScriptView>()?.ViewModel?.FinishPinDrag(ViewModel, targetPin);
|
this.FindAncestorOfType<NodeScriptView>()?.ViewModel?.FinishPinDrag(ViewModel, targetPin, point.Position);
|
||||||
_pinPoint.Cursor = new Cursor(StandardCursorType.Hand);
|
_pinPoint.Cursor = new Cursor(StandardCursorType.Hand);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,33 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Exceptions;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Templates;
|
using Avalonia.Controls.Templates;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
using 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)
|
// 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.
|
||||||
Type dataType = data.GetType();
|
if (data is IActivatableViewModel && type != null && !type.IsOfGenericType(typeof(ReactiveUserControl<>)))
|
||||||
string name = dataType.FullName!.Split('`')[0].Replace("ViewModel", "View");
|
throw new ArtemisUIException($"The views of activatable view models should inherit ReactiveUserControl<T>, in this case ReactiveUserControl<{data.GetType().Name}>.");
|
||||||
Type? type = dataType.Assembly.GetType(name);
|
|
||||||
|
|
||||||
if (type != null)
|
if (type != null)
|
||||||
return (Control) Activator.CreateInstance(type)!;
|
return (Control) Activator.CreateInstance(type)!;
|
||||||
return new TextBlock {Text = "Not Found: " + name};
|
return new TextBlock {Text = "Not Found: " + name};
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Match(object data)
|
public bool Match(object data)
|
||||||
{
|
{
|
||||||
return data is ReactiveObject;
|
return data is ReactiveObject;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,32 +76,13 @@
|
|||||||
},
|
},
|
||||||
"Avalonia.Xaml.Behaviors": {
|
"Avalonia.Xaml.Behaviors": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[0.10.12.2, )",
|
"requested": "[0.10.13.2, )",
|
||||||
"resolved": "0.10.12.2",
|
"resolved": "0.10.13.2",
|
||||||
"contentHash": "EqfzwstvqQcWnTJnaBvezxKwBSddozXpkFi5WrzVe976zedE+A1NruFgnC19aG7Vvy0mTQdlWFTtbAInv6IQyg==",
|
"contentHash": "sZlq6FFzNNzYmHK+vARWFpxtDY4XUdnU6q6zVIm4l1iQ3/ZXor4SeUnYDdd3lFZtoJ9yc8K2g4X7d/lVEgV9tA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Avalonia": "0.10.12",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.Xaml.Interactions": "0.10.12.2",
|
"Avalonia.Xaml.Interactions": "0.10.13.2",
|
||||||
"Avalonia.Xaml.Interactivity": "0.10.12.2"
|
"Avalonia.Xaml.Interactivity": "0.10.13.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"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"DynamicData": {
|
"DynamicData": {
|
||||||
@ -284,6 +265,23 @@
|
|||||||
"Avalonia.Skia": "0.10.13"
|
"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": {
|
"Castle.Core": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "4.2.0",
|
"resolved": "4.2.0",
|
||||||
@ -1788,6 +1786,7 @@
|
|||||||
"Avalonia": "0.10.13",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.ReactiveUI": "0.10.13",
|
"Avalonia.ReactiveUI": "0.10.13",
|
||||||
"Avalonia.Svg.Skia": "0.10.12",
|
"Avalonia.Svg.Skia": "0.10.12",
|
||||||
|
"Avalonia.Xaml.Behaviors": "0.10.13.2",
|
||||||
"DynamicData": "7.5.4",
|
"DynamicData": "7.5.4",
|
||||||
"FluentAvaloniaUI": "1.3.0",
|
"FluentAvaloniaUI": "1.3.0",
|
||||||
"Material.Icons.Avalonia": "1.0.2",
|
"Material.Icons.Avalonia": "1.0.2",
|
||||||
@ -1804,6 +1803,7 @@
|
|||||||
"Artemis.UI.Shared": "1.0.0",
|
"Artemis.UI.Shared": "1.0.0",
|
||||||
"Avalonia": "0.10.13",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.ReactiveUI": "0.10.13",
|
"Avalonia.ReactiveUI": "0.10.13",
|
||||||
|
"Avalonia.Xaml.Behaviors": "0.10.13.2",
|
||||||
"Ninject": "3.3.4",
|
"Ninject": "3.3.4",
|
||||||
"NoStringEvaluating": "2.2.2",
|
"NoStringEvaluating": "2.2.2",
|
||||||
"ReactiveUI": "17.1.50",
|
"ReactiveUI": "17.1.50",
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="0.10.13" />
|
<PackageReference Include="Avalonia" Version="0.10.13" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
|
||||||
|
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="0.10.13.2" />
|
||||||
<PackageReference Include="Ninject" Version="3.3.4" />
|
<PackageReference Include="Ninject" Version="3.3.4" />
|
||||||
<PackageReference Include="NoStringEvaluating" Version="2.2.2" />
|
<PackageReference Include="NoStringEvaluating" Version="2.2.2" />
|
||||||
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||||
|
|||||||
@ -12,18 +12,15 @@ public class NumericConverter : IValueConverter
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
if (targetType == typeof(Numeric))
|
if (value is not Numeric numeric)
|
||||||
return new Numeric(value);
|
return value;
|
||||||
|
|
||||||
return value;
|
return Numeric.IsTypeCompatible(targetType) ? numeric.ToType(targetType, NumberFormatInfo.InvariantInfo) : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
if (targetType == typeof(Numeric))
|
return new Numeric(value);
|
||||||
return new Numeric(value);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,18 +1,47 @@
|
|||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
using Artemis.UI.Shared.VisualScripting;
|
using Artemis.UI.Shared.VisualScripting;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.VisualScripting.Nodes.CustomViewModels;
|
namespace Artemis.VisualScripting.Nodes.CustomViewModels;
|
||||||
|
|
||||||
public class StaticNumericValueNodeCustomViewModel : CustomNodeViewModel
|
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<Numeric>(_node, value ?? new Numeric()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class StaticStringValueNodeCustomViewModel : CustomNodeViewModel
|
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<string>(_node, value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5,15 +5,16 @@
|
|||||||
xmlns:customViewModels="clr-namespace:Artemis.VisualScripting.Nodes.CustomViewModels"
|
xmlns:customViewModels="clr-namespace:Artemis.VisualScripting.Nodes.CustomViewModels"
|
||||||
xmlns:converters="clr-namespace:Artemis.VisualScripting.Converters"
|
xmlns:converters="clr-namespace:Artemis.VisualScripting.Converters"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
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"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.VisualScripting.Nodes.CustomViews.StaticNumericValueNodeCustomView"
|
x:Class="Artemis.VisualScripting.Nodes.CustomViews.StaticNumericValueNodeCustomView"
|
||||||
x:DataType="customViewModels:StaticNumericValueNodeCustomViewModel">
|
x:DataType="customViewModels:StaticNumericValueNodeCustomViewModel">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:NumericConverter x:Key="NumericConverter" />
|
<converters:NumericConverter x:Key="NumericConverter" />
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
<controls:NumberBox VerticalAlignment="Center"
|
<controls:NumberBox VerticalAlignment="Center" MinWidth="75" SimpleNumberFormat="F3" Classes="condensed">
|
||||||
MinWidth="75"
|
<Interaction.Behaviors>
|
||||||
Value="{Binding Node.Storage, Converter={StaticResource NumericConverter}}"
|
<behaviors:LostFocusNumberBoxBindingBehavior Value="{CompiledBinding CurrentValue, Converter={StaticResource NumericConverter}}"/>
|
||||||
SimpleNumberFormat="F3"
|
</Interaction.Behaviors>
|
||||||
Classes="condensed"/>
|
</controls:NumberBox>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,9 +1,11 @@
|
|||||||
|
using Artemis.VisualScripting.Nodes.CustomViewModels;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.VisualScripting.Nodes.CustomViews
|
namespace Artemis.VisualScripting.Nodes.CustomViews
|
||||||
{
|
{
|
||||||
public partial class StaticNumericValueNodeCustomView : UserControl
|
public partial class StaticNumericValueNodeCustomView : ReactiveUserControl<StaticNumericValueNodeCustomViewModel>
|
||||||
{
|
{
|
||||||
public StaticNumericValueNodeCustomView()
|
public StaticNumericValueNodeCustomView()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,8 +3,13 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:customViewModels="clr-namespace:Artemis.VisualScripting.Nodes.CustomViewModels"
|
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"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.VisualScripting.Nodes.CustomViews.StaticStringValueNodeCustomView"
|
x:Class="Artemis.VisualScripting.Nodes.CustomViews.StaticStringValueNodeCustomView"
|
||||||
x:DataType="customViewModels:StaticStringValueNodeCustomViewModel">
|
x:DataType="customViewModels:StaticStringValueNodeCustomViewModel">
|
||||||
<TextBox VerticalAlignment="Center" MinWidth="75" Text="{Binding Node.Storage}" Classes="condensed" />
|
<TextBox VerticalAlignment="Center" MinWidth="75" Classes="condensed">
|
||||||
|
<Interaction.Behaviors>
|
||||||
|
<behaviors:LostFocusTextBoxBindingBehavior Text="{CompiledBinding CurrentValue}"/>
|
||||||
|
</Interaction.Behaviors>
|
||||||
|
</TextBox>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@ -1,9 +1,11 @@
|
|||||||
|
using Artemis.VisualScripting.Nodes.CustomViewModels;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.VisualScripting.Nodes.CustomViews
|
namespace Artemis.VisualScripting.Nodes.CustomViews
|
||||||
{
|
{
|
||||||
public partial class StaticStringValueNodeCustomView : UserControl
|
public partial class StaticStringValueNodeCustomView : ReactiveUserControl<StaticStringValueNodeCustomViewModel>
|
||||||
{
|
{
|
||||||
public StaticStringValueNodeCustomView()
|
public StaticStringValueNodeCustomView()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
|
using Artemis.VisualScripting.Nodes.Easing.CustomViewModels;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.VisualScripting.Nodes.Easing.CustomViews
|
namespace Artemis.VisualScripting.Nodes.Easing.CustomViews
|
||||||
{
|
{
|
||||||
public partial class EasingTypeNodeCustomView : UserControl
|
public partial class EasingTypeNodeCustomView : ReactiveUserControl<EasingTypeNodeCustomViewModel>
|
||||||
{
|
{
|
||||||
public EasingTypeNodeCustomView()
|
public EasingTypeNodeCustomView()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -28,6 +28,17 @@
|
|||||||
"System.Reactive": "5.0.0"
|
"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": {
|
"Ninject": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[3.3.4, )",
|
"requested": "[3.3.4, )",
|
||||||
@ -175,6 +186,23 @@
|
|||||||
"Avalonia.Skia": "0.10.13"
|
"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": {
|
"Castle.Core": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "4.2.0",
|
"resolved": "4.2.0",
|
||||||
@ -1707,6 +1735,7 @@
|
|||||||
"Avalonia": "0.10.13",
|
"Avalonia": "0.10.13",
|
||||||
"Avalonia.ReactiveUI": "0.10.13",
|
"Avalonia.ReactiveUI": "0.10.13",
|
||||||
"Avalonia.Svg.Skia": "0.10.12",
|
"Avalonia.Svg.Skia": "0.10.12",
|
||||||
|
"Avalonia.Xaml.Behaviors": "0.10.13.2",
|
||||||
"DynamicData": "7.5.4",
|
"DynamicData": "7.5.4",
|
||||||
"FluentAvaloniaUI": "1.3.0",
|
"FluentAvaloniaUI": "1.3.0",
|
||||||
"Material.Icons.Avalonia": "1.0.2",
|
"Material.Icons.Avalonia": "1.0.2",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user