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:
runs-on: ubuntu-latest
outputs:
version-suffix: ${{ steps.get-version.outputs.version-suffix }}
version-number: ${{ steps.get-version.outputs.version-number }}
steps:
- name: Checkout
uses: actions/checkout@v3
@ -22,9 +22,12 @@ jobs:
run: |
$MidnightUtc = [DateTime]::UtcNow.Date
$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
$VersionSuffix = "$BranchName.$($MidnightUtc.ToString("yyyyMMdd")).$NumberOfCommitsToday"
Write-Output "::set-output name=version-suffix::$VersionSuffix"
$VersionNumber = "$ApiVersion.$($MidnightUtc.ToString("yyyy.MMdd")).$NumberOfCommitsToday"
# 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:
name: Publish Nuget Packages
@ -38,8 +41,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- 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
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
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>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageId>ArtemisRGB.Core</PackageId>
<!-- API Version -->
<VersionPrefix>1.0.0.0</VersionPrefix>
<PluginApiVersion>1</PluginApiVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Artemis.Storage\Artemis.Storage.csproj"/>
<ProjectReference Condition="'$(BuildingNuget)' == 'True'" Update="..\Artemis.Storage\Artemis.Storage.csproj" PrivateAssets="All"/>
<!--Used to embed the above PluginApiVersion property into the assembly as metadata-->
<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

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using Artemis.Core.JsonConverters;
using Artemis.Core.Services;
@ -61,7 +62,8 @@ public static class Constants
/// <summary>
/// The current API version for plugins
/// </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>
/// The plugin info used by core components of Artemis

View File

@ -16,12 +16,13 @@ namespace Artemis.Core;
/// </summary>
public sealed class Layer : RenderProfileElement
{
private readonly List<Layer> _renderCopies;
private LayerGeneralProperties _general;
private readonly List<Layer> _renderCopies = new();
private LayerGeneralProperties _general = new();
private LayerTransformProperties _transform = new();
private BaseLayerBrush? _layerBrush;
private LayerShape? _layerShape;
private List<ArtemisLed> _leds;
private LayerTransformProperties _transform;
private List<ArtemisLed> _leds = new();
private List<LedEntity> _missingLeds = new();
/// <summary>
/// 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;
Name = name;
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);
Adapter = new LayerAdapter(this);
Initialize();
}
@ -63,16 +57,9 @@ public sealed class Layer : RenderProfileElement
Profile = profile;
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);
Adapter = new LayerAdapter(this);
Load();
Initialize();
}
@ -327,6 +314,7 @@ public sealed class Layer : RenderProfileElement
// LEDs
LayerEntity.Leds.Clear();
SaveMissingLeds();
foreach (ArtemisLed artemisLed in Leds)
{
LedEntity ledEntity = new()
@ -786,6 +774,8 @@ public sealed class Layer : RenderProfileElement
a.RgbLed.Id.ToString() == ledEntity.LedName);
if (match != null)
leds.Add(match);
else
_missingLeds.Add(ledEntity);
}
_leds = leds;
@ -793,6 +783,11 @@ public sealed class Layer : RenderProfileElement
CalculateRenderProperties();
}
private void SaveMissingLeds()
{
LayerEntity.Leds.AddRange(_missingLeds.Except(LayerEntity.Leds, LedEntity.LedEntityComparer));
}
#endregion
#region Brush management

View File

@ -191,7 +191,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
/// <summary>
/// Gets a boolean indicating whether this plugin is compatible with the current operating system and API version
/// </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)}";

View File

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

View File

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

View File

@ -24,6 +24,11 @@ public class NodeAttribute : Attribute
/// </summary>
public string Category { get; } = string.Empty;
/// <summary>
/// Gets the help URL of the node
/// </summary>
public string HelpUrl { get; init; } = string.Empty;
/// <summary>
/// Gets the primary input type of the node
/// </summary>
@ -65,5 +70,16 @@ public class NodeAttribute : Attribute
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
}

View File

