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

Event condition - Added toggle off mode

Profile editor - Added element copy/pasting
Shared UI - Fix namespaces
This commit is contained in:
Robert 2022-04-17 20:23:18 +02:00
parent 8c7bbc3f0f
commit 52f2338154
84 changed files with 1059 additions and 608 deletions

View File

@ -75,6 +75,9 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinput_005Cevents/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinput_005Cevents/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinput_005Cinterfaces/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinput_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinterfaces/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cprocessmonitor/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cprocessmonitor_005Cevents/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cprocessmonitor_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cregistration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cregistration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cregistration_005Cinterfaces/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cregistration_005Cinterfaces/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cstorage/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cstorage/@EntryIndexedValue">True</s:Boolean>

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using Artemis.Core.Internal; using Artemis.Core.Internal;
using Artemis.Core.VisualScripting.Internal;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions; using Artemis.Storage.Entities.Profile.Conditions;
@ -13,13 +14,15 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{ {
private readonly string _displayName; private readonly string _displayName;
private readonly EventConditionEntity _entity; private readonly EventConditionEntity _entity;
private EventDefaultNode _eventNode; private IEventConditionNode _startNode;
private DataModelPath? _eventPath; private DataModelPath? _eventPath;
private DateTime _lastProcessedTrigger;
private EventOverlapMode _overlapMode;
private NodeScript<bool> _script; private NodeScript<bool> _script;
private EventTriggerMode _triggerMode;
private bool _wasMet; private bool _wasMet;
private DateTime _lastProcessedTrigger;
private object? _lastProcessedValue;
private EventOverlapMode _overlapMode;
private EventTriggerMode _triggerMode;
private EventToggleOffMode _toggleOffMode;
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="EventCondition" /> class /// Creates a new instance of the <see cref="EventCondition" /> class
@ -30,7 +33,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
_entity = new EventConditionEntity(); _entity = new EventConditionEntity();
_displayName = profileElement.GetType().Name; _displayName = profileElement.GetType().Name;
_eventNode = new EventDefaultNode {X = -300}; _startNode = new EventConditionEventStartNode {X = -300};
_script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile); _script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile);
} }
@ -40,7 +43,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
_entity = entity; _entity = entity;
_displayName = profileElement.GetType().Name; _displayName = profileElement.GetType().Name;
_eventNode = new EventDefaultNode(); _startNode = new EventConditionEventStartNode();
_script = null!; _script = null!;
Load(); Load();
@ -83,18 +86,70 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
set => SetAndNotify(ref _overlapMode, value); set => SetAndNotify(ref _overlapMode, value);
} }
/// <summary>
/// Gets or sets the mode for render elements when toggling off the event when using <see cref="EventTriggerMode.Toggle"/>.
/// </summary>
public EventToggleOffMode ToggleOffMode
{
get => _toggleOffMode;
set => SetAndNotify(ref _toggleOffMode, value);
}
/// <summary> /// <summary>
/// Updates the event node, applying the selected event /// Updates the event node, applying the selected event
/// </summary> /// </summary>
public void UpdateEventNode() public void UpdateEventNode()
{ {
IDataModelEvent? dataModelEvent = EventPath?.GetValue() as IDataModelEvent; if (EventPath == null)
_eventNode.CreatePins(dataModelEvent); return;
if (dataModelEvent != null && !Script.Nodes.Contains(_eventNode)) Type? pathType = EventPath.GetPropertyType();
Script.AddNode(_eventNode); if (pathType == null)
else if (dataModelEvent == null && Script.Nodes.Contains(_eventNode)) return;
Script.RemoveNode(_eventNode);
// 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);
} }
/// <summary> /// <summary>
@ -103,15 +158,30 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
/// <returns>The start node of the event script, if any.</returns> /// <returns>The start node of the event script, if any.</returns>
public INode GetStartNode() public INode GetStartNode()
{ {
return _eventNode; return _startNode;
} }
private bool Evaluate() private bool Evaluate()
{ {
if (EventPath?.GetValue() is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger) if (EventPath == null)
return false; 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) if (!Script.ExitNodeConnected)
return true; return true;
@ -151,6 +221,8 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{ {
if (IsMet && !_wasMet) if (IsMet && !_wasMet)
ProfileElement.Timeline.JumpToStart(); ProfileElement.Timeline.JumpToStart();
if (!IsMet && _wasMet && ToggleOffMode == EventToggleOffMode.SkipToEnd)
ProfileElement.Timeline.JumpToEndSegment();
} }
else else
{ {
@ -191,6 +263,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{ {
TriggerMode = (EventTriggerMode) _entity.TriggerMode; TriggerMode = (EventTriggerMode) _entity.TriggerMode;
OverlapMode = (EventOverlapMode) _entity.OverlapMode; OverlapMode = (EventOverlapMode) _entity.OverlapMode;
ToggleOffMode = (EventToggleOffMode) _entity.ToggleOffMode;
if (_entity.EventPath != null) if (_entity.EventPath != null)
EventPath = new DataModelPath(_entity.EventPath); EventPath = new DataModelPath(_entity.EventPath);
@ -206,6 +279,8 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{ {
_entity.TriggerMode = (int) TriggerMode; _entity.TriggerMode = (int) TriggerMode;
_entity.OverlapMode = (int) OverlapMode; _entity.OverlapMode = (int) OverlapMode;
_entity.ToggleOffMode = (int) ToggleOffMode;
Script.Save(); Script.Save();
_entity.Script = Script?.Entity; _entity.Script = Script?.Entity;
EventPath?.Save(); EventPath?.Save();
@ -221,9 +296,9 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
Script.Load(); Script.Load();
// The load action may have created an event node, use that one over the one we have here // 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) if (existingEventNode != null)
_eventNode = (EventDefaultNode) existingEventNode; _startNode = (IEventConditionNode) existingEventNode;
UpdateEventNode(); UpdateEventNode();
Script.LoadConnections(); Script.LoadConnections();
@ -269,3 +344,19 @@ public enum EventOverlapMode
/// </summary> /// </summary>
Ignore Ignore
} }
/// <summary>
/// Represents a mode for render elements when toggling off the event when using <see cref="EventTriggerMode.Toggle"/>.
/// </summary>
public enum EventToggleOffMode
{
/// <summary>
/// When the event toggles the condition off, finish the the current run of the main timeline
/// </summary>
Finish,
/// <summary>
/// When the event toggles the condition off, skip to the end segment of the timeline
/// </summary>
SkipToEnd
}

View File

@ -1,67 +0,0 @@
using System;
using System.Collections.Generic;
namespace Artemis.Core
{
internal class DataModelValueChangedEvent<T> : 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<T>);
public string TriggerPastParticiple => "changed";
public bool TrackHistory { get; set; } = false;
public DataModelEventArgs? LastEventArgumentsUntyped { get; private set; }
public List<DataModelEventArgs> 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<T>(CurrentValue, LastValue);
LastTrigger = DateTime.Now;
TriggerCount++;
OnEventTriggered();
}
#region Events
public event EventHandler? EventTriggered;
internal virtual void OnEventTriggered()
{
EventTriggered?.Invoke(this, EventArgs.Empty);
}
#endregion
}
}

View File

@ -1,18 +0,0 @@
using Artemis.Core.Modules;
namespace Artemis.Core
{
internal class DataModelValueChangedEventArgs<T> : 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; }
}
}

View File

@ -334,20 +334,18 @@ namespace Artemis.Core
/// Explorer /// Explorer
/// </summary> /// </summary>
/// <returns>The resulting name i.e. <c>New layer</c> or <c>New layer (2)</c></returns> /// <returns>The resulting name i.e. <c>New layer</c> or <c>New layer (2)</c></returns>
public string GetNewLayerName() public string GetNewLayerName(string baseName = "New layer")
{ {
if (!Children.Any(c => c is Layer)) if (!Children.Any(c => c is Layer && c.Name == baseName))
return "New layer"; return baseName;
// Grab existing unnamed layers and get the first available number int current = 2;
// Looks slow but it's not https://stackoverflow.com/a/8865806/5015269 while (true)
Regex regex = new(@"New layer \((\d+)\)"); {
int firstAvailable = Enumerable.Range(1, int.MaxValue) if (Children.All(c => c is Layer && c.Name != $"{baseName} ({current})"))
.Except(Children.Where(c => c is Layer && c.Name != null && regex.IsMatch(c.Name)) return $"{baseName} ({current})";
.Select(c => int.Parse(regex.Match(c.Name!).Groups[1].Value)) current++;
.OrderBy(i => i)) }
.First();
return $"New layer ({firstAvailable})";
} }
/// <summary> /// <summary>
@ -355,20 +353,18 @@ namespace Artemis.Core
/// in Explorer /// in Explorer
/// </summary> /// </summary>
/// <returns>The resulting name i.e. <c>New folder</c> or <c>New folder (2)</c></returns> /// <returns>The resulting name i.e. <c>New folder</c> or <c>New folder (2)</c></returns>
public string GetNewFolderName() public string GetNewFolderName(string baseName = "New folder")
{ {
if (!Children.Any(c => c is Folder)) if (!Children.Any(c => c is Folder && c.Name == baseName))
return "New folder"; return baseName;
// Grab existing unnamed layers and get the first available number int current = 2;
// Looks slow but it's not https://stackoverflow.com/a/8865806/5015269 while (true)
Regex regex = new(@"New folder \((\d+)\)"); {
int firstAvailable = Enumerable.Range(1, int.MaxValue) if (Children.All(c => c is Folder && c.Name != $"{baseName} ({current})"))
.Except(Children.Where(c => c is Folder && c.Name != null && regex.IsMatch(c.Name)) return $"{baseName} ({current})";
.Select(c => int.Parse(regex.Match(c.Name!).Groups[1].Value)) current++;
.OrderBy(i => i)) }
.First();
return $"New folder ({firstAvailable})";
} }
#endregion #endregion

View File

@ -38,13 +38,14 @@ namespace Artemis.Core.Services
// ReSharper disable UnusedParameter.Local // ReSharper disable UnusedParameter.Local
public CoreService(IKernel kernel, public CoreService(IKernel kernel,
ILogger logger, ILogger logger,
StorageMigrationService _, // injected to ensure migration runs early StorageMigrationService _1, // injected to ensure migration runs early
ISettingsService settingsService, ISettingsService settingsService,
IPluginManagementService pluginManagementService, IPluginManagementService pluginManagementService,
IRgbService rgbService, IRgbService rgbService,
IProfileService profileService, IProfileService profileService,
IModuleService moduleService, IModuleService moduleService,
IScriptingService scriptingService) IScriptingService scriptingService,
IProcessMonitorService _2)
{ {
Kernel = kernel; Kernel = kernel;
Constants.CorePlugin.Kernel = kernel; Constants.CorePlugin.Kernel = kernel;

View File

@ -11,7 +11,6 @@ namespace Artemis.Core.Services
internal class ProcessMonitorService : IProcessMonitorService internal class ProcessMonitorService : IProcessMonitorService
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly Timer _processScanTimer;
private readonly ProcessComparer _comparer; private readonly ProcessComparer _comparer;
private Process[] _lastScannedProcesses; private Process[] _lastScannedProcesses;
@ -19,16 +18,15 @@ namespace Artemis.Core.Services
{ {
_logger = logger; _logger = logger;
_lastScannedProcesses = Process.GetProcesses(); _lastScannedProcesses = Process.GetProcesses();
_processScanTimer = new Timer(1000); Timer processScanTimer = new(1000);
_processScanTimer.Elapsed += OnTimerElapsed; processScanTimer.Elapsed += OnTimerElapsed;
_processScanTimer.Start(); processScanTimer.Start();
_comparer = new ProcessComparer(); _comparer = new ProcessComparer();
ProcessActivationRequirement.ProcessMonitorService = this; ProcessActivationRequirement.ProcessMonitorService = this;
} }
public event EventHandler<ProcessEventArgs>? ProcessStarted; public event EventHandler<ProcessEventArgs>? ProcessStarted;
public event EventHandler<ProcessEventArgs>? ProcessStopped; public event EventHandler<ProcessEventArgs>? ProcessStopped;
public IEnumerable<Process> GetRunningProcesses() public IEnumerable<Process> GetRunningProcesses()
@ -36,7 +34,7 @@ namespace Artemis.Core.Services
return _lastScannedProcesses; return _lastScannedProcesses;
} }
private void OnTimerElapsed(object sender, ElapsedEventArgs e) private void OnTimerElapsed(object? sender, ElapsedEventArgs e)
{ {
Process[] newProcesses = Process.GetProcesses(); Process[] newProcesses = Process.GetProcesses();
foreach (Process startedProcess in newProcesses.Except(_lastScannedProcesses, _comparer)) 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; 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; if (obj == null) return 0;
return obj.Id; return obj.Id;

View File

@ -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<OutputPin> _pinBucket = new();
private readonly Dictionary<PropertyInfo, OutputPin> _propertyPins;
private IDataModelEvent? _dataModelEvent;
public EventConditionEventStartNode() : base(NodeId, "Event Arguments", "Contains the event arguments that triggered the evaluation")
{
_propertyPins = new Dictionary<PropertyInfo, OutputPin>();
}
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()!;
}
}
/// <summary>
/// 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.
/// </summary>
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;
}
}

