1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Merge branch 'development'

This commit is contained in:
Robert Beekman 2022-10-06 16:43:11 +02:00
commit 5a571f09d7
85 changed files with 453 additions and 199 deletions

View File

@ -10,7 +10,7 @@ jobs:
version: version:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
version-suffix: ${{ steps.get-version.outputs.version-suffix }} version-number: ${{ steps.get-version.outputs.version-number }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -22,9 +22,12 @@ jobs:
run: | run: |
$MidnightUtc = [DateTime]::UtcNow.Date $MidnightUtc = [DateTime]::UtcNow.Date
$BranchName = "${{ github.ref_name }}".replace('/','-').replace('.','-') $BranchName = "${{ github.ref_name }}".replace('/','-').replace('.','-')
$ApiVersion = (Select-Xml -Path 'src/Artemis.Core/Artemis.Core.csproj' -XPath '//PluginApiVersion').Node.InnerText
$NumberOfCommitsToday = (git log --after=$($MidnightUtc.ToString("o")) --oneline | Measure-Object -Line).Lines $NumberOfCommitsToday = (git log --after=$($MidnightUtc.ToString("o")) --oneline | Measure-Object -Line).Lines
$VersionSuffix = "$BranchName.$($MidnightUtc.ToString("yyyyMMdd")).$NumberOfCommitsToday" $VersionNumber = "$ApiVersion.$($MidnightUtc.ToString("yyyy.MMdd")).$NumberOfCommitsToday"
Write-Output "::set-output name=version-suffix::$VersionSuffix" # If we're not in master, add the branch name to the version so it counts as prerelease
if ($BranchName -ne "master") { $VersionNumber += "-$BranchName" }
Write-Output "::set-output name=version-number::$VersionNumber"
nuget: nuget:
name: Publish Nuget Packages name: Publish Nuget Packages
@ -38,8 +41,8 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Pack Artemis.Core - name: Pack Artemis.Core
run: dotnet pack -c Release -p:VersionSuffix=${{ needs.version.outputs.version-suffix }} -p:BuildingNuget=True src/Artemis.Core/Artemis.Core.csproj run: dotnet pack -c Release -p:Version=${{ needs.version.outputs.version-number }} -p:BuildingNuget=True src/Artemis.Core/Artemis.Core.csproj
- name: Pack Artemis.UI.Shared - name: Pack Artemis.UI.Shared
run: dotnet pack -c Release -p:VersionSuffix=${{ needs.version.outputs.version-suffix }} src/Artemis.UI.Shared/Artemis.UI.Shared.csproj run: dotnet pack -c Release -p:Version=${{ needs.version.outputs.version-number }} src/Artemis.UI.Shared/Artemis.UI.Shared.csproj
- name: Push Nugets - name: Push Nugets
run: dotnet nuget push **/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate run: dotnet nuget push **/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate

View File

@ -10,13 +10,18 @@
<Platforms>x64</Platforms> <Platforms>x64</Platforms>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageId>ArtemisRGB.Core</PackageId> <PackageId>ArtemisRGB.Core</PackageId>
<!-- API Version --> <PluginApiVersion>1</PluginApiVersion>
<VersionPrefix>1.0.0.0</VersionPrefix> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Artemis.Storage\Artemis.Storage.csproj"/> <!--Used to embed the above PluginApiVersion property into the assembly as metadata-->
<ProjectReference Condition="'$(BuildingNuget)' == 'True'" Update="..\Artemis.Storage\Artemis.Storage.csproj" PrivateAssets="All"/> <AssemblyMetadata Include="PluginApiVersion" Value="$(PluginApiVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Artemis.Storage\Artemis.Storage.csproj" />
<ProjectReference Condition="'$(BuildingNuget)' == 'True'" Update="..\Artemis.Storage\Artemis.Storage.csproj" PrivateAssets="All" />
<!-- <!--
Include Artemis.Storage directly in the NuGet package instead of expecting it as an external dependency Include Artemis.Storage directly in the NuGet package instead of expecting it as an external dependency

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using Artemis.Core.JsonConverters; using Artemis.Core.JsonConverters;
using Artemis.Core.Services; using Artemis.Core.Services;
@ -61,7 +62,8 @@ public static class Constants
/// <summary> /// <summary>
/// The current API version for plugins /// The current API version for plugins
/// </summary> /// </summary>
public static readonly Version PluginApi = CoreAssembly.GetName().Version!; public static readonly int PluginApiVersion = int.Parse(CoreAssembly.GetCustomAttributes<AssemblyMetadataAttribute>()
.First(a => a.Key == "PluginApiVersion").Value);
/// <summary> /// <summary>
/// The plugin info used by core components of Artemis /// The plugin info used by core components of Artemis

View File

@ -16,12 +16,13 @@ namespace Artemis.Core;
/// </summary> /// </summary>
public sealed class Layer : RenderProfileElement public sealed class Layer : RenderProfileElement
{ {
private readonly List<Layer> _renderCopies; private readonly List<Layer> _renderCopies = new();
private LayerGeneralProperties _general; private LayerGeneralProperties _general = new();
private LayerTransformProperties _transform = new();
private BaseLayerBrush? _layerBrush; private BaseLayerBrush? _layerBrush;
private LayerShape? _layerShape; private LayerShape? _layerShape;
private List<ArtemisLed> _leds; private List<ArtemisLed> _leds = new();
private LayerTransformProperties _transform; private List<LedEntity> _missingLeds = new();
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="Layer" /> class and adds itself to the child collection of the provided /// Creates a new instance of the <see cref="Layer" /> class and adds itself to the child collection of the provided
@ -37,16 +38,9 @@ public sealed class Layer : RenderProfileElement
Profile = Parent.Profile; Profile = Parent.Profile;
Name = name; Name = name;
Suspended = false; Suspended = false;
// TODO: move to top
_renderCopies = new List<Layer>();
_general = new LayerGeneralProperties();
_transform = new LayerTransformProperties();
_leds = new List<ArtemisLed>();
Leds = new ReadOnlyCollection<ArtemisLed>(_leds); Leds = new ReadOnlyCollection<ArtemisLed>(_leds);
Adapter = new LayerAdapter(this); Adapter = new LayerAdapter(this);
Initialize(); Initialize();
} }
@ -63,16 +57,9 @@ public sealed class Layer : RenderProfileElement
Profile = profile; Profile = profile;
Parent = parent; Parent = parent;
// TODO: move to top
_renderCopies = new List<Layer>();
_general = new LayerGeneralProperties();
_transform = new LayerTransformProperties();
_leds = new List<ArtemisLed>();
Leds = new ReadOnlyCollection<ArtemisLed>(_leds); Leds = new ReadOnlyCollection<ArtemisLed>(_leds);
Adapter = new LayerAdapter(this); Adapter = new LayerAdapter(this);
Load(); Load();
Initialize(); Initialize();
} }
@ -327,6 +314,7 @@ public sealed class Layer : RenderProfileElement
// LEDs // LEDs
LayerEntity.Leds.Clear(); LayerEntity.Leds.Clear();
SaveMissingLeds();
foreach (ArtemisLed artemisLed in Leds) foreach (ArtemisLed artemisLed in Leds)
{ {
LedEntity ledEntity = new() LedEntity ledEntity = new()
@ -786,6 +774,8 @@ public sealed class Layer : RenderProfileElement
a.RgbLed.Id.ToString() == ledEntity.LedName); a.RgbLed.Id.ToString() == ledEntity.LedName);
if (match != null) if (match != null)
leds.Add(match); leds.Add(match);
else
_missingLeds.Add(ledEntity);
} }
_leds = leds; _leds = leds;
@ -793,6 +783,11 @@ public sealed class Layer : RenderProfileElement
CalculateRenderProperties(); CalculateRenderProperties();
} }
private void SaveMissingLeds()
{
LayerEntity.Leds.AddRange(_missingLeds.Except(LayerEntity.Leds, LedEntity.LedEntityComparer));
}
#endregion #endregion
#region Brush management #region Brush management

View File

@ -191,7 +191,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary> /// <summary>
/// Gets a boolean indicating whether this plugin is compatible with the current operating system and API version /// Gets a boolean indicating whether this plugin is compatible with the current operating system and API version
/// </summary> /// </summary>
public bool IsCompatible => Platforms.MatchesCurrentOperatingSystem() && Api != null && Api >= Constants.PluginApi; public bool IsCompatible => Platforms.MatchesCurrentOperatingSystem() && Api != null && Api.Major >= Constants.PluginApiVersion;
internal string PreferredPluginDirectory => $"{Main.Split(".dll")[0].Replace("/", "").Replace("\\", "")}-{Guid.ToString().Substring(0, 8)}"; internal string PreferredPluginDirectory => $"{Main.Split(".dll")[0].Replace("/", "").Replace("\\", "")}-{Guid.ToString().Substring(0, 8)}";

View File

@ -75,8 +75,9 @@ internal class NodeService : INodeService
string name = nodeAttribute?.Name ?? nodeType.Name; string name = nodeAttribute?.Name ?? nodeType.Name;
string description = nodeAttribute?.Description ?? string.Empty; string description = nodeAttribute?.Description ?? string.Empty;
string category = nodeAttribute?.Category ?? string.Empty; string category = nodeAttribute?.Category ?? string.Empty;
string helpUrl = nodeAttribute?.HelpUrl ?? string.Empty;
NodeData nodeData = new(plugin, nodeType, name, description, category, nodeAttribute?.InputType, nodeAttribute?.OutputType, (s, e) => CreateNode(s, e, nodeType)); NodeData nodeData = new(plugin, nodeType, name, description, category, helpUrl, nodeAttribute?.InputType, nodeAttribute?.OutputType, (s, e) => CreateNode(s, e, nodeType));
return NodeTypeStore.Add(nodeData); return NodeTypeStore.Add(nodeData);
} }

View File