@ -1,5 +1,6 @@
using System;
using Artemis.Storage.Entities.Profile.Nodes;
using Castle.Core.Internal;
namespace Artemis.Core;
@ -10,13 +11,14 @@ public class NodeData
{
#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;
Type = type;
Name = name;
Description = description;
Category = category;
HelpUrl = helpUrl;
InputType = inputType;
OutputType = outputType;
_create = create;
@ -34,7 +36,15 @@ public class NodeData
/// <returns>The returning node of type <see cref="Type" /></returns>
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
@ -101,6 +111,11 @@ public class NodeData
/// </summary>
public string Category { get; }
/// <summary>
/// Gets the help URL of the node this data represents
/// </summary>
public string HelpUrl { get; }
/// <summary>
/// Gets the primary input type of the node this data represents
/// </summary>

View File

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

View File

@ -4,6 +4,22 @@ namespace Artemis.Storage.Entities.Profile.Nodes;
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 Guid SourceNode { get; set; }
public Guid TargetNode { get; set; }

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Artemis.Storage.Entities.Profile.Nodes;
@ -10,6 +11,22 @@ public class NodeEntity
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 string Type { get; set; }
public Guid PluginId { get; set; }

View File

@ -2,6 +2,17 @@
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 Direction { set; get; }
public int Amount { get; set; }

View File

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

View File

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

View File

@ -8,9 +8,10 @@
<!-- Add Controls for Previewer Here -->
<TextBox Text="99999999"
<TextBox Text="99999999"
attached:TextBoxAssist.PrefixText="%"
attached:TextBoxAssist.SuffixText="%"></TextBox>
attached:TextBoxAssist.SuffixText="%">
</TextBox>
<controls:NumberBox Value="99999999"
attached:NumberBoxAssist.PrefixText="%"
attached:NumberBoxAssist.SuffixText="%" />
@ -30,80 +31,95 @@
<!-- Add Styles Here -->
<Style Selector="TextBox">
<Setter Property="Template">
<ControlTemplate>
<DataValidationErrors>
<Panel>
<!-- This is flipped (scaleY(-1)) for the elevation brush effect
<Setter Property="Template">
<ControlTemplate>
<DataValidationErrors>
<Panel>
<!-- This is flipped (scaleY(-1)) for the elevation brush effect
-->
<Border
Name="PART_BorderElement"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
RenderTransform="scaleY(-1)"
CornerRadius="{TemplateBinding CornerRadius}"/>
<Border
Name="PART_BorderElement"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
RenderTransform="scaleY(-1)"
CornerRadius="{TemplateBinding CornerRadius}" />
<Border
Margin="{TemplateBinding BorderThickness}">
<Grid ColumnDefinitions="Auto,*,Auto" >
<ContentPresenter Grid.Column="0" Content="{TemplateBinding InnerLeftContent}"/>
<DockPanel x:Name="PART_InnerDockPanel" Grid.Column="1" Margin="{TemplateBinding Padding}">
<TextBlock Name="PART_Prefix"
Text="{TemplateBinding attached:TextBoxAssist.PrefixText}"
IsHitTestVisible="False"
DockPanel.Dock="Left"/>
<TextBlock Name="PART_FloatingWatermark"
<Border
Margin="{TemplateBinding BorderThickness}">
<Grid ColumnDefinitions="Auto,*,Auto">
<ContentPresenter Grid.Column="0" Content="{TemplateBinding InnerLeftContent}" />
<Grid x:Name="PART_InnerGrid"
Grid.Column="1"
RowDefinitions="Auto,Auto"
ColumnDefinitions="Auto,*,Auto"
Cursor="IBeam"
Margin="{TemplateBinding Padding}">
<TextBlock Grid.Row="0"
Grid.ColumnSpan="3"
Name="PART_FloatingWatermark"
Foreground="{DynamicResource SystemAccentColor}"
FontSize="{TemplateBinding FontSize}"
Text="{TemplateBinding Watermark}"
DockPanel.Dock="Top" />
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<Panel>
<TextBlock Name="PART_Watermark"
Text="{TemplateBinding Watermark}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
IsHitTestVisible="False"/>
<TextPresenter Name="PART_TextPresenter"
Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}"
SelectionStart="{TemplateBinding SelectionStart}"
SelectionEnd="{TemplateBinding SelectionEnd}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"
PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
CaretBrush="{TemplateBinding CaretBrush}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Panel>
</ScrollViewer>
Text="{TemplateBinding Watermark}" />
<TextBlock Name="PART_Suffix"
Text="{TemplateBinding attached:TextBoxAssist.SuffixText}"
<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"
HorizontalAlignment="Right"
DockPanel.Dock="Right"/>
</DockPanel>
<ContentPresenter Grid.Column="2" Content="{TemplateBinding InnerRightContent}"/>
</Grid>
</Border>
</Panel>
</DataValidationErrors>
</ControlTemplate>
</Setter>
DockPanel.Dock="Left" />
<ScrollViewer Grid.Row="1"
Grid.Column="1"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<Panel>
<TextBlock Name="PART_Watermark"
Text="{TemplateBinding Watermark}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
IsHitTestVisible="False" />
<TextPresenter Name="PART_TextPresenter"
Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}"
SelectionStart="{TemplateBinding SelectionStart}"
SelectionEnd="{TemplateBinding SelectionEnd}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"
PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
CaretBrush="{TemplateBinding CaretBrush}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Panel>
</ScrollViewer>
<TextBlock Grid.Row="1"
Grid.Column="2"
Name="PART_Suffix"
Text="{TemplateBinding attached:TextBoxAssist.SuffixText}"
IsVisible="{TemplateBinding attached:TextBoxAssist.SuffixText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
IsHitTestVisible="False"
HorizontalAlignment="Right" />
</Grid>
<ContentPresenter Grid.Column="2" Content="{TemplateBinding InnerRightContent}" />
</Grid>
</Border>
</Panel>
</DataValidationErrors>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TextBox /template/ TextBlock#PART_Prefix">
<Style Selector="TextBox /template/ TextBlock#PART_Prefix">
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundDisabled}"></Setter>
<Setter Property="Margin" Value="-4 0 4 0"></Setter>
</Style>

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>
<Grid RowDefinitions="60,Auto,Auto,*,Auto,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"
FontSize="24"
VerticalAlignment="Center"
@ -46,6 +46,8 @@
Width="44"
Height="44"
ToolTip.Tip="View website"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://artemis-rgb.com">
<avalonia:MaterialIcon Kind="Web" Width="20" Height="20" />
</controls:HyperlinkButton>
@ -53,6 +55,8 @@
Width="44"
Height="44"
ToolTip.Tip="View GitHub repository"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" Width="20" Height="20" />
</controls:HyperlinkButton>
@ -60,6 +64,8 @@
Width="44"
Height="44"
ToolTip.Tip="View Wiki"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://wiki.artemis-rgb.com">
<avalonia:MaterialIcon Kind="BookOpenOutline" Width="20" Height="20" />
</controls:HyperlinkButton>
@ -67,6 +73,8 @@
Width="44"
Height="44"
ToolTip.Tip="Join our Discord"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://discord.gg/S3MVaC9">
<avalonia:MaterialIcon Kind="Chat" Width="20" Height="20" />
</controls:HyperlinkButton>
@ -74,6 +82,8 @@
Width="44"
Height="44"
ToolTip.Tip="View donation options"
ToolTip.Placement="Top"
ToolTip.VerticalOffset="-5"
NavigateUri="https://wiki.artemis-rgb.com/en/donating">
<avalonia:MaterialIcon Kind="Gift" Width="20" Height="20" />
</controls:HyperlinkButton>