View File

@ -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;
}
/// <inheritdoc />
public override void Evaluate()
{
NewValue.Value = _newValue;
OldValue.Value = _oldValue;
}
}

View File

@ -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<PropertyInfo, OutputPin> _propertyPins;
private readonly List<OutputPin> _pinBucket = new();
private IDataModelEvent? _dataModelEvent;
public EventDefaultNode() : base(NodeId, "Event Arguments", "Contains the event arguments that triggered the evaluation")
{
_propertyPins = new Dictionary<PropertyInfo, OutputPin>();
}
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()!;
}
}
/// <summary>
/// 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.
/// </summary>
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;
}
}
}

View File

@ -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
{
}
}

View File

@ -7,6 +7,7 @@ namespace Artemis.Storage.Entities.Profile.Conditions
{ {
public int TriggerMode { get; set; } public int TriggerMode { get; set; }
public int OverlapMode { get; set; } public int OverlapMode { get; set; }
public int ToggleOffMode { get; set; }
public DataModelPathEntity EventPath { get; set; } public DataModelPathEntity EventPath { get; set; }
public NodeScriptEntity Script { get; set; } public NodeScriptEntity Script { get; set; }
} }

View File

@ -3,6 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" 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! Welcome to Avalonia!
</UserControl> </UserControl>

View File

@ -8,7 +8,7 @@ using Avalonia.Svg.Skia;
using Material.Icons; using Material.Icons;
using Material.Icons.Avalonia; using Material.Icons.Avalonia;
namespace Artemis.UI.Shared.Controls namespace Artemis.UI.Shared
{ {
/// <summary> /// <summary>
/// Represents a control that can display an arbitrary kind of icon. /// Represents a control that can display an arbitrary kind of icon.

View File

@ -13,10 +13,11 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Threading;
using Material.Icons.Avalonia; using Material.Icons.Avalonia;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Shared.Controls.DataModelPicker; namespace Artemis.UI.Shared.DataModelPicker;
/// <summary> /// <summary>
/// Represents a data model picker picker that can be used to select a data model path. /// 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<DataModelPropertiesViewModel?> DataModelViewModelProperty = public static readonly StyledProperty<DataModelPropertiesViewModel?> DataModelViewModelProperty =
AvaloniaProperty.Register<DataModelPicker, DataModelPropertiesViewModel?>(nameof(DataModelViewModel)); AvaloniaProperty.Register<DataModelPicker, DataModelPropertiesViewModel?>(nameof(DataModelViewModel));
/// <summary>
/// A list of data model view models to show
/// </summary>
public static readonly StyledProperty<ObservableCollection<DataModelPropertiesViewModel>?> ExtraDataModelViewModelsProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<DataModelPropertiesViewModel>?>(nameof(ExtraDataModelViewModels), new ObservableCollection<DataModelPropertiesViewModel>());
/// <summary> /// <summary>
/// A list of types to filter the selectable paths on. /// A list of types to filter the selectable paths on.
/// </summary> /// </summary>
public static readonly StyledProperty<ObservableCollection<Type>?> FilterTypesProperty = public static readonly StyledProperty<ObservableCollection<Type>?> FilterTypesProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<Type>?>(nameof(FilterTypes), new ObservableCollection<Type>()); AvaloniaProperty.Register<DataModelPicker, ObservableCollection<Type>?>(nameof(FilterTypes), new ObservableCollection<Type>());
private MaterialIcon? _currentPathIcon; /// <summary>
private TextBlock? _currentPathDisplay; /// Gets or sets a boolean indicating whether the picker is in event picker mode.
/// When <see langword="true" /> event children aren't selectable and non-events are described as "{PropertyName}
/// changed".
/// </summary>
public static readonly StyledProperty<bool> IsEventPickerProperty =
AvaloniaProperty.Register<DataModelPicker, bool>(nameof(IsEventPicker));
private TextBlock? _currentPathDescription; private TextBlock? _currentPathDescription;
private TextBlock? _currentPathDisplay;
private MaterialIcon? _currentPathIcon;
private TreeView? _dataModelTreeView; private TreeView? _dataModelTreeView;
private DispatcherTimer? _updateTimer;
static DataModelPicker() static DataModelPicker()
{ {
ModulesProperty.Changed.Subscribe(ModulesChanged); ModulesProperty.Changed.Subscribe(ModulesChanged);
DataModelPathProperty.Changed.Subscribe(DataModelPathPropertyChanged); DataModelPathProperty.Changed.Subscribe(DataModelPathPropertyChanged);
DataModelViewModelProperty.Changed.Subscribe(DataModelViewModelPropertyChanged); DataModelViewModelProperty.Changed.Subscribe(DataModelViewModelPropertyChanged);
ExtraDataModelViewModelsProperty.Changed.Subscribe(ExtraDataModelViewModelsChanged);
} }
/// <summary> /// <summary>
@ -126,15 +130,6 @@ public class DataModelPicker : TemplatedControl
private set => SetValue(DataModelViewModelProperty, value); private set => SetValue(DataModelViewModelProperty, value);
} }
/// <summary>
/// A list of data model view models to show.
/// </summary>
public ObservableCollection<DataModelPropertiesViewModel>? ExtraDataModelViewModels
{
get => GetValue(ExtraDataModelViewModelsProperty);
set => SetValue(ExtraDataModelViewModelsProperty, value);
}
/// <summary> /// <summary>
/// A list of types to filter the selectable paths on. /// A list of types to filter the selectable paths on.
/// </summary> /// </summary>
@ -144,6 +139,17 @@ public class DataModelPicker : TemplatedControl
set => SetValue(FilterTypesProperty, value); set => SetValue(FilterTypesProperty, value);
} }
/// <summary>
/// Gets or sets a boolean indicating whether the picker is in event picker mode.
/// When <see langword="true" /> event children aren't selectable and non-events are described as "{PropertyName}
/// changed".
/// </summary>
public bool IsEventPicker
{
get => GetValue(IsEventPickerProperty);
set => SetValue(IsEventPickerProperty, value);
}
/// <summary> /// <summary>
/// Occurs when a new path has been selected /// Occurs when a new path has been selected
/// </summary> /// </summary>
@ -158,42 +164,6 @@ public class DataModelPicker : TemplatedControl
DataModelPathSelected?.Invoke(this, e); DataModelPathSelected?.Invoke(this, e);
} }
#region Overrides of TemplatedControl
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_dataModelTreeView != null)
_dataModelTreeView.SelectionChanged -= DataModelTreeViewOnSelectionChanged;
_currentPathIcon = e.NameScope.Find<MaterialIcon>("CurrentPathIcon");
_currentPathDisplay = e.NameScope.Find<TextBlock>("CurrentPathDisplay");
_currentPathDescription = e.NameScope.Find<TextBlock>("CurrentPathDescription");
_dataModelTreeView = e.NameScope.Find<TreeView>("DataModelTreeView");
if (_dataModelTreeView != null)
_dataModelTreeView.SelectionChanged += DataModelTreeViewOnSelectionChanged;
}
#region Overrides of Visual
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
GetDataModel();
UpdateCurrentPath(true);
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DataModelViewModel?.Dispose();
}
#endregion
#endregion
private static void ModulesChanged(AvaloniaPropertyChangedEventArgs<ObservableCollection<Module>?> e) private static void ModulesChanged(AvaloniaPropertyChangedEventArgs<ObservableCollection<Module>?> e)
{ {
if (e.Sender is DataModelPicker dataModelPicker) if (e.Sender is DataModelPicker dataModelPicker)
@ -212,11 +182,6 @@ public class DataModelPicker : TemplatedControl
e.OldValue.Value.Dispose(); e.OldValue.Value.Dispose();
} }
private static void ExtraDataModelViewModelsChanged(AvaloniaPropertyChangedEventArgs<ObservableCollection<DataModelPropertiesViewModel>?> e)
{
// TODO, the original did nothing here either and I can't remember why
}
private void ExecuteSelectPropertyCommand(DataModelVisualizationViewModel selected) private void ExecuteSelectPropertyCommand(DataModelVisualizationViewModel selected)
{ {
if (selected.DataModelPath == null) if (selected.DataModelPath == null)
@ -228,6 +193,14 @@ public class DataModelPicker : TemplatedControl
OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath)); OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath));
} }
private void Update(object? sender, EventArgs e)
{
if (DataModelUIService == null)
return;
DataModelViewModel?.Update(DataModelUIService, new DataModelUpdateConfiguration(!IsEventPicker));
}
private void GetDataModel() private void GetDataModel()
{ {
if (DataModelUIService == null) if (DataModelUIService == null)
@ -251,10 +224,11 @@ public class DataModelPicker : TemplatedControl
private void DataModelOnUpdateRequested(object? sender, EventArgs e) private void DataModelOnUpdateRequested(object? sender, EventArgs e)
{ {
DataModelViewModel?.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes); if (DataModelUIService == null || DataModelViewModel == null)
if (ExtraDataModelViewModels == null) return; return;
foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels)
extraDataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes); DataModelViewModel.Update(DataModelUIService, new DataModelUpdateConfiguration(IsEventPicker));
DataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
} }
private void DataModelTreeViewOnSelectionChanged(object? sender, SelectionChangedEventArgs e) private void DataModelTreeViewOnSelectionChanged(object? sender, SelectionChangedEventArgs e)
@ -295,6 +269,45 @@ public class DataModelPicker : TemplatedControl
_currentPathDescription.Text = DataModelPath.GetPropertyDescription()?.Description; _currentPathDescription.Text = DataModelPath.GetPropertyDescription()?.Description;
if (_currentPathIcon != null) if (_currentPathIcon != null)
_currentPathIcon.Kind = DataModelPath.GetPropertyType().GetTypeIcon(); _currentPathIcon.Kind = DataModelPath.GetPropertyType().GetTypeIcon();
} }
#region Overrides of TemplatedControl
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_dataModelTreeView != null)
_dataModelTreeView.SelectionChanged -= DataModelTreeViewOnSelectionChanged;
_currentPathIcon = e.NameScope.Find<MaterialIcon>("CurrentPathIcon");
_currentPathDisplay = e.NameScope.Find<TextBlock>("CurrentPathDisplay");
_currentPathDescription = e.NameScope.Find<TextBlock>("CurrentPathDescription");
_dataModelTreeView = e.NameScope.Find<TreeView>("DataModelTreeView");
if (_dataModelTreeView != null)
_dataModelTreeView.SelectionChanged += DataModelTreeViewOnSelectionChanged;
}
#region Overrides of Visual
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
GetDataModel();
UpdateCurrentPath(true);
_updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(200), DispatcherPriority.Normal, Update);
_updateTimer.Start();
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DataModelViewModel?.Dispose();
_updateTimer?.Stop();
_updateTimer = null;
}
#endregion
#endregion
} }

View File