@ -18,12 +18,12 @@ public interface INode : INotifyPropertyChanged, IBreakableModel
/// <summary> /// <summary>
/// Gets the name of the node /// Gets the name of the node
/// </summary> /// </summary>
string Name { get; } string Name { get; set; }
/// <summary> /// <summary>
/// Gets the description of the node /// Gets the description of the node
/// </summary> /// </summary>
string Description { get; } string Description { get; set; }
/// <summary> /// <summary>
/// Gets a boolean indicating whether the node is the exit node of the script /// Gets a boolean indicating whether the node is the exit node of the script
@ -45,6 +45,11 @@ public interface INode : INotifyPropertyChanged, IBreakableModel
/// </summary> /// </summary>
public double Y { get; set; } public double Y { get; set; }
/// <summary>
/// Gets or sets the help URL of the node
/// </summary>
string HelpUrl { get; set; }
/// <summary> /// <summary>
/// Gets a read-only collection of the pins on this node /// Gets a read-only collection of the pins on this node
/// </summary> /// </summary>

View File

@ -24,6 +24,11 @@ public class NodeAttribute : Attribute
/// </summary> /// </summary>
public string Category { get; } = string.Empty; public string Category { get; } = string.Empty;
/// <summary>
/// Gets the help URL of the node
/// </summary>
public string HelpUrl { get; init; } = string.Empty;
/// <summary> /// <summary>
/// Gets the primary input type of the node /// Gets the primary input type of the node
/// </summary> /// </summary>
@ -65,5 +70,16 @@ public class NodeAttribute : Attribute
Category = category; Category = category;
} }
/// <summary>
/// Creates a new instance of the <see cref="NodeAttribute" /> class
/// </summary>
public NodeAttribute(string name, string description, string category, string helpUrl)
{
Name = name;
Description = description;
Category = category;
HelpUrl = helpUrl;
}
#endregion #endregion
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using Artemis.Storage.Entities.Profile.Nodes; using Artemis.Storage.Entities.Profile.Nodes;
using Castle.Core.Internal;
namespace Artemis.Core; namespace Artemis.Core;
@ -10,13 +11,14 @@ public class NodeData
{ {
#region Constructors #region Constructors
internal NodeData(Plugin plugin, Type type, string name, string description, string category, Type? inputType, Type? outputType, Func<INodeScript, NodeEntity?, INode> create) internal NodeData(Plugin plugin, Type type, string name, string description, string category, string helpUrl, Type? inputType, Type? outputType, Func<INodeScript, NodeEntity?, INode> create)
{ {
Plugin = plugin; Plugin = plugin;
Type = type; Type = type;
Name = name; Name = name;
Description = description; Description = description;
Category = category; Category = category;
HelpUrl = helpUrl;
InputType = inputType; InputType = inputType;
OutputType = outputType; OutputType = outputType;
_create = create; _create = create;
@ -34,7 +36,15 @@ public class NodeData
/// <returns>The returning node of type <see cref="Type" /></returns> /// <returns>The returning node of type <see cref="Type" /></returns>
public INode CreateNode(INodeScript script, NodeEntity? entity) public INode CreateNode(INodeScript script, NodeEntity? entity)
{ {
return _create(script, entity); INode node = _create(script, entity);
if (string.IsNullOrWhiteSpace(node.Name))
node.Name = Name;
if (string.IsNullOrWhiteSpace(node.Description))
node.Description = Description;
if (string.IsNullOrWhiteSpace(node.HelpUrl))
node.HelpUrl = HelpUrl;
return node;
} }
#endregion #endregion
@ -101,6 +111,11 @@ public class NodeData
/// </summary> /// </summary>
public string Category { get; } public string Category { get; }
/// <summary>
/// Gets the help URL of the node this data represents
/// </summary>
public string HelpUrl { get; }
/// <summary> /// <summary>
/// Gets the primary input type of the node this data represents /// Gets the primary input type of the node this data represents
/// </summary> /// </summary>

View File

@ -46,7 +46,7 @@ public abstract class Node : BreakableModel, INode
public string Name public string Name
{ {
get => _name; get => _name;
protected set => SetAndNotify(ref _name, value); set => SetAndNotify(ref _name, value);
} }
private string _description; private string _description;
@ -55,7 +55,7 @@ public abstract class Node : BreakableModel, INode
public string Description public string Description
{ {
get => _description; get => _description;
protected set => SetAndNotify(ref _description, value); set => SetAndNotify(ref _description, value);
} }
private double _x; private double _x;
@ -76,6 +76,13 @@ public abstract class Node : BreakableModel, INode
set => SetAndNotify(ref _y, value); set => SetAndNotify(ref _y, value);
} }
/// <inheritdoc />
public string HelpUrl
{
get => _helpUrl;
set => SetAndNotify(ref _helpUrl, value);
}
/// <inheritdoc /> /// <inheritdoc />
public virtual bool IsExitNode => false; public virtual bool IsExitNode => false;
@ -88,6 +95,7 @@ public abstract class Node : BreakableModel, INode
public IReadOnlyCollection<IPin> Pins => new ReadOnlyCollection<IPin>(_pins); public IReadOnlyCollection<IPin> Pins => new ReadOnlyCollection<IPin>(_pins);
private readonly List<IPinCollection> _pinCollections = new(); private readonly List<IPinCollection> _pinCollections = new();
private string _helpUrl;
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyCollection<IPinCollection> PinCollections => new ReadOnlyCollection<IPinCollection>(_pinCollections); public IReadOnlyCollection<IPinCollection> PinCollections => new ReadOnlyCollection<IPinCollection>(_pinCollections);
@ -106,6 +114,7 @@ public abstract class Node : BreakableModel, INode
{ {
_name = string.Empty; _name = string.Empty;
_description = string.Empty; _description = string.Empty;
_helpUrl = string.Empty;
_id = Guid.NewGuid(); _id = Guid.NewGuid();
} }
@ -116,6 +125,7 @@ public abstract class Node : BreakableModel, INode
{ {
_name = name; _name = name;
_description = description; _description = description;
_helpUrl = string.Empty;
_id = Guid.NewGuid(); _id = Guid.NewGuid();
} }

View File

@ -1,7 +1,37 @@
namespace Artemis.Storage.Entities.Profile; using System;
using System.Collections.Generic;
namespace Artemis.Storage.Entities.Profile;
public class LedEntity public class LedEntity
{ {
#region LedEntityEqualityComparer
private sealed class LedEntityEqualityComparer : IEqualityComparer<LedEntity>
{
public bool Equals(LedEntity x, LedEntity y)
{
if (ReferenceEquals(x, y))
return true;
if (ReferenceEquals(x, null))
return false;
if (ReferenceEquals(y, null))
return false;
if (x.GetType() != y.GetType())
return false;
return x.LedName == y.LedName && x.DeviceIdentifier == y.DeviceIdentifier && x.PhysicalLayout == y.PhysicalLayout;
}
public int GetHashCode(LedEntity obj)
{
return HashCode.Combine(obj.LedName, obj.DeviceIdentifier, obj.PhysicalLayout);
}
}
public static IEqualityComparer<LedEntity> LedEntityComparer { get; } = new LedEntityEqualityComparer();
#endregion
public string LedName { get; set; } public string LedName { get; set; }
public string DeviceIdentifier { get; set; } public string DeviceIdentifier { get; set; }

View File

@ -4,6 +4,22 @@ namespace Artemis.Storage.Entities.Profile.Nodes;
public class NodeConnectionEntity public class NodeConnectionEntity
{ {
public NodeConnectionEntity()
{
}
public NodeConnectionEntity(NodeConnectionEntity nodeConnectionEntity)
{
SourceType = nodeConnectionEntity.SourceType;
SourceNode = nodeConnectionEntity.SourceNode;
TargetNode = nodeConnectionEntity.TargetNode;
SourcePinCollectionId = nodeConnectionEntity.SourcePinCollectionId;
SourcePinId = nodeConnectionEntity.SourcePinId;
TargetType = nodeConnectionEntity.TargetType;
TargetPinCollectionId = nodeConnectionEntity.TargetPinCollectionId;
TargetPinId = nodeConnectionEntity.TargetPinId;
}
public string SourceType { get; set; } public string SourceType { get; set; }
public Guid SourceNode { get; set; } public Guid SourceNode { get; set; }
public Guid TargetNode { get; set; } public Guid TargetNode { get; set; }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace Artemis.Storage.Entities.Profile.Nodes; namespace Artemis.Storage.Entities.Profile.Nodes;
@ -10,6 +11,22 @@ public class NodeEntity
PinCollections = new List<NodePinCollectionEntity>(); PinCollections = new List<NodePinCollectionEntity>();
} }
public NodeEntity(NodeEntity nodeEntity)
{
Id = nodeEntity.Id;
Type = nodeEntity.Type;
PluginId = nodeEntity.PluginId;
Name = nodeEntity.Name;
Description = nodeEntity.Description;
IsExitNode = nodeEntity.IsExitNode;
X = nodeEntity.X;
Y = nodeEntity.Y;
Storage = nodeEntity.Storage;
PinCollections = nodeEntity.PinCollections.Select(p => new NodePinCollectionEntity(p)).ToList();
}
public Guid Id { get; set; } public Guid Id { get; set; }
public string Type { get; set; } public string Type { get; set; }
public Guid PluginId { get; set; } public Guid PluginId { get; set; }

View File

@ -2,6 +2,17 @@
public class NodePinCollectionEntity public class NodePinCollectionEntity
{ {
public NodePinCollectionEntity()
{
}
public NodePinCollectionEntity(NodePinCollectionEntity nodePinCollectionEntity)
{
Id = nodePinCollectionEntity.Id;
Direction = nodePinCollectionEntity.Direction;
Amount = nodePinCollectionEntity.Amount;
}
public int Id { get; set; } public int Id { get; set; }
public int Direction { set; get; } public int Direction { set; get; }
public int Amount { get; set; } public int Amount { get; set; }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using Artemis.Core; using Artemis.Core;
namespace Artemis.UI.Shared.Services.NodeEditor.Commands; namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
@ -36,6 +37,7 @@ public class AddNode : INodeEditorCommand, IDisposable
/// <inheritdoc /> /// <inheritdoc />
public void Execute() public void Execute()
{ {
if (!_nodeScript.Nodes.Contains(_node))
_nodeScript.AddNode(_node); _nodeScript.AddNode(_node);
_isRemoved = false; _isRemoved = false;
} }

View File

@ -45,7 +45,7 @@
<!-- Add Styles Here --> <!-- Add Styles Here -->
<Style Selector="TextBox.condensed"> <Style Selector="TextBox.condensed">
<Setter Property="Padding" Value="2" /> <Setter Property="Padding" Value="6 1" />
<Setter Property="FontSize" Value="13" /> <Setter Property="FontSize" Value="13" />
<Setter Property="MinHeight" Value="24" /> <Setter Property="MinHeight" Value="24" />
</Style> </Style>

View File

@ -10,7 +10,8 @@
<!-- Add Controls for Previewer Here --> <!-- Add Controls for Previewer Here -->
<TextBox Text="99999999" <TextBox Text="99999999"
attached:TextBoxAssist.PrefixText="%" attached:TextBoxAssist.PrefixText="%"
attached:TextBoxAssist.SuffixText="%"></TextBox> attached:TextBoxAssist.SuffixText="%">
</TextBox>
<controls:NumberBox Value="99999999" <controls:NumberBox Value="99999999"
attached:NumberBoxAssist.PrefixText="%" attached:NumberBoxAssist.PrefixText="%"
attached:NumberBoxAssist.SuffixText="%" /> attached:NumberBoxAssist.SuffixText="%" />
@ -44,23 +45,36 @@
MinWidth="{TemplateBinding MinWidth}" MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}" MinHeight="{TemplateBinding MinHeight}"
RenderTransform="scaleY(-1)" RenderTransform="scaleY(-1)"
CornerRadius="{TemplateBinding CornerRadius}"/> CornerRadius="{TemplateBinding CornerRadius}" />
<Border <Border
Margin="{TemplateBinding BorderThickness}"> Margin="{TemplateBinding BorderThickness}">
<Grid ColumnDefinitions="Auto,*,Auto" > <Grid ColumnDefinitions="Auto,*,Auto">
<ContentPresenter Grid.Column="0" Content="{TemplateBinding InnerLeftContent}"/> <ContentPresenter Grid.Column="0" Content="{TemplateBinding InnerLeftContent}" />
<DockPanel x:Name="PART_InnerDockPanel" Grid.Column="1" Margin="{TemplateBinding Padding}"> <Grid x:Name="PART_InnerGrid"
<TextBlock Name="PART_Prefix" Grid.Column="1"
Text="{TemplateBinding attached:TextBoxAssist.PrefixText}" RowDefinitions="Auto,Auto"
IsHitTestVisible="False" ColumnDefinitions="Auto,*,Auto"
DockPanel.Dock="Left"/> Cursor="IBeam"
<TextBlock Name="PART_FloatingWatermark" Margin="{TemplateBinding Padding}">
<TextBlock Grid.Row="0"
Grid.ColumnSpan="3"
Name="PART_FloatingWatermark"
Foreground="{DynamicResource SystemAccentColor}" Foreground="{DynamicResource SystemAccentColor}"
FontSize="{TemplateBinding FontSize}" FontSize="{TemplateBinding FontSize}"
Text="{TemplateBinding Watermark}" Text="{TemplateBinding Watermark}" />
DockPanel.Dock="Top" />
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}" <TextBlock Grid.Row="1"
Grid.Column="0"
Name="PART_Prefix"
Text="{TemplateBinding attached:TextBoxAssist.PrefixText}"
IsVisible="{TemplateBinding attached:TextBoxAssist.PrefixText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
IsHitTestVisible="False"
DockPanel.Dock="Left" />
<ScrollViewer Grid.Row="1"
Grid.Column="1"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"> VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<Panel> <Panel>
<TextBlock Name="PART_Watermark" <TextBlock Name="PART_Watermark"
@ -70,7 +84,7 @@
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}" IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
IsHitTestVisible="False"/> IsHitTestVisible="False" />
<TextPresenter Name="PART_TextPresenter" <TextPresenter Name="PART_TextPresenter"
Text="{TemplateBinding Text, Mode=TwoWay}" Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}" CaretIndex="{TemplateBinding CaretIndex}"
@ -84,17 +98,19 @@
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
CaretBrush="{TemplateBinding CaretBrush}" CaretBrush="{TemplateBinding CaretBrush}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Panel> </Panel>
</ScrollViewer> </ScrollViewer>
<TextBlock Name="PART_Suffix" <TextBlock Grid.Row="1"
Grid.Column="2"
Name="PART_Suffix"
Text="{TemplateBinding attached:TextBoxAssist.SuffixText}" Text="{TemplateBinding attached:TextBoxAssist.SuffixText}"
IsVisible="{TemplateBinding attached:TextBoxAssist.SuffixText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
IsHitTestVisible="False" IsHitTestVisible="False"
HorizontalAlignment="Right" HorizontalAlignment="Right" />
DockPanel.Dock="Right"/> </Grid>
</DockPanel> <ContentPresenter Grid.Column="2" Content="{TemplateBinding InnerRightContent}" />
<ContentPresenter Grid.Column="2" Content="{TemplateBinding InnerRightContent}"/>
</Grid> </Grid>
</Border> </Border>
</Panel> </Panel>

View File

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core;
using Artemis.Storage.Entities.Profile.Nodes;
using FluentAvalonia.Core;
namespace Artemis.UI.Models;
public class NodesClipboardModel
{
public NodesClipboardModel(NodeScript nodeScript, List<INode> nodes)
{
nodeScript.Save();
// Grab all entities belonging to provided nodes
Nodes = nodeScript.Entity.Nodes.Where(e => nodes.Any(n => n.Id == e.Id)).ToList();
// Grab all connections between provided nodes
Connections = nodeScript.Entity.Connections.Where(e => nodes.Any(n => n.Id == e.SourceNode) && nodes.Any(n => n.Id == e.TargetNode)).ToList();
}
public NodesClipboardModel()
{
Nodes = new List<NodeEntity>();
Connections = new List<NodeConnectionEntity>();
}
public List<NodeEntity> Nodes { get; set; }
public List<NodeConnectionEntity> Connections { get; set; }
public List<INode> Paste(NodeScript nodeScript, double x, double y)
{
if (!Nodes.Any())
return new List<INode>();
nodeScript.Save();
// Copy the entities, not messing with the originals
List<NodeEntity> nodes = Nodes.Select(n => new NodeEntity(n)).ToList();
List<NodeConnectionEntity> connections = Connections.Select(c => new NodeConnectionEntity(c)).ToList();
double xOffset = x - nodes.Min(n => n.X);
double yOffset = y - nodes.Min(n => n.Y);
foreach (NodeEntity node in nodes)
{
// Give each node a new GUID, updating any connections to it
Guid newGuid = Guid.NewGuid();
foreach (NodeConnectionEntity connection in connections)
{
if (connection.SourceNode == node.Id)
connection.SourceNode = newGuid;
else if (connection.TargetNode == node.Id)
connection.TargetNode = newGuid;
// Only add the connection if this is the first time we hit it
if (!nodeScript.Entity.Connections.Contains(connection))
nodeScript.Entity.Connections.Add(connection);
}
node.Id = newGuid;
node.X += xOffset;
node.Y += yOffset;
nodeScript.Entity.Nodes.Add(node);
}
nodeScript.Load();
// Return the newly created nodes
return nodeScript.Nodes.Where(n => nodes.Any(e => e.Id == n.Id)).ToList();
}
}

View File

@ -11,7 +11,7 @@
</UserControl.Styles> </UserControl.Styles>
<Grid RowDefinitions="60,Auto,Auto,*,Auto,Auto"> <Grid RowDefinitions="60,Auto,Auto,*,Auto,Auto">
<Grid Grid.Row="0" IsHitTestVisible="False" ColumnDefinitions="Auto,*"> <Grid Grid.Row="0" IsHitTestVisible="False" ColumnDefinitions="Auto,*">
<Image Grid.Column="0" Margin="12" Source="/Assets/Images/Logo/bow.png" RenderOptions.BitmapInterpolationMode="HighQuality"/> <Image Grid.Column="0" Margin="12" Source="/Assets/Images/Logo/bow.png" RenderOptions.BitmapInterpolationMode="HighQuality" />
<TextBlock Grid.Column="1" <TextBlock Grid.Column="1"
FontSize="24" FontSize="24"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -46,6 +46,8 @@
Width="44" Width="44"
Height="44" Height="44"
ToolTip.Tip="View website" ToolTip.Tip="View website"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://artemis-rgb.com"> NavigateUri="https://artemis-rgb.com">
<avalonia:MaterialIcon Kind="Web" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Web" Width="20" Height="20" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
@ -53,6 +55,8 @@
Width="44" Width="44"
Height="44" Height="44"
ToolTip.Tip="View GitHub repository" ToolTip.Tip="View GitHub repository"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://github.com/Artemis-RGB/Artemis"> NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Github" Width="20" Height="20" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
@ -60,6 +64,8 @@
Width="44" Width="44"
Height="44" Height="44"
ToolTip.Tip="View Wiki" ToolTip.Tip="View Wiki"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://wiki.artemis-rgb.com"> NavigateUri="https://wiki.artemis-rgb.com">
<avalonia:MaterialIcon Kind="BookOpenOutline" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="BookOpenOutline" Width="20" Height="20" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
@ -67,6 +73,8 @@
Width="44" Width="44"
Height="44" Height="44"
ToolTip.Tip="Join our Discord" ToolTip.Tip="Join our Discord"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://discord.gg/S3MVaC9"> NavigateUri="https://discord.gg/S3MVaC9">
<avalonia:MaterialIcon Kind="Chat" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Chat" Width="20" Height="20" />
</controls:HyperlinkButton> </controls:HyperlinkButton>
@ -74,6 +82,8 @@
Width="44" Width="44"
Height="44" Height="44"
ToolTip.Tip="View donation options" ToolTip.Tip="View donation options"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://wiki.artemis-rgb.com/en/donating"> NavigateUri="https://wiki.artemis-rgb.com/en/donating">
<avalonia:MaterialIcon Kind="Gift" Width="20" Height="20" /> <avalonia:MaterialIcon Kind="Gift" Width="20" Height="20" />
</controls:HyperlinkButton> </controls:HyperlinkButton>

View File

@ -24,11 +24,6 @@
Stroke="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}" Stroke="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
StrokeThickness="4" StrokeThickness="4"
StrokeLineCap="Round"> StrokeLineCap="Round">
<Path.Transitions>
<Transitions>
<ThicknessTransition Property="Margin" Duration="200"></ThicknessTransition>
</Transitions>
</Path.Transitions>
<Path.Data> <Path.Data>
<PathGeometry> <PathGeometry>
<PathGeometry.Figures> <PathGeometry.Figures>
@ -47,8 +42,6 @@
BorderThickness="2" BorderThickness="2"
CornerRadius="3" CornerRadius="3"
Padding="4" Padding="4"
Canvas.Left="{CompiledBinding ValuePoint.X}"
Canvas.Top="{CompiledBinding ValuePoint.Y}"
IsVisible="{CompiledBinding DisplayValue}"> IsVisible="{CompiledBinding DisplayValue}">
<ContentControl Content="{CompiledBinding FromViewModel.Pin.PinValue}"> <ContentControl Content="{CompiledBinding FromViewModel.Pin.PinValue}">
<ContentControl.DataTemplates> <ContentControl.DataTemplates>

View File

@ -8,6 +8,7 @@ using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Avalonia.Rendering;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting; namespace Artemis.UI.Screens.VisualScripting;
@ -29,9 +30,9 @@ public class CableView : ReactiveUserControl<CableViewModel>
{ {
_valueBorder.GetObservable(BoundsProperty).Subscribe(rect => _valueBorder.RenderTransform = new TranslateTransform(rect.Width / 2 * -1, rect.Height / 2 * -1)).DisposeWith(d); _valueBorder.GetObservable(BoundsProperty).Subscribe(rect => _valueBorder.RenderTransform = new TranslateTransform(rect.Width / 2 * -1, rect.Height / 2 * -1)).DisposeWith(d);
ViewModel.WhenAnyValue(vm => vm.FromPoint).Subscribe(_ => Update()).DisposeWith(d); ViewModel.WhenAnyValue(vm => vm.FromPoint).Subscribe(_ => Update(true)).DisposeWith(d);
ViewModel.WhenAnyValue(vm => vm.ToPoint).Subscribe(_ => Update()).DisposeWith(d); ViewModel.WhenAnyValue(vm => vm.ToPoint).Subscribe(_ => Update(false)).DisposeWith(d);
Update(); Update(true);
}); });
} }
@ -40,10 +41,12 @@ public class CableView : ReactiveUserControl<CableViewModel>
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private void Update() private void Update(bool from)
{ {
// Workaround for https://github.com/AvaloniaUI/Avalonia/issues/4748 // Workaround for https://github.com/AvaloniaUI/Avalonia/issues/4748
_cablePath.Margin = _cablePath.Margin != new Thickness(0, 0, 0, 0) ? new Thickness(0, 0, 0, 0) : new Thickness(1, 1, 0, 0); _cablePath.Margin = new Thickness(_cablePath.Margin.Left + 1, _cablePath.Margin.Top + 1, 0, 0);
if (_cablePath.Margin.Left > 2)
_cablePath.Margin = new Thickness(0, 0, 0, 0);
PathFigure pathFigure = ((PathGeometry) _cablePath.Data).Figures.First(); PathFigure pathFigure = ((PathGeometry) _cablePath.Data).Figures.First();
BezierSegment segment = (BezierSegment) pathFigure.Segments!.First(); BezierSegment segment = (BezierSegment) pathFigure.Segments!.First();
@ -51,6 +54,11 @@ public class CableView : ReactiveUserControl<CableViewModel>
segment.Point1 = new Point(ViewModel.FromPoint.X + CABLE_OFFSET, ViewModel.FromPoint.Y); segment.Point1 = new Point(ViewModel.FromPoint.X + CABLE_OFFSET, ViewModel.FromPoint.Y);
segment.Point2 = new Point(ViewModel.ToPoint.X - CABLE_OFFSET, ViewModel.ToPoint.Y); segment.Point2 = new Point(ViewModel.ToPoint.X - CABLE_OFFSET, ViewModel.ToPoint.Y);
segment.Point3 = new Point(ViewModel.ToPoint.X, ViewModel.ToPoint.Y); segment.Point3 = new Point(ViewModel.ToPoint.X, ViewModel.ToPoint.Y);
Canvas.SetLeft(_valueBorder, ViewModel.FromPoint.X + (ViewModel.ToPoint.X - ViewModel.FromPoint.X) / 2);
Canvas.SetTop(_valueBorder, ViewModel.FromPoint.Y + (ViewModel.ToPoint.Y - ViewModel.FromPoint.Y) / 2);
_cablePath.InvalidateVisual();
} }
private void OnPointerEnter(object? sender, PointerEventArgs e) private void OnPointerEnter(object? sender, PointerEventArgs e)

