diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index 535046f29..13a2166e3 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -75,6 +75,9 @@ True True True + True + True + True True True True diff --git a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs index 8c54d17f5..26c262cd0 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/EventCondition.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Artemis.Core.Internal; +using Artemis.Core.VisualScripting.Internal; using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Conditions; @@ -13,13 +14,15 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition { private readonly string _displayName; private readonly EventConditionEntity _entity; - private EventDefaultNode _eventNode; + private IEventConditionNode _startNode; private DataModelPath? _eventPath; - private DateTime _lastProcessedTrigger; - private EventOverlapMode _overlapMode; private NodeScript _script; - private EventTriggerMode _triggerMode; private bool _wasMet; + private DateTime _lastProcessedTrigger; + private object? _lastProcessedValue; + private EventOverlapMode _overlapMode; + private EventTriggerMode _triggerMode; + private EventToggleOffMode _toggleOffMode; /// /// Creates a new instance of the class @@ -30,7 +33,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition _entity = new EventConditionEntity(); _displayName = profileElement.GetType().Name; - _eventNode = new EventDefaultNode {X = -300}; + _startNode = new EventConditionEventStartNode {X = -300}; _script = new NodeScript($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile); } @@ -40,7 +43,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition _entity = entity; _displayName = profileElement.GetType().Name; - _eventNode = new EventDefaultNode(); + _startNode = new EventConditionEventStartNode(); _script = null!; Load(); @@ -83,18 +86,70 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition set => SetAndNotify(ref _overlapMode, value); } + /// + /// Gets or sets the mode for render elements when toggling off the event when using . + /// + public EventToggleOffMode ToggleOffMode + { + get => _toggleOffMode; + set => SetAndNotify(ref _toggleOffMode, value); + } + /// /// Updates the event node, applying the selected event /// public void UpdateEventNode() { - IDataModelEvent? dataModelEvent = EventPath?.GetValue() as IDataModelEvent; - _eventNode.CreatePins(dataModelEvent); + if (EventPath == null) + return; - if (dataModelEvent != null && !Script.Nodes.Contains(_eventNode)) - Script.AddNode(_eventNode); - else if (dataModelEvent == null && Script.Nodes.Contains(_eventNode)) - Script.RemoveNode(_eventNode); + Type? pathType = EventPath.GetPropertyType(); + if (pathType == null) + return; + + // Create an event node if the path type is a data model event + if (pathType.IsAssignableTo(typeof(IDataModelEvent))) + { + EventConditionEventStartNode eventNode; + // Ensure the start node is an event node + if (_startNode is not EventConditionEventStartNode node) + { + eventNode = new EventConditionEventStartNode(); + ReplaceStartNode(eventNode); + _startNode = eventNode; + } + else + eventNode = node; + + IDataModelEvent? dataModelEvent = EventPath?.GetValue() as IDataModelEvent; + eventNode.CreatePins(dataModelEvent); + } + // Create a value changed node if the path type is a regular value + else + { + // Ensure the start nod is a value changed node + EventConditionValueChangedStartNode valueChangedNode; + // Ensure the start node is an event node + if (_startNode is not EventConditionValueChangedStartNode node) + { + valueChangedNode = new EventConditionValueChangedStartNode(); + ReplaceStartNode(valueChangedNode); + } + else + valueChangedNode = node; + + valueChangedNode.UpdateOutputPins(EventPath); + } + } + + private void ReplaceStartNode(IEventConditionNode newStartNode) + { + if (Script.Nodes.Contains(_startNode)) + Script.RemoveNode(_startNode); + + _startNode = newStartNode; + if (!Script.Nodes.Contains(_startNode)) + Script.AddNode(_startNode); } /// @@ -103,15 +158,30 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition /// The start node of the event script, if any. public INode GetStartNode() { - return _eventNode; + return _startNode; } private bool Evaluate() { - if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger) + if (EventPath == null) return false; - _lastProcessedTrigger = dataModelEvent.LastTrigger; + object? value = EventPath.GetValue(); + if (_startNode is EventConditionEventStartNode) + { + if (value is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger) + return false; + + _lastProcessedTrigger = dataModelEvent.LastTrigger; + } + else if (_startNode is EventConditionValueChangedStartNode valueChangedNode) + { + if (Equals(value, _lastProcessedValue)) + return false; + + valueChangedNode.UpdateValues(value, _lastProcessedValue); + _lastProcessedValue = value; + } if (!Script.ExitNodeConnected) return true; @@ -151,6 +221,8 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition { if (IsMet && !_wasMet) ProfileElement.Timeline.JumpToStart(); + if (!IsMet && _wasMet && ToggleOffMode == EventToggleOffMode.SkipToEnd) + ProfileElement.Timeline.JumpToEndSegment(); } else { @@ -191,6 +263,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition { TriggerMode = (EventTriggerMode) _entity.TriggerMode; OverlapMode = (EventOverlapMode) _entity.OverlapMode; + ToggleOffMode = (EventToggleOffMode) _entity.ToggleOffMode; if (_entity.EventPath != null) EventPath = new DataModelPath(_entity.EventPath); @@ -206,6 +279,8 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition { _entity.TriggerMode = (int) TriggerMode; _entity.OverlapMode = (int) OverlapMode; + _entity.ToggleOffMode = (int) ToggleOffMode; + Script.Save(); _entity.Script = Script?.Entity; EventPath?.Save(); @@ -221,9 +296,9 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition Script.Load(); // The load action may have created an event node, use that one over the one we have here - INode? existingEventNode = Script.Nodes.FirstOrDefault(n => n.Id == EventDefaultNode.NodeId); + INode? existingEventNode = Script.Nodes.FirstOrDefault(n => n.Id == EventConditionEventStartNode.NodeId || n.Id == EventConditionValueChangedStartNode.NodeId); if (existingEventNode != null) - _eventNode = (EventDefaultNode) existingEventNode; + _startNode = (IEventConditionNode) existingEventNode; UpdateEventNode(); Script.LoadConnections(); @@ -268,4 +343,20 @@ public enum EventOverlapMode /// Ignore subsequent event fires until the timeline finishes /// Ignore +} + +/// +/// Represents a mode for render elements when toggling off the event when using . +/// +public enum EventToggleOffMode +{ + /// + /// When the event toggles the condition off, finish the the current run of the main timeline + /// + Finish, + + /// + /// When the event toggles the condition off, skip to the end segment of the timeline + /// + SkipToEnd } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEvent.cs b/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEvent.cs deleted file mode 100644 index 9bb880516..000000000 --- a/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEvent.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Artemis.Core -{ - internal class DataModelValueChangedEvent : IDataModelEvent - { - public DataModelValueChangedEvent(DataModelPath path) - { - Path = path; - } - - public DataModelPath Path { get; } - public T? LastValue { get; private set; } - public T? CurrentValue { get; private set; } - public DateTime LastTrigger { get; private set; } - public TimeSpan TimeSinceLastTrigger => DateTime.Now - LastTrigger; - public int TriggerCount { get; private set; } - public Type ArgumentsType { get; } = typeof(DataModelValueChangedEventArgs); - public string TriggerPastParticiple => "changed"; - public bool TrackHistory { get; set; } = false; - public DataModelEventArgs? LastEventArgumentsUntyped { get; private set; } - public List EventArgumentsHistoryUntyped { get; } = new(); - - public void Update() - { - if (!Path.IsValid) - return; - - object? value = Path.GetValue(); - if (value != null) - CurrentValue = (T?) value; - else - CurrentValue = default; - - if (!Equals(LastValue, CurrentValue)) - Trigger(); - - LastValue = CurrentValue; - } - - public void Reset() - { - TriggerCount = 0; - } - - private void Trigger() - { - LastEventArgumentsUntyped = new DataModelValueChangedEventArgs(CurrentValue, LastValue); - LastTrigger = DateTime.Now; - TriggerCount++; - - OnEventTriggered(); - } - - #region Events - - public event EventHandler? EventTriggered; - - internal virtual void OnEventTriggered() - { - EventTriggered?.Invoke(this, EventArgs.Empty); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEventArgs.cs b/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEventArgs.cs deleted file mode 100644 index b25976a61..000000000 --- a/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEventArgs.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Artemis.Core.Modules; - -namespace Artemis.Core -{ - internal class DataModelValueChangedEventArgs : DataModelEventArgs - { - public DataModelValueChangedEventArgs(T? currentValue, T? previousValue) - { - CurrentValue = currentValue; - PreviousValue = previousValue; - } - - [DataModelProperty(Description = "The current value of the property")] - public T? CurrentValue { get; } - [DataModelProperty(Description = "The previous value of the property")] - public T? PreviousValue { get; } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 60ecda321..2f70ff8f8 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -334,20 +334,18 @@ namespace Artemis.Core /// Explorer /// /// The resulting name i.e. New layer or New layer (2) - public string GetNewLayerName() + public string GetNewLayerName(string baseName = "New layer") { - if (!Children.Any(c => c is Layer)) - return "New layer"; + if (!Children.Any(c => c is Layer && c.Name == baseName)) + return baseName; - // Grab existing unnamed layers and get the first available number - // Looks slow but it's not https://stackoverflow.com/a/8865806/5015269 - Regex regex = new(@"New layer \((\d+)\)"); - int firstAvailable = Enumerable.Range(1, int.MaxValue) - .Except(Children.Where(c => c is Layer && c.Name != null && regex.IsMatch(c.Name)) - .Select(c => int.Parse(regex.Match(c.Name!).Groups[1].Value)) - .OrderBy(i => i)) - .First(); - return $"New layer ({firstAvailable})"; + int current = 2; + while (true) + { + if (Children.All(c => c is Layer && c.Name != $"{baseName} ({current})")) + return $"{baseName} ({current})"; + current++; + } } /// @@ -355,20 +353,18 @@ namespace Artemis.Core /// in Explorer /// /// The resulting name i.e. New folder or New folder (2) - public string GetNewFolderName() + public string GetNewFolderName(string baseName = "New folder") { - if (!Children.Any(c => c is Folder)) - return "New folder"; + if (!Children.Any(c => c is Folder && c.Name == baseName)) + return baseName; - // Grab existing unnamed layers and get the first available number - // Looks slow but it's not https://stackoverflow.com/a/8865806/5015269 - Regex regex = new(@"New folder \((\d+)\)"); - int firstAvailable = Enumerable.Range(1, int.MaxValue) - .Except(Children.Where(c => c is Folder && c.Name != null && regex.IsMatch(c.Name)) - .Select(c => int.Parse(regex.Match(c.Name!).Groups[1].Value)) - .OrderBy(i => i)) - .First(); - return $"New folder ({firstAvailable})"; + int current = 2; + while (true) + { + if (Children.All(c => c is Folder && c.Name != $"{baseName} ({current})")) + return $"{baseName} ({current})"; + current++; + } } #endregion diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 15a9a71a5..ed00c54fe 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -38,13 +38,14 @@ namespace Artemis.Core.Services // ReSharper disable UnusedParameter.Local public CoreService(IKernel kernel, ILogger logger, - StorageMigrationService _, // injected to ensure migration runs early + StorageMigrationService _1, // injected to ensure migration runs early ISettingsService settingsService, IPluginManagementService pluginManagementService, IRgbService rgbService, IProfileService profileService, IModuleService moduleService, - IScriptingService scriptingService) + IScriptingService scriptingService, + IProcessMonitorService _2) { Kernel = kernel; Constants.CorePlugin.Kernel = kernel; diff --git a/src/Artemis.Core/Services/ProcessMonitor/ProcessMonitorService.cs b/src/Artemis.Core/Services/ProcessMonitor/ProcessMonitorService.cs index bd1451101..3f272c348 100644 --- a/src/Artemis.Core/Services/ProcessMonitor/ProcessMonitorService.cs +++ b/src/Artemis.Core/Services/ProcessMonitor/ProcessMonitorService.cs @@ -11,7 +11,6 @@ namespace Artemis.Core.Services internal class ProcessMonitorService : IProcessMonitorService { private readonly ILogger _logger; - private readonly Timer _processScanTimer; private readonly ProcessComparer _comparer; private Process[] _lastScannedProcesses; @@ -19,16 +18,15 @@ namespace Artemis.Core.Services { _logger = logger; _lastScannedProcesses = Process.GetProcesses(); - _processScanTimer = new Timer(1000); - _processScanTimer.Elapsed += OnTimerElapsed; - _processScanTimer.Start(); + Timer processScanTimer = new(1000); + processScanTimer.Elapsed += OnTimerElapsed; + processScanTimer.Start(); _comparer = new ProcessComparer(); ProcessActivationRequirement.ProcessMonitorService = this; } public event EventHandler? ProcessStarted; - public event EventHandler? ProcessStopped; public IEnumerable GetRunningProcesses() @@ -36,7 +34,7 @@ namespace Artemis.Core.Services return _lastScannedProcesses; } - private void OnTimerElapsed(object sender, ElapsedEventArgs e) + private void OnTimerElapsed(object? sender, ElapsedEventArgs e) { Process[] newProcesses = Process.GetProcesses(); foreach (Process startedProcess in newProcesses.Except(_lastScannedProcesses, _comparer)) @@ -64,7 +62,7 @@ namespace Artemis.Core.Services return x.Id == y.Id && x.ProcessName == y.ProcessName && x.SessionId == y.SessionId; } - public int GetHashCode(Process obj) + public int GetHashCode(Process? obj) { if (obj == null) return 0; return obj.Id; diff --git a/src/Artemis.Core/VisualScripting/Internal/EventConditionEventStartNode.cs b/src/Artemis.Core/VisualScripting/Internal/EventConditionEventStartNode.cs new file mode 100644 index 000000000..c169e0555 --- /dev/null +++ b/src/Artemis.Core/VisualScripting/Internal/EventConditionEventStartNode.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Artemis.Core.Modules; +using Artemis.Core.VisualScripting.Internal; +using Humanizer; + +namespace Artemis.Core.Internal; + +internal class EventConditionEventStartNode : DefaultNode, IEventConditionNode +{ + internal static readonly Guid NodeId = new("278735FE-69E9-4A73-A6B8-59E83EE19305"); + private readonly List _pinBucket = new(); + private readonly Dictionary _propertyPins; + private IDataModelEvent? _dataModelEvent; + + public EventConditionEventStartNode() : base(NodeId, "Event Arguments", "Contains the event arguments that triggered the evaluation") + { + _propertyPins = new Dictionary(); + } + + public void CreatePins(IDataModelEvent? dataModelEvent) + { + if (_dataModelEvent == dataModelEvent) + return; + + while (Pins.Any()) + RemovePin((Pin) Pins.First()); + _propertyPins.Clear(); + + _dataModelEvent = dataModelEvent; + if (dataModelEvent == null) + return; + + foreach (PropertyInfo propertyInfo in dataModelEvent.ArgumentsType + .GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof(DataModelIgnoreAttribute)))) + _propertyPins.Add(propertyInfo, CreateOrAddOutputPin(propertyInfo.PropertyType, propertyInfo.Name.Humanize())); + } + + public override void Evaluate() + { + if (_dataModelEvent?.LastEventArgumentsUntyped == null) + return; + + foreach ((PropertyInfo propertyInfo, OutputPin outputPin) in _propertyPins) + { + if (outputPin.ConnectedTo.Any()) + outputPin.Value = propertyInfo.GetValue(_dataModelEvent.LastEventArgumentsUntyped) ?? outputPin.Type.GetDefault()!; + } + } + + /// + /// Creates or adds an input pin to the node using a bucket. + /// The bucket might grow a bit over time as the user edits the node but pins won't get lost, enabling undo/redo in the + /// editor. + /// + private OutputPin CreateOrAddOutputPin(Type valueType, string displayName) + { + // Grab the first pin from the bucket that isn't on the node yet + OutputPin? pin = _pinBucket.FirstOrDefault(p => !Pins.Contains(p)); + + // If there is none, create a new one and add it to the bucket + if (pin == null) + { + pin = CreateOutputPin(valueType, displayName); + _pinBucket.Add(pin); + } + // If there was a pin in the bucket, update it's type and display name and reuse it + else + { + pin.ChangeType(valueType); + pin.Name = displayName; + AddPin(pin); + } + + return pin; + } +} \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Internal/EventConditionValueChangedStartNode.cs b/src/Artemis.Core/VisualScripting/Internal/EventConditionValueChangedStartNode.cs new file mode 100644 index 000000000..5bbf1d77b --- /dev/null +++ b/src/Artemis.Core/VisualScripting/Internal/EventConditionValueChangedStartNode.cs @@ -0,0 +1,46 @@ +using System; +using Artemis.Core.VisualScripting.Internal; + +namespace Artemis.Core.Internal; + +internal class EventConditionValueChangedStartNode : DefaultNode, IEventConditionNode +{ + internal static readonly Guid NodeId = new("F9A270DB-A231-4800-BAB3-DC1F96856756"); + private object? _newValue; + private object? _oldValue; + + public EventConditionValueChangedStartNode() : base(NodeId, "Changed values", "Contains the old and new values of the property chat was changed.") + { + NewValue = CreateOutputPin(typeof(object), "New value"); + OldValue = CreateOutputPin(typeof(object), "Old value"); + } + + public OutputPin NewValue { get; } + public OutputPin OldValue { get; } + + public void UpdateOutputPins(DataModelPath dataModelPath) + { + Type? type = dataModelPath?.GetPropertyType(); + if (Numeric.IsTypeCompatible(type)) + type = typeof(Numeric); + type ??= typeof(object); + + if (NewValue.Type != type) + NewValue.ChangeType(type); + if (OldValue.Type != type) + OldValue.ChangeType(type); + } + + public void UpdateValues(object? newValue, object? oldValue) + { + _newValue = newValue; + _oldValue = oldValue; + } + + /// + public override void Evaluate() + { + NewValue.Value = _newValue; + OldValue.Value = _oldValue; + } +} \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Internal/EventDefaultNode.cs b/src/Artemis.Core/VisualScripting/Internal/EventDefaultNode.cs deleted file mode 100644 index 4e35b99e6..000000000 --- a/src/Artemis.Core/VisualScripting/Internal/EventDefaultNode.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Artemis.Core.Modules; -using Humanizer; - -namespace Artemis.Core.Internal -{ - internal class EventDefaultNode : DefaultNode - { - internal static readonly Guid NodeId = new("278735FE-69E9-4A73-A6B8-59E83EE19305"); - private readonly Dictionary _propertyPins; - private readonly List _pinBucket = new(); - private IDataModelEvent? _dataModelEvent; - - public EventDefaultNode() : base(NodeId, "Event Arguments", "Contains the event arguments that triggered the evaluation") - { - _propertyPins = new Dictionary(); - } - - public void CreatePins(IDataModelEvent? dataModelEvent) - { - if (_dataModelEvent == dataModelEvent) - return; - - while (Pins.Any()) - RemovePin((Pin) Pins.First()); - _propertyPins.Clear(); - - _dataModelEvent = dataModelEvent; - if (dataModelEvent == null) - return; - - foreach (PropertyInfo propertyInfo in dataModelEvent.ArgumentsType - .GetProperties(BindingFlags.Instance | BindingFlags.Public) - .Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof(DataModelIgnoreAttribute)))) - { - _propertyPins.Add(propertyInfo, CreateOrAddOutputPin(propertyInfo.PropertyType, propertyInfo.Name.Humanize())); - } - } - - public override void Evaluate() - { - if (_dataModelEvent?.LastEventArgumentsUntyped == null) - return; - - foreach ((PropertyInfo propertyInfo, OutputPin outputPin) in _propertyPins) - { - if (outputPin.ConnectedTo.Any()) - outputPin.Value = propertyInfo.GetValue(_dataModelEvent.LastEventArgumentsUntyped) ?? outputPin.Type.GetDefault()!; - } - } - - /// - /// Creates or adds an input pin to the node using a bucket. - /// The bucket might grow a bit over time as the user edits the node but pins won't get lost, enabling undo/redo in the - /// editor. - /// - private OutputPin CreateOrAddOutputPin(Type valueType, string displayName) - { - // Grab the first pin from the bucket that isn't on the node yet - OutputPin? pin = _pinBucket.FirstOrDefault(p => !Pins.Contains(p)); - - // If there is none, create a new one and add it to the bucket - if (pin == null) - { - pin = CreateOutputPin(valueType, displayName); - _pinBucket.Add(pin); - } - // If there was a pin in the bucket, update it's type and display name and reuse it - else - { - pin.ChangeType(valueType); - pin.Name = displayName; - AddPin(pin); - } - - return pin; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Core/VisualScripting/Internal/IEventConditionNode.cs b/src/Artemis.Core/VisualScripting/Internal/IEventConditionNode.cs new file mode 100644 index 000000000..b5436aea7 --- /dev/null +++ b/src/Artemis.Core/VisualScripting/Internal/IEventConditionNode.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Artemis.Core.VisualScripting.Internal +{ + internal interface IEventConditionNode : INode + { + } +} diff --git a/src/Artemis.Storage/Entities/Profile/Conditions/EventConditionEntity.cs b/src/Artemis.Storage/Entities/Profile/Conditions/EventConditionEntity.cs index c7e83890a..93bc4990a 100644 --- a/src/Artemis.Storage/Entities/Profile/Conditions/EventConditionEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/Conditions/EventConditionEntity.cs @@ -7,6 +7,7 @@ namespace Artemis.Storage.Entities.Profile.Conditions { public int TriggerMode { get; set; } public int OverlapMode { get; set; } + public int ToggleOffMode { get; set; } public DataModelPathEntity EventPath { get; set; } public NodeScriptEntity Script { get; set; } } diff --git a/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml b/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml index 4245d6b76..56dcf901e 100644 --- a/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml +++ b/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml @@ -3,6 +3,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Shared.Controls.ArtemisIcon"> + x:Class="Artemis.UI.Shared.ArtemisIcon"> Welcome to Avalonia! diff --git a/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs b/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs index a115738d5..7805c02a8 100644 --- a/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs +++ b/src/Artemis.UI.Shared/Controls/ArtemisIcon.axaml.cs @@ -8,7 +8,7 @@ using Avalonia.Svg.Skia; using Material.Icons; using Material.Icons.Avalonia; -namespace Artemis.UI.Shared.Controls +namespace Artemis.UI.Shared { /// /// Represents a control that can display an arbitrary kind of icon. diff --git a/src/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs b/src/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs index 520a54a12..bb343bec1 100644 --- a/src/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs +++ b/src/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs @@ -13,10 +13,11 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Data; +using Avalonia.Threading; using Material.Icons.Avalonia; using ReactiveUI; -namespace Artemis.UI.Shared.Controls.DataModelPicker; +namespace Artemis.UI.Shared.DataModelPicker; /// /// Represents a data model picker picker that can be used to select a data model path. @@ -52,29 +53,32 @@ public class DataModelPicker : TemplatedControl public static readonly StyledProperty DataModelViewModelProperty = AvaloniaProperty.Register(nameof(DataModelViewModel)); - /// - /// A list of data model view models to show - /// - public static readonly StyledProperty?> ExtraDataModelViewModelsProperty = - AvaloniaProperty.Register?>(nameof(ExtraDataModelViewModels), new ObservableCollection()); - /// /// A list of types to filter the selectable paths on. /// public static readonly StyledProperty?> FilterTypesProperty = AvaloniaProperty.Register?>(nameof(FilterTypes), new ObservableCollection()); - private MaterialIcon? _currentPathIcon; - private TextBlock? _currentPathDisplay; + /// + /// Gets or sets a boolean indicating whether the picker is in event picker mode. + /// When event children aren't selectable and non-events are described as "{PropertyName} + /// changed". + /// + public static readonly StyledProperty IsEventPickerProperty = + AvaloniaProperty.Register(nameof(IsEventPicker)); + private TextBlock? _currentPathDescription; + private TextBlock? _currentPathDisplay; + + private MaterialIcon? _currentPathIcon; private TreeView? _dataModelTreeView; + private DispatcherTimer? _updateTimer; static DataModelPicker() { ModulesProperty.Changed.Subscribe(ModulesChanged); DataModelPathProperty.Changed.Subscribe(DataModelPathPropertyChanged); DataModelViewModelProperty.Changed.Subscribe(DataModelViewModelPropertyChanged); - ExtraDataModelViewModelsProperty.Changed.Subscribe(ExtraDataModelViewModelsChanged); } /// @@ -125,16 +129,7 @@ public class DataModelPicker : TemplatedControl get => GetValue(DataModelViewModelProperty); private set => SetValue(DataModelViewModelProperty, value); } - - /// - /// A list of data model view models to show. - /// - public ObservableCollection? ExtraDataModelViewModels - { - get => GetValue(ExtraDataModelViewModelsProperty); - set => SetValue(ExtraDataModelViewModelsProperty, value); - } - + /// /// A list of types to filter the selectable paths on. /// @@ -144,6 +139,17 @@ public class DataModelPicker : TemplatedControl set => SetValue(FilterTypesProperty, value); } + /// + /// Gets or sets a boolean indicating whether the picker is in event picker mode. + /// When event children aren't selectable and non-events are described as "{PropertyName} + /// changed". + /// + public bool IsEventPicker + { + get => GetValue(IsEventPickerProperty); + set => SetValue(IsEventPickerProperty, value); + } + /// /// Occurs when a new path has been selected /// @@ -158,42 +164,6 @@ public class DataModelPicker : TemplatedControl DataModelPathSelected?.Invoke(this, e); } - #region Overrides of TemplatedControl - - /// - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - if (_dataModelTreeView != null) - _dataModelTreeView.SelectionChanged -= DataModelTreeViewOnSelectionChanged; - - _currentPathIcon = e.NameScope.Find("CurrentPathIcon"); - _currentPathDisplay = e.NameScope.Find("CurrentPathDisplay"); - _currentPathDescription = e.NameScope.Find("CurrentPathDescription"); - _dataModelTreeView = e.NameScope.Find("DataModelTreeView"); - - if (_dataModelTreeView != null) - _dataModelTreeView.SelectionChanged += DataModelTreeViewOnSelectionChanged; - } - - #region Overrides of Visual - - /// - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - GetDataModel(); - UpdateCurrentPath(true); - } - - /// - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) - { - DataModelViewModel?.Dispose(); - } - - #endregion - - #endregion - private static void ModulesChanged(AvaloniaPropertyChangedEventArgs?> e) { if (e.Sender is DataModelPicker dataModelPicker) @@ -212,11 +182,6 @@ public class DataModelPicker : TemplatedControl e.OldValue.Value.Dispose(); } - private static void ExtraDataModelViewModelsChanged(AvaloniaPropertyChangedEventArgs?> e) - { - // TODO, the original did nothing here either and I can't remember why - } - private void ExecuteSelectPropertyCommand(DataModelVisualizationViewModel selected) { if (selected.DataModelPath == null) @@ -228,6 +193,14 @@ public class DataModelPicker : TemplatedControl OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath)); } + private void Update(object? sender, EventArgs e) + { + if (DataModelUIService == null) + return; + + DataModelViewModel?.Update(DataModelUIService, new DataModelUpdateConfiguration(!IsEventPicker)); + } + private void GetDataModel() { if (DataModelUIService == null) @@ -251,10 +224,11 @@ public class DataModelPicker : TemplatedControl private void DataModelOnUpdateRequested(object? sender, EventArgs e) { - DataModelViewModel?.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes); - if (ExtraDataModelViewModels == null) return; - foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels) - extraDataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes); + if (DataModelUIService == null || DataModelViewModel == null) + return; + + DataModelViewModel.Update(DataModelUIService, new DataModelUpdateConfiguration(IsEventPicker)); + DataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes); } private void DataModelTreeViewOnSelectionChanged(object? sender, SelectionChangedEventArgs e) @@ -295,6 +269,45 @@ public class DataModelPicker : TemplatedControl _currentPathDescription.Text = DataModelPath.GetPropertyDescription()?.Description; if (_currentPathIcon != null) _currentPathIcon.Kind = DataModelPath.GetPropertyType().GetTypeIcon(); - } + + #region Overrides of TemplatedControl + + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + if (_dataModelTreeView != null) + _dataModelTreeView.SelectionChanged -= DataModelTreeViewOnSelectionChanged; + + _currentPathIcon = e.NameScope.Find("CurrentPathIcon"); + _currentPathDisplay = e.NameScope.Find("CurrentPathDisplay"); + _currentPathDescription = e.NameScope.Find("CurrentPathDescription"); + _dataModelTreeView = e.NameScope.Find("DataModelTreeView"); + + if (_dataModelTreeView != null) + _dataModelTreeView.SelectionChanged += DataModelTreeViewOnSelectionChanged; + } + + #region Overrides of Visual + + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + GetDataModel(); + UpdateCurrentPath(true); + _updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(200), DispatcherPriority.Normal, Update); + _updateTimer.Start(); + } + + /// + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + DataModelViewModel?.Dispose(); + _updateTimer?.Stop(); + _updateTimer = null; + } + + #endregion + + #endregion } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPickerButton.cs b/src/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPickerButton.cs index 2955c92f4..825105231 100644 --- a/src/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPickerButton.cs +++ b/src/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPickerButton.cs @@ -3,8 +3,7 @@ using System.Collections.ObjectModel; using System.Linq; using Artemis.Core; using Artemis.Core.Modules; -using Artemis.UI.Shared.Controls.Flyouts; -using Artemis.UI.Shared.DataModelVisualization.Shared; +using Artemis.UI.Shared.Flyouts; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; @@ -13,7 +12,7 @@ using Avalonia.Interactivity; using Avalonia.Threading; using FluentAvalonia.Core; -namespace Artemis.UI.Shared.Controls.DataModelPicker; +namespace Artemis.UI.Shared.DataModelPicker; /// /// Represents a button that can be used to pick a data model path in a flyout. @@ -68,18 +67,20 @@ public class DataModelPickerButton : TemplatedControl public static readonly StyledProperty?> ModulesProperty = AvaloniaProperty.Register?>(nameof(Modules), new ObservableCollection()); - /// - /// A list of data model view models to show - /// - public static readonly StyledProperty?> ExtraDataModelViewModelsProperty = - AvaloniaProperty.Register?>(nameof(ExtraDataModelViewModels), new ObservableCollection()); - /// /// A list of types to filter the selectable paths on. /// public static readonly StyledProperty?> FilterTypesProperty = AvaloniaProperty.Register?>(nameof(FilterTypes), new ObservableCollection()); + /// + /// Gets or sets a boolean indicating whether the picker is in event picker mode. + /// When event children aren't selectable and non-events are described as "{PropertyName} + /// changed". + /// + public static readonly StyledProperty IsEventPickerProperty = + AvaloniaProperty.Register(nameof(IsEventPicker)); + private bool _attached; private bool _flyoutActive; private Button? _button; @@ -165,15 +166,6 @@ public class DataModelPickerButton : TemplatedControl set => SetValue(ModulesProperty, value); } - /// - /// A list of data model view models to show. - /// - public ObservableCollection? ExtraDataModelViewModels - { - get => GetValue(ExtraDataModelViewModelsProperty); - set => SetValue(ExtraDataModelViewModelsProperty, value); - } - /// /// A list of types to filter the selectable paths on. /// @@ -183,6 +175,17 @@ public class DataModelPickerButton : TemplatedControl set => SetValue(FilterTypesProperty, value); } + /// + /// Gets or sets a boolean indicating whether the picker is in event picker mode. + /// When event children aren't selectable and non-events are described as "{PropertyName} + /// changed". + /// + public bool IsEventPicker + { + get => GetValue(IsEventPickerProperty); + set => SetValue(IsEventPickerProperty, value); + } + /// /// Raised when the flyout opens. /// @@ -239,14 +242,24 @@ public class DataModelPickerButton : TemplatedControl } else { + // If a valid path is selected, gather all its segments and create a visual representation of the path string? formattedPath = null; if (DataModelPath != null && DataModelPath.IsValid) formattedPath = string.Join(" › ", DataModelPath.Segments.Where(s => s.GetPropertyDescription() != null).Select(s => s.GetPropertyDescription()!.Name)); + // Always show the full path in the tooltip ToolTip.SetTip(_button, formattedPath); - _label.Text = ShowFullPath + + // Reuse the tooltip value when showing the full path, otherwise only show the last segment + string? labelText = ShowFullPath ? formattedPath : DataModelPath?.Segments.LastOrDefault()?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier; + + // Add "changed" to the end of the display value if this is an event picker but no event was picked + if (IsEventPicker && labelText != null && DataModelPath?.GetPropertyType()?.IsAssignableTo(typeof(IDataModelEvent)) == false) + labelText += " changed"; + + _label.Text = labelText ?? Placeholder; } } @@ -257,8 +270,8 @@ public class DataModelPickerButton : TemplatedControl // Logic here is taken from Fluent Avalonia's ColorPicker which also reuses the same control since it's large _flyout.DataModelPicker.DataModelPath = DataModelPath; - _flyout.DataModelPicker.ExtraDataModelViewModels = ExtraDataModelViewModels; _flyout.DataModelPicker.FilterTypes = FilterTypes; + _flyout.DataModelPicker.IsEventPicker = IsEventPicker; _flyout.DataModelPicker.Modules = Modules; _flyout.DataModelPicker.ShowDataModelValues = ShowDataModelValues; diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs index b5a944faf..a90ca2550 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -18,7 +18,7 @@ using Avalonia.Rendering; using Avalonia.Threading; using Avalonia.Visuals.Media.Imaging; -namespace Artemis.UI.Shared.Controls +namespace Artemis.UI.Shared { /// /// Visualizes an with optional per-LED colors diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs index 9ccc4bcb6..32b84c1ec 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizerLed.cs @@ -11,7 +11,7 @@ using Color = Avalonia.Media.Color; using Point = Avalonia.Point; using SolidColorBrush = Avalonia.Media.SolidColorBrush; -namespace Artemis.UI.Shared.Controls +namespace Artemis.UI.Shared { internal class DeviceVisualizerLed { diff --git a/src/Artemis.UI.Shared/Controls/EnumComboBox.axaml b/src/Artemis.UI.Shared/Controls/EnumComboBox.axaml index bf79ed332..36ada5068 100644 --- a/src/Artemis.UI.Shared/Controls/EnumComboBox.axaml +++ b/src/Artemis.UI.Shared/Controls/EnumComboBox.axaml @@ -3,7 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Shared.Controls.EnumComboBox"> + x:Class="Artemis.UI.Shared.EnumComboBox"> diff --git a/src/Artemis.UI.Shared/Controls/EnumComboBox.axaml.cs b/src/Artemis.UI.Shared/Controls/EnumComboBox.axaml.cs index c172e11af..4f0c33187 100644 --- a/src/Artemis.UI.Shared/Controls/EnumComboBox.axaml.cs +++ b/src/Artemis.UI.Shared/Controls/EnumComboBox.axaml.cs @@ -8,7 +8,7 @@ using Avalonia.Data; using Avalonia.LogicalTree; using Avalonia.Markup.Xaml; -namespace Artemis.UI.Shared.Controls +namespace Artemis.UI.Shared { /// /// Represents a combobox that can display the values of an enum. diff --git a/src/Artemis.UI.Shared/Controls/Flyouts/DataModelPickerFlyout.cs b/src/Artemis.UI.Shared/Controls/Flyouts/DataModelPickerFlyout.cs index 8449f3d7f..73bd6efee 100644 --- a/src/Artemis.UI.Shared/Controls/Flyouts/DataModelPickerFlyout.cs +++ b/src/Artemis.UI.Shared/Controls/Flyouts/DataModelPickerFlyout.cs @@ -5,7 +5,7 @@ using FluentAvalonia.Core; using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls.Primitives; -namespace Artemis.UI.Shared.Controls.Flyouts; +namespace Artemis.UI.Shared.Flyouts; /// /// Defines a flyout that hosts a data model picker. diff --git a/src/Artemis.UI.Shared/Controls/Flyouts/GradientPickerFlyout.cs b/src/Artemis.UI.Shared/Controls/Flyouts/GradientPickerFlyout.cs index 16a13122b..30258306d 100644 --- a/src/Artemis.UI.Shared/Controls/Flyouts/GradientPickerFlyout.cs +++ b/src/Artemis.UI.Shared/Controls/Flyouts/GradientPickerFlyout.cs @@ -1,6 +1,6 @@ using Avalonia.Controls; -namespace Artemis.UI.Shared.Controls.Flyouts; +namespace Artemis.UI.Shared.Flyouts; /// /// Defines a flyout that hosts a gradient picker. diff --git a/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPicker.cs b/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPicker.cs index 05d2ca73e..6eef1fb88 100644 --- a/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPicker.cs +++ b/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPicker.cs @@ -16,7 +16,7 @@ using FluentAvalonia.UI.Media; using ReactiveUI; using Button = Avalonia.Controls.Button; -namespace Artemis.UI.Shared.Controls.GradientPicker; +namespace Artemis.UI.Shared.GradientPicker; /// /// Represents a gradient picker that can be used to edit a gradient. diff --git a/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPickerButton.cs b/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPickerButton.cs index b6d149e1e..6a2ae8265 100644 --- a/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPickerButton.cs +++ b/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPickerButton.cs @@ -2,7 +2,7 @@ using System.Collections.Specialized; using System.Linq; using Artemis.Core; -using Artemis.UI.Shared.Controls.Flyouts; +using Artemis.UI.Shared.Flyouts; using Artemis.UI.Shared.Providers; using Avalonia; using Avalonia.Controls; @@ -13,7 +13,7 @@ using Avalonia.Media; using FluentAvalonia.Core; using Button = FluentAvalonia.UI.Controls.Button; -namespace Artemis.UI.Shared.Controls.GradientPicker; +namespace Artemis.UI.Shared.GradientPicker; /// /// Represents a gradient picker box that can be used to edit a gradient diff --git a/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPickerColorStop.cs b/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPickerColorStop.cs index de169e523..a13e67bfa 100644 --- a/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPickerColorStop.cs +++ b/src/Artemis.UI.Shared/Controls/GradientPicker/GradientPickerColorStop.cs @@ -2,11 +2,10 @@ using Artemis.Core; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Input; -namespace Artemis.UI.Shared.Controls.GradientPicker; +namespace Artemis.UI.Shared.GradientPicker; public class GradientPickerColorStop : TemplatedControl { diff --git a/src/Artemis.UI.Shared/Controls/HotkeyBox.axaml b/src/Artemis.UI.Shared/Controls/HotkeyBox.axaml index 29ae38f38..0e53282e1 100644 --- a/src/Artemis.UI.Shared/Controls/HotkeyBox.axaml +++ b/src/Artemis.UI.Shared/Controls/HotkeyBox.axaml @@ -2,9 +2,9 @@ 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:controls="clr-namespace:Artemis.UI.Shared.Controls" + xmlns:shared="clr-namespace:Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Shared.Controls.HotkeyBox"> + x:Class="Artemis.UI.Shared.HotkeyBox"> - /// Represents a control that can be used to display or edit instances. diff --git a/src/Artemis.UI.Shared/Controls/NoInputTextBox.cs b/src/Artemis.UI.Shared/Controls/NoInputTextBox.cs index 6d20bb00e..05859bc49 100644 --- a/src/Artemis.UI.Shared/Controls/NoInputTextBox.cs +++ b/src/Artemis.UI.Shared/Controls/NoInputTextBox.cs @@ -3,7 +3,7 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Styling; -namespace Artemis.UI.Shared.Controls +namespace Artemis.UI.Shared { internal class NoInputTextBox : TextBox, IStyleable { diff --git a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml index 22e77952d..fe83bd1ac 100644 --- a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml +++ b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml @@ -3,5 +3,5 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Artemis.UI.Shared.Controls.ProfileConfigurationIcon"> + x:Class="Artemis.UI.Shared.ProfileConfigurationIcon"> diff --git a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs index b145cb84a..026dbb8e5 100644 --- a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs +++ b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs @@ -10,7 +10,7 @@ using Avalonia.Svg.Skia; using Material.Icons; using Material.Icons.Avalonia; -namespace Artemis.UI.Shared.Controls +namespace Artemis.UI.Shared { /// /// Represents a control that can display the icon of a specific . diff --git a/src/Artemis.UI.Shared/Controls/SelectionRectangle.cs b/src/Artemis.UI.Shared/Controls/SelectionRectangle.cs index 5c43e5cdb..f422b646d 100644 --- a/src/Artemis.UI.Shared/Controls/SelectionRectangle.cs +++ b/src/Artemis.UI.Shared/Controls/SelectionRectangle.cs @@ -6,7 +6,7 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Media; -namespace Artemis.UI.Shared.Controls; +namespace Artemis.UI.Shared; /// /// Visualizes an with optional per-LED colors diff --git a/src/Artemis.UI.Shared/Events/DataModelSelectedEventArgs.cs b/src/Artemis.UI.Shared/Events/DataModelSelectedEventArgs.cs index 7f59aedad..35b97d2af 100644 --- a/src/Artemis.UI.Shared/Events/DataModelSelectedEventArgs.cs +++ b/src/Artemis.UI.Shared/Events/DataModelSelectedEventArgs.cs @@ -1,6 +1,5 @@ using System; using Artemis.Core; -using Artemis.UI.Shared.Controls; namespace Artemis.UI.Shared.Events { diff --git a/src/Artemis.UI.Shared/Events/SelectionRectangleEventArgs.cs b/src/Artemis.UI.Shared/Events/SelectionRectangleEventArgs.cs index f522cd3fe..1980d5b5c 100644 --- a/src/Artemis.UI.Shared/Events/SelectionRectangleEventArgs.cs +++ b/src/Artemis.UI.Shared/Events/SelectionRectangleEventArgs.cs @@ -1,5 +1,4 @@ using System; -using Artemis.UI.Shared.Controls; using Avalonia; using Avalonia.Input; diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventToggleOffMode.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventToggleOffMode.cs new file mode 100644 index 000000000..b820e07f8 --- /dev/null +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/Commands/UpdateEventToggleOffMode.cs @@ -0,0 +1,38 @@ +using Artemis.Core; + +namespace Artemis.UI.Shared.Services.ProfileEditor.Commands; + +/// +/// Represents a profile editor command that can be used to update an event condition's overlap mode. +/// +public class UpdateEventToggleOffMode : IProfileEditorCommand +{ + private readonly EventCondition _eventCondition; + private readonly EventToggleOffMode _value; + private readonly EventToggleOffMode _oldValue; + + /// + /// Creates a new instance of the class. + /// + public UpdateEventToggleOffMode(EventCondition eventCondition, EventToggleOffMode value) + { + _eventCondition = eventCondition; + _value = value; + _oldValue = eventCondition.ToggleOffMode; + } + + /// + public string DisplayName => "Update event toggle off mode"; + + /// + public void Execute() + { + _eventCondition.ToggleOffMode = _value; + } + + /// + public void Undo() + { + _eventCondition.ToggleOffMode = _oldValue; + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Styles/Condensed.axaml b/src/Artemis.UI.Shared/Styles/Condensed.axaml index 78c83629b..287589530 100644 --- a/src/Artemis.UI.Shared/Styles/Condensed.axaml +++ b/src/Artemis.UI.Shared/Styles/Condensed.axaml @@ -1,9 +1,9 @@  + xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.GradientPicker" + xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker" + xmlns:controls1="clr-namespace:Artemis.UI.Shared"> diff --git a/src/Artemis.UI.Shared/Styles/Controls/DataModelPicker.axaml b/src/Artemis.UI.Shared/Styles/Controls/DataModelPicker.axaml index 4f0c80e28..67ff351ca 100644 --- a/src/Artemis.UI.Shared/Styles/Controls/DataModelPicker.axaml +++ b/src/Artemis.UI.Shared/Styles/Controls/DataModelPicker.axaml @@ -1,9 +1,9 @@ + xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker"> @@ -20,7 +20,22 @@ RowDefinitions="*" MinHeight="38" IsVisible="{Binding DataModelPath, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static ObjectConverters.IsNotNull}}"> - + + @@ -42,6 +57,7 @@ @@ -58,7 +74,13 @@ - + + + + + diff --git a/src/Artemis.UI.Shared/Styles/Controls/DataModelPickerButton.axaml b/src/Artemis.UI.Shared/Styles/Controls/DataModelPickerButton.axaml index b573b409b..45885f134 100644 --- a/src/Artemis.UI.Shared/Styles/Controls/DataModelPickerButton.axaml +++ b/src/Artemis.UI.Shared/Styles/Controls/DataModelPickerButton.axaml @@ -1,8 +1,8 @@  + xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker" + xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.GradientPicker"> diff --git a/src/Artemis.UI.Shared/Styles/Controls/GradientPicker.axaml b/src/Artemis.UI.Shared/Styles/Controls/GradientPicker.axaml index 208eba1da..d10a9fd21 100644 --- a/src/Artemis.UI.Shared/Styles/Controls/GradientPicker.axaml +++ b/src/Artemis.UI.Shared/Styles/Controls/GradientPicker.axaml @@ -1,10 +1,10 @@  + xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters" + xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.GradientPicker"> @@ -18,10 +18,10 @@ - + - - - - - - - - - - + + diff --git a/src/Artemis.UI/ArtemisBootstrapper.cs b/src/Artemis.UI/ArtemisBootstrapper.cs index 591cde846..0764be57d 100644 --- a/src/Artemis.UI/ArtemisBootstrapper.cs +++ b/src/Artemis.UI/ArtemisBootstrapper.cs @@ -5,7 +5,7 @@ using Artemis.Core.Ninject; using Artemis.UI.Exceptions; using Artemis.UI.Ninject; using Artemis.UI.Screens.Root; -using Artemis.UI.Shared.Controls.DataModelPicker; +using Artemis.UI.Shared.DataModelPicker; using Artemis.UI.Shared.Ninject; using Artemis.UI.Shared.Services; using Artemis.VisualScripting.Ninject; diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml b/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml index 8912a6a9b..d96240a61 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/BoolPropertyInputView.axaml @@ -2,12 +2,12 @@ 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:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.DefaultTypes.PropertyInput.BoolPropertyInputView" x:DataType="propertyInput:BoolPropertyInputViewModel"> - diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml index 79b3a3fd2..6a2041784 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml @@ -2,8 +2,8 @@ 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:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker;assembly=Artemis.UI.Shared" xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput" + xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.GradientPicker;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.DefaultTypes.PropertyInput.ColorGradientPropertyInputView" x:DataType="propertyInput:ColorGradientPropertyInputViewModel"> diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml.cs b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml.cs index 2288a4821..b14a38ca4 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml.cs +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/ColorGradientPropertyInputView.axaml.cs @@ -1,5 +1,5 @@ using System; -using Artemis.UI.Shared.Controls.GradientPicker; +using Artemis.UI.Shared.GradientPicker; using Avalonia.Markup.Xaml; using Avalonia.ReactiveUI; diff --git a/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml b/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml index f04e9e77c..7a49d4e22 100644 --- a/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml +++ b/src/Artemis.UI/DefaultTypes/PropertyInput/EnumPropertyInputView.axaml @@ -2,8 +2,8 @@ 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:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.DefaultTypes.PropertyInput.EnumPropertyInputView"> - + \ No newline at end of file diff --git a/src/Artemis.UI/Extensions/ProfileElementExtensions.cs b/src/Artemis.UI/Extensions/ProfileElementExtensions.cs new file mode 100644 index 000000000..21f53e40a --- /dev/null +++ b/src/Artemis.UI/Extensions/ProfileElementExtensions.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Artemis.Core; +using Artemis.Storage.Entities.Profile; +using Avalonia; +using Avalonia.Input; +using Newtonsoft.Json; + +namespace Artemis.UI.Extensions; + +/// +/// Provides static extension methods for UI related profile element tasks. +/// +public static class ProfileElementExtensions +{ + public const string ClipboardDataFormat = "Artemis.ProfileElement"; + + public static async Task CopyToClipboard(this Folder folder) + { + if (Application.Current?.Clipboard == null) + return; + + DataObject dataObject = new(); + string copy = CoreJson.SerializeObject(folder.FolderEntity, true); + dataObject.Set(ClipboardDataFormat, copy); + await Application.Current.Clipboard.SetDataObjectAsync(dataObject); + } + + public static async Task CopyToClipboard(this Layer layer) + { + if (Application.Current?.Clipboard == null) + return; + + DataObject dataObject = new(); + string copy = CoreJson.SerializeObject(layer.LayerEntity, true); + dataObject.Set(ClipboardDataFormat, copy); + await Application.Current.Clipboard.SetDataObjectAsync(dataObject); + } + + + public static async Task PasteChildFromClipboard(this Folder parent) + { + if (Application.Current?.Clipboard == null) + return null; + + byte[]? bytes = (byte[]?) await Application.Current.Clipboard.GetDataAsync(ClipboardDataFormat); + if (bytes == null!) + return null; + + object? entity = CoreJson.DeserializeObject(Encoding.Unicode.GetString(bytes), true); + switch (entity) + { + case FolderEntity folderEntity: + folderEntity.Id = Guid.NewGuid(); + return new Folder(parent.Profile, parent, folderEntity); + case LayerEntity layerEntity: + layerEntity.Id = Guid.NewGuid(); + return new Layer(parent.Profile, parent, layerEntity); + default: + return null; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugPluginView.axaml b/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugPluginView.axaml index e85aa0b4d..e062eff63 100644 --- a/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugPluginView.axaml +++ b/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugPluginView.axaml @@ -2,13 +2,13 @@ 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:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Debugger.Performance.PerformanceDebugPluginView"> - + diff --git a/src/Artemis.UI/Screens/Device/DevicePropertiesView.axaml b/src/Artemis.UI/Screens/Device/DevicePropertiesView.axaml index 14c185c15..31070f5e3 100644 --- a/src/Artemis.UI/Screens/Device/DevicePropertiesView.axaml +++ b/src/Artemis.UI/Screens/Device/DevicePropertiesView.axaml @@ -2,8 +2,8 @@ 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:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800" x:Class="Artemis.UI.Screens.Device.DevicePropertiesView" Icon="/Assets/Images/Logo/application.ico" @@ -26,7 +26,7 @@ - - @@ -23,7 +23,7 @@ - - @@ -17,9 +17,9 @@ + ShowFullPath="{CompiledBinding ShowFullPaths.Value}" + IsEventPicker="True"/> When the event fires.. @@ -48,6 +48,16 @@ + When toggling off.. + + Finish the main segment + Skip forward to the end segment + + - + LostFocus="InputElement_OnLostFocus" /> - public override bool SupportsChildren => false; - - #endregion + Layer = layer; } + + public Layer Layer { get; } + + #region Overrides of TreeItemViewModel + + /// + protected override async Task ExecuteDuplicate() + { + await ProfileEditorService.SaveProfileAsync(); + + LayerEntity copy = CoreJson.DeserializeObject(CoreJson.SerializeObject(Layer.LayerEntity, true), true)!; + copy.Id = Guid.NewGuid(); + copy.Name = Layer.Parent.GetNewFolderName(copy.Name + " - copy"); + + Layer copied = new(Layer.Profile, Layer.Parent, copy); + ProfileEditorService.ExecuteCommand(new AddProfileElement(copied, Layer.Parent, Layer.Order - 1)); + } + + protected override async Task ExecuteCopy() + { + await ProfileEditorService.SaveProfileAsync(); + await Layer.CopyToClipboard(); + } + + protected override async Task ExecutePaste() + { + if (Layer.Parent is not Folder parent) + return; + RenderProfileElement? pasted = await parent.PasteChildFromClipboard(); + if (pasted == null) + return; + + // If the target contains an element with the same name, affix with " - copy" + if (parent.Children.Any(c => c.Name == pasted.Name)) + pasted.Name = parent.GetNewLayerName(pasted.Name + " - copy"); + + ProfileEditorService.ExecuteCommand(new AddProfileElement(pasted, parent, Layer.Order - 1)); + } + + /// + public override bool SupportsChildren => false; + + #endregion } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml index d2499dc9b..6ed6ef893 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml @@ -10,7 +10,7 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.ProfileTreeView" x:DataType="profileTree:ProfileTreeViewModel"> - + @@ -101,7 +101,7 @@ - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml.cs index c89630273..02069da6b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeView.axaml.cs @@ -1,5 +1,3 @@ -using System; -using System.Diagnostics; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; @@ -17,10 +15,10 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree; public class ProfileTreeView : ReactiveUserControl { - private TreeView _treeView; private Image? _dragAdorner; private Point _dragStartPosition; private Point _elementDragOffset; + private TreeView _treeView; public ProfileTreeView() { diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs index fc19a05ed..458ce9a42 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/ProfileTreeViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Disposables; +using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; @@ -88,6 +89,26 @@ public class ProfileTreeViewModel : TreeItemViewModel SelectedChild?.Paste.Execute().Subscribe(); } + public void UpdateCanPaste() + { + throw new NotImplementedException(); + } + + protected override Task ExecuteDuplicate() + { + throw new NotSupportedException(); + } + + protected override Task ExecuteCopy() + { + throw new NotSupportedException(); + } + + protected override Task ExecutePaste() + { + throw new NotSupportedException(); + } + private void SelectCurrentProfileElement(RenderProfileElement? element) { if (SelectedChild?.ProfileElement == element) diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs index 31913585c..c9d3ae8f4 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs @@ -8,22 +8,28 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; +using Artemis.UI.Extensions; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor.Commands; +using Avalonia; using ReactiveUI; namespace Artemis.UI.Screens.ProfileEditor.ProfileTree; public abstract class TreeItemViewModel : ActivatableViewModelBase { - private readonly IProfileEditorService _profileEditorService; + private readonly ILayerBrushService _layerBrushService; private readonly IProfileEditorVmFactory _profileEditorVmFactory; + private readonly IRgbService _rgbService; private readonly IWindowService _windowService; + protected readonly IProfileEditorService ProfileEditorService; + private bool _canPaste; private RenderProfileElement? _currentProfileElement; private bool _isExpanded; + private bool _isFlyoutOpen; private ProfileElement? _profileElement; private string? _renameValue; private bool _renaming; @@ -35,63 +41,31 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase ILayerBrushService layerBrushService, IProfileEditorVmFactory profileEditorVmFactory) { + ProfileEditorService = profileEditorService; + _rgbService = rgbService; + _layerBrushService = layerBrushService; _windowService = windowService; - _profileEditorService = profileEditorService; _profileEditorVmFactory = profileEditorVmFactory; Parent = parent; ProfileElement = profileElement; - AddLayer = ReactiveCommand.Create(() => - { - if (ProfileElement is Layer targetLayer) - { - Layer layer = new(targetLayer.Parent, targetLayer.GetNewLayerName()); - layerBrushService.ApplyDefaultBrush(layer); - - layer.AddLeds(rgbService.EnabledDevices.SelectMany(d => d.Leds)); - profileEditorService.ExecuteCommand(new AddProfileElement(layer, targetLayer.Parent, targetLayer.Order)); - } - else if (ProfileElement != null) - { - Layer layer = new(ProfileElement, ProfileElement.GetNewLayerName()); - layerBrushService.ApplyDefaultBrush(layer); - - layer.AddLeds(rgbService.EnabledDevices.SelectMany(d => d.Leds)); - profileEditorService.ExecuteCommand(new AddProfileElement(layer, ProfileElement, 0)); - } - }); - - AddFolder = ReactiveCommand.Create(() => - { - if (ProfileElement is Layer targetLayer) - profileEditorService.ExecuteCommand(new AddProfileElement(new Folder(targetLayer.Parent, targetLayer.Parent.GetNewFolderName()), targetLayer.Parent, targetLayer.Order)); - else if (ProfileElement != null) - profileEditorService.ExecuteCommand(new AddProfileElement(new Folder(ProfileElement, ProfileElement.GetNewFolderName()), ProfileElement, 0)); - }); - - Rename = ReactiveCommand.Create(() => - { - Renaming = true; - RenameValue = ProfileElement?.Name; - }); - - Duplicate = ReactiveCommand.Create(() => throw new NotImplementedException()); - Copy = ReactiveCommand.Create(() => throw new NotImplementedException()); - Paste = ReactiveCommand.Create(() => throw new NotImplementedException()); - - Delete = ReactiveCommand.Create(() => - { - if (ProfileElement is RenderProfileElement renderProfileElement) - profileEditorService.ExecuteCommand(new RemoveProfileElement(renderProfileElement)); - }); + AddLayer = ReactiveCommand.Create(ExecuteAddLayer); + AddFolder = ReactiveCommand.Create(ExecuteAddFolder); + Rename = ReactiveCommand.Create(ExecuteRename); + Delete = ReactiveCommand.Create(ExecuteDelete); + Duplicate = ReactiveCommand.CreateFromTask(ExecuteDuplicate); + Copy = ReactiveCommand.CreateFromTask(ExecuteCopy); + Paste = ReactiveCommand.CreateFromTask(ExecutePaste, this.WhenAnyValue(vm => vm.CanPaste)); this.WhenActivated(d => { - _profileEditorService.ProfileElement.Subscribe(element => _currentProfileElement = element).DisposeWith(d); + ProfileEditorService.ProfileElement.Subscribe(element => _currentProfileElement = element).DisposeWith(d); SubscribeToProfileElement(d); CreateTreeItems(); }); + + this.WhenAnyValue(vm => vm.IsFlyoutOpen).Subscribe(UpdateCanPaste); } public ProfileElement? ProfileElement @@ -106,12 +80,24 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase set => RaiseAndSetIfChanged(ref _isExpanded, value); } + public bool IsFlyoutOpen + { + get => _isFlyoutOpen; + set => RaiseAndSetIfChanged(ref _isFlyoutOpen, value); + } + public bool Renaming { get => _renaming; set => RaiseAndSetIfChanged(ref _renaming, value); } + public bool CanPaste + { + get => _canPaste; + set => RaiseAndSetIfChanged(ref _canPaste, value); + } + public TreeItemViewModel? Parent { get; set; } public ObservableCollection Children { get; } = new(); @@ -154,7 +140,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase return; } - _profileEditorService.ExecuteCommand(new RenameProfileElement(ProfileElement, RenameValue)); + ProfileEditorService.ExecuteCommand(new RenameProfileElement(ProfileElement, RenameValue)); Renaming = false; } @@ -169,18 +155,24 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase return; if (ProfileElement != null && elementViewModel.ProfileElement != null) - _profileEditorService.ExecuteCommand(new MoveProfileElement(ProfileElement, elementViewModel.ProfileElement, targetIndex)); + ProfileEditorService.ExecuteCommand(new MoveProfileElement(ProfileElement, elementViewModel.ProfileElement, targetIndex)); } + protected abstract Task ExecuteDuplicate(); + protected abstract Task ExecuteCopy(); + protected abstract Task ExecutePaste(); + protected void SubscribeToProfileElement(CompositeDisposable d) { if (ProfileElement == null) return; Observable.FromEventPattern(x => ProfileElement.ChildAdded += x, x => ProfileElement.ChildAdded -= x) - .Subscribe(c => AddTreeItemIfMissing(c.EventArgs.ProfileElement)).DisposeWith(d); + .Subscribe(c => AddTreeItemIfMissing(c.EventArgs.ProfileElement)) + .DisposeWith(d); Observable.FromEventPattern(x => ProfileElement.ChildRemoved += x, x => ProfileElement.ChildRemoved -= x) - .Subscribe(c => RemoveTreeItemsIfFound(c.Sender, c.EventArgs.ProfileElement)).DisposeWith(d); + .Subscribe(c => RemoveTreeItemsIfFound(c.Sender, c.EventArgs.ProfileElement)) + .DisposeWith(d); } protected void RemoveTreeItemsIfFound(object? sender, ProfileElement profileElement) @@ -202,7 +194,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase newSelection = parent; } - _profileEditorService.ChangeCurrentProfileElement(newSelection as RenderProfileElement); + ProfileEditorService.ChangeCurrentProfileElement(newSelection as RenderProfileElement); } protected void AddTreeItemIfMissing(ProfileElement profileElement) @@ -217,7 +209,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase // Select the newly added element if (profileElement is RenderProfileElement renderProfileElement) - _profileEditorService.ChangeCurrentProfileElement(renderProfileElement); + ProfileEditorService.ChangeCurrentProfileElement(renderProfileElement); } protected void CreateTreeItems() @@ -236,4 +228,56 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase Children.Add(_profileEditorVmFactory.LayerTreeItemViewModel(this, layer)); } } + + private void ExecuteDelete() + { + if (ProfileElement is RenderProfileElement renderProfileElement) + ProfileEditorService.ExecuteCommand(new RemoveProfileElement(renderProfileElement)); + } + + private void ExecuteRename() + { + Renaming = true; + RenameValue = ProfileElement?.Name; + } + + private void ExecuteAddFolder() + { + if (ProfileElement is Layer targetLayer) + ProfileEditorService.ExecuteCommand(new AddProfileElement(new Folder(targetLayer.Parent, targetLayer.Parent.GetNewFolderName()), targetLayer.Parent, targetLayer.Order)); + else if (ProfileElement != null) + ProfileEditorService.ExecuteCommand(new AddProfileElement(new Folder(ProfileElement, ProfileElement.GetNewFolderName()), ProfileElement, 0)); + } + + private void ExecuteAddLayer() + { + if (ProfileElement is Layer targetLayer) + { + Layer layer = new(targetLayer.Parent, targetLayer.GetNewLayerName()); + _layerBrushService.ApplyDefaultBrush(layer); + + layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds)); + ProfileEditorService.ExecuteCommand(new AddProfileElement(layer, targetLayer.Parent, targetLayer.Order)); + } + else if (ProfileElement != null) + { + Layer layer = new(ProfileElement, ProfileElement.GetNewLayerName()); + _layerBrushService.ApplyDefaultBrush(layer); + + layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds)); + ProfileEditorService.ExecuteCommand(new AddProfileElement(layer, ProfileElement, 0)); + } + } + + private async void UpdateCanPaste(bool isFlyoutOpen) + { + if (Application.Current?.Clipboard == null) + { + CanPaste = false; + return; + } + + string[] formats = await Application.Current.Clipboard.GetFormatsAsync(); + CanPaste = formats.Contains(ProfileElementExtensions.ClipboardDataFormat); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml index b01eacb4e..9986bc9a9 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml @@ -3,8 +3,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties" - xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" xmlns:timeline="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Timeline" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineView" x:DataType="timeline:TimelineViewModel"> @@ -23,7 +23,7 @@ - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml.cs index c23f6e109..9b1d11f85 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Timeline/TimelineView.axaml.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes; -using Artemis.UI.Shared.Controls; +using Artemis.UI.Shared; using Artemis.UI.Shared.Events; using Artemis.UI.Shared.Extensions; using Avalonia; diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml index 999fc6af2..9fdd258eb 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/Properties/Tree/TreeGroupView.axaml @@ -3,11 +3,11 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" - xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:viewModel="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Tree" xmlns:properties="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Tree.TreeGroupView"> @@ -68,7 +68,7 @@ - - - - + - - + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml index 69dbdc20e..0a3572f7b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml @@ -2,21 +2,21 @@ 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:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.SelectionRemoveToolView" ClipToBounds="False"> - - + - - + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml index 60ef0da43..43fd4a0c0 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml @@ -4,8 +4,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom" xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" - xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" xmlns:visualEditor="clr-namespace:Artemis.UI.Screens.ProfileEditor.VisualEditor" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.VisualEditorView" x:DataType="visualEditor:VisualEditorViewModel"> @@ -53,7 +53,7 @@ - + @@ -88,7 +88,11 @@ - + diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml index 1a64ea58b..274a2ed7d 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml @@ -4,8 +4,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:layerBrushes="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" - xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="2400" x:Class="Artemis.UI.Screens.Settings.GeneralTabView" x:DataType="settings:GeneralTabViewModel"> @@ -61,7 +61,7 @@ - + @@ -76,7 +76,7 @@ - + diff --git a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditView.axaml b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditView.axaml index b79452edb..9ed4ec6dc 100644 --- a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditView.axaml +++ b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditView.axaml @@ -3,12 +3,12 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" - xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:svg="clr-namespace:Avalonia.Svg.Skia;assembly=Avalonia.Svg.Skia" xmlns:local="clr-namespace:Artemis.UI.Screens.Sidebar" xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="850" x:Class="Artemis.UI.Screens.Sidebar.ProfileConfigurationEditView" Title="{Binding DisplayName}" @@ -55,7 +55,7 @@ - + @@ -148,7 +148,7 @@ IsChecked="{Binding HotkeyMode, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationHotkeyMode.EnableDisable}}" /> - - - + + diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarCategoryView.axaml b/src/Artemis.UI/Screens/Sidebar/SidebarCategoryView.axaml index 2d75e58eb..530396d61 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarCategoryView.axaml +++ b/src/Artemis.UI/Screens/Sidebar/SidebarCategoryView.axaml @@ -3,7 +3,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" - xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" xmlns:local="clr-namespace:Artemis.UI.Screens.Sidebar" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Sidebar.SidebarCategoryView"> diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.axaml b/src/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.axaml index a803394db..86f6e3b05 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.axaml +++ b/src/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationView.axaml @@ -4,8 +4,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:converters="clr-namespace:Artemis.UI.Converters" - xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" xmlns:sidebar="clr-namespace:Artemis.UI.Screens.Sidebar" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.Sidebar.SidebarProfileConfigurationView" x:DataType="sidebar:SidebarProfileConfigurationViewModel"> @@ -68,7 +68,7 @@ - @@ -20,7 +20,7 @@ - + @@ -101,15 +101,15 @@ - - + - - + + - - + - - + + diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs b/src/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs index ed93397ef..ea59272a8 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs +++ b/src/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs @@ -6,7 +6,7 @@ using System.Reactive.Disposables; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Events; -using Artemis.UI.Shared.Controls; +using Artemis.UI.Shared; using Artemis.UI.Shared.Events; using Avalonia; using Avalonia.Controls; @@ -153,7 +153,7 @@ public class NodeScriptView : ReactiveUserControl private void ZoomBorder_OnPointerReleased(object? sender, PointerReleasedEventArgs e) { - if (!_selectionRectangle.IsSelecting) + if (!_selectionRectangle.IsSelecting && e.InitialPressMouseButton == MouseButton.Left) ViewModel?.ClearNodeSelection(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Workshop/WorkshopView.axaml b/src/Artemis.UI/Screens/Workshop/WorkshopView.axaml index 6d084802d..ab3ff22da 100644 --- a/src/Artemis.UI/Screens/Workshop/WorkshopView.axaml +++ b/src/Artemis.UI/Screens/Workshop/WorkshopView.axaml @@ -3,65 +3,66 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:builders="clr-namespace:Artemis.UI.Shared.Services.Builders;assembly=Artemis.UI.Shared" - xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:attachedProperties="clr-namespace:Artemis.UI.Shared.AttachedProperties;assembly=Artemis.UI.Shared" xmlns:workshop="clr-namespace:Artemis.UI.Screens.Workshop" - xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker;assembly=Artemis.UI.Shared" - xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker;assembly=Artemis.UI.Shared" - mc:Ignorable="d" d:DesignWidth="800" + xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" + xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.GradientPicker;assembly=Artemis.UI.Shared" + mc:Ignorable="d" d:DesignWidth="800" x:Class="Artemis.UI.Screens.Workshop.WorkshopView" x:DataType="workshop:WorkshopViewModel"> - - - - Nodes tests - - - - - - - Notification tests - - - - + + + + + Nodes tests + + + + + + + Notification tests + + + + - + - + + - + + - - - - - - + + + + + + - - - - - - - + + + + + + \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Branching/BooleanBranchNode.cs b/src/Artemis.VisualScripting/Nodes/Branching/BooleanBranchNode.cs new file mode 100644 index 000000000..63e60c2c9 --- /dev/null +++ b/src/Artemis.VisualScripting/Nodes/Branching/BooleanBranchNode.cs @@ -0,0 +1,59 @@ +using Artemis.Core; +using Artemis.Core.Events; + +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") + { + BooleanInput = CreateInputPin(); + TrueInput = CreateInputPin(typeof(object), "True"); + FalseInput = CreateInputPin(typeof(object), "False"); + + Output = CreateOutputPin(typeof(object)); + + TrueInput.PinConnected += InputPinConnected; + FalseInput.PinConnected += InputPinConnected; + TrueInput.PinDisconnected += InputPinDisconnected; + FalseInput.PinDisconnected += InputPinDisconnected; + } + + private void InputPinConnected(object? sender, SingleValueEventArgs e) + { + if (TrueInput.ConnectedTo.Any() && !FalseInput.ConnectedTo.Any()) + ChangeType(TrueInput.ConnectedTo.First().Type); + if (FalseInput.ConnectedTo.Any() && !TrueInput.ConnectedTo.Any()) + ChangeType(FalseInput.ConnectedTo.First().Type); + } + + private void InputPinDisconnected(object? sender, SingleValueEventArgs e) + { + if (!TrueInput.ConnectedTo.Any() && !FalseInput.ConnectedTo.Any()) + ChangeType(typeof(object)); + } + + private void ChangeType(Type type) + { + TrueInput.ChangeType(type); + FalseInput.ChangeType(type); + Output.ChangeType(type); + } + + public InputPin BooleanInput { get; set; } + public InputPin TrueInput { get; set; } + public InputPin FalseInput { get; set; } + + public OutputPin Output { get; set; } + + #region Overrides of Node + + /// + public override void Evaluate() + { + Output.Value = BooleanInput.Value ? TrueInput.Value : FalseInput.Value; + } + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/DataModel/DataModelEventNode.cs b/src/Artemis.VisualScripting/Nodes/DataModel/DataModelEventNode.cs index 8c3f1bc45..ad2531da8 100644 --- a/src/Artemis.VisualScripting/Nodes/DataModel/DataModelEventNode.cs +++ b/src/Artemis.VisualScripting/Nodes/DataModel/DataModelEventNode.cs @@ -12,8 +12,9 @@ public class DataModelEventNode : Node _lastTrigger) - { - _lastTrigger = dataModelEvent.LastTrigger; - _currentIndex++; + _currentIndex++; - if (_currentIndex >= CycleValues.Count()) - _currentIndex = 0; - } - - outputValue = CycleValues.ElementAt(_currentIndex).PinValue; + if (_currentIndex >= CycleValues.Count()) + _currentIndex = 0; } + object? outputValue = CycleValues.ElementAt(_currentIndex).PinValue; if (Output.Type.IsInstanceOfType(outputValue)) Output.Value = outputValue; else if (Output.Type.IsValueType) Output.Value = Output.Type.GetDefault()!; } + private bool EvaluateEvent(IDataModelEvent dataModelEvent) + { + if (dataModelEvent.LastTrigger <= _lastTrigger) + return false; + + _lastTrigger = dataModelEvent.LastTrigger; + return true; + } + + private bool EvaluateValue(object? pathValue) + { + if (Equals(pathValue, _lastPathValue)) + return false; + + _lastPathValue = pathValue; + return true; + } + private void CycleValuesOnPinAdded(object? sender, SingleValueEventArgs e) { e.Value.PinConnected += OnPinConnected; @@ -109,7 +127,6 @@ public class DataModelEventNode : Node @@ -11,6 +11,8 @@ DataModelPath="{CompiledBinding DataModelPath}" Modules="{CompiledBinding Modules}" ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}" - FilterTypes="{CompiledBinding FilterTypes}" - VerticalAlignment="Top"/> + ShowFullPath="{CompiledBinding ShowFullPaths.Value}" + IsEventPicker="True" + VerticalAlignment="Top" + MaxWidth="300"/> \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelEventNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelEventNodeCustomViewModel.cs index 59149f30c..dd1334f4d 100644 --- a/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelEventNodeCustomViewModel.cs +++ b/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelEventNodeCustomViewModel.cs @@ -50,7 +50,6 @@ public class DataModelEventNodeCustomViewModel : CustomNodeViewModel public PluginSetting ShowFullPaths { get; } public PluginSetting ShowDataModelValues { get; } - public ObservableCollection FilterTypes { get; } = new() {typeof(IDataModelEvent)}; public ObservableCollection? Modules { diff --git a/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelNodeCustomView.axaml b/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelNodeCustomView.axaml index 1d7ae7c0d..376e79962 100644 --- a/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelNodeCustomView.axaml +++ b/src/Artemis.VisualScripting/Nodes/DataModel/Screens/DataModelNodeCustomView.axaml @@ -2,8 +2,8 @@ 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:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker;assembly=Artemis.UI.Shared" xmlns:screens="clr-namespace:Artemis.VisualScripting.Nodes.DataModel.Screens" + xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker;assembly=Artemis.UI.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.VisualScripting.Nodes.DataModel.Screens.DataModelNodeCustomView" x:DataType="screens:DataModelNodeCustomViewModel"> @@ -11,5 +11,6 @@ DataModelPath="{CompiledBinding DataModelPath}" Modules="{CompiledBinding Modules}" ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}" - ShowFullPath="{CompiledBinding ShowFullPaths.Value}"/> + ShowFullPath="{CompiledBinding ShowFullPaths.Value}" + MaxWidth="300"/> \ No newline at end of file diff --git a/src/Artemis.VisualScripting/Nodes/Operators/Screens/EnumEqualsNodeCustomView.axaml b/src/Artemis.VisualScripting/Nodes/Operators/Screens/EnumEqualsNodeCustomView.axaml index 55bc99fd7..cb4208523 100644 --- a/src/Artemis.VisualScripting/Nodes/Operators/Screens/EnumEqualsNodeCustomView.axaml +++ b/src/Artemis.VisualScripting/Nodes/Operators/Screens/EnumEqualsNodeCustomView.axaml @@ -9,7 +9,7 @@ \ No newline at end of file