@ -3,8 +3,7 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.UI.Shared.Controls.Flyouts; using Artemis.UI.Shared.Flyouts;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
@ -13,7 +12,7 @@ using Avalonia.Interactivity;
using Avalonia.Threading; using Avalonia.Threading;
using FluentAvalonia.Core; using FluentAvalonia.Core;
namespace Artemis.UI.Shared.Controls.DataModelPicker; namespace Artemis.UI.Shared.DataModelPicker;
/// <summary> /// <summary>
/// Represents a button that can be used to pick a data model path in a flyout. /// 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<ObservableCollection<Module>?> ModulesProperty = public static readonly StyledProperty<ObservableCollection<Module>?> ModulesProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<Module>?>(nameof(Modules), new ObservableCollection<Module>()); AvaloniaProperty.Register<DataModelPicker, ObservableCollection<Module>?>(nameof(Modules), new ObservableCollection<Module>());
/// <summary>
/// A list of data model view models to show
/// </summary>
public static readonly StyledProperty<ObservableCollection<DataModelPropertiesViewModel>?> ExtraDataModelViewModelsProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<DataModelPropertiesViewModel>?>(nameof(ExtraDataModelViewModels), new ObservableCollection<DataModelPropertiesViewModel>());
/// <summary> /// <summary>
/// A list of types to filter the selectable paths on. /// A list of types to filter the selectable paths on.
/// </summary> /// </summary>
public static readonly StyledProperty<ObservableCollection<Type>?> FilterTypesProperty = public static readonly StyledProperty<ObservableCollection<Type>?> FilterTypesProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<Type>?>(nameof(FilterTypes), new ObservableCollection<Type>()); AvaloniaProperty.Register<DataModelPicker, ObservableCollection<Type>?>(nameof(FilterTypes), new ObservableCollection<Type>());
/// <summary>
/// Gets or sets a boolean indicating whether the picker is in event picker mode.
/// When <see langword="true" /> event children aren't selectable and non-events are described as "{PropertyName}
/// changed".
/// </summary>
public static readonly StyledProperty<bool> IsEventPickerProperty =
AvaloniaProperty.Register<DataModelPicker, bool>(nameof(IsEventPicker));
private bool _attached; private bool _attached;
private bool _flyoutActive; private bool _flyoutActive;
private Button? _button; private Button? _button;
@ -165,15 +166,6 @@ public class DataModelPickerButton : TemplatedControl
set => SetValue(ModulesProperty, value); set => SetValue(ModulesProperty, value);
} }
/// <summary>
/// A list of data model view models to show.
/// </summary>
public ObservableCollection<DataModelPropertiesViewModel>? ExtraDataModelViewModels
{
get => GetValue(ExtraDataModelViewModelsProperty);
set => SetValue(ExtraDataModelViewModelsProperty, value);
}
/// <summary> /// <summary>
/// A list of types to filter the selectable paths on. /// A list of types to filter the selectable paths on.
/// </summary> /// </summary>
@ -183,6 +175,17 @@ public class DataModelPickerButton : TemplatedControl
set => SetValue(FilterTypesProperty, value); set => SetValue(FilterTypesProperty, value);
} }
/// <summary>
/// Gets or sets a boolean indicating whether the picker is in event picker mode.
/// When <see langword="true" /> event children aren't selectable and non-events are described as "{PropertyName}
/// changed".
/// </summary>
public bool IsEventPicker
{
get => GetValue(IsEventPickerProperty);
set => SetValue(IsEventPickerProperty, value);
}
/// <summary> /// <summary>
/// Raised when the flyout opens. /// Raised when the flyout opens.
/// </summary> /// </summary>
@ -239,14 +242,24 @@ public class DataModelPickerButton : TemplatedControl
} }
else else
{ {
// If a valid path is selected, gather all its segments and create a visual representation of the path
string? formattedPath = null; string? formattedPath = null;
if (DataModelPath != null && DataModelPath.IsValid) if (DataModelPath != null && DataModelPath.IsValid)
formattedPath = string.Join(" ", DataModelPath.Segments.Where(s => s.GetPropertyDescription() != null).Select(s => s.GetPropertyDescription()!.Name)); 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); 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 ? formattedPath
: DataModelPath?.Segments.LastOrDefault()?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier; : 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 // 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.DataModelPath = DataModelPath;
_flyout.DataModelPicker.ExtraDataModelViewModels = ExtraDataModelViewModels;
_flyout.DataModelPicker.FilterTypes = FilterTypes; _flyout.DataModelPicker.FilterTypes = FilterTypes;
_flyout.DataModelPicker.IsEventPicker = IsEventPicker;
_flyout.DataModelPicker.Modules = Modules; _flyout.DataModelPicker.Modules = Modules;
_flyout.DataModelPicker.ShowDataModelValues = ShowDataModelValues; _flyout.DataModelPicker.ShowDataModelValues = ShowDataModelValues;

View File

@ -18,7 +18,7 @@ using Avalonia.Rendering;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Visuals.Media.Imaging; using Avalonia.Visuals.Media.Imaging;
namespace Artemis.UI.Shared.Controls namespace Artemis.UI.Shared
{ {
/// <summary> /// <summary>
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors /// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors

View File

@ -11,7 +11,7 @@ using Color = Avalonia.Media.Color;
using Point = Avalonia.Point; using Point = Avalonia.Point;
using SolidColorBrush = Avalonia.Media.SolidColorBrush; using SolidColorBrush = Avalonia.Media.SolidColorBrush;
namespace Artemis.UI.Shared.Controls namespace Artemis.UI.Shared
{ {
internal class DeviceVisualizerLed internal class DeviceVisualizerLed
{ {

View File

@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.Controls.EnumComboBox"> x:Class="Artemis.UI.Shared.EnumComboBox">
<ComboBox x:Name="EnumComboBox" HorizontalAlignment="Stretch"> <ComboBox x:Name="EnumComboBox" HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>

View File

@ -8,7 +8,7 @@ using Avalonia.Data;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace Artemis.UI.Shared.Controls namespace Artemis.UI.Shared
{ {
/// <summary> /// <summary>
/// Represents a combobox that can display the values of an enum. /// Represents a combobox that can display the values of an enum.

View File

@ -5,7 +5,7 @@ using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Controls.Primitives; using FluentAvalonia.UI.Controls.Primitives;
namespace Artemis.UI.Shared.Controls.Flyouts; namespace Artemis.UI.Shared.Flyouts;
/// <summary> /// <summary>
/// Defines a flyout that hosts a data model picker. /// Defines a flyout that hosts a data model picker.

View File

@ -1,6 +1,6 @@
using Avalonia.Controls; using Avalonia.Controls;
namespace Artemis.UI.Shared.Controls.Flyouts; namespace Artemis.UI.Shared.Flyouts;
/// <summary> /// <summary>
/// Defines a flyout that hosts a gradient picker. /// Defines a flyout that hosts a gradient picker.

View File

@ -16,7 +16,7 @@ using FluentAvalonia.UI.Media;
using ReactiveUI; using ReactiveUI;
using Button = Avalonia.Controls.Button; using Button = Avalonia.Controls.Button;
namespace Artemis.UI.Shared.Controls.GradientPicker; namespace Artemis.UI.Shared.GradientPicker;
/// <summary> /// <summary>
/// Represents a gradient picker that can be used to edit a gradient. /// Represents a gradient picker that can be used to edit a gradient.

View File

@ -2,7 +2,7 @@
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Controls.Flyouts; using Artemis.UI.Shared.Flyouts;
using Artemis.UI.Shared.Providers; using Artemis.UI.Shared.Providers;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
@ -13,7 +13,7 @@ using Avalonia.Media;
using FluentAvalonia.Core; using FluentAvalonia.Core;
using Button = FluentAvalonia.UI.Controls.Button; using Button = FluentAvalonia.UI.Controls.Button;
namespace Artemis.UI.Shared.Controls.GradientPicker; namespace Artemis.UI.Shared.GradientPicker;
/// <summary> /// <summary>
/// Represents a gradient picker box that can be used to edit a gradient /// Represents a gradient picker box that can be used to edit a gradient

View File

@ -2,11 +2,10 @@
using Artemis.Core; using Artemis.Core;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
namespace Artemis.UI.Shared.Controls.GradientPicker; namespace Artemis.UI.Shared.GradientPicker;
public class GradientPickerColorStop : TemplatedControl public class GradientPickerColorStop : TemplatedControl
{ {

View File

@ -2,9 +2,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls" xmlns:shared="clr-namespace:Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.Controls.HotkeyBox"> x:Class="Artemis.UI.Shared.HotkeyBox">
<UserControl.Styles> <UserControl.Styles>
<Style Selector="TextBox#DisplayTextBox:focus:not(TextBox:empty)"> <Style Selector="TextBox#DisplayTextBox:focus:not(TextBox:empty)">
@ -17,7 +17,7 @@
</Style> </Style>
</UserControl.Styles> </UserControl.Styles>
<controls:NoInputTextBox x:Name="DisplayTextBox" <shared:NoInputTextBox x:Name="DisplayTextBox"
Watermark="{Binding $parent.Watermark}" Watermark="{Binding $parent.Watermark}"
UseFloatingWatermark="{Binding $parent.UseFloatingWatermark}" UseFloatingWatermark="{Binding $parent.UseFloatingWatermark}"
Classes="clearButton" Classes="clearButton"

View File

@ -11,7 +11,7 @@ using Avalonia.Markup.Xaml;
using Humanizer; using Humanizer;
using Material.Icons; using Material.Icons;
namespace Artemis.UI.Shared.Controls namespace Artemis.UI.Shared
{ {
/// <summary> /// <summary>
/// Represents a control that can be used to display or edit <see cref="Core.Hotkey"/> instances. /// Represents a control that can be used to display or edit <see cref="Core.Hotkey"/> instances.

View File

@ -3,7 +3,7 @@ using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Styling; using Avalonia.Styling;
namespace Artemis.UI.Shared.Controls namespace Artemis.UI.Shared
{ {
internal class NoInputTextBox : TextBox, IStyleable internal class NoInputTextBox : TextBox, IStyleable
{ {

View File

@ -3,5 +3,5 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.Controls.ProfileConfigurationIcon"> x:Class="Artemis.UI.Shared.ProfileConfigurationIcon">
</UserControl> </UserControl>

View File

@ -10,7 +10,7 @@ using Avalonia.Svg.Skia;
using Material.Icons; using Material.Icons;
using Material.Icons.Avalonia; using Material.Icons.Avalonia;
namespace Artemis.UI.Shared.Controls namespace Artemis.UI.Shared
{ {
/// <summary> /// <summary>
/// Represents a control that can display the icon of a specific <see cref="ProfileConfiguration"/>. /// Represents a control that can display the icon of a specific <see cref="ProfileConfiguration"/>.

View File

@ -6,7 +6,7 @@ using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Media; using Avalonia.Media;
namespace Artemis.UI.Shared.Controls; namespace Artemis.UI.Shared;
/// <summary> /// <summary>
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors /// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors

View File

@ -1,6 +1,5 @@
using System; using System;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Controls;
namespace Artemis.UI.Shared.Events namespace Artemis.UI.Shared.Events
{ {

View File

@ -1,5 +1,4 @@
using System; using System;
using Artemis.UI.Shared.Controls;
using Avalonia; using Avalonia;
using Avalonia.Input; using Avalonia.Input;

View File

@ -0,0 +1,38 @@
using Artemis.Core;
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
/// <summary>
/// Represents a profile editor command that can be used to update an event condition's overlap mode.
/// </summary>
public class UpdateEventToggleOffMode : IProfileEditorCommand
{
private readonly EventCondition _eventCondition;
private readonly EventToggleOffMode _value;
private readonly EventToggleOffMode _oldValue;
/// <summary>
/// Creates a new instance of the <see cref="UpdateEventOverlapMode" /> class.
/// </summary>
public UpdateEventToggleOffMode(EventCondition eventCondition, EventToggleOffMode value)
{
_eventCondition = eventCondition;
_value = value;
_oldValue = eventCondition.ToggleOffMode;
}
/// <inheritdoc />
public string DisplayName => "Update event toggle off mode";
/// <inheritdoc />
public void Execute()
{
_eventCondition.ToggleOffMode = _value;
}
/// <inheritdoc />
public void Undo()
{
_eventCondition.ToggleOffMode = _oldValue;
}
}

View File

@ -1,9 +1,9 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:controls1="clr-namespace:Artemis.UI.Shared.Controls" xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.GradientPicker"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker" xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker"> xmlns:controls1="clr-namespace:Artemis.UI.Shared">
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="50"> <Border Padding="50">
<StackPanel Spacing="5"> <StackPanel Spacing="5">

View File

@ -1,9 +1,9 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared" xmlns:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"> xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker">
<Design.PreviewWith> <Design.PreviewWith>
<dataModelPicker:DataModelPicker /> <dataModelPicker:DataModelPicker />
</Design.PreviewWith> </Design.PreviewWith>
@ -20,7 +20,22 @@
RowDefinitions="*" RowDefinitions="*"
MinHeight="38" MinHeight="38"
IsVisible="{Binding DataModelPath, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static ObjectConverters.IsNotNull}}"> IsVisible="{Binding DataModelPath, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static ObjectConverters.IsNotNull}}">
<avalonia:MaterialIcon Grid.Column="0" Grid.Row="0" Name="CurrentPathIcon" Kind="QuestionMarkCircle" Height="22" Width="22" Margin="5 0 15 0" /> <avalonia:MaterialIcon Grid.Column="0"
Grid.Row="0"
Name="CurrentPathIcon"
Kind="QuestionMarkCircle"
Height="22"
Width="22"
Margin="5 0 15 0"
IsVisible="{Binding !IsEventPicker, RelativeSource={RelativeSource TemplatedParent}}"/>
<avalonia:MaterialIcon Grid.Column="0"
Grid.Row="0"
Name="EventIcon"
Kind="LightningBolt"
Height="22"
Width="22"
Margin="5 0 15 0"
IsVisible="{Binding IsEventPicker, RelativeSource={RelativeSource TemplatedParent}}"/>
<StackPanel Grid.Column="1" Grid.Row="0" VerticalAlignment="Center"> <StackPanel Grid.Column="1" Grid.Row="0" VerticalAlignment="Center">
<TextBlock Name="CurrentPathDisplay" Classes="BodyStrongTextBlockStyle" MaxHeight="50" /> <TextBlock Name="CurrentPathDisplay" Classes="BodyStrongTextBlockStyle" MaxHeight="50" />
<TextBlock Name="CurrentPathDescription" Classes="BodyTextBlockStyle" Foreground="{DynamicResource TextFillColorSecondary}" MaxHeight="50" /> <TextBlock Name="CurrentPathDescription" Classes="BodyTextBlockStyle" Foreground="{DynamicResource TextFillColorSecondary}" MaxHeight="50" />
@ -42,6 +57,7 @@
<TreeView.Styles> <TreeView.Styles>
<Style Selector="TreeViewItem"> <Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" /> <Setter Property="IsExpanded" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />
<Setter Property="IsEnabled" Value="{Binding IsMatchingFilteredTypes, Mode=OneWay}" />
</Style> </Style>
</TreeView.Styles> </TreeView.Styles>
<TreeView.DataTemplates> <TreeView.DataTemplates>
@ -58,7 +74,13 @@
<TreeDataTemplate DataType="{x:Type dataModel:DataModelPropertyViewModel}"> <TreeDataTemplate DataType="{x:Type dataModel:DataModelPropertyViewModel}">
<Grid ColumnDefinitions="Auto,*"> <Grid ColumnDefinitions="Auto,*">
<TextBlock Grid.Column="0" Text="{Binding PropertyDescription.Name}" ToolTip.Tip="{Binding PropertyDescription.Description}" /> <StackPanel Grid.Column="0" Orientation="Horizontal">
<TextBlock Text="{Binding PropertyDescription.Name}" ToolTip.Tip="{Binding PropertyDescription.Description}" />
<TextBlock Text=" changed"
ToolTip.Tip="{Binding PropertyDescription.Description}"
IsVisible="{Binding IsEventPicker, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dataModelPicker:DataModelPicker}}}"/>
</StackPanel>
<ContentControl Grid.Column="1" Content="{Binding DisplayViewModel}" FontFamily="Consolas" Margin="0 0 10 0" /> <ContentControl Grid.Column="1" Content="{Binding DisplayViewModel}" FontFamily="Consolas" Margin="0 0 10 0" />
</Grid> </Grid>
</TreeDataTemplate> </TreeDataTemplate>

View File

@ -1,8 +1,8 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker"> xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.GradientPicker">
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20" Width="200"> <Border Padding="20" Width="200">
<StackPanel Spacing="5"> <StackPanel Spacing="5">

View File

@ -1,10 +1,10 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Artemis.UI.Shared.Controls.GradientPicker"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
xmlns:fluent="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:fluent="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters"> xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.GradientPicker">
<Styles.Resources> <Styles.Resources>
<VisualBrush x:Key="LightCheckerboardBrush" TileMode="Tile" Stretch="Uniform" DestinationRect="3,0,10,10"> <VisualBrush x:Key="LightCheckerboardBrush" TileMode="Tile" Stretch="Uniform" DestinationRect="3,0,10,10">
<VisualBrush.Visual> <VisualBrush.Visual>
@ -18,10 +18,10 @@
</VisualBrush> </VisualBrush>
</Styles.Resources> </Styles.Resources>
<Design.PreviewWith> <Design.PreviewWith>
<controls:GradientPicker /> <gradientPicker:GradientPicker />
</Design.PreviewWith> </Design.PreviewWith>
<Style Selector="controls|GradientPickerColorStop /template/ Border.stop-handle"> <Style Selector="gradientPicker|GradientPickerColorStop /template/ Border.stop-handle">
<Setter Property="CornerRadius" Value="18" /> <Setter Property="CornerRadius" Value="18" />
<Setter Property="Width" Value="18" /> <Setter Property="Width" Value="18" />
<Setter Property="Height" Value="60" /> <Setter Property="Height" Value="60" />
@ -30,32 +30,32 @@
<Setter Property="BorderBrush" Value="{DynamicResource ToolTipBorderBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource ToolTipBorderBrush}" />
<Setter Property="Cursor" Value="Hand" /> <Setter Property="Cursor" Value="Hand" />
</Style> </Style>
<Style Selector="controls|GradientPickerColorStop /template/ Border.stop-handle > Border"> <Style Selector="gradientPicker|GradientPickerColorStop /template/ Border.stop-handle > Border">
<Setter Property="CornerRadius" Value="18" /> <Setter Property="CornerRadius" Value="18" />
<Setter Property="BorderThickness" Value="3" /> <Setter Property="BorderThickness" Value="3" />
<Setter Property="BorderBrush" Value="{DynamicResource SolidBackgroundFillColorQuarternaryBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource SolidBackgroundFillColorQuarternaryBrush}" />
<Setter Property="Background" Value="{DynamicResource LightCheckerboardBrush}" /> <Setter Property="Background" Value="{DynamicResource LightCheckerboardBrush}" />
</Style> </Style>
<Style Selector="controls|GradientPickerColorStop /template/ Border.stop-handle > Border > Border"> <Style Selector="gradientPicker|GradientPickerColorStop /template/ Border.stop-handle > Border > Border">
<Setter Property="CornerRadius" Value="18" /> <Setter Property="CornerRadius" Value="18" />
<Setter Property="BorderThickness" Value="2" /> <Setter Property="BorderThickness" Value="2" />
<Setter Property="Margin" Value="-1" /> <Setter Property="Margin" Value="-1" />
<Setter Property="BorderBrush" Value="{DynamicResource SolidBackgroundFillColorSecondaryBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource SolidBackgroundFillColorSecondaryBrush}" />
</Style> </Style>
<Style Selector="controls|GradientPickerColorStop:selected /template/ Border.stop-handle > Border"> <Style Selector="gradientPicker|GradientPickerColorStop:selected /template/ Border.stop-handle > Border">
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColorLight2}" /> <Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColorLight2}" />
</Style> </Style>
<Style Selector="controls|GradientPicker Border#Gradient"> <Style Selector="gradientPicker|GradientPicker Border#Gradient">
<Setter Property="Height" Value="40" /> <Setter Property="Height" Value="40" />
<Setter Property="CornerRadius" Value="4" /> <Setter Property="CornerRadius" Value="4" />
</Style> </Style>
<Style Selector="controls|GradientPicker Border#Gradient Border"> <Style Selector="gradientPicker|GradientPicker Border#Gradient Border">
<Setter Property="CornerRadius" Value="4" /> <Setter Property="CornerRadius" Value="4" />
</Style> </Style>
<Style Selector="controls|GradientPicker Border.stop-position"> <Style Selector="gradientPicker|GradientPicker Border.stop-position">
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorBaseBrush}" /> <Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorBaseBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="1" /> <Setter Property="BorderThickness" Value="1" />
@ -65,12 +65,12 @@
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
</Style> </Style>
<Style Selector="controls|GradientPicker Border.stop-position TextBlock"> <Style Selector="gradientPicker|GradientPicker Border.stop-position TextBlock">
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="HorizontalAlignment" Value="Center" />
</Style> </Style>
<Style Selector="controls|GradientPicker"> <Style Selector="gradientPicker|GradientPicker">
<Style.Resources> <Style.Resources>
<converters:ColorGradientToGradientStopsConverter x:Key="ColorGradientToGradientStopsConverter" /> <converters:ColorGradientToGradientStopsConverter x:Key="ColorGradientToGradientStopsConverter" />
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" /> <converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
@ -103,11 +103,11 @@
</ItemsControl.Styles> </ItemsControl.Styles>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate DataType="core:ColorGradientStop"> <DataTemplate DataType="core:ColorGradientStop">
<controls:GradientPickerColorStop ColorStop="{Binding}" <gradientPicker:GradientPickerColorStop ColorStop="{Binding}"
PositionReference="{Binding $parent[Border]}" PositionReference="{Binding $parent[Border]}"
Classes="gradient-handle" Classes="gradient-handle"
GradientPicker="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GradientPicker}}}"> GradientPicker="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type gradientPicker:GradientPicker}}}">
</controls:GradientPickerColorStop> </gradientPicker:GradientPickerColorStop>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
@ -202,7 +202,7 @@
<Button Name="DeleteButton" <Button Name="DeleteButton"
Grid.Column="3" Grid.Column="3"
Classes="icon-button" Classes="icon-button"
Command="{Binding DeleteStop, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GradientPicker}}}" Command="{Binding DeleteStop, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type gradientPicker:GradientPicker}}}"
CommandParameter="{Binding}"> CommandParameter="{Binding}">
<avalonia:MaterialIcon Kind="Close" /> <avalonia:MaterialIcon Kind="Close" />
</Button> </Button>
@ -247,7 +247,7 @@
</Setter> </Setter>
</Style> </Style>
<Style Selector="controls|GradientPickerColorStop.gradient-handle"> <Style Selector="gradientPicker|GradientPickerColorStop.gradient-handle">
<Style.Resources> <Style.Resources>
<converters:SKColorToBrushConverter x:Key="SKColorToBrushConverter" /> <converters:SKColorToBrushConverter x:Key="SKColorToBrushConverter" />
</Style.Resources> </Style.Resources>

View File

@ -1,7 +1,7 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"> xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.GradientPicker">
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20" Width="200"> <Border Padding="20" Width="200">
<StackPanel Spacing="5"> <StackPanel Spacing="5">

View File

@ -32,7 +32,7 @@
<TreeViewItem Header="SubItem Item3" /> <TreeViewItem Header="SubItem Item3" />
</TreeViewItem> </TreeViewItem>
</TreeViewItem> </TreeViewItem>
<TreeViewItem Header="Item3" /> <TreeViewItem Header="Item3" IsEnabled="False"/>
<TreeViewItem Header="Item4" /> <TreeViewItem Header="Item4" />
</TreeView> </TreeView>
</Border> </Border>
@ -94,6 +94,10 @@
<Setter Property="Margin" Value="0"></Setter> <Setter Property="Margin" Value="0"></Setter>
</Style> </Style>
<Style Selector="TreeView TreeViewItem[IsEnabled=False]">
<Setter Property="Foreground" Value="{DynamicResource ButtonDisabledForegroundThemeBrush}"/>
</Style>
<!-- <Style Selector="TreeView.no-right-margin TreeViewItem /template/ Border#TreeViewItemLayoutRoot"> --> <!-- <Style Selector="TreeView.no-right-margin TreeViewItem /template/ Border#TreeViewItemLayoutRoot"> -->
<!-- <Setter Property="Margin" Value="2 2 0 2"/> --> <!-- <Setter Property="Margin" Value="2 2 0 2"/> -->
<!-- </Style> --> <!-- </Style> -->

View File

@ -5,7 +5,7 @@ using Artemis.Core.Ninject;
using Artemis.UI.Exceptions; using Artemis.UI.Exceptions;
using Artemis.UI.Ninject; using Artemis.UI.Ninject;
using Artemis.UI.Screens.Root; 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.Ninject;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.VisualScripting.Ninject; using Artemis.VisualScripting.Ninject;

View File

@ -2,12 +2,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput" 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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.BoolPropertyInputView" x:Class="Artemis.UI.DefaultTypes.PropertyInput.BoolPropertyInputView"
x:DataType="propertyInput:BoolPropertyInputViewModel"> x:DataType="propertyInput:BoolPropertyInputViewModel">
<controls:EnumComboBox Classes="condensed" <shared:EnumComboBox Classes="condensed"
Width="200" Width="200"
Value="{CompiledBinding SelectedBooleanOption}" Value="{CompiledBinding SelectedBooleanOption}"
VerticalAlignment="Center" /> VerticalAlignment="Center" />

View File

@ -2,8 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker;assembly=Artemis.UI.Shared"
xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput" 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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.ColorGradientPropertyInputView" x:Class="Artemis.UI.DefaultTypes.PropertyInput.ColorGradientPropertyInputView"
x:DataType="propertyInput:ColorGradientPropertyInputViewModel"> x:DataType="propertyInput:ColorGradientPropertyInputViewModel">

View File

@ -1,5 +1,5 @@
using System; using System;
using Artemis.UI.Shared.Controls.GradientPicker; using Artemis.UI.Shared.GradientPicker;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;

View File

@ -2,8 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns: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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.DefaultTypes.PropertyInput.EnumPropertyInputView"> x:Class="Artemis.UI.DefaultTypes.PropertyInput.EnumPropertyInputView">
<controls:EnumComboBox Classes="condensed" Width="200" Value="{Binding InputValue}" VerticalAlignment="Center" /> <shared:EnumComboBox Classes="condensed" Width="200" Value="{Binding InputValue}" VerticalAlignment="Center" />
</UserControl> </UserControl>

View File

@ -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;
/// <summary>
/// Provides static extension methods for UI related profile element tasks.
/// </summary>
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<RenderProfileElement?> 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;
}
}
}

View File

@ -2,13 +2,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns: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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Debugger.Performance.PerformanceDebugPluginView"> x:Class="Artemis.UI.Screens.Debugger.Performance.PerformanceDebugPluginView">
<Border Classes="card-condensed" Margin="0 5"> <Border Classes="card-condensed" Margin="0 5">
<StackPanel> <StackPanel>
<Grid ColumnDefinitions="40,*"> <Grid ColumnDefinitions="40,*">
<controls:ArtemisIcon Grid.Column="0" Icon="{Binding Plugin.Info.ResolvedIcon}" Width="24" Height="24" /> <shared:ArtemisIcon Grid.Column="0" Icon="{Binding Plugin.Info.ResolvedIcon}" Width="24" Height="24" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" Classes="h5" Text="{Binding Plugin.Info.Name}" /> <TextBlock Grid.Column="1" VerticalAlignment="Center" Classes="h5" Text="{Binding Plugin.Info.Name}" />
</Grid> </Grid>

View File

@ -2,8 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" 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" mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800"
x:Class="Artemis.UI.Screens.Device.DevicePropertiesView" x:Class="Artemis.UI.Screens.Device.DevicePropertiesView"
Icon="/Assets/Images/Logo/application.ico" Icon="/Assets/Images/Logo/application.ico"
@ -26,7 +26,7 @@
</Grid.Background> </Grid.Background>
<Grid Grid.Column="0" Name="DeviceDisplayGrid"> <Grid Grid.Column="0" Name="DeviceDisplayGrid">
<!-- No need to provide LEDs to highlight as LEDs are already physically highlighted --> <!-- No need to provide LEDs to highlight as LEDs are already physically highlighted -->
<controls:DeviceVisualizer Device="{Binding Device}" <shared:DeviceVisualizer Device="{Binding Device}"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
ShowColors="True" ShowColors="True"

View File

@ -4,13 +4,13 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:controls1="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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Device.DeviceSettingsView"> x:Class="Artemis.UI.Screens.Device.DeviceSettingsView">
<Border Classes="card" Padding="0" Width="200" ClipToBounds="True" Margin="5"> <Border Classes="card" Padding="0" Width="200" ClipToBounds="True" Margin="5">
<Grid RowDefinitions="140,*,Auto"> <Grid RowDefinitions="140,*,Auto">
<Rectangle Grid.Row="0" Fill="{DynamicResource CheckerboardBrush}"/> <Rectangle Grid.Row="0" Fill="{DynamicResource CheckerboardBrush}"/>
<controls1:DeviceVisualizer VerticalAlignment="Center" <shared:DeviceVisualizer VerticalAlignment="Center"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Margin="5" Margin="5"
ShowColors="False" ShowColors="False"

View File

@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginFeatureView"> x:Class="Artemis.UI.Screens.Plugins.PluginFeatureView">
<Grid ColumnDefinitions="30,*,Auto"> <Grid ColumnDefinitions="30,*,Auto">
@ -23,7 +23,7 @@
</Grid.ContextFlyout> </Grid.ContextFlyout>
<!-- Icon column --> <!-- Icon column -->
<controls:ArtemisIcon Grid.Column="0" <shared:ArtemisIcon Grid.Column="0"
Icon="{Binding FeatureInfo.ResolvedIcon}" Icon="{Binding FeatureInfo.ResolvedIcon}"
Width="20" Width="20"
Height="20" Height="20"

View File

@ -4,13 +4,13 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:controls1="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="900" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Plugins.PluginSettingsView"> x:Class="Artemis.UI.Screens.Plugins.PluginSettingsView">
<Border Classes="card" Padding="15" Margin="0 5"> <Border Classes="card" Padding="15" Margin="0 5">
<Grid RowDefinitions="*,Auto" ColumnDefinitions="4*,5*"> <Grid RowDefinitions="*,Auto" ColumnDefinitions="4*,5*">
<Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" ColumnDefinitions="80,*"> <Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" ColumnDefinitions="80,*">
<controls1:ArtemisIcon Icon="{Binding Plugin.Info.ResolvedIcon}" <shared:ArtemisIcon Icon="{Binding Plugin.Info.ResolvedIcon}"
Width="48" Width="48"
Height="48" Height="48"
Margin="0 5 0 0" Margin="0 5 0 0"

View File

@ -2,8 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker;assembly=Artemis.UI.Shared"
xmlns:conditionTypes="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes" xmlns:conditionTypes="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes.EventConditionView" x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.ConditionTypes.EventConditionView"
x:DataType="conditionTypes:EventConditionViewModel"> x:DataType="conditionTypes:EventConditionViewModel">
@ -17,9 +17,9 @@
<dataModelPicker:DataModelPickerButton Placeholder="Select an event" <dataModelPicker:DataModelPickerButton Placeholder="Select an event"
DockPanel.Dock="Top" DockPanel.Dock="Top"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
FilterTypes="{CompiledBinding FilterTypes}"
DataModelPath="{CompiledBinding EventPath}" DataModelPath="{CompiledBinding EventPath}"
ShowFullPath="{CompiledBinding ShowFullPaths.Value}"/> ShowFullPath="{CompiledBinding ShowFullPaths.Value}"
IsEventPicker="True"/>
<TextBlock DockPanel.Dock="Top">When the event fires..</TextBlock> <TextBlock DockPanel.Dock="Top">When the event fires..</TextBlock>
<ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top" SelectedIndex="{CompiledBinding SelectedTriggerMode}"> <ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top" SelectedIndex="{CompiledBinding SelectedTriggerMode}">
@ -48,6 +48,16 @@
</ComboBoxItem> </ComboBoxItem>
</ComboBox> </ComboBox>
<TextBlock DockPanel.Dock="Top" IsVisible="{CompiledBinding ShowToggleOffOptions}">When toggling off..</TextBlock>
<ComboBox PlaceholderText="Select a play mode"
HorizontalAlignment="Stretch"
DockPanel.Dock="Top"
SelectedIndex="{CompiledBinding SelectedToggleOffMode}"
IsVisible="{CompiledBinding ShowToggleOffOptions}">
<ComboBoxItem>Finish the main segment</ComboBoxItem>
<ComboBoxItem>Skip forward to the end segment</ComboBoxItem>
</ComboBox>
<Button DockPanel.Dock="Bottom" <Button DockPanel.Dock="Bottom"
ToolTip.Tip="Open editor" ToolTip.Tip="Open editor"
Margin="0 15 0 5" Margin="0 15 0 5"

View File

@ -1,6 +1,4 @@
using System; using System.Reactive;
using System.Collections.ObjectModel;
using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
@ -19,11 +17,13 @@ public class EventConditionViewModel : ActivatableViewModelBase
{ {
private readonly EventCondition _eventCondition; private readonly EventCondition _eventCondition;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly ObservableAsPropertyHelper<bool> _showOverlapOptions;
private readonly IWindowService _windowService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private readonly ObservableAsPropertyHelper<bool> _showOverlapOptions;
private readonly ObservableAsPropertyHelper<bool> _showToggleOffOptions;
private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<DataModelPath?>? _eventPath; private ObservableAsPropertyHelper<DataModelPath?>? _eventPath;
private ObservableAsPropertyHelper<int>? _selectedOverlapMode; private ObservableAsPropertyHelper<int>? _selectedOverlapMode;
private ObservableAsPropertyHelper<int>? _selectedToggleOffMode;
private ObservableAsPropertyHelper<int>? _selectedTriggerMode; private ObservableAsPropertyHelper<int>? _selectedTriggerMode;
public EventConditionViewModel(EventCondition eventCondition, IProfileEditorService profileEditorService, IWindowService windowService, ISettingsService settingsService) public EventConditionViewModel(EventCondition eventCondition, IProfileEditorService profileEditorService, IWindowService windowService, ISettingsService settingsService)
@ -35,20 +35,24 @@ public class EventConditionViewModel : ActivatableViewModelBase
_showOverlapOptions = this.WhenAnyValue(vm => vm.SelectedTriggerMode) _showOverlapOptions = this.WhenAnyValue(vm => vm.SelectedTriggerMode)
.Select(m => m == 0) .Select(m => m == 0)
.ToProperty(this, vm => vm.ShowOverlapOptions); .ToProperty(this, vm => vm.ShowOverlapOptions);
_showToggleOffOptions = this.WhenAnyValue(vm => vm.SelectedTriggerMode)
.Select(m => m == 1)
.ToProperty(this, vm => vm.ShowToggleOffOptions);
this.WhenActivated(d => this.WhenActivated(d =>
{ {
_eventPath = eventCondition.WhenAnyValue(c => c.EventPath).ToProperty(this, vm => vm.EventPath).DisposeWith(d); _eventPath = eventCondition.WhenAnyValue(c => c.EventPath).ToProperty(this, vm => vm.EventPath).DisposeWith(d);
_selectedTriggerMode = eventCondition.WhenAnyValue(c => c.TriggerMode).Select(m => (int) m).ToProperty(this, vm => vm.SelectedTriggerMode).DisposeWith(d); _selectedTriggerMode = eventCondition.WhenAnyValue(c => c.TriggerMode).Select(m => (int) m).ToProperty(this, vm => vm.SelectedTriggerMode).DisposeWith(d);
_selectedOverlapMode = eventCondition.WhenAnyValue(c => c.OverlapMode).Select(m => (int) m).ToProperty(this, vm => vm.SelectedOverlapMode).DisposeWith(d); _selectedOverlapMode = eventCondition.WhenAnyValue(c => c.OverlapMode).Select(m => (int) m).ToProperty(this, vm => vm.SelectedOverlapMode).DisposeWith(d);
_selectedToggleOffMode = eventCondition.WhenAnyValue(c => c.OverlapMode).Select(m => (int) m).ToProperty(this, vm => vm.SelectedOverlapMode).DisposeWith(d);
}); });
OpenEditor = ReactiveCommand.CreateFromTask(ExecuteOpenEditor); OpenEditor = ReactiveCommand.CreateFromTask(ExecuteOpenEditor);
} }
public ObservableCollection<Type> FilterTypes { get; } = new() {typeof(IDataModelEvent)};
public ReactiveCommand<Unit, Unit> OpenEditor { get; } public ReactiveCommand<Unit, Unit> OpenEditor { get; }
public bool ShowOverlapOptions => _showOverlapOptions.Value; public bool ShowOverlapOptions => _showOverlapOptions.Value;
public bool ShowToggleOffOptions => _showToggleOffOptions.Value;
public bool IsConditionForLayer => _eventCondition.ProfileElement is Layer; public bool IsConditionForLayer => _eventCondition.ProfileElement is Layer;
public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false); public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false);
@ -70,6 +74,12 @@ public class EventConditionViewModel : ActivatableViewModelBase
set => _profileEditorService.ExecuteCommand(new UpdateEventOverlapMode(_eventCondition, (EventOverlapMode) value)); set => _profileEditorService.ExecuteCommand(new UpdateEventOverlapMode(_eventCondition, (EventOverlapMode) value));
} }
public int SelectedToggleOffMode
{
get => _selectedToggleOffMode?.Value ?? 0;
set => _profileEditorService.ExecuteCommand(new UpdateEventToggleOffMode(_eventCondition, (EventToggleOffMode) value));
}
private async Task ExecuteOpenEditor() private async Task ExecuteOpenEditor()
{ {
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", _eventCondition.NodeScript)); await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", _eventCondition.NodeScript));

View File

@ -28,10 +28,8 @@ public class ProfileTreeViewDropHandler : DropHandlerBase
result = Validate<TreeItemViewModel>(treeView, e, sourceContext, targetContext, true); result = Validate<TreeItemViewModel>(treeView, e, sourceContext, targetContext, true);
if (sender is ItemsControl itemsControl) if (sender is ItemsControl itemsControl)
{
foreach (TreeViewItem treeViewItem in GetFlattenedTreeView(itemsControl)) foreach (TreeViewItem treeViewItem in GetFlattenedTreeView(itemsControl))
SetDraggingPseudoClasses(treeViewItem, TreeDropType.None); SetDraggingPseudoClasses(treeViewItem, TreeDropType.None);
}
return result; return result;
} }
@ -39,10 +37,8 @@ public class ProfileTreeViewDropHandler : DropHandlerBase
public override void Cancel(object? sender, RoutedEventArgs e) public override void Cancel(object? sender, RoutedEventArgs e)
{ {
if (sender is ItemsControl itemsControl) if (sender is ItemsControl itemsControl)
{
foreach (TreeViewItem treeViewItem in GetFlattenedTreeView(itemsControl)) foreach (TreeViewItem treeViewItem in GetFlattenedTreeView(itemsControl))
SetDraggingPseudoClasses(treeViewItem, TreeDropType.None); SetDraggingPseudoClasses(treeViewItem, TreeDropType.None);
}
base.Cancel(sender, e); base.Cancel(sender, e);
} }

View File

@ -34,7 +34,7 @@
Text="{CompiledBinding RenameValue}" Text="{CompiledBinding RenameValue}"
x:Name="Input" x:Name="Input"
KeyUp="InputElement_OnKeyUp" KeyUp="InputElement_OnKeyUp"
LostFocus="InputElement_OnLostFocus"/> LostFocus="InputElement_OnLostFocus" />
<TextBlock Grid.Column="2" IsVisible="{CompiledBinding !Renaming}" Text="{Binding Folder.Name}" VerticalAlignment="Center" /> <TextBlock Grid.Column="2" IsVisible="{CompiledBinding !Renaming}" Text="{Binding Folder.Name}" VerticalAlignment="Center" />
<ToggleButton Grid.Column="3" <ToggleButton Grid.Column="3"
Classes="icon-button icon-button-small" Classes="icon-button icon-button-small"

View File

@ -1,33 +1,71 @@
using Artemis.Core; using System;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
public class FolderTreeItemViewModel : TreeItemViewModel
{ {
public class FolderTreeItemViewModel : TreeItemViewModel public FolderTreeItemViewModel(TreeItemViewModel? parent,
Folder folder,
IWindowService windowService,
IProfileEditorService profileEditorService,
ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory,
IRgbService rgbService)
: base(parent, folder, windowService, profileEditorService, rgbService, layerBrushService, profileEditorVmFactory)
{ {
public FolderTreeItemViewModel(TreeItemViewModel? parent, Folder = folder;
Folder folder,
IWindowService windowService,
IProfileEditorService profileEditorService,
ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory,
IRgbService rgbService)
: base(parent, folder, windowService, profileEditorService, rgbService, layerBrushService, profileEditorVmFactory)
{
Folder = folder;
}
public Folder Folder { get; }
#region Overrides of TreeItemViewModel
/// <inheritdoc />
public override bool SupportsChildren => true;
#endregion
} }
public Folder Folder { get; }
#region Overrides of TreeItemViewModel
protected override async Task ExecuteDuplicate()
{
await ProfileEditorService.SaveProfileAsync();
FolderEntity copy = CoreJson.DeserializeObject<FolderEntity>(CoreJson.SerializeObject(Folder.FolderEntity, true), true)!;
copy.Id = Guid.NewGuid();
copy.Name = Folder.Parent.GetNewFolderName(copy.Name + " - copy");
Folder copied = new(Folder.Profile, Folder.Parent, copy);
ProfileEditorService.ExecuteCommand(new AddProfileElement(copied, Folder.Parent, Folder.Order - 1));
}
protected override async Task ExecuteCopy()
{
await ProfileEditorService.SaveProfileAsync();
await Folder.CopyToClipboard();
}
protected override async Task ExecutePaste()
{
if (Folder.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.GetNewFolderName(pasted.Name + " - copy");
ProfileEditorService.ExecuteCommand(new AddProfileElement(pasted, parent, Folder.Order - 1));
}
/// <inheritdoc />
public override bool SupportsChildren => true;
#endregion
} }

View File

@ -18,14 +18,14 @@
<avalonia:MaterialIcon Kind="AlertCircle" /> <avalonia:MaterialIcon Kind="AlertCircle" />
</Button> </Button>
<avalonia:MaterialIcon Grid.Column="1" Kind="{CompiledBinding Layer.LayerBrush.Descriptor.Icon, FallbackValue=Layers}" Margin="0 0 5 0" /> <avalonia:MaterialIcon Grid.Column="1" Kind="{CompiledBinding Layer.LayerBrush.Descriptor.Icon, FallbackValue=Layers}" Margin="0 0 5 0" />
<TextBox Grid.Column="2" <TextBox Grid.Column="2"
Margin="-5 0 0 0" Margin="-5 0 0 0"
Classes="condensed" Classes="condensed"
x:Name="Input" x:Name="Input"
IsVisible="{CompiledBinding Renaming}" IsVisible="{CompiledBinding Renaming}"
Text="{CompiledBinding RenameValue}" Text="{CompiledBinding RenameValue}"
KeyUp="InputElement_OnKeyUp" KeyUp="InputElement_OnKeyUp"
LostFocus="InputElement_OnLostFocus"></TextBox> LostFocus="InputElement_OnLostFocus" />
<TextBlock Grid.Column="2" IsVisible="{CompiledBinding !Renaming}" Text="{CompiledBinding Layer.Name}" VerticalAlignment="Center" /> <TextBlock Grid.Column="2" IsVisible="{CompiledBinding !Renaming}" Text="{CompiledBinding Layer.Name}" VerticalAlignment="Center" />
<ToggleButton Grid.Column="3" <ToggleButton Grid.Column="3"
Classes="icon-button icon-button-small" Classes="icon-button icon-button-small"

View File

@ -1,32 +1,71 @@
using Artemis.Core; using System;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
public class LayerTreeItemViewModel : TreeItemViewModel
{ {
public class LayerTreeItemViewModel : TreeItemViewModel public LayerTreeItemViewModel(TreeItemViewModel? parent,
Layer layer,
IWindowService windowService,
IProfileEditorService profileEditorService,
IRgbService rgbService,
ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory)
: base(parent, layer, windowService, profileEditorService, rgbService, layerBrushService, profileEditorVmFactory)
{ {
public LayerTreeItemViewModel(TreeItemViewModel? parent, Layer = layer;
Layer layer,
IWindowService windowService,
IProfileEditorService profileEditorService,
IRgbService rgbService,
ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory)
: base(parent, layer, windowService, profileEditorService, rgbService, layerBrushService, profileEditorVmFactory)
{
Layer = layer;
}
public Layer Layer { get; }
#region Overrides of TreeItemViewModel
/// <inheritdoc />
public override bool SupportsChildren => false;
#endregion
} }
public Layer Layer { get; }
#region Overrides of TreeItemViewModel
/// <inheritdoc />
protected override async Task ExecuteDuplicate()
{
await ProfileEditorService.SaveProfileAsync();
LayerEntity copy = CoreJson.DeserializeObject<LayerEntity>(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));
}
/// <inheritdoc />
public override bool SupportsChildren => false;
#endregion
} }

View File

@ -10,7 +10,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.ProfileTreeView" x:Class="Artemis.UI.Screens.ProfileEditor.ProfileTree.ProfileTreeView"
x:DataType="profileTree:ProfileTreeViewModel"> x:DataType="profileTree:ProfileTreeViewModel">
<!-- These cause binding errors, not my fault - https://github.com/AvaloniaUI/Avalonia/issues/5762 --> <!-- These cause binding errors, not my fault - https://github.com/AvaloniaUI/Avalonia/issues/5762 -->
<UserControl.KeyBindings> <UserControl.KeyBindings>
<KeyBinding Gesture="Escape" Command="{Binding ClearSelection}" /> <KeyBinding Gesture="Escape" Command="{Binding ClearSelection}" />
<KeyBinding Gesture="F2" Command="{Binding RenameSelected}" /> <KeyBinding Gesture="F2" Command="{Binding RenameSelected}" />
@ -101,7 +101,7 @@
<TreeDataTemplate ItemsSource="{Binding Children}"> <TreeDataTemplate ItemsSource="{Binding Children}">
<ContentControl Content="{Binding}" x:DataType="profileTree:TreeItemViewModel"> <ContentControl Content="{Binding}" x:DataType="profileTree:TreeItemViewModel">
<ContentControl.ContextFlyout> <ContentControl.ContextFlyout>
<MenuFlyout> <MenuFlyout IsOpen="{CompiledBinding IsFlyoutOpen, Mode=OneWayToSource}">
<MenuItem Header="Add new folder" Command="{CompiledBinding AddFolder}"> <MenuItem Header="Add new folder" Command="{CompiledBinding AddFolder}">
<MenuItem.Icon> <MenuItem.Icon>
<avalonia:MaterialIcon Kind="CreateNewFolder" /> <avalonia:MaterialIcon Kind="CreateNewFolder" />

View File

@ -1,5 +1,3 @@
using System;
using System.Diagnostics;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
@ -17,10 +15,10 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
public class ProfileTreeView : ReactiveUserControl<ProfileTreeViewModel> public class ProfileTreeView : ReactiveUserControl<ProfileTreeViewModel>
{ {
private TreeView _treeView;
private Image? _dragAdorner; private Image? _dragAdorner;
private Point _dragStartPosition; private Point _dragStartPosition;
private Point _elementDragOffset; private Point _elementDragOffset;
private TreeView _treeView;
public ProfileTreeView() public ProfileTreeView()
{ {

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
@ -88,6 +89,26 @@ public class ProfileTreeViewModel : TreeItemViewModel
SelectedChild?.Paste.Execute().Subscribe(); 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) private void SelectCurrentProfileElement(RenderProfileElement? element)
{ {
if (SelectedChild?.ProfileElement == element) if (SelectedChild?.ProfileElement == element)

View File

@ -8,22 +8,28 @@ using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Avalonia;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree; namespace Artemis.UI.Screens.ProfileEditor.ProfileTree;
public abstract class TreeItemViewModel : ActivatableViewModelBase public abstract class TreeItemViewModel : ActivatableViewModelBase
{ {
private readonly IProfileEditorService _profileEditorService; private readonly ILayerBrushService _layerBrushService;
private readonly IProfileEditorVmFactory _profileEditorVmFactory; private readonly IProfileEditorVmFactory _profileEditorVmFactory;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
protected readonly IProfileEditorService ProfileEditorService;
private bool _canPaste;
private RenderProfileElement? _currentProfileElement; private RenderProfileElement? _currentProfileElement;
private bool _isExpanded; private bool _isExpanded;
private bool _isFlyoutOpen;
private ProfileElement? _profileElement; private ProfileElement? _profileElement;
private string? _renameValue; private string? _renameValue;
private bool _renaming; private bool _renaming;
@ -35,63 +41,31 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
ILayerBrushService layerBrushService, ILayerBrushService layerBrushService,
IProfileEditorVmFactory profileEditorVmFactory) IProfileEditorVmFactory profileEditorVmFactory)
{ {
ProfileEditorService = profileEditorService;
_rgbService = rgbService;
_layerBrushService = layerBrushService;
_windowService = windowService; _windowService = windowService;
_profileEditorService = profileEditorService;
_profileEditorVmFactory = profileEditorVmFactory; _profileEditorVmFactory = profileEditorVmFactory;
Parent = parent; Parent = parent;
ProfileElement = profileElement; ProfileElement = profileElement;
AddLayer = ReactiveCommand.Create(() => AddLayer = ReactiveCommand.Create(ExecuteAddLayer);
{ AddFolder = ReactiveCommand.Create(ExecuteAddFolder);
if (ProfileElement is Layer targetLayer) Rename = ReactiveCommand.Create(ExecuteRename);
{ Delete = ReactiveCommand.Create(ExecuteDelete);
Layer layer = new(targetLayer.Parent, targetLayer.GetNewLayerName()); Duplicate = ReactiveCommand.CreateFromTask(ExecuteDuplicate);
layerBrushService.ApplyDefaultBrush(layer); Copy = ReactiveCommand.CreateFromTask(ExecuteCopy);
Paste = ReactiveCommand.CreateFromTask(ExecutePaste, this.WhenAnyValue(vm => vm.CanPaste));
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));
});
this.WhenActivated(d => this.WhenActivated(d =>
{ {
_profileEditorService.ProfileElement.Subscribe(element => _currentProfileElement = element).DisposeWith(d); ProfileEditorService.ProfileElement.Subscribe(element => _currentProfileElement = element).DisposeWith(d);
SubscribeToProfileElement(d); SubscribeToProfileElement(d);
CreateTreeItems(); CreateTreeItems();
}); });
this.WhenAnyValue(vm => vm.IsFlyoutOpen).Subscribe(UpdateCanPaste);
} }
public ProfileElement? ProfileElement public ProfileElement? ProfileElement
@ -106,12 +80,24 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
set => RaiseAndSetIfChanged(ref _isExpanded, value); set => RaiseAndSetIfChanged(ref _isExpanded, value);
} }
public bool IsFlyoutOpen
{
get => _isFlyoutOpen;
set => RaiseAndSetIfChanged(ref _isFlyoutOpen, value);
}
public bool Renaming public bool Renaming
{ {
get => _renaming; get => _renaming;
set => RaiseAndSetIfChanged(ref _renaming, value); set => RaiseAndSetIfChanged(ref _renaming, value);
} }
public bool CanPaste
{
get => _canPaste;
set => RaiseAndSetIfChanged(ref _canPaste, value);
}
public TreeItemViewModel? Parent { get; set; } public TreeItemViewModel? Parent { get; set; }
public ObservableCollection<TreeItemViewModel> Children { get; } = new(); public ObservableCollection<TreeItemViewModel> Children { get; } = new();
@ -154,7 +140,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
return; return;
} }
_profileEditorService.ExecuteCommand(new RenameProfileElement(ProfileElement, RenameValue)); ProfileEditorService.ExecuteCommand(new RenameProfileElement(ProfileElement, RenameValue));
Renaming = false; Renaming = false;
} }
@ -169,18 +155,24 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
return; return;
if (ProfileElement != null && elementViewModel.ProfileElement != null) 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) protected void SubscribeToProfileElement(CompositeDisposable d)
{ {
if (ProfileElement == null) if (ProfileElement == null)
return; return;
Observable.FromEventPattern<ProfileElementEventArgs>(x => ProfileElement.ChildAdded += x, x => ProfileElement.ChildAdded -= x) Observable.FromEventPattern<ProfileElementEventArgs>(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<ProfileElementEventArgs>(x => ProfileElement.ChildRemoved += x, x => ProfileElement.ChildRemoved -= x) Observable.FromEventPattern<ProfileElementEventArgs>(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) protected void RemoveTreeItemsIfFound(object? sender, ProfileElement profileElement)
@ -202,7 +194,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
newSelection = parent; newSelection = parent;
} }
_profileEditorService.ChangeCurrentProfileElement(newSelection as RenderProfileElement); ProfileEditorService.ChangeCurrentProfileElement(newSelection as RenderProfileElement);
} }
protected void AddTreeItemIfMissing(ProfileElement profileElement) protected void AddTreeItemIfMissing(ProfileElement profileElement)
@ -217,7 +209,7 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
// Select the newly added element // Select the newly added element
if (profileElement is RenderProfileElement renderProfileElement) if (profileElement is RenderProfileElement renderProfileElement)
_profileEditorService.ChangeCurrentProfileElement(renderProfileElement); ProfileEditorService.ChangeCurrentProfileElement(renderProfileElement);
} }
protected void CreateTreeItems() protected void CreateTreeItems()
@ -236,4 +228,56 @@ public abstract class TreeItemViewModel : ActivatableViewModelBase
Children.Add(_profileEditorVmFactory.LayerTreeItemViewModel(this, layer)); 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);
}
} }