View File

@ -1,4 +1,5 @@
using System; using System;
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;
@ -27,7 +28,6 @@ public class CableViewModel : ActivatableViewModelBase
private PinViewModel? _fromViewModel; private PinViewModel? _fromViewModel;
private ObservableAsPropertyHelper<Point>? _toPoint; private ObservableAsPropertyHelper<Point>? _toPoint;
private PinViewModel? _toViewModel; private PinViewModel? _toViewModel;
private ObservableAsPropertyHelper<Point>? _valuePoint;
public CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to, ISettingsService settingsService) public CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to, ISettingsService settingsService)
{ {
@ -63,11 +63,6 @@ public class CableViewModel : ActivatableViewModelBase
.Switch() .Switch()
.ToProperty(this, vm => vm.ToPoint) .ToProperty(this, vm => vm.ToPoint)
.DisposeWith(d); .DisposeWith(d);
_valuePoint = this.WhenAnyValue(vm => vm.FromPoint, vm => vm.ToPoint).Select(tuple => new Point(
tuple.Item1.X + (tuple.Item2.X - tuple.Item1.X) / 2,
tuple.Item1.Y + (tuple.Item2.Y - tuple.Item1.Y) / 2
)).ToProperty(this, vm => vm.ValuePoint)
.DisposeWith(d);
// Not a perfect solution but this makes sure the cable never renders at 0,0 (can happen when the cable spawns before the pin ever rendered) // Not a perfect solution but this makes sure the cable never renders at 0,0 (can happen when the cable spawns before the pin ever rendered)
_connected = this.WhenAnyValue(vm => vm.FromPoint, vm => vm.ToPoint) _connected = this.WhenAnyValue(vm => vm.FromPoint, vm => vm.ToPoint)
@ -104,11 +99,10 @@ public class CableViewModel : ActivatableViewModelBase
} }
public bool Connected => _connected?.Value ?? false; public bool Connected => _connected?.Value ?? false;
public bool IsFirst => _from.ConnectedTo[0] == _to; public bool IsFirst => _from.ConnectedTo.FirstOrDefault() == _to;
public Point FromPoint => _fromPoint?.Value ?? new Point(); public Point FromPoint => _fromPoint?.Value ?? new Point();
public Point ToPoint => _toPoint?.Value ?? new Point(); public Point ToPoint => _toPoint?.Value ?? new Point();
public Point ValuePoint => _valuePoint?.Value ?? new Point();
public Color CableColor => _cableColor?.Value ?? new Color(255, 255, 255, 255); public Color CableColor => _cableColor?.Value ?? new Color(255, 255, 255, 255);
public void UpdateDisplayValue(bool hoveringOver) public void UpdateDisplayValue(bool hoveringOver)