View File

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

View File

@ -8,6 +8,7 @@ using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.ReactiveUI;
using Avalonia.Rendering;
using ReactiveUI;
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);
ViewModel.WhenAnyValue(vm => vm.FromPoint).Subscribe(_ => Update()).DisposeWith(d);
ViewModel.WhenAnyValue(vm => vm.ToPoint).Subscribe(_ => Update()).DisposeWith(d);
Update();
ViewModel.WhenAnyValue(vm => vm.FromPoint).Subscribe(_ => Update(true)).DisposeWith(d);
ViewModel.WhenAnyValue(vm => vm.ToPoint).Subscribe(_ => Update(false)).DisposeWith(d);
Update(true);
});
}
@ -40,10 +41,12 @@ public class CableView : ReactiveUserControl<CableViewModel>
AvaloniaXamlLoader.Load(this);
}
private void Update()
private void Update(bool from)
{
// 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();
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.Point2 = new Point(ViewModel.ToPoint.X - CABLE_OFFSET, 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)

View File

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Artemis.Core;
@ -27,7 +28,6 @@ public class CableViewModel : ActivatableViewModelBase
private PinViewModel? _fromViewModel;
private ObservableAsPropertyHelper<Point>? _toPoint;
private PinViewModel? _toViewModel;
private ObservableAsPropertyHelper<Point>? _valuePoint;
public CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to, ISettingsService settingsService)
{
@ -63,12 +63,7 @@ public class CableViewModel : ActivatableViewModelBase
.Switch()
.ToProperty(this, vm => vm.ToPoint)
.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)
_connected = this.WhenAnyValue(vm => vm.FromPoint, vm => vm.ToPoint)
.Select(tuple => tuple.Item1 != new Point(0, 0) && tuple.Item2 != new Point(0, 0))
@ -104,11 +99,10 @@ public class CableViewModel : ActivatableViewModelBase
}
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 ToPoint => _toPoint?.Value ?? new Point();
public Point ValuePoint => _valuePoint?.Value ?? new Point();
public Color CableColor => _cableColor?.Value ?? new Color(255, 255, 255, 255);
public void UpdateDisplayValue(bool hoveringOver)