View File

@ -3,8 +3,8 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties" 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: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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineView" x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineView"
x:DataType="timeline:TimelineViewModel"> x:DataType="timeline:TimelineViewModel">
@ -23,7 +23,7 @@
</TreeDataTemplate> </TreeDataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<controls:SelectionRectangle Name="SelectionRectangle" InputElement="{CompiledBinding $parent}" SelectionFinished="SelectionRectangle_OnSelectionFinished"></controls:SelectionRectangle> <shared:SelectionRectangle Name="SelectionRectangle" InputElement="{CompiledBinding $parent}" SelectionFinished="SelectionRectangle_OnSelectionFinished"></shared:SelectionRectangle>
</Grid> </Grid>

View File

@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Keyframes; 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.Events;
using Artemis.UI.Shared.Extensions; using Artemis.UI.Shared.Extensions;
using Avalonia; using Avalonia;

View File

@ -3,11 +3,11 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" 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:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:converters="clr-namespace:Artemis.UI.Converters" xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:viewModel="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Tree" xmlns:viewModel="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Tree"
xmlns:properties="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties" 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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Tree.TreeGroupView"> x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Tree.TreeGroupView">
<UserControl.Resources> <UserControl.Resources>
@ -68,7 +68,7 @@
<!-- Type: LayerBrushRoot --> <!-- Type: LayerBrushRoot -->
<Grid IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.LayerBrushRoot}}" <Grid IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.LayerBrushRoot}}"
ColumnDefinitions="Auto,Auto,Auto,*"> ColumnDefinitions="Auto,Auto,Auto,*">
<controls:ArtemisIcon Grid.Column="0" <shared:ArtemisIcon Grid.Column="0"
Icon="{Binding LayerBrush.Descriptor.Icon}" Icon="{Binding LayerBrush.Descriptor.Icon}"
Width="16" Width="16"
Height="16" Height="16"
@ -104,7 +104,7 @@
<Grid Height="24" <Grid Height="24"
IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.LayerEffectRoot}}" IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.LayerEffectRoot}}"
ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto"> ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto">
<controls:ArtemisIcon <shared:ArtemisIcon
Grid.Column="0" Grid.Column="0"
Cursor="SizeNorthSouth" Cursor="SizeNorthSouth"
Icon="{Binding LayerEffect.Descriptor.Icon}" Icon="{Binding LayerEffect.Descriptor.Icon}"