View File

@ -30,6 +30,8 @@
<KeyBinding Command="{CompiledBinding ClearSelection}" Gesture="Escape" /> <KeyBinding Command="{CompiledBinding ClearSelection}" Gesture="Escape" />
<KeyBinding Command="{CompiledBinding DeleteSelected}" Gesture="Delete" /> <KeyBinding Command="{CompiledBinding DeleteSelected}" Gesture="Delete" />
<KeyBinding Command="{CompiledBinding DuplicateSelected}" Gesture="Ctrl+D" /> <KeyBinding Command="{CompiledBinding DuplicateSelected}" Gesture="Ctrl+D" />
<KeyBinding Command="{CompiledBinding CopySelected}" Gesture="Ctrl+C" />
<KeyBinding Command="{CompiledBinding PasteSelected}" Gesture="Ctrl+V" />
<KeyBinding Command="{CompiledBinding History.Undo}" Gesture="Ctrl+Z" /> <KeyBinding Command="{CompiledBinding History.Undo}" Gesture="Ctrl+Z" />
<KeyBinding Command="{CompiledBinding History.Redo}" Gesture="Ctrl+Y" /> <KeyBinding Command="{CompiledBinding History.Redo}" Gesture="Ctrl+Y" />
</UserControl.KeyBindings> </UserControl.KeyBindings>

View File