View File

@ -30,6 +30,8 @@
<KeyBinding Command="{CompiledBinding ClearSelection}" Gesture="Escape" />
<KeyBinding Command="{CompiledBinding DeleteSelected}" Gesture="Delete" />
<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.Redo}" Gesture="Ctrl+Y" />
</UserControl.KeyBindings>

View File

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

View File

@ -6,9 +6,12 @@ using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Events;
using Artemis.Core.Services;
using Artemis.UI.Models;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.VisualScripting.Pins;
using Artemis.UI.Shared;
@ -16,6 +19,7 @@ using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.Services.NodeEditor.Commands;
using Avalonia;
using Avalonia.Controls.Mixins;
using Avalonia.Input;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;
@ -24,6 +28,8 @@ namespace Artemis.UI.Screens.VisualScripting;
public class NodeScriptViewModel : ActivatableViewModelBase
{
public const string CLIPBOARD_DATA_FORMAT = "Artemis.Nodes";
private readonly INodeEditorService _nodeEditorService;
private readonly INodeService _nodeService;
private readonly SourceList<NodeViewModel> _nodeViewModels;
@ -33,6 +39,7 @@ public class NodeScriptViewModel : ActivatableViewModelBase
private DragCableViewModel? _dragViewModel;
private List<NodeViewModel>? _initialNodeSelection;
private Matrix _panMatrix;
private Point _pastePosition;
public NodeScriptViewModel(NodeScript nodeScript, bool isPreview, INodeVmFactory nodeVmFactory, INodeService nodeService, INodeEditorService nodeEditorService)
{
@ -86,8 +93,8 @@ public class NodeScriptViewModel : ActivatableViewModelBase
ClearSelection = ReactiveCommand.Create(ExecuteClearSelection);
DeleteSelected = ReactiveCommand.Create(ExecuteDeleteSelected);
DuplicateSelected = ReactiveCommand.Create(ExecuteDuplicateSelected);
CopySelected = ReactiveCommand.Create(ExecuteCopySelected);
PasteSelected = ReactiveCommand.Create(ExecutePasteSelected);
CopySelected = ReactiveCommand.CreateFromTask(ExecuteCopySelected);
PasteSelected = ReactiveCommand.CreateFromTask(ExecutePasteSelected);
}
public NodeScript NodeScript { get; }
@ -118,6 +125,12 @@ public class NodeScriptViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _panMatrix, value);
}
public Point PastePosition
{
get => _pastePosition;
set => RaiseAndSetIfChanged(ref _pastePosition, value);
}
public void DeleteSelectedNodes()
{
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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
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"
x:Class="Artemis.UI.Screens.VisualScripting.NodeView"
x:DataType="visualScripting:NodeViewModel">
@ -37,7 +38,7 @@
ClipToBounds="True"
Background="{DynamicResource ContentDialogBackground}">
<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"
VerticalAlignment="Center"
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}" />
<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>
</Button>
</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))]
public class BooleanBranchNode : Node
{
public BooleanBranchNode() : base("Branch", "Forwards one of two values depending on an input boolean")
public BooleanBranchNode()
{
BooleanInput = CreateInputPin<bool>();
TrueInput = CreateInputPin(typeof(object), "True");

View File

@ -9,7 +9,7 @@ public class EnumSwitchNode : Node
{
private readonly Dictionary<Enum, InputPin> _inputPins;
public EnumSwitchNode() : base("Enum Branch", "desc")
public EnumSwitchNode()
{
_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))]
public class BrightenSKColorNode : Node
{
public BrightenSKColorNode() : base("Brighten Color", "Brightens a color by a specified amount in percent")
public BrightenSKColorNode()
{
Input = CreateInputPin<SKColor>("Color");
Percentage = CreateInputPin<Numeric>("%");

View File

@ -11,7 +11,7 @@ public class ColorGradientFromPinsNode : Node
public InputPinCollection<SKColor> Colors { 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);
Positions = CreateInputPinCollection<Numeric>("Positions", 0);

View File

@ -10,7 +10,7 @@ public class ColorGradientNode : Node<ColorGradient, ColorGradientNodeCustomView
{
private readonly List<InputPin> _inputPins;
public ColorGradientNode() : base("Color Gradient", "Outputs a color gradient with the given colors")
public ColorGradientNode()
{
_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))]
public class DarkenSKColorNode : Node
{
public DarkenSKColorNode() : base("Darken Color", "Darkens a color by a specified amount in percent")
public DarkenSKColorNode()
{
Input = CreateInputPin<SKColor>("Color");
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))]
public class DesaturateSKColorNode : Node
{
public DesaturateSKColorNode() : base("Desaturate Color", "Desaturates a color by a specified amount in percent")
public DesaturateSKColorNode()
{
Input = CreateInputPin<SKColor>("Color");
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))]
public class HslSKColorNode : Node
{
public HslSKColorNode() : base("HSL Color", "Creates a color from hue, saturation and lightness values")
public HslSKColorNode()
{
H = CreateInputPin<Numeric>("H");
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))]
public class InvertSKColorNode : Node
{
public InvertSKColorNode() : base("Invert Color", "Inverts a color")
public InvertSKColorNode()
{
Input = CreateInputPin<SKColor>();
Output = CreateOutputPin<SKColor>();

View File

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

View File

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

View File

@ -18,7 +18,6 @@ public class RgbSKColorNode : Node
#region Constructors
public RgbSKColorNode()
: base("RGB Color", "Creates a color from red, green and blue values")
{
R = CreateInputPin<Numeric>("R");
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))]
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");
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))]
public class SaturateSKColorNode : Node
{
public SaturateSKColorNode() : base("Saturate Color", "Saturates a color by a specified amount in percent")
public SaturateSKColorNode()
{
Input = CreateInputPin<SKColor>("Color");
Percentage = CreateInputPin<Numeric>("%");

View File

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

View File

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

View File

@ -2,13 +2,12 @@
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
{
#region Constructors
public ConvertToStringNode()
: base("To String", "Converts the input to a string.")
{
Input = CreateInputPin<object>();
String = CreateOutputPin<string>();

View File

@ -15,7 +15,7 @@ public class DataModelEventCycleNode : Node<DataModelPathEntity, DataModelEventC
private DateTime _lastTrigger;
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);

View File

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

View File

@ -9,7 +9,7 @@ public class DataModelNode : Node<DataModelPathEntity, DataModelNodeCustomViewMo
{
private DataModelPath? _dataModelPath;
public DataModelNode() : base("Data Model", "Outputs a selectable data model value")
public DataModelNode()
{
Output = CreateOutputPin(typeof(object));
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))]
public class EasingTypeNode : Node<Easings.Functions, EasingTypeNodeCustomViewModel>
{
public EasingTypeNode() : base("Easing Type", "Outputs a selectable easing type.")
public EasingTypeNode()
{
Output = CreateOutputPin<Easings.Functions>();
}

View File

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

View File

@ -12,7 +12,7 @@ public class SKColorEasingNode : Node
private SKColor _sourceValue;
private SKColor _targetValue;
public SKColorEasingNode() : base("Color Easing", "Outputs an eased color value")
public SKColorEasingNode()
{
Input = CreateInputPin<SKColor>();
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))]
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>();
InputValue = CreateInputPin<object>();

View File

@ -11,7 +11,7 @@ public class ListOperatorPredicateNode : Node<ListOperatorEntity, ListOperatorPr
private readonly object _scriptLock = new();
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};

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ using NoStringEvaluating.Models.FormulaChecker;
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>
{
private readonly IFormulaChecker _checker;
@ -15,7 +15,6 @@ public class MathExpressionNode : Node<string, MathExpressionNodeCustomViewModel
#region Constructors
public MathExpressionNode(INoStringEvaluator evaluator, IFormulaChecker checker)
: base("Math Expression", "Outputs the result of a math expression.")
{
_evaluator = evaluator;
_checker = checker;

View File

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

View File

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

View File

@ -19,7 +19,6 @@ public class RangeNode : Node
#region Constructors
public RangeNode()
: base("Range", "Selects the best integer value in the given range by the given percentage")
{
Min = CreateInputPin<Numeric>("Min");
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))]
public class RoundNode : Node
{
public RoundNode() : base("Round", "Outputs a rounded numeric value.")
public RoundNode()
{
Input = CreateInputPin<Numeric>();
Output = CreateOutputPin<Numeric>();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,6 @@ public class XorNode : Node
#region Constructors
public XorNode()
: base("Exclusive Or", "Checks if one of the inputs is true.")
{
Input = CreateInputPinCollection<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))]
public class DisplayValueNode : Node<string, DisplayValueNodeCustomViewModel>
{
public DisplayValueNode() : base("Display Value", "Displays an input value for testing purposes.")
public DisplayValueNode()
{
Input = CreateInputPin<object>();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,7 +25,6 @@ public class LatchNode : Node
#region Constructors
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");
Input = CreateInputPinCollection(typeof(object), initialCount: 0);

View File

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