View File

@ -2,19 +2,19 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns: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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.SelectionAddToolView" ClipToBounds="False"> x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.SelectionAddToolView" ClipToBounds="False">
<Grid> <Grid>
<controls:SelectionRectangle InputElement="{Binding $parent[ZoomBorder]}" <shared:SelectionRectangle InputElement="{Binding $parent[ZoomBorder]}"
BorderBrush="{DynamicResource SystemAccentColor}" BorderBrush="{DynamicResource SystemAccentColor}"
BorderRadius="8" BorderRadius="8"
BorderThickness="2" BorderThickness="2"
SelectionFinished="SelectionRectangle_OnSelectionFinished" SelectionFinished="SelectionRectangle_OnSelectionFinished"
ZoomRatio="{Binding $parent[ZoomBorder].ZoomX}"> ZoomRatio="{Binding $parent[ZoomBorder].ZoomX}">
<controls:SelectionRectangle.Background> <shared:SelectionRectangle.Background>
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush> <SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush>
</controls:SelectionRectangle.Background> </shared:SelectionRectangle.Background>
</controls:SelectionRectangle> </shared:SelectionRectangle>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -2,21 +2,21 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom" 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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.SelectionRemoveToolView" x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.SelectionRemoveToolView"
ClipToBounds="False"> ClipToBounds="False">
<Grid> <Grid>
<controls:SelectionRectangle InputElement="{Binding $parent[paz:ZoomBorder]}" <shared:SelectionRectangle InputElement="{Binding $parent[paz:ZoomBorder]}"
BorderBrush="{DynamicResource SystemAccentColor}" BorderBrush="{DynamicResource SystemAccentColor}"
BorderRadius="8" BorderRadius="8"
BorderThickness="2" BorderThickness="2"
SelectionFinished="SelectionRectangle_OnSelectionFinished" SelectionFinished="SelectionRectangle_OnSelectionFinished"
ZoomRatio="{Binding $parent[ZoomBorder].ZoomX}"> ZoomRatio="{Binding $parent[ZoomBorder].ZoomX}">
<controls:SelectionRectangle.Background> <shared:SelectionRectangle.Background>
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush> <SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush>
</controls:SelectionRectangle.Background> </shared:SelectionRectangle.Background>
</controls:SelectionRectangle> </shared:SelectionRectangle>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -4,8 +4,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom" xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom"
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" 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: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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.VisualEditorView" x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.VisualEditorView"
x:DataType="visualEditor:VisualEditorViewModel"> x:DataType="visualEditor:VisualEditorViewModel">
@ -53,7 +53,7 @@
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate DataType="core:ArtemisDevice"> <DataTemplate DataType="core:ArtemisDevice">
<controls:DeviceVisualizer Device="{Binding}" ShowColors="True" /> <shared:DeviceVisualizer Device="{Binding}" ShowColors="True" />
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
@ -88,7 +88,11 @@
<SolidColorBrush Color="{DynamicResource CardStrokeColorDefaultSolid}" Opacity="0.65"></SolidColorBrush> <SolidColorBrush Color="{DynamicResource CardStrokeColorDefaultSolid}" Opacity="0.65"></SolidColorBrush>
</Border.Background> </Border.Background>
<StackPanel Orientation="Horizontal" Margin="8"> <StackPanel Orientation="Horizontal" Margin="8">
<controls:ProfileConfigurationIcon ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}" Margin="0 0 5 0"></controls:ProfileConfigurationIcon> <shared:ProfileConfigurationIcon ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}"
Foreground="{DynamicResource ToolTipForeground}"
Width="18"
Height="18"
Margin="0 0 5 0"></shared:ProfileConfigurationIcon>
<TextBlock Text="{CompiledBinding ProfileConfiguration.Name}"/> <TextBlock Text="{CompiledBinding ProfileConfiguration.Name}"/>
</StackPanel> </StackPanel>
</Border> </Border>

