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:
commit
20b332d0c9
@ -468,6 +468,8 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
|
|||||||
{
|
{
|
||||||
_stops.Add(item);
|
_stops.Add(item);
|
||||||
item.ColorGradient = this;
|
item.ColorGradient = this;
|
||||||
|
// Update the position, reapplying the overlap-check in Position's setter
|
||||||
|
item.Position = item.Position;
|
||||||
item.PropertyChanged += ItemOnPropertyChanged;
|
item.PropertyChanged += ItemOnPropertyChanged;
|
||||||
|
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item)));
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item)));
|
||||||
@ -624,4 +626,36 @@ public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionC
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
public ColorGradient Interpolate(ColorGradient targetValue, float progress)
|
||||||
|
{
|
||||||
|
ColorGradient interpolated = new(this);
|
||||||
|
|
||||||
|
// Add new stops
|
||||||
|
if (targetValue.Count > interpolated.Count)
|
||||||
|
{
|
||||||
|
// Prefer the stops on a vacant position
|
||||||
|
foreach (ColorGradientStop stop in targetValue.Take(targetValue.Count - interpolated.Count))
|
||||||
|
interpolated.Add(new ColorGradientStop(GetColor(stop.Position), stop.Position));
|
||||||
|
}
|
||||||
|
// Interpolate stops
|
||||||
|
int index = 0;
|
||||||
|
foreach (ColorGradientStop stop in interpolated.ToList())
|
||||||
|
{
|
||||||
|
if (index < targetValue.Count)
|
||||||
|
{
|
||||||
|
ColorGradientStop targetStop = targetValue[index];
|
||||||
|
stop.Interpolate(targetStop, progress);
|
||||||
|
}
|
||||||
|
// Interpolate stops not on the target gradient
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stop.Color = stop.Color.Interpolate(targetValue.GetColor(stop.Position), progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return interpolated;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -95,4 +95,10 @@ public class ColorGradientStop : CorePropertyChanged
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
public void Interpolate(ColorGradientStop targetValue, float progress)
|
||||||
|
{
|
||||||
|
Color = Color.Interpolate(targetValue.Color, progress);
|
||||||
|
Position = Position + ((targetValue.Position - Position) * progress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -237,7 +237,7 @@ public class LayerProperty<T> : CorePropertyChanged, ILayerProperty
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the base value of this layer property without any keyframes applied
|
/// Gets or sets the base value of this layer property without any keyframes or data bindings applied
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public T BaseValue
|
public T BaseValue
|
||||||
{
|
{
|
||||||
|
|||||||
@ -22,7 +22,13 @@ public class RampSKColorNode : Node<ColorGradient, RampSKColorNodeCustomViewMode
|
|||||||
|
|
||||||
public override void Evaluate()
|
public override void Evaluate()
|
||||||
{
|
{
|
||||||
Output.Value = Storage?.GetColor(Input.Value % 1.0) ?? SKColor.Empty;
|
// Wrap the input between 0 and 1
|
||||||
|
// 1 % 1 = 0, 2 % 1 = 0 etc. but we want that to be 1 but 0 should stay 0, call me stupid but this works and makes sense
|
||||||
|
float value = Input.Value % 1;
|
||||||
|
if (value == 0 && Input.Value != 0)
|
||||||
|
value = 1;
|
||||||
|
|
||||||
|
Output.Value = Storage?.GetColor(value) ?? SKColor.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@ -0,0 +1,71 @@
|
|||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.VisualScripting.Nodes.Easing;
|
||||||
|
|
||||||
|
[Node("Color Gradient Easing", "Outputs an eased color gradient value", "Easing", InputType = typeof(ColorGradient), OutputType = typeof(ColorGradient))]
|
||||||
|
public class ColorGradientEasingNode : Node
|
||||||
|
{
|
||||||
|
private DateTime _lastEvaluate = DateTime.MinValue;
|
||||||
|
private float _progress;
|
||||||
|
private ColorGradient? _currentValue;
|
||||||
|
private ColorGradient? _sourceValue;
|
||||||
|
private ColorGradient? _targetValue;
|
||||||
|
|
||||||
|
public ColorGradientEasingNode()
|
||||||
|
{
|
||||||
|
Input = CreateInputPin<ColorGradient>();
|
||||||
|
EasingTime = CreateInputPin<Numeric>("delay");
|
||||||
|
EasingFunction = CreateInputPin<Easings.Functions>("function");
|
||||||
|
|
||||||
|
Output = CreateOutputPin<ColorGradient>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputPin<ColorGradient> Input { get; set; }
|
||||||
|
public InputPin<Numeric> EasingTime { get; set; }
|
||||||
|
public InputPin<Easings.Functions> EasingFunction { get; set; }
|
||||||
|
|
||||||
|
public OutputPin<ColorGradient> Output { get; set; }
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
DateTime now = DateTime.Now;
|
||||||
|
|
||||||
|
if (Input.Value == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If the value changed reset progress
|
||||||
|
if (!Equals(_targetValue, Input.Value))
|
||||||
|
{
|
||||||
|
_sourceValue = _currentValue ?? new ColorGradient(Input.Value);
|
||||||
|
_targetValue = new ColorGradient(Input.Value);
|
||||||
|
_progress = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update until finished
|
||||||
|
if (_progress < 1f)
|
||||||
|
{
|
||||||
|
Update();
|
||||||
|
Output.Value = _currentValue;
|
||||||
|
}
|
||||||
|
// Stop updating past 1 and use the target value
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Output.Value = _targetValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastEvaluate = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (_sourceValue == null || _targetValue == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float easingTime = EasingTime.Value != 0f ? EasingTime.Value : 1f;
|
||||||
|
TimeSpan delta = DateTime.Now - _lastEvaluate;
|
||||||
|
|
||||||
|
// In case of odd delta's, keep progress between 0f and 1f
|
||||||
|
_progress = Math.Clamp(_progress + (float) delta.TotalMilliseconds / easingTime, 0f, 1f);
|
||||||
|
_currentValue = _sourceValue.Interpolate(_targetValue, (float) Easings.Interpolate(_progress, EasingFunction.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.Core.Services;
|
||||||
|
using Artemis.VisualScripting.Nodes.Input.Screens;
|
||||||
|
using SkiaSharp;
|
||||||
|
using static Artemis.VisualScripting.Nodes.Input.PressedKeyPositionNodeEntity;
|
||||||
|
|
||||||
|
namespace Artemis.VisualScripting.Nodes.Input;
|
||||||
|
|
||||||
|
[Node("Pressed Key Position", "Outputs the position of a pressed key relative to a layer", "Input", OutputType = typeof(Numeric))]
|
||||||
|
public class PressedKeyPositionNode : Node<PressedKeyPositionNodeEntity, PressedKeyPositionNodeCustomViewModel>, IDisposable
|
||||||
|
{
|
||||||
|
private readonly IInputService _inputService;
|
||||||
|
private Layer? _layer;
|
||||||
|
private SKPoint _ledPosition;
|
||||||
|
private Profile? _profile;
|
||||||
|
|
||||||
|
public PressedKeyPositionNode(IInputService inputService)
|
||||||
|
{
|
||||||
|
_inputService = inputService;
|
||||||
|
XPosition = CreateOutputPin<Numeric>("X");
|
||||||
|
YPosition = CreateOutputPin<Numeric>("Y");
|
||||||
|
|
||||||
|
StorageModified += OnStorageModified;
|
||||||
|
_inputService.KeyboardKeyDown += InputServiceOnKeyboardKeyDown;
|
||||||
|
_inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputPin<Numeric> XPosition { get; }
|
||||||
|
public OutputPin<Numeric> YPosition { get; }
|
||||||
|
|
||||||
|
public override void Initialize(INodeScript script)
|
||||||
|
{
|
||||||
|
Storage ??= new PressedKeyPositionNodeEntity();
|
||||||
|
|
||||||
|
_profile = script.Context as Profile;
|
||||||
|
_layer = _profile?.GetAllLayers().FirstOrDefault(l => l.EntityId == Storage.LayerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Evaluate()
|
||||||
|
{
|
||||||
|
XPosition.Value = _ledPosition.X;
|
||||||
|
YPosition.Value = _ledPosition.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InputServiceOnKeyboardKeyDown(object? sender, ArtemisKeyboardKeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (Storage?.RespondTo is KeyPressType.Down or KeyPressType.UpDown)
|
||||||
|
SetLedPosition(e.Led);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (Storage?.RespondTo is KeyPressType.Up or KeyPressType.UpDown)
|
||||||
|
SetLedPosition(e.Led);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetLedPosition(ArtemisLed? led)
|
||||||
|
{
|
||||||
|
if (_layer != null && led != null)
|
||||||
|
_ledPosition = new SKPoint((led.AbsoluteRectangle.MidX - _layer.Bounds.Left) / _layer.Bounds.Width, (led.AbsoluteRectangle.MidY - _layer.Bounds.Top) / _layer.Bounds.Height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStorageModified(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_layer = _profile?.GetAllLayers().FirstOrDefault(l => l.EntityId == Storage?.LayerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_inputService.KeyboardKeyDown -= InputServiceOnKeyboardKeyDown;
|
||||||
|
_inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace Artemis.VisualScripting.Nodes.Input;
|
||||||
|
|
||||||
|
public class PressedKeyPositionNodeEntity
|
||||||
|
{
|
||||||
|
public PressedKeyPositionNodeEntity()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PressedKeyPositionNodeEntity(Guid layerId, KeyPressType respondTo)
|
||||||
|
{
|
||||||
|
LayerId = layerId;
|
||||||
|
RespondTo = respondTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid LayerId { get; set; }
|
||||||
|
public KeyPressType RespondTo { get; set; }
|
||||||
|
|
||||||
|
public enum KeyPressType
|
||||||
|
{
|
||||||
|
[Description("Up")]
|
||||||
|
Up,
|
||||||
|
[Description("Down")]
|
||||||
|
Down,
|
||||||
|
[Description("Up/down")]
|
||||||
|
UpDown
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:screens="clr-namespace:Artemis.VisualScripting.Nodes.Input.Screens"
|
||||||
|
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
||||||
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
|
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.VisualScripting.Nodes.Input.Screens.PressedKeyPositionNodeCustomView"
|
||||||
|
x:DataType="screens:PressedKeyPositionNodeCustomViewModel">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock FontSize="13" Margin="0 -2 0 5">Layer</TextBlock>
|
||||||
|
<ComboBox Classes="condensed" Items="{CompiledBinding Layers}" SelectedItem="{CompiledBinding SelectedLayer}" PlaceholderText="Select a layer">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate DataType="core:Layer">
|
||||||
|
<StackPanel Spacing="5" Orientation="Horizontal">
|
||||||
|
<avalonia:MaterialIcon Kind="{CompiledBinding LayerBrush.Descriptor.Icon, FallbackValue=QuestionMark}" VerticalAlignment="Center" />
|
||||||
|
<TextBlock Text="{CompiledBinding Name}" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
|
||||||
|
<TextBlock FontSize="13" Margin="0 5">Respond to</TextBlock>
|
||||||
|
<shared:EnumComboBox Classes="condensed" Value="{CompiledBinding RespondTo}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
|
namespace Artemis.VisualScripting.Nodes.Input.Screens;
|
||||||
|
|
||||||
|
public partial class PressedKeyPositionNodeCustomView : ReactiveUserControl<PressedKeyPositionNodeCustomViewModel>
|
||||||
|
{
|
||||||
|
public PressedKeyPositionNodeCustomView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor;
|
||||||
|
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||||
|
using Artemis.UI.Shared.VisualScripting;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using ReactiveUI;
|
||||||
|
using static Artemis.VisualScripting.Nodes.Input.PressedKeyPositionNodeEntity;
|
||||||
|
|
||||||
|
namespace Artemis.VisualScripting.Nodes.Input.Screens;
|
||||||
|
|
||||||
|
public class PressedKeyPositionNodeCustomViewModel : CustomNodeViewModel
|
||||||
|
{
|
||||||
|
private readonly INodeEditorService _nodeEditorService;
|
||||||
|
private readonly Profile? _profile;
|
||||||
|
private readonly PressedKeyPositionNode _node;
|
||||||
|
private Layer? _selectedLayer;
|
||||||
|
private KeyPressType _respondTo;
|
||||||
|
|
||||||
|
public PressedKeyPositionNodeCustomViewModel(PressedKeyPositionNode node, INodeScript script, INodeEditorService nodeEditorService) : base(node, script)
|
||||||
|
{
|
||||||
|
_nodeEditorService = nodeEditorService;
|
||||||
|
_profile = script.Context as Profile;
|
||||||
|
_node = node;
|
||||||
|
|
||||||
|
Layers = new ObservableCollection<Layer>();
|
||||||
|
|
||||||
|
this.WhenActivated(d =>
|
||||||
|
{
|
||||||
|
if (_profile == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Observable.FromEventPattern<ProfileElementEventArgs>(x => _profile.DescendentAdded += x, x => _profile.DescendentAdded -= x).Subscribe(_ => GetLayers()).DisposeWith(d);
|
||||||
|
Observable.FromEventPattern<ProfileElementEventArgs>(x => _profile.DescendentRemoved += x, x => _profile.DescendentRemoved -= x).Subscribe(_ => GetLayers()).DisposeWith(d);
|
||||||
|
Observable.FromEventPattern(x => _node.StorageModified += x, x => _node.StorageModified -= x).Subscribe(_ => Update()).DisposeWith(d);
|
||||||
|
|
||||||
|
GetLayers();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.WhenAnyValue(vm => vm.SelectedLayer).Subscribe(UpdateSelectedLayer);
|
||||||
|
this.WhenAnyValue(vm => vm.RespondTo).Subscribe(UpdateSelectedRespondTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<Layer> Layers { get; }
|
||||||
|
|
||||||
|
public Layer? SelectedLayer
|
||||||
|
{
|
||||||
|
get => _selectedLayer;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _selectedLayer, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyPressType RespondTo
|
||||||
|
{
|
||||||
|
get => _respondTo;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _respondTo, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetLayers()
|
||||||
|
{
|
||||||
|
Layers.Clear();
|
||||||
|
if (_profile == null)
|
||||||
|
return;
|
||||||
|
foreach (Layer layer in _profile.GetAllLayers())
|
||||||
|
Layers.Add(layer);
|
||||||
|
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
SelectedLayer = Layers.FirstOrDefault(l => l.EntityId == _node.Storage?.LayerId);
|
||||||
|
RespondTo = _node.Storage?.RespondTo ?? KeyPressType.Up;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSelectedLayer(Layer? layer)
|
||||||
|
{
|
||||||
|
if (layer == null || _node.Storage?.LayerId == layer.EntityId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_nodeEditorService.ExecuteCommand(
|
||||||
|
Script,
|
||||||
|
new UpdateStorage<PressedKeyPositionNodeEntity>(_node, new PressedKeyPositionNodeEntity(layer.EntityId, _node.Storage?.RespondTo ?? KeyPressType.Up), "layer")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSelectedRespondTo(KeyPressType respondTo)
|
||||||
|
{
|
||||||
|
if (_node.Storage?.RespondTo == respondTo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_nodeEditorService.ExecuteCommand(Script, new UpdateStorage<PressedKeyPositionNodeEntity>(_node, new PressedKeyPositionNodeEntity(_node.Storage?.LayerId ?? Guid.Empty, respondTo), "layer"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user