@ -14,6 +14,7 @@ using Avalonia.Markup.Xaml;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree;
using DynamicData.Binding; using DynamicData.Binding;
using ReactiveUI; using ReactiveUI;
@ -39,6 +40,8 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
_zoomBorder.AddHandler(PointerReleasedEvent, CanvasOnPointerReleased, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true); _zoomBorder.AddHandler(PointerReleasedEvent, CanvasOnPointerReleased, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
_zoomBorder.AddHandler(PointerWheelChangedEvent, ZoomOnPointerWheelChanged, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true); _zoomBorder.AddHandler(PointerWheelChangedEvent, ZoomOnPointerWheelChanged, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
_zoomBorder.AddHandler(PointerMovedEvent, ZoomOnPointerMoved, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
this.WhenActivated(d => this.WhenActivated(d =>
{ {
ViewModel!.AutoFitRequested += ViewModelOnAutoFitRequested; ViewModel!.AutoFitRequested += ViewModelOnAutoFitRequested;
@ -67,6 +70,12 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
e.Handled = true; e.Handled = true;
} }
private void ZoomOnPointerMoved(object? sender, PointerEventArgs e)
{
if (ViewModel != null)
ViewModel.PastePosition = e.GetPosition(_grid);
}
private void ShowPickerAt(Point point) private void ShowPickerAt(Point point)
{ {
if (ViewModel == null) if (ViewModel == null)

View File

@ -6,9 +6,12 @@ using System.Linq;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Text;
using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Events; using Artemis.Core.Events;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Models;
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;
@ -16,6 +19,7 @@ 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 Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Avalonia.Input;
using DynamicData; using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
using ReactiveUI; using ReactiveUI;
@ -24,6 +28,8 @@ namespace Artemis.UI.Screens.VisualScripting;
public class NodeScriptViewModel : ActivatableViewModelBase public class NodeScriptViewModel : ActivatableViewModelBase
{ {
public const string CLIPBOARD_DATA_FORMAT = "Artemis.Nodes";
private readonly INodeEditorService _nodeEditorService; private readonly INodeEditorService _nodeEditorService;
private readonly INodeService _nodeService; private readonly INodeService _nodeService;
private readonly SourceList<NodeViewModel> _nodeViewModels; private readonly SourceList<NodeViewModel> _nodeViewModels;
@ -33,6 +39,7 @@ public class NodeScriptViewModel : ActivatableViewModelBase
private DragCableViewModel? _dragViewModel; private DragCableViewModel? _dragViewModel;
private List<NodeViewModel>? _initialNodeSelection; private List<NodeViewModel>? _initialNodeSelection;
private Matrix _panMatrix; private Matrix _panMatrix;
private Point _pastePosition;
public NodeScriptViewModel(NodeScript nodeScript, bool isPreview, INodeVmFactory nodeVmFactory, INodeService nodeService, INodeEditorService nodeEditorService) public NodeScriptViewModel(NodeScript nodeScript, bool isPreview, INodeVmFactory nodeVmFactory, INodeService nodeService, INodeEditorService nodeEditorService)
{ {
@ -86,8 +93,8 @@ public class NodeScriptViewModel : ActivatableViewModelBase
ClearSelection = ReactiveCommand.Create(ExecuteClearSelection); ClearSelection = ReactiveCommand.Create(ExecuteClearSelection);
DeleteSelected = ReactiveCommand.Create(ExecuteDeleteSelected); DeleteSelected = ReactiveCommand.Create(ExecuteDeleteSelected);
DuplicateSelected = ReactiveCommand.Create(ExecuteDuplicateSelected); DuplicateSelected = ReactiveCommand.Create(ExecuteDuplicateSelected);
CopySelected = ReactiveCommand.Create(ExecuteCopySelected); CopySelected = ReactiveCommand.CreateFromTask(ExecuteCopySelected);
PasteSelected = ReactiveCommand.Create(ExecutePasteSelected); PasteSelected = ReactiveCommand.CreateFromTask(ExecutePasteSelected);
} }
public NodeScript NodeScript { get; } public NodeScript NodeScript { get; }
@ -118,6 +125,12 @@ public class NodeScriptViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _panMatrix, value); set => RaiseAndSetIfChanged(ref _panMatrix, value);
} }
public Point PastePosition
{
get => _pastePosition;
set => RaiseAndSetIfChanged(ref _pastePosition, value);
}
public void DeleteSelectedNodes() public void DeleteSelectedNodes()
{ {
List<NodeViewModel> toRemove = NodeViewModels.Where(vm => vm.IsSelected && !vm.Node.IsDefaultNode && !vm.Node.IsExitNode).ToList(); List<NodeViewModel> toRemove = NodeViewModels.Where(vm => vm.IsSelected && !vm.Node.IsDefaultNode && !vm.Node.IsExitNode).ToList();
@ -279,11 +292,39 @@ public class NodeScriptViewModel : ActivatableViewModelBase
} }
} }
private void ExecuteCopySelected() private async Task ExecuteCopySelected()
{ {
if (Application.Current?.Clipboard == null)
return;
List<INode> nodes = NodeViewModels.Where(vm => vm.IsSelected).Select(vm => vm.Node).Where(n => !n.IsDefaultNode && !n.IsExitNode).ToList();
DataObject dataObject = new();
string copy = CoreJson.SerializeObject(new NodesClipboardModel(NodeScript, nodes), true);
dataObject.Set(CLIPBOARD_DATA_FORMAT, copy);
await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
} }
private void ExecutePasteSelected() private async Task ExecutePasteSelected()
{ {
if (Application.Current?.Clipboard == null)
return;
byte[]? bytes = (byte[]?) await Application.Current.Clipboard.GetDataAsync(CLIPBOARD_DATA_FORMAT);
if (bytes == null!)
return;
NodesClipboardModel? nodesClipboardModel = CoreJson.DeserializeObject<NodesClipboardModel>(Encoding.Unicode.GetString(bytes), true);
if (nodesClipboardModel == null)
return;
List<INode> nodes = nodesClipboardModel.Paste(NodeScript, PastePosition.X, PastePosition.Y);
using NodeEditorCommandScope scope = _nodeEditorService.CreateCommandScope(NodeScript, "Paste nodes");
foreach (INode node in nodes)
_nodeEditorService.ExecuteCommand(NodeScript, new AddNode(NodeScript, node));
// Select only the new nodes
foreach (NodeViewModel nodeViewModel in NodeViewModels)
nodeViewModel.IsSelected = nodes.Contains(nodeViewModel.Node);
} }
} }

View File

@ -4,6 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting" xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="250" d:DesignHeight="150" mc:Ignorable="d" d:DesignWidth="250" d:DesignHeight="150"
x:Class="Artemis.UI.Screens.VisualScripting.NodeView" x:Class="Artemis.UI.Screens.VisualScripting.NodeView"
x:DataType="visualScripting:NodeViewModel"> x:DataType="visualScripting:NodeViewModel">
@ -37,7 +38,7 @@
ClipToBounds="True" ClipToBounds="True"
Background="{DynamicResource ContentDialogBackground}"> Background="{DynamicResource ContentDialogBackground}">
<Border Background="{DynamicResource TaskDialogHeaderBackground}"> <Border Background="{DynamicResource TaskDialogHeaderBackground}">
<Grid Classes="node-header" VerticalAlignment="Top" ColumnDefinitions="Auto,*,Auto"> <Grid Classes="node-header" VerticalAlignment="Top" ColumnDefinitions="Auto,*,Auto,Auto">
<Button Grid.Column="0" <Button Grid.Column="0"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{CompiledBinding Node.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}" IsVisible="{CompiledBinding Node.BrokenState, Converter={x:Static ObjectConverters.IsNotNull}}"
@ -53,7 +54,17 @@
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="10 0 0 0" Text="{CompiledBinding Node.Name}" ToolTip.Tip="{CompiledBinding Node.Description}" /> <TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="10 0 0 0" Text="{CompiledBinding Node.Name}" ToolTip.Tip="{CompiledBinding Node.Description}" />
<Button Grid.Column="2" VerticalAlignment="Center" Classes="icon-button icon-button-small" Margin="5" Command="{CompiledBinding DeleteNode}">
<controls:HyperlinkButton Grid.Column="2"
IsVisible="{CompiledBinding Node.HelpUrl, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
VerticalAlignment="Center"
Classes="icon-button icon-button-small"
Margin="5 5 -3 5"
ToolTip.Tip="View node help"
NavigateUri="{CompiledBinding Node.HelpUrl}">
<avalonia:MaterialIcon Kind="Help" />
</controls:HyperlinkButton>
<Button Grid.Column="3" VerticalAlignment="Center" Classes="icon-button icon-button-small" Margin="5" Command="{CompiledBinding DeleteNode}">
<avalonia:MaterialIcon Kind="Close"></avalonia:MaterialIcon> <avalonia:MaterialIcon Kind="Close"></avalonia:MaterialIcon>
</Button> </Button>
</Grid> </Grid>

View File

@ -6,7 +6,7 @@ namespace Artemis.VisualScripting.Nodes.Branching;
[Node("Branch", "Forwards one of two values depending on an input boolean", "Branching", InputType = typeof(object), OutputType = typeof(object))] [Node("Branch", "Forwards one of two values depending on an input boolean", "Branching", InputType = typeof(object), OutputType = typeof(object))]
public class BooleanBranchNode : Node public class BooleanBranchNode : Node
{ {
public BooleanBranchNode() : base("Branch", "Forwards one of two values depending on an input boolean") public BooleanBranchNode()
{ {
BooleanInput = CreateInputPin<bool>(); BooleanInput = CreateInputPin<bool>();
TrueInput = CreateInputPin(typeof(object), "True"); TrueInput = CreateInputPin(typeof(object), "True");

View File

@ -9,7 +9,7 @@ public class EnumSwitchNode : Node
{ {
private readonly Dictionary<Enum, InputPin> _inputPins; private readonly Dictionary<Enum, InputPin> _inputPins;
public EnumSwitchNode() : base("Enum Branch", "desc") public EnumSwitchNode()
{ {
_inputPins = new Dictionary<Enum, InputPin>(); _inputPins = new Dictionary<Enum, InputPin>();

View File

@ -6,7 +6,7 @@ namespace Artemis.VisualScripting.Nodes.Color;
[Node("Brighten Color", "Brightens a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] [Node("Brighten Color", "Brightens a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))]
public class BrightenSKColorNode : Node public class BrightenSKColorNode : Node
{ {
public BrightenSKColorNode() : base("Brighten Color", "Brightens a color by a specified amount in percent") public BrightenSKColorNode()
{ {
Input = CreateInputPin<SKColor>("Color"); Input = CreateInputPin<SKColor>("Color");
Percentage = CreateInputPin<Numeric>("%"); Percentage = CreateInputPin<Numeric>("%");

View File

@ -11,7 +11,7 @@ public class ColorGradientFromPinsNode : Node
public InputPinCollection<SKColor> Colors { get; set; } public InputPinCollection<SKColor> Colors { get; set; }
public InputPinCollection<Numeric> Positions { get; set; } public InputPinCollection<Numeric> Positions { get; set; }
public ColorGradientFromPinsNode() : base("Color Gradient", "Outputs a Color Gradient from colors and positions") public ColorGradientFromPinsNode()
{ {
Colors = CreateInputPinCollection<SKColor>("Colors", 0); Colors = CreateInputPinCollection<SKColor>("Colors", 0);
Positions = CreateInputPinCollection<Numeric>("Positions", 0); Positions = CreateInputPinCollection<Numeric>("Positions", 0);

View File

@ -10,7 +10,7 @@ public class ColorGradientNode : Node<ColorGradient, ColorGradientNodeCustomView
{ {
private readonly List<InputPin> _inputPins; private readonly List<InputPin> _inputPins;
public ColorGradientNode() : base("Color Gradient", "Outputs a color gradient with the given colors") public ColorGradientNode()
{ {
_inputPins = new List<InputPin>(); _inputPins = new List<InputPin>();

View File

@ -6,7 +6,7 @@ namespace Artemis.VisualScripting.Nodes.Color;
[Node("Darken Color", "Darkens a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] [Node("Darken Color", "Darkens a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))]
public class DarkenSKColorNode : Node public class DarkenSKColorNode : Node
{ {
public DarkenSKColorNode() : base("Darken Color", "Darkens a color by a specified amount in percent") public DarkenSKColorNode()
{ {
Input = CreateInputPin<SKColor>("Color"); Input = CreateInputPin<SKColor>("Color");
Percentage = CreateInputPin<Numeric>("%"); Percentage = CreateInputPin<Numeric>("%");

View File

@ -6,7 +6,7 @@ namespace Artemis.VisualScripting.Nodes.Color;
[Node("Desaturate Color", "Desaturates a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] [Node("Desaturate Color", "Desaturates a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))]
public class DesaturateSKColorNode : Node public class DesaturateSKColorNode : Node
{ {
public DesaturateSKColorNode() : base("Desaturate Color", "Desaturates a color by a specified amount in percent") public DesaturateSKColorNode()
{ {
Input = CreateInputPin<SKColor>("Color"); Input = CreateInputPin<SKColor>("Color");
Percentage = CreateInputPin<Numeric>("%"); Percentage = CreateInputPin<Numeric>("%");

View File

@ -6,7 +6,7 @@ namespace Artemis.VisualScripting.Nodes.Color;
[Node("HSL Color", "Creates a color from hue, saturation and lightness values", "Color", InputType = typeof(Numeric), OutputType = typeof(SKColor))] [Node("HSL Color", "Creates a color from hue, saturation and lightness values", "Color", InputType = typeof(Numeric), OutputType = typeof(SKColor))]
public class HslSKColorNode : Node public class HslSKColorNode : Node
{ {
public HslSKColorNode() : base("HSL Color", "Creates a color from hue, saturation and lightness values") public HslSKColorNode()
{ {
H = CreateInputPin<Numeric>("H"); H = CreateInputPin<Numeric>("H");
S = CreateInputPin<Numeric>("S"); S = CreateInputPin<Numeric>("S");

View File

@ -6,7 +6,7 @@ namespace Artemis.VisualScripting.Nodes.Color;
[Node("Invert Color", "Inverts a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] [Node("Invert Color", "Inverts a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))]
public class InvertSKColorNode : Node public class InvertSKColorNode : Node
{ {
public InvertSKColorNode() : base("Invert Color", "Inverts a color") public InvertSKColorNode()
{ {
Input = CreateInputPin<SKColor>(); Input = CreateInputPin<SKColor>();
Output = CreateOutputPin<SKColor>(); Output = CreateOutputPin<SKColor>();

View File

@ -20,8 +20,8 @@ public class LerpSKColorNode : Node
#region Constructors #region Constructors
public LerpSKColorNode() public LerpSKColorNode()
: base("Lerp", "Interpolates linear between the two values A and B")
{ {
Name = "Lerp";
A = CreateInputPin<SKColor>("A"); A = CreateInputPin<SKColor>("A");
B = CreateInputPin<SKColor>("B"); B = CreateInputPin<SKColor>("B");
T = CreateInputPin<Numeric>("T"); T = CreateInputPin<Numeric>("T");

View File

@ -10,7 +10,6 @@ public class RampSKColorNode : Node<ColorGradient, RampSKColorNodeCustomViewMode
#region Constructors #region Constructors
public RampSKColorNode() public RampSKColorNode()
: base("Color Ramp", "Maps values to colors with the use of a gradient.")
{ {
Input = CreateInputPin<Numeric>(); Input = CreateInputPin<Numeric>();
Output = CreateOutputPin<SKColor>(); Output = CreateOutputPin<SKColor>();

View File

@ -18,7 +18,6 @@ public class RgbSKColorNode : Node
#region Constructors #region Constructors
public RgbSKColorNode() public RgbSKColorNode()
: base("RGB Color", "Creates a color from red, green and blue values")
{ {
R = CreateInputPin<Numeric>("R"); R = CreateInputPin<Numeric>("R");
G = CreateInputPin<Numeric>("G"); G = CreateInputPin<Numeric>("G");

View File

@ -6,7 +6,7 @@ namespace Artemis.VisualScripting.Nodes.Color;
[Node("Rotate Color Hue", "Rotates the hue of a color by a specified amount in degrees", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] [Node("Rotate Color Hue", "Rotates the hue of a color by a specified amount in degrees", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))]
public class RotateHueSKColorNode : Node public class RotateHueSKColorNode : Node
{ {
public RotateHueSKColorNode() : base("Rotate Color Hue", "Rotates the hue of a color by a specified amount in degrees") public RotateHueSKColorNode()
{ {
Input = CreateInputPin<SKColor>("Color"); Input = CreateInputPin<SKColor>("Color");
Amount = CreateInputPin<Numeric>("Amount"); Amount = CreateInputPin<Numeric>("Amount");

View File

@ -6,7 +6,7 @@ namespace Artemis.VisualScripting.Nodes.Color;
[Node("Saturate Color", "Saturates a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))] [Node("Saturate Color", "Saturates a color by a specified amount in percent", "Color", InputType = typeof(SKColor), OutputType = typeof(SKColor))]
public class SaturateSKColorNode : Node public class SaturateSKColorNode : Node
{ {
public SaturateSKColorNode() : base("Saturate Color", "Saturates a color by a specified amount in percent") public SaturateSKColorNode()
{ {
Input = CreateInputPin<SKColor>("Color"); Input = CreateInputPin<SKColor>("Color");
Percentage = CreateInputPin<Numeric>("%"); Percentage = CreateInputPin<Numeric>("%");

View File

@ -9,8 +9,8 @@ public class SumSKColorsNode : Node
#region Constructors #region Constructors
public SumSKColorsNode() public SumSKColorsNode()
: base("Sum", "Sums the connected color values.")
{ {
Name = "Sum";
Values = CreateInputPinCollection<SKColor>("Values", 2); Values = CreateInputPinCollection<SKColor>("Values", 2);
Sum = CreateOutputPin<SKColor>("Sum"); Sum = CreateOutputPin<SKColor>("Sum");
} }

View File

@ -8,7 +8,6 @@ public class ConvertToNumericNode : Node
#region Constructors #region Constructors
public ConvertToNumericNode() public ConvertToNumericNode()
: base("To Numeric", "Converts the input to a numeric.")
{ {
Input = CreateInputPin<object>(); Input = CreateInputPin<object>();
Output = CreateOutputPin<Numeric>(); Output = CreateOutputPin<Numeric>();

View File

@ -2,13 +2,12 @@
namespace Artemis.VisualScripting.Nodes.Conversion; namespace Artemis.VisualScripting.Nodes.Conversion;
[Node("To String", "Converts the input to a string.", "Conversion", InputType = typeof(object), OutputType = typeof(string))] [Node("To Text", "Converts the input to text.", "Conversion", InputType = typeof(object), OutputType = typeof(string))]
public class ConvertToStringNode : Node public class ConvertToStringNode : Node
{ {
#region Constructors #region Constructors
public ConvertToStringNode() public ConvertToStringNode()
: base("To String", "Converts the input to a string.")
{ {
Input = CreateInputPin<object>(); Input = CreateInputPin<object>();
String = CreateOutputPin<string>(); String = CreateOutputPin<string>();

View File

@ -15,7 +15,7 @@ public class DataModelEventCycleNode : Node<DataModelPathEntity, DataModelEventC
private DateTime _lastTrigger; private DateTime _lastTrigger;
private bool _updating; private bool _updating;
public DataModelEventCycleNode() : base("Data Model-Event Value Cycle", "Cycles through provided values each time the select event fires.") public DataModelEventCycleNode()
{ {
_currentType = typeof(object); _currentType = typeof(object);

View File

@ -16,7 +16,7 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
private OutputPin? _oldValuePin; private OutputPin? _oldValuePin;
private int _valueChangeCount; private int _valueChangeCount;
public DataModelEventNode() : base("Data Model-Event", "Outputs the latest values of a data model event.") public DataModelEventNode()
{ {
_objectOutputPins = new ObjectOutputPins(this); _objectOutputPins = new ObjectOutputPins(this);

View File

@ -9,7 +9,7 @@ public class DataModelNode : Node<DataModelPathEntity, DataModelNodeCustomViewMo
{ {
private DataModelPath? _dataModelPath; private DataModelPath? _dataModelPath;
public DataModelNode() : base("Data Model", "Outputs a selectable data model value") public DataModelNode()
{ {
Output = CreateOutputPin(typeof(object)); Output = CreateOutputPin(typeof(object));
StorageModified += (_, _) => UpdateDataModelPath(); StorageModified += (_, _) => UpdateDataModelPath();

View File

@ -6,7 +6,7 @@ namespace Artemis.VisualScripting.Nodes.Easing;
[Node("Easing Type", "Outputs a selectable easing type.", "Easing", OutputType = typeof(Easings.Functions))] [Node("Easing Type", "Outputs a selectable easing type.", "Easing", OutputType = typeof(Easings.Functions))]
public class EasingTypeNode : Node<Easings.Functions, EasingTypeNodeCustomViewModel> public class EasingTypeNode : Node<Easings.Functions, EasingTypeNodeCustomViewModel>
{ {
public EasingTypeNode() : base("Easing Type", "Outputs a selectable easing type.") public EasingTypeNode()
{ {
Output = CreateOutputPin<Easings.Functions>(); Output = CreateOutputPin<Easings.Functions>();
} }

View File

@ -11,7 +11,7 @@ public class NumericEasingNode : Node
private float _sourceValue; private float _sourceValue;
private float _targetValue; private float _targetValue;
public NumericEasingNode() : base("Numeric Easing", "Outputs an eased numeric value") public NumericEasingNode()
{ {
Input = CreateInputPin<Numeric>(); Input = CreateInputPin<Numeric>();
EasingTime = CreateInputPin<Numeric>("delay"); EasingTime = CreateInputPin<Numeric>("delay");

View File

@ -12,7 +12,7 @@ public class SKColorEasingNode : Node
private SKColor _sourceValue; private SKColor _sourceValue;
private SKColor _targetValue; private SKColor _targetValue;
public SKColorEasingNode() : base("Color Easing", "Outputs an eased color value") public SKColorEasingNode()
{ {
Input = CreateInputPin<SKColor>(); Input = CreateInputPin<SKColor>();
EasingTime = CreateInputPin<Numeric>("delay"); EasingTime = CreateInputPin<Numeric>("delay");

View File

@ -7,7 +7,7 @@ namespace Artemis.VisualScripting.Nodes.List;
[Node("List Operator (Simple)", "Checks if any/all/no values in the input list match the input value", "List", InputType = typeof(IEnumerable), OutputType = typeof(bool))] [Node("List Operator (Simple)", "Checks if any/all/no values in the input list match the input value", "List", InputType = typeof(IEnumerable), OutputType = typeof(bool))]
public class ListOperatorNode : Node<ListOperator, ListOperatorNodeCustomViewModel> public class ListOperatorNode : Node<ListOperator, ListOperatorNodeCustomViewModel>
{ {
public ListOperatorNode() : base("List Operator (Simple)", "Checks if any/all/no values in the input list match the input value") public ListOperatorNode()
{ {
InputList = CreateInputPin<IList>(); InputList = CreateInputPin<IList>();
InputValue = CreateInputPin<object>(); InputValue = CreateInputPin<object>();

View File

@ -11,7 +11,7 @@ public class ListOperatorPredicateNode : Node<ListOperatorEntity, ListOperatorPr
private readonly object _scriptLock = new(); private readonly object _scriptLock = new();
private ListOperatorPredicateStartNode _startNode; private ListOperatorPredicateStartNode _startNode;
public ListOperatorPredicateNode() : base("List Operator (Advanced)", "Checks if any/all/no values in the input list match a condition") public ListOperatorPredicateNode()
{ {
_startNode = new ListOperatorPredicateStartNode {X = -200}; _startNode = new ListOperatorPredicateStartNode {X = -200};

View File

@ -19,7 +19,6 @@ public class ClampNode : Node
#region Constructors #region Constructors
public ClampNode() public ClampNode()
: base("Clamp", "Clamps the value to be in between min and max")
{ {
Value = CreateInputPin<Numeric>("Value"); Value = CreateInputPin<Numeric>("Value");
Min = CreateInputPin<Numeric>("Min"); Min = CreateInputPin<Numeric>("Min");

View File

@ -9,7 +9,6 @@ public class CounterNode : Node
private float _progress; private float _progress;
public CounterNode() public CounterNode()
: base("Counter", "Counts from 0.0 to 1.0 at a configurable rate.")
{ {
Time = CreateInputPin<Numeric>("Time (ms)"); Time = CreateInputPin<Numeric>("Time (ms)");
Output = CreateOutputPin<Numeric>(); Output = CreateOutputPin<Numeric>();

View File

@ -19,7 +19,6 @@ public class LerpNode : Node
#region Constructors #region Constructors
public LerpNode() public LerpNode()
: base("Lerp", "Interpolates linear between the two values A and B")
{ {
A = CreateInputPin<Numeric>("A"); A = CreateInputPin<Numeric>("A");
B = CreateInputPin<Numeric>("B"); B = CreateInputPin<Numeric>("B");

View File

@ -5,7 +5,7 @@ using NoStringEvaluating.Models.FormulaChecker;
namespace Artemis.VisualScripting.Nodes.Mathematics; namespace Artemis.VisualScripting.Nodes.Mathematics;
[Node("Math Expression", "Outputs the result of a math expression.", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))] [Node("Math Expression", "Outputs the result of a math expression.", "Mathematics", "https://wiki.artemis-rgb.com/en/guides/user/profiles/nodes/mathematics/math-expression", InputType = typeof(Numeric), OutputType = typeof(Numeric))]
public class MathExpressionNode : Node<string, MathExpressionNodeCustomViewModel> public class MathExpressionNode : Node<string, MathExpressionNodeCustomViewModel>
{ {
private readonly IFormulaChecker _checker; private readonly IFormulaChecker _checker;
@ -15,7 +15,6 @@ public class MathExpressionNode : Node<string, MathExpressionNodeCustomViewModel
#region Constructors #region Constructors
public MathExpressionNode(INoStringEvaluator evaluator, IFormulaChecker checker) public MathExpressionNode(INoStringEvaluator evaluator, IFormulaChecker checker)
: base("Math Expression", "Outputs the result of a math expression.")
{ {
_evaluator = evaluator; _evaluator = evaluator;
_checker = checker; _checker = checker;

View File

@ -8,7 +8,6 @@ public class MaxNumericsNode : Node
#region Constructors #region Constructors
public MaxNumericsNode() public MaxNumericsNode()
: base("Max", "Outputs the largest of the connected numeric values.")
{ {
Values = CreateInputPinCollection<Numeric>("Values", 2); Values = CreateInputPinCollection<Numeric>("Values", 2);
Max = CreateOutputPin<Numeric>("Max"); Max = CreateOutputPin<Numeric>("Max");

View File

@ -8,7 +8,6 @@ public class MinNumericsNode : Node
#region Constructors #region Constructors
public MinNumericsNode() public MinNumericsNode()
: base("Min", "Outputs the smallest of the connected numeric values.")
{ {
Values = CreateInputPinCollection<Numeric>("Values", 2); Values = CreateInputPinCollection<Numeric>("Values", 2);
Min = CreateOutputPin<Numeric>("Min"); Min = CreateOutputPin<Numeric>("Min");

View File

@ -19,7 +19,6 @@ public class RangeNode : Node
#region Constructors #region Constructors
public RangeNode() public RangeNode()
: base("Range", "Selects the best integer value in the given range by the given percentage")
{ {
Min = CreateInputPin<Numeric>("Min"); Min = CreateInputPin<Numeric>("Min");
Max = CreateInputPin<Numeric>("Max"); Max = CreateInputPin<Numeric>("Max");

View File

@ -5,7 +5,7 @@ namespace Artemis.VisualScripting.Nodes.Mathematics;
[Node("Round", "Outputs a rounded numeric value.", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))] [Node("Round", "Outputs a rounded numeric value.", "Mathematics", InputType = typeof(Numeric), OutputType = typeof(Numeric))]
public class RoundNode : Node public class RoundNode : Node
{ {
public RoundNode() : base("Round", "Outputs a rounded numeric value.") public RoundNode()
{ {
Input = CreateInputPin<Numeric>(); Input = CreateInputPin<Numeric>();
Output = CreateOutputPin<Numeric>(); Output = CreateOutputPin<Numeric>();

View File

@ -17,7 +17,6 @@ public class SaturateNode : Node
#region Constructors #region Constructors
public SaturateNode() public SaturateNode()
: base("Saturate", "Clamps the value to be in between 0 and 1")
{ {
Value = CreateInputPin<Numeric>(); Value = CreateInputPin<Numeric>();

View File

@ -8,7 +8,6 @@ public class SumNumericsNode : Node
#region Constructors #region Constructors
public SumNumericsNode() public SumNumericsNode()
: base("Sum", "Sums the connected numeric values.")
{ {
Values = CreateInputPinCollection<Numeric>("Values", 2); Values = CreateInputPinCollection<Numeric>("Values", 2);
Sum = CreateOutputPin<Numeric>("Sum"); Sum = CreateOutputPin<Numeric>("Sum");

View File

@ -8,7 +8,6 @@ public class AndNode : Node
#region Constructors #region Constructors
public AndNode() public AndNode()
: base("And", "Checks if all inputs are true.")
{ {
Input = CreateInputPinCollection<bool>(); Input = CreateInputPinCollection<bool>();
Result = CreateOutputPin<bool>(); Result = CreateOutputPin<bool>();

View File

@ -6,7 +6,7 @@ namespace Artemis.VisualScripting.Nodes.Operators;
[Node("Enum Equals", "Determines the equality between an input and a selected enum value", "Operators", InputType = typeof(Enum), OutputType = typeof(bool))] [Node("Enum Equals", "Determines the equality between an input and a selected enum value", "Operators", InputType = typeof(Enum), OutputType = typeof(bool))]
public class EnumEqualsNode : Node<long, EnumEqualsNodeCustomViewModel> public class EnumEqualsNode : Node<long, EnumEqualsNodeCustomViewModel>
{ {
public EnumEqualsNode() : base("Enum Equals", "Determines the equality between an input and a selected enum value") public EnumEqualsNode()
{ {
InputPin = CreateInputPin<Enum>(); InputPin = CreateInputPin<Enum>();
OutputPin = CreateOutputPin<bool>(); OutputPin = CreateOutputPin<bool>();

View File

@ -8,7 +8,6 @@ public class EqualsNode : Node
#region Constructors #region Constructors
public EqualsNode() public EqualsNode()
: base("Equals", "Checks if the two inputs are equals.")
{ {
Input1 = CreateInputPin<object>(); Input1 = CreateInputPin<object>();
Input2 = CreateInputPin<object>(); Input2 = CreateInputPin<object>();

View File

@ -9,7 +9,6 @@ public class GreaterThanNode : Node
#region Constructors #region Constructors
public GreaterThanNode() public GreaterThanNode()
: base("Greater than", "Checks if the first input is greater than the second.")
{ {
Input1 = CreateInputPin<object>(); Input1 = CreateInputPin<object>();
Input2 = CreateInputPin<object>(); Input2 = CreateInputPin<object>();

View File

@ -9,7 +9,6 @@ public class LessThanNode : Node
#region Constructors #region Constructors
public LessThanNode() public LessThanNode()
: base("Less than", "Checks if the first input is less than the second.")
{ {
Input1 = CreateInputPin<object>(); Input1 = CreateInputPin<object>();
Input2 = CreateInputPin<object>(); Input2 = CreateInputPin<object>();

View File

@ -8,7 +8,6 @@ public class NegateNode : Node
#region Constructors #region Constructors
public NegateNode() public NegateNode()
: base("Negate", "Negates the boolean.")
{ {
Input = CreateInputPin<bool>(); Input = CreateInputPin<bool>();
Output = CreateOutputPin<bool>(); Output = CreateOutputPin<bool>();

View File

@ -8,7 +8,6 @@ public class OrNode : Node
#region Constructors #region Constructors
public OrNode() public OrNode()
: base("Or", "Checks if any inputs are true.")
{ {
Input = CreateInputPinCollection<bool>(); Input = CreateInputPinCollection<bool>();
Result = CreateOutputPin<bool>(); Result = CreateOutputPin<bool>();

View File

@ -8,7 +8,6 @@ public class XorNode : Node
#region Constructors #region Constructors
public XorNode() public XorNode()
: base("Exclusive Or", "Checks if one of the inputs is true.")
{ {
Input = CreateInputPinCollection<bool>(); Input = CreateInputPinCollection<bool>();
Result = CreateOutputPin<bool>(); Result = CreateOutputPin<bool>();

View File

@ -6,7 +6,7 @@ namespace Artemis.VisualScripting.Nodes.Static;
[Node("Display Value", "Displays an input value for testing purposes.", "Static", InputType = typeof(object))] [Node("Display Value", "Displays an input value for testing purposes.", "Static", InputType = typeof(object))]
public class DisplayValueNode : Node<string, DisplayValueNodeCustomViewModel> public class DisplayValueNode : Node<string, DisplayValueNodeCustomViewModel>
{ {
public DisplayValueNode() : base("Display Value", "Displays an input value for testing purposes.") public DisplayValueNode()
{ {
Input = CreateInputPin<object>(); Input = CreateInputPin<object>();
} }

View File

@ -16,7 +16,6 @@ public class RandomNumericValueNode : Node
#region Constructors #region Constructors
public RandomNumericValueNode() public RandomNumericValueNode()
: base("Random", "Generates a random value between 0 and 1")
{ {
Output = CreateOutputPin<Numeric>(); Output = CreateOutputPin<Numeric>();
} }

View File

@ -9,8 +9,8 @@ public class StaticBooleanValueNode : Node<bool, StaticBooleanValueNodeCustomVie
#region Constructors #region Constructors
public StaticBooleanValueNode() public StaticBooleanValueNode()
: base("Boolean", "Outputs a configurable static boolean value.")
{ {
Name = "Boolean";
Output = CreateOutputPin<bool>(); Output = CreateOutputPin<bool>();
} }

View File

@ -9,8 +9,8 @@ public class StaticNumericValueNode : Node<Numeric, StaticNumericValueNodeCustom
#region Constructors #region Constructors
public StaticNumericValueNode() public StaticNumericValueNode()
: base("Numeric", "Outputs a configurable numeric value.")
{ {
Name = "Numeric";
Output = CreateOutputPin<Numeric>(); Output = CreateOutputPin<Numeric>();
} }

View File

@ -10,8 +10,8 @@ public class StaticSKColorValueNode : Node<SKColor, StaticSKColorValueNodeCustom
#region Constructors #region Constructors
public StaticSKColorValueNode() public StaticSKColorValueNode()
: base("Color", "Outputs a configurable color value.")
{ {
Name = "Color";
Output = CreateOutputPin<SKColor>(); Output = CreateOutputPin<SKColor>();
Storage = new SKColor(255, 0, 0); Storage = new SKColor(255, 0, 0);
} }

View File

@ -9,8 +9,8 @@ public class StaticStringValueNode : Node<string, StaticStringValueNodeCustomVie
#region Constructors #region Constructors
public StaticStringValueNode() public StaticStringValueNode()
: base("Text", "Outputs a configurable text value.")
{ {
Name = "Text";
Output = CreateOutputPin<string>(); Output = CreateOutputPin<string>();
} }

View File

@ -6,7 +6,6 @@ namespace Artemis.VisualScripting.Nodes.Text;
public class StringContainsNode : Node public class StringContainsNode : Node
{ {
public StringContainsNode() public StringContainsNode()
: base("Contains", "Checks whether the first input is contained in the second input.")
{ {
Input1 = CreateInputPin<string>(); Input1 = CreateInputPin<string>();
Input2 = CreateInputPin<string>(); Input2 = CreateInputPin<string>();

View File

@ -8,7 +8,6 @@ public class StringFormatNode : Node
#region Constructors #region Constructors
public StringFormatNode() public StringFormatNode()
: base("Format", "Formats the input string.")
{ {
Format = CreateInputPin<string>("Format"); Format = CreateInputPin<string>("Format");
Values = CreateInputPinCollection<object>("Values"); Values = CreateInputPinCollection<object>("Values");

View File

@ -7,7 +7,6 @@ namespace Artemis.VisualScripting.Nodes.Text;
public class StringLengthNode : Node public class StringLengthNode : Node
{ {
public StringLengthNode() public StringLengthNode()
: base("Text Length", "Outputs text length.")
{ {
Input1 = CreateInputPin<string>(); Input1 = CreateInputPin<string>();
Result = CreateOutputPin<Numeric>(); Result = CreateOutputPin<Numeric>();

View File

@ -7,7 +7,6 @@ namespace Artemis.VisualScripting.Nodes.Text;
public class StringNullOrEmptyNode : Node public class StringNullOrEmptyNode : Node
{ {
public StringNullOrEmptyNode() public StringNullOrEmptyNode()
: base("Text is empty", "Outputs true if empty")
{ {
Input1 = CreateInputPin<string>(); Input1 = CreateInputPin<string>();
Output1 = CreateOutputPin<bool>(); Output1 = CreateOutputPin<bool>();

View File

@ -10,7 +10,7 @@ public class StringRegexMatchNode : Node
private Regex? _regex; private Regex? _regex;
private Exception? _exception; private Exception? _exception;
public StringRegexMatchNode() : base("Regex Match", "Checks provided regex pattern matches the input.") public StringRegexMatchNode()
{ {
Pattern = CreateInputPin<string>("Pattern"); Pattern = CreateInputPin<string>("Pattern");
Input = CreateInputPin<string>("Input"); Input = CreateInputPin<string>("Input");

View File

@ -25,7 +25,6 @@ public class DelayNode : Node
#region Constructors #region Constructors
public DelayNode() public DelayNode()
: base("Delay", "Delays the resolution of the input pin(s) for the given time after each update")
{ {
Delay = CreateInputPin<Numeric>("Delay"); Delay = CreateInputPin<Numeric>("Delay");
Input = CreateInputPinCollection(typeof(object), initialCount: 0); Input = CreateInputPinCollection(typeof(object), initialCount: 0);

View File

@ -17,7 +17,6 @@ public class EdgeNode : Node
#region Constructors #region Constructors
public EdgeNode() public EdgeNode()
: base("Edge", "Outputs true on each edge when the input changes")
{ {
Input = CreateInputPin<bool>(); Input = CreateInputPin<bool>();
Output = CreateOutputPin<bool>(); Output = CreateOutputPin<bool>();

View File

@ -18,7 +18,6 @@ public class FlipFlopNode : Node
#region Constructors #region Constructors
public FlipFlopNode() public FlipFlopNode()
: base("FlipFlop", "Inverts the output when the input changes from false to true")
{ {
Input = CreateInputPin<bool>(); Input = CreateInputPin<bool>();
Output = CreateOutputPin<bool>(); Output = CreateOutputPin<bool>();

View File

@ -25,7 +25,6 @@ public class LatchNode : Node
#region Constructors #region Constructors
public LatchNode() public LatchNode()
: base("Latch", "Only passes the input to the output as long as the control-pin is true. If the control pin is false the last passed value is provided.")
{ {
Control = CreateInputPin<bool>("Control"); Control = CreateInputPin<bool>("Control");
Input = CreateInputPinCollection(typeof(object), initialCount: 0); Input = CreateInputPinCollection(typeof(object), initialCount: 0);

View File

@ -25,7 +25,6 @@ public class SequencerNode : Node
#region Constructors #region Constructors
public SequencerNode() public SequencerNode()
: base("Sequencer", "Advances on input every time the control has a rising edge (change to true)")
{ {
_currentType = typeof(object); _currentType = typeof(object);