View File

@ -4,8 +4,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:layerBrushes="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core" xmlns:layerBrushes="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" 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: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" mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="2400"
x:Class="Artemis.UI.Screens.Settings.GeneralTabView" x:Class="Artemis.UI.Screens.Settings.GeneralTabView"
x:DataType="settings:GeneralTabViewModel"> x:DataType="settings:GeneralTabViewModel">
@ -61,7 +61,7 @@
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<controls:EnumComboBox Width="150" Value="{CompiledBinding UIColorScheme.Value}" /> <shared:EnumComboBox Width="150" Value="{CompiledBinding UIColorScheme.Value}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
<Separator Classes="card-separator" /> <Separator Classes="card-separator" />
@ -76,7 +76,7 @@
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center"> <StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<controls:EnumComboBox Width="150" Value="{CompiledBinding CoreLoggingLevel.Value}" /> <shared:EnumComboBox Width="150" Value="{CompiledBinding CoreLoggingLevel.Value}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
<Separator Classes="card-separator" /> <Separator Classes="card-separator" />

View File

@ -3,12 +3,12 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" 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:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:svg="clr-namespace:Avalonia.Svg.Skia;assembly=Avalonia.Svg.Skia" xmlns:svg="clr-namespace:Avalonia.Svg.Skia;assembly=Avalonia.Svg.Skia"
xmlns:local="clr-namespace:Artemis.UI.Screens.Sidebar" xmlns:local="clr-namespace:Artemis.UI.Screens.Sidebar"
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" 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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="850"
x:Class="Artemis.UI.Screens.Sidebar.ProfileConfigurationEditView" x:Class="Artemis.UI.Screens.Sidebar.ProfileConfigurationEditView"
Title="{Binding DisplayName}" Title="{Binding DisplayName}"
@ -55,7 +55,7 @@
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:ProfileModuleViewModel}"> <DataTemplate DataType="{x:Type local:ProfileModuleViewModel}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<controls:ArtemisIcon Icon="{Binding Icon}" Width="16" Height="16" Margin="0 0 5 0" /> <shared:ArtemisIcon Icon="{Binding Icon}" Width="16" Height="16" Margin="0 0 5 0" />
<TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding Name}" />
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
@ -148,7 +148,7 @@
IsChecked="{Binding HotkeyMode, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationHotkeyMode.EnableDisable}}" /> IsChecked="{Binding HotkeyMode, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationHotkeyMode.EnableDisable}}" />
</WrapPanel> </WrapPanel>
<controls:HotkeyBox Watermark="Toggle hotkey" <shared:HotkeyBox Watermark="Toggle hotkey"
Hotkey="{Binding EnableHotkey}" Hotkey="{Binding EnableHotkey}"
IsVisible="{Binding HotkeyMode, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationHotkeyMode.Toggle}}" IsVisible="{Binding HotkeyMode, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationHotkeyMode.Toggle}}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
@ -158,8 +158,8 @@
IsVisible="{Binding HotkeyMode, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationHotkeyMode.EnableDisable}}" IsVisible="{Binding HotkeyMode, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static core:ProfileConfigurationHotkeyMode.EnableDisable}}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Margin="0 5 0 0"> Margin="0 5 0 0">
<controls:HotkeyBox Watermark="Enable hotkey" Hotkey="{Binding EnableHotkey}" Margin="0 0 4 0" Width="200"/> <shared:HotkeyBox Watermark="Enable hotkey" Hotkey="{Binding EnableHotkey}" Margin="0 0 4 0" Width="200"/>
<controls:HotkeyBox Watermark="Disable hotkey" Hotkey="{Binding DisableHotkey}" Margin="4 0 0 0" Width="200"/> <shared:HotkeyBox Watermark="Disable hotkey" Hotkey="{Binding DisableHotkey}" Margin="4 0 0 0" Width="200"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>

View File

@ -3,7 +3,6 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" 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" xmlns:local="clr-namespace:Artemis.UI.Screens.Sidebar"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Sidebar.SidebarCategoryView"> x:Class="Artemis.UI.Screens.Sidebar.SidebarCategoryView">

View File

@ -4,8 +4,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters" 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: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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Sidebar.SidebarProfileConfigurationView" x:Class="Artemis.UI.Screens.Sidebar.SidebarProfileConfigurationView"
x:DataType="sidebar:SidebarProfileConfigurationViewModel"> x:DataType="sidebar:SidebarProfileConfigurationViewModel">
@ -68,7 +68,7 @@
</ContextMenu> </ContextMenu>
</UserControl.ContextMenu> </UserControl.ContextMenu>
<Grid ColumnDefinitions="Auto,*,Auto,Auto"> <Grid ColumnDefinitions="Auto,*,Auto,Auto">
<controls:ProfileConfigurationIcon Grid.Column="0" <shared:ProfileConfigurationIcon Grid.Column="0"
x:Name="ProfileIcon" x:Name="ProfileIcon"
VerticalAlignment="Center" VerticalAlignment="Center"
ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}" ConfigurationIcon="{CompiledBinding ProfileConfiguration.Icon}"

View File

@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns: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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.SurfaceEditor.SurfaceDeviceView"> x:Class="Artemis.UI.Screens.SurfaceEditor.SurfaceDeviceView">
<Grid> <Grid>
@ -20,7 +20,7 @@
</Style> </Style>
</Grid.Styles> </Grid.Styles>
<controls:DeviceVisualizer Device="{Binding Device}" ShowColors="True"/> <shared:DeviceVisualizer Device="{Binding Device}" ShowColors="True"/>
<Border x:Name="SurfaceDeviceBorder" <Border x:Name="SurfaceDeviceBorder"
Classes="selection-border" Classes="selection-border"
Classes.selection-border-selected="{Binding Highlighted}" Classes.selection-border-selected="{Binding Highlighted}"

View File

@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom" xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.SurfaceEditor.SurfaceEditorView"> x:Class="Artemis.UI.Screens.SurfaceEditor.SurfaceEditorView">
<UserControl.Resources> <UserControl.Resources>
@ -101,15 +101,15 @@
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<controls:SelectionRectangle Name="SelectionRectangle" <shared:SelectionRectangle Name="SelectionRectangle"
InputElement="{Binding #ZoomBorder}" InputElement="{Binding #ZoomBorder}"
SelectionUpdated="SelectionRectangle_OnSelectionUpdated" SelectionUpdated="SelectionRectangle_OnSelectionUpdated"
BorderBrush="{DynamicResource SystemAccentColor}" BorderBrush="{DynamicResource SystemAccentColor}"
BorderRadius="8"> BorderRadius="8">
<controls:SelectionRectangle.Background> <shared:SelectionRectangle.Background>
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush> <SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush>
</controls:SelectionRectangle.Background> </shared:SelectionRectangle.Background>
</controls:SelectionRectangle> </shared:SelectionRectangle>
<Border Name="SurfaceBounds" <Border Name="SurfaceBounds"
VerticalAlignment="Top" VerticalAlignment="Top"

View File

@ -1,5 +1,5 @@
using System.Reactive; using System.Reactive;
using Artemis.UI.Shared.Controls; using Artemis.UI.Shared;
using Artemis.UI.Shared.Events; using Artemis.UI.Shared.Events;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;

View File

@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom" xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom"
xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting" xmlns:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptView" x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptView"
x:DataType="visualScripting:NodeScriptViewModel" x:DataType="visualScripting:NodeScriptViewModel"
@ -76,16 +76,16 @@
</ItemsControl.Styles> </ItemsControl.Styles>
</ItemsControl> </ItemsControl>
<controls:SelectionRectangle Name="SelectionRectangle" <shared:SelectionRectangle Name="SelectionRectangle"
InputElement="{Binding #ZoomBorder}" InputElement="{Binding #ZoomBorder}"
SelectionUpdated="SelectionRectangle_OnSelectionUpdated" SelectionUpdated="SelectionRectangle_OnSelectionUpdated"
SelectionFinished="SelectionRectangle_OnSelectionFinished" SelectionFinished="SelectionRectangle_OnSelectionFinished"
BorderBrush="{DynamicResource SystemAccentColor}" BorderBrush="{DynamicResource SystemAccentColor}"
BorderRadius="8"> BorderRadius="8">
<controls:SelectionRectangle.Background> <shared:SelectionRectangle.Background>
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush> <SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush>
</controls:SelectionRectangle.Background> </shared:SelectionRectangle.Background>
</controls:SelectionRectangle> </shared:SelectionRectangle>
</Grid> </Grid>
</paz:ZoomBorder> </paz:ZoomBorder>

View File

@ -6,7 +6,7 @@ using System.Reactive.Disposables;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Events; using Artemis.Core.Events;
using Artemis.UI.Shared.Controls; using Artemis.UI.Shared;
using Artemis.UI.Shared.Events; using Artemis.UI.Shared.Events;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
@ -153,7 +153,7 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
private void ZoomBorder_OnPointerReleased(object? sender, PointerReleasedEventArgs e) private void ZoomBorder_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{ {
if (!_selectionRectangle.IsSelecting) if (!_selectionRectangle.IsSelecting && e.InitialPressMouseButton == MouseButton.Left)
ViewModel?.ClearNodeSelection(); ViewModel?.ClearNodeSelection();
} }
} }

View File

@ -3,65 +3,66 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:builders="clr-namespace:Artemis.UI.Shared.Services.Builders;assembly=Artemis.UI.Shared" 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:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:attachedProperties="clr-namespace:Artemis.UI.Shared.AttachedProperties;assembly=Artemis.UI.Shared" xmlns:attachedProperties="clr-namespace:Artemis.UI.Shared.AttachedProperties;assembly=Artemis.UI.Shared"
xmlns:workshop="clr-namespace:Artemis.UI.Screens.Workshop" xmlns:workshop="clr-namespace:Artemis.UI.Screens.Workshop"
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker;assembly=Artemis.UI.Shared" xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker;assembly=Artemis.UI.Shared" xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.GradientPicker;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" mc:Ignorable="d" d:DesignWidth="800"
x:Class="Artemis.UI.Screens.Workshop.WorkshopView" x:Class="Artemis.UI.Screens.Workshop.WorkshopView"
x:DataType="workshop:WorkshopViewModel"> x:DataType="workshop:WorkshopViewModel">
<Border Classes="router-container"> <Border Classes="router-container">
<StackPanel Margin="12" Spacing="5"> <ScrollViewer>
<Border Classes="card"> <StackPanel Margin="12" Spacing="5">
<StackPanel Spacing="5"> <Border Classes="card">
<TextBlock Classes="h4">Nodes tests</TextBlock> <StackPanel Spacing="5">
<!-- <dataModelPicker:DataModelPickerButton Placement="BottomEdgeAlignedLeft"/> --> <TextBlock Classes="h4">Nodes tests</TextBlock>
<ContentControl Content="{CompiledBinding VisualEditorViewModel}" HorizontalAlignment="Stretch" Height="800"></ContentControl> <!-- <dataModelPicker:DataModelPickerButton Placement="BottomEdgeAlignedLeft"/> -->
</StackPanel> <ContentControl Content="{CompiledBinding VisualEditorViewModel}" HorizontalAlignment="Stretch" Height="800"></ContentControl>
</Border> </StackPanel>
<Border Classes="card"> </Border>
<StackPanel Spacing="5"> <Border Classes="card">
<TextBlock Classes="h4">Notification tests</TextBlock> <StackPanel Spacing="5">
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Informational}"> <TextBlock Classes="h4">Notification tests</TextBlock>
Notification test (default) <Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Informational}">
</Button> Notification test (default)
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Warning}"> </Button>
Notification test (warning) <Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Warning}">
</Button> Notification test (warning)
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Error}"> </Button>
Notification test (error) <Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Error}">
</Button> Notification test (error)
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Success}"> </Button>
Notification test (success) <Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Success}">
</Button> Notification test (success)
</Button>
<controls:HotkeyBox Watermark="Some hotkey" Width="250" HorizontalAlignment="Left" /> <shared:HotkeyBox Watermark="Some hotkey" Width="250" HorizontalAlignment="Left" />
<controls1:NumberBox <controls1:NumberBox
attachedProperties:NumberBoxAssist.PrefixText="$" attachedProperties:NumberBoxAssist.PrefixText="$"
attachedProperties:NumberBoxAssist.SuffixText="%"></controls1:NumberBox> attachedProperties:NumberBoxAssist.SuffixText="%">
</controls1:NumberBox>
<TextBox <TextBox
attachedProperties:TextBoxAssist.PrefixText="$" attachedProperties:TextBoxAssist.PrefixText="$"
attachedProperties:TextBoxAssist.SuffixText="%"></TextBox> attachedProperties:TextBoxAssist.SuffixText="%">
</TextBox>
<StackPanel Orientation="Horizontal" Spacing="5"> <StackPanel Orientation="Horizontal" Spacing="5">
<Border Classes="card" Cursor="{CompiledBinding Cursor}"> <Border Classes="card" Cursor="{CompiledBinding Cursor}">
<TextBlock Text="{CompiledBinding SelectedCursor}"></TextBlock> <TextBlock Text="{CompiledBinding SelectedCursor}"></TextBlock>
</Border> </Border>
<controls:EnumComboBox Value="{CompiledBinding SelectedCursor}"></controls:EnumComboBox> <shared:EnumComboBox Value="{CompiledBinding SelectedCursor}"></shared:EnumComboBox>
</StackPanel> </StackPanel>
<Button Command="{Binding CreateRandomGradient}"> <Button Command="{Binding CreateRandomGradient}">
Create random gradient Create random gradient
</Button> </Button>
<gradientPicker:GradientPickerButton ColorGradient="{CompiledBinding ColorGradient}" IsCompact="True" />
</StackPanel>
<gradientPicker:GradientPickerButton ColorGradient="{CompiledBinding ColorGradient}" IsCompact="True"/> </Border>
</StackPanel> </StackPanel>
</Border> </ScrollViewer>
</StackPanel>
</Border> </Border>
</UserControl> </UserControl>

View File

@ -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<bool>();
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<IPin> 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<IPin> 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<bool> BooleanInput { get; set; }
public InputPin TrueInput { get; set; }
public InputPin FalseInput { get; set; }
public OutputPin Output { get; set; }
#region Overrides of Node
/// <inheritdoc />
public override void Evaluate()
{
Output.Value = BooleanInput.Value ? TrueInput.Value : FalseInput.Value;
}
#endregion
}

View File

@ -12,8 +12,9 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
private int _currentIndex; private int _currentIndex;
private Type _currentType; private Type _currentType;
private DataModelPath? _dataModelPath; private DataModelPath? _dataModelPath;
private DateTime _lastTrigger;
private bool _updating; private bool _updating;
private DateTime _lastTrigger;
private object? _lastPathValue;
public DataModelEventNode() : base("Data Model-Event", "Responds to a data model event trigger") public DataModelEventNode() : base("Data Model-Event", "Responds to a data model event trigger")
{ {
@ -47,27 +48,44 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
public override void Evaluate() public override void Evaluate()
{ {
object? outputValue = null; object? pathValue = _dataModelPath?.GetValue();
if (_dataModelPath?.GetValue() is IDataModelEvent dataModelEvent) bool hasTriggered = pathValue is IDataModelEvent dataModelEvent
? EvaluateEvent(dataModelEvent)
: EvaluateValue(pathValue);
if (hasTriggered)
{ {
if (dataModelEvent.LastTrigger > _lastTrigger) _currentIndex++;
{
_lastTrigger = dataModelEvent.LastTrigger;
_currentIndex++;
if (_currentIndex >= CycleValues.Count()) if (_currentIndex >= CycleValues.Count())
_currentIndex = 0; _currentIndex = 0;
}
outputValue = CycleValues.ElementAt(_currentIndex).PinValue;
} }
object? outputValue = CycleValues.ElementAt(_currentIndex).PinValue;
if (Output.Type.IsInstanceOfType(outputValue)) if (Output.Type.IsInstanceOfType(outputValue))
Output.Value = outputValue; Output.Value = outputValue;
else if (Output.Type.IsValueType) else if (Output.Type.IsValueType)
Output.Value = Output.Type.GetDefault()!; 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<IPin> e) private void CycleValuesOnPinAdded(object? sender, SingleValueEventArgs<IPin> e)
{ {
e.Value.PinConnected += OnPinConnected; e.Value.PinConnected += OnPinConnected;
@ -109,7 +127,6 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
} }
} }
private void UpdateDataModelPath() private void UpdateDataModelPath()
{ {
DataModelPath? old = _dataModelPath; DataModelPath? old = _dataModelPath;

View File

@ -2,8 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker;assembly=Artemis.UI.Shared"
xmlns:screens="clr-namespace:Artemis.VisualScripting.Nodes.DataModel.Screens" 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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.VisualScripting.Nodes.DataModel.Screens.DataModelEventNodeCustomView" x:Class="Artemis.VisualScripting.Nodes.DataModel.Screens.DataModelEventNodeCustomView"
x:DataType="screens:DataModelEventNodeCustomViewModel"> x:DataType="screens:DataModelEventNodeCustomViewModel">
@ -11,6 +11,8 @@
DataModelPath="{CompiledBinding DataModelPath}" DataModelPath="{CompiledBinding DataModelPath}"
Modules="{CompiledBinding Modules}" Modules="{CompiledBinding Modules}"
ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}" ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}"
FilterTypes="{CompiledBinding FilterTypes}" ShowFullPath="{CompiledBinding ShowFullPaths.Value}"
VerticalAlignment="Top"/> IsEventPicker="True"
VerticalAlignment="Top"
MaxWidth="300"/>
</UserControl> </UserControl>

View File

@ -50,7 +50,6 @@ public class DataModelEventNodeCustomViewModel : CustomNodeViewModel
public PluginSetting<bool> ShowFullPaths { get; } public PluginSetting<bool> ShowFullPaths { get; }
public PluginSetting<bool> ShowDataModelValues { get; } public PluginSetting<bool> ShowDataModelValues { get; }
public ObservableCollection<Type> FilterTypes { get; } = new() {typeof(IDataModelEvent)};
public ObservableCollection<Module>? Modules public ObservableCollection<Module>? Modules
{ {

View File

@ -2,8 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker;assembly=Artemis.UI.Shared"
xmlns:screens="clr-namespace:Artemis.VisualScripting.Nodes.DataModel.Screens" 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" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.VisualScripting.Nodes.DataModel.Screens.DataModelNodeCustomView" x:Class="Artemis.VisualScripting.Nodes.DataModel.Screens.DataModelNodeCustomView"
x:DataType="screens:DataModelNodeCustomViewModel"> x:DataType="screens:DataModelNodeCustomViewModel">
@ -11,5 +11,6 @@
DataModelPath="{CompiledBinding DataModelPath}" DataModelPath="{CompiledBinding DataModelPath}"
Modules="{CompiledBinding Modules}" Modules="{CompiledBinding Modules}"
ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}" ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}"
ShowFullPath="{CompiledBinding ShowFullPaths.Value}"/> ShowFullPath="{CompiledBinding ShowFullPaths.Value}"
MaxWidth="300"/>
</UserControl> </UserControl>

View File

@ -9,7 +9,7 @@
<ComboBox IsEnabled="{CompiledBinding EnumValues.Count}" <ComboBox IsEnabled="{CompiledBinding EnumValues.Count}"
Items="{CompiledBinding EnumValues}" Items="{CompiledBinding EnumValues}"
SelectedIndex="{CompiledBinding CurrentValue}" SelectedIndex="{CompiledBinding CurrentValue}"
PlaceholderText="Connect a pin..." PlaceholderText="Select a value"
Classes="condensed" Classes="condensed"
VerticalAlignment="Center" /> VerticalAlignment="Center" />
</UserControl> </UserControl>