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

Profile editor - Integrated visual scripting

This commit is contained in:
Robert 2022-04-03 22:21:56 +02:00
parent 372991a69b
commit 66ea718316
96 changed files with 2413 additions and 238 deletions

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Artemis.Core;
using BenchmarkDotNet.Attributes;
namespace Artemis.Benchmarking
{
public class NumericConversion
{
private readonly float _float;
private readonly Numeric _numeric;
public NumericConversion()
{
_float = 255235235f;
_numeric = new Numeric(_float);
}
[Benchmark]
public void FloatToIntCast()
{
var integer = (int) _float;
}
[Benchmark]
public void NumericToIntCast()
{
var integer = (int) _numeric;
}
[Benchmark]
public void NumericToIntConvertTo()
{
var integer = Convert.ChangeType(_numeric, typeof(int));
}
}
}

View File

@ -0,0 +1,3 @@
using BenchmarkDotNet.Running;
BenchmarkRunner.Run(typeof(Program).Assembly);

File diff suppressed because it is too large Load Diff

View File

@ -195,6 +195,9 @@ namespace Artemis.Core
_entity.EventPath = EventPath?.Entity; _entity.EventPath = EventPath?.Entity;
} }
/// <inheritdoc />
public INodeScript NodeScript => Script;
/// <inheritdoc /> /// <inheritdoc />
public void LoadNodeScript() public void LoadNodeScript()
{ {

View File

@ -1,51 +1,55 @@
using System; using System;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
namespace Artemis.Core namespace Artemis.Core;
/// <summary>
/// Represents a condition applied to a <see cref="ProfileElement" />
/// </summary>
public interface ICondition : IDisposable, IStorageModel
{ {
/// <summary> /// <summary>
/// Represents a condition applied to a <see cref="ProfileElement" /> /// Gets the entity used to store this condition
/// </summary> /// </summary>
public interface ICondition : IDisposable, IStorageModel public IConditionEntity Entity { get; }
{
/// <summary>
/// Gets the entity used to store this condition
/// </summary>
public IConditionEntity Entity { get; }
/// <summary>
/// Gets the profile element this condition applies to
/// </summary>
public ProfileElement ProfileElement { get; }
/// <summary>
/// Gets a boolean indicating whether the condition is currently met
/// </summary>
bool IsMet { get; }
/// <summary>
/// Updates the condition
/// </summary>
void Update();
/// <summary>
/// Applies the display condition to the provided timeline
/// </summary>
/// <param name="isMet"></param>
/// <param name="wasMet"></param>
/// <param name="timeline">The timeline to apply the display condition to</param>
void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline);
}
/// <summary> /// <summary>
/// Represents a condition applied to a <see cref="ProfileElement" /> using a <see cref="INodeScript" /> /// Gets the profile element this condition applies to
/// </summary> /// </summary>
public interface INodeScriptCondition : ICondition public ProfileElement ProfileElement { get; }
{
/// <summary> /// <summary>
/// Loads the node script this node script condition uses /// Gets a boolean indicating whether the condition is currently met
/// </summary> /// </summary>
void LoadNodeScript();
} bool IsMet { get; }
/// <summary>
/// Updates the condition
/// </summary>
void Update();
/// <summary>
/// Applies the display condition to the provided timeline
/// </summary>
/// <param name="isMet"></param>
/// <param name="wasMet"></param>
/// <param name="timeline">The timeline to apply the display condition to</param>
void ApplyToTimeline(bool isMet, bool wasMet, Timeline timeline);
}
/// <summary>
/// Represents a condition applied to a <see cref="ProfileElement" /> using a <see cref="INodeScript" />
/// </summary>
public interface INodeScriptCondition : ICondition
{
/// <summary>
/// Gets the node script of this node script condition
/// </summary>
INodeScript? NodeScript { get; }
/// <summary>
/// Loads the node script this node script condition uses
/// </summary>
void LoadNodeScript();
} }

View File

@ -91,6 +91,9 @@ namespace Artemis.Core
_entity.Script = Script.Entity; _entity.Script = Script.Entity;
} }
/// <inheritdoc />
public INodeScript? NodeScript => Script;
/// <inheritdoc /> /// <inheritdoc />
public void LoadNodeScript() public void LoadNodeScript()
{ {

View File

@ -40,9 +40,7 @@ namespace Artemis.Core
/// </summary> /// </summary>
public LayerProperty<TLayerProperty> LayerProperty { get; } public LayerProperty<TLayerProperty> LayerProperty { get; }
/// <summary> /// <inheritdoc />
/// Gets the script used to populate the data binding
/// </summary>
public INodeScript Script => _script; public INodeScript Script => _script;
/// <summary> /// <summary>
@ -126,7 +124,7 @@ namespace Artemis.Core
} }
/// <summary> /// <summary>
/// Invokes the <see cref="DataBindingDisabled" /> event /// Invokes the <see cref="DataBindingPropertiesCleared" /> event
/// </summary> /// </summary>
protected virtual void OnDataBindingPropertiesCleared() protected virtual void OnDataBindingPropertiesCleared()
{ {

View File

@ -37,10 +37,27 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public void SetValue(object? value) public void SetValue(object? value)
{ {
if (value is TProperty match) // Numeric has a bunch of conversion, this seems the cheapest way to use them :)
Setter(match); switch (value)
else {
throw new ArgumentException("Value must match the type of the data binding registration", nameof(value)); case TProperty match:
Setter(match);
break;
case Numeric numeric when Setter is Action<float> floatSetter:
floatSetter(numeric);
break;
case Numeric numeric when Setter is Action<int> intSetter:
intSetter(numeric);
break;
case Numeric numeric when Setter is Action<double> doubleSetter:
doubleSetter(numeric);
break;
case Numeric numeric when Setter is Action<byte> byteSetter:
byteSetter(numeric);
break;
default:
throw new ArgumentException("Value must match the type of the data binding registration", nameof(value));
}
} }
} }
} }

View File

@ -15,6 +15,11 @@ namespace Artemis.Core
/// </summary> /// </summary>
ILayerProperty BaseLayerProperty { get; } ILayerProperty BaseLayerProperty { get; }
/// <summary>
/// Gets the script used to populate the data binding
/// </summary>
INodeScript Script { get; }
/// <summary> /// <summary>
/// Gets a list of sub-properties this data binding applies to /// Gets a list of sub-properties this data binding applies to
/// </summary> /// </summary>

View File

@ -363,7 +363,7 @@ namespace Artemis.Core
private bool _displayConditionMet; private bool _displayConditionMet;
/// <summary> /// <summary>
/// Gets the display condition used to determine whether this element is active or not /// Gets or sets the display condition used to determine whether this element is active or not
/// </summary> /// </summary>
public ICondition? DisplayCondition public ICondition? DisplayCondition
{ {

View File

@ -53,7 +53,12 @@ namespace Artemis.Core.Internal
ClearInputPins(); ClearInputPins();
foreach (IDataBindingProperty property in DataBinding.Properties) foreach (IDataBindingProperty property in DataBinding.Properties)
_propertyPins.Add(property, CreateInputPin(property.ValueType, property.DisplayName)); {
_propertyPins.Add(property, Numeric.IsTypeCompatible(property.ValueType)
? CreateInputPin(typeof(Numeric), property.DisplayName)
: CreateInputPin(property.ValueType, property.DisplayName)
);
}
} }
#region Event handlers #region Event handlers

View File

@ -7,7 +7,7 @@ using System.Net.Http;
using System.Threading; using System.Threading;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Avalonia; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Threading; using Avalonia.Threading;

View File

@ -8,7 +8,7 @@ using Artemis.Core.Modules;
using Artemis.UI.Shared.DataModelVisualization.Shared; using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Events; using Artemis.UI.Shared.Events;
using Artemis.UI.Shared.Extensions; using Artemis.UI.Shared.Extensions;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;

View File

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Shared.DataModelVisualization namespace Artemis.UI.Shared.DataModelVisualization
{ {

View File

@ -3,7 +3,6 @@ using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared namespace Artemis.UI.Shared.DataModelVisualization.Shared

View File

@ -1,6 +1,5 @@
using System; using System;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared namespace Artemis.UI.Shared.DataModelVisualization.Shared

View File

@ -4,7 +4,6 @@ using System.Collections.ObjectModel;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared namespace Artemis.UI.Shared.DataModelVisualization.Shared

View File

@ -2,7 +2,6 @@
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared namespace Artemis.UI.Shared.DataModelVisualization.Shared

View File

@ -2,7 +2,6 @@
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared namespace Artemis.UI.Shared.DataModelVisualization.Shared

View File

@ -6,7 +6,7 @@ using System.Reflection;
using System.Text; using System.Text;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared; namespace Artemis.UI.Shared.DataModelVisualization.Shared;

View File

@ -0,0 +1,31 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:display="clr-namespace:Artemis.UI.Shared.DefaultTypes.DataModel.Display"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Shared.DefaultTypes.DataModel.Display.SKColorDataModelDisplayView"
x:DataType="display:SKColorDataModelDisplayViewModel">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock x:Name="HexDisplay"
Text="{CompiledBinding DisplayValue, Converter={StaticResource SKColorToStringConverter}}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch" />
<Border Margin="5 0 0 0"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
BorderThickness="1"
MinWidth="18"
MinHeight="18"
Background="{DynamicResource CheckerboardBrush}"
BorderBrush="{DynamicResource ColorPickerButtonOutline}"
CornerRadius="4"
ClipToBounds="True">
<Border CornerRadius="4">
<Border.Background>
<SolidColorBrush Color="{CompiledBinding DisplayValue, Converter={StaticResource SKColorToColorConverter}}" />
</Border.Background>
</Border>
</Border>
</StackPanel>
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
{
public partial class SKColorDataModelDisplayView : UserControl
{
public SKColorDataModelDisplayView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,11 @@
using Artemis.UI.Shared.DataModelVisualization;
using SkiaSharp;
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display;
/// <summary>
/// Represents a data model display view model used to display <see cref="SKColor" /> values.
/// </summary>
public class SKColorDataModelDisplayViewModel : DataModelDisplayViewModel<SKColor>
{
}

View File

@ -1,5 +1,5 @@
using System; using System;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Ninject.Extensions.Conventions; using Ninject.Extensions.Conventions;
using Ninject.Modules; using Ninject.Modules;

View File

@ -1,5 +1,4 @@
using Artemis.Core.ScriptingProviders; using Artemis.Core.ScriptingProviders;
using ReactiveUI;
namespace Artemis.UI.Shared.ScriptingProviders namespace Artemis.UI.Shared.ScriptingProviders
{ {

View File

@ -8,7 +8,6 @@ using Artemis.Core.Services;
using Artemis.UI.Shared.DataModelVisualization; using Artemis.UI.Shared.DataModelVisualization;
using Artemis.UI.Shared.DataModelVisualization.Shared; using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.DefaultTypes.DataModel.Display; using Artemis.UI.Shared.DefaultTypes.DataModel.Display;
using Artemis.UI.Shared.Services.Interfaces;
using Ninject; using Ninject;
using Ninject.Parameters; using Ninject.Parameters;

View File

@ -1,6 +1,4 @@
using Artemis.UI.Shared.Services.Interfaces; namespace Artemis.UI.Shared.Services;
namespace Artemis.UI.Shared.Services;
public class GradientPickerService : IGradientPickerService public class GradientPickerService : IGradientPickerService
{ {

View File

@ -1,4 +1,4 @@
namespace Artemis.UI.Shared.Services.Interfaces namespace Artemis.UI.Shared.Services
{ {
/// <summary> /// <summary>
/// Represents a service provided by the Artemis Shared UI library /// Represents a service provided by the Artemis Shared UI library

View File

@ -5,7 +5,7 @@ using Artemis.Core.Modules;
using Artemis.UI.Shared.DataModelVisualization; using Artemis.UI.Shared.DataModelVisualization;
using Artemis.UI.Shared.DataModelVisualization.Shared; using Artemis.UI.Shared.DataModelVisualization.Shared;
namespace Artemis.UI.Shared.Services.Interfaces namespace Artemis.UI.Shared.Services
{ {
/// <summary> /// <summary>
/// A service for UI related data model tasks /// A service for UI related data model tasks

View File

@ -1,6 +1,6 @@
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
namespace Artemis.UI.Shared.Services.Interfaces namespace Artemis.UI.Shared.Services
{ {
/// <summary> /// <summary>
/// A service that can be used to create notifications in either the application or on the desktop. /// A service that can be used to create notifications in either the application or on the desktop.

View File

@ -3,7 +3,7 @@ using System.Threading.Tasks;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Avalonia.Controls; using Avalonia.Controls;
namespace Artemis.UI.Shared.Services.Interfaces namespace Artemis.UI.Shared.Services
{ {
/// <summary> /// <summary>
/// A service that can be used to show windows and dialogs. /// A service that can be used to show windows and dialogs.

View File

@ -1,5 +1,4 @@
using System; using System;
using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Shared.Services.MainWindow namespace Artemis.UI.Shared.Services.MainWindow
{ {

View File

@ -1,6 +1,5 @@
using System; using System;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Services.Interfaces;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
namespace Artemis.UI.Shared.Services.NodeEditor; namespace Artemis.UI.Shared.Services.NodeEditor;

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Services.Interfaces;
using Artemis.UI.Shared.Services.NodeEditor.Commands; using Artemis.UI.Shared.Services.NodeEditor.Commands;
namespace Artemis.UI.Shared.Services.NodeEditor; namespace Artemis.UI.Shared.Services.NodeEditor;

View File

@ -1,5 +1,4 @@
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia.Controls; using Avalonia.Controls;
namespace Artemis.UI.Shared.Services namespace Artemis.UI.Shared.Services

View File

@ -0,0 +1,53 @@
using System;
using Artemis.Core;
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
/// <summary>
/// Represents a profile editor command that can be used to change the condition type of a profile element.
/// </summary>
public class ChangeConditionType : IProfileEditorCommand, IDisposable
{
private readonly RenderProfileElement _profileElement;
private readonly ICondition? _condition;
private readonly ICondition? _oldCondition;
private bool _executed;
/// <summary>
/// Creates a new instance of the <see cref="ChangeConditionType" /> class.
/// </summary>
/// <param name="profileElement">The profile element whose condition type to change.</param>
/// <param name="condition">The new condition type.</param>
public ChangeConditionType(RenderProfileElement profileElement, ICondition? condition)
{
_profileElement = profileElement;
_condition = condition;
_oldCondition = _profileElement.DisplayCondition;
}
/// <inheritdoc />
public string DisplayName => "Change element display condition type";
/// <inheritdoc />
public void Execute()
{
_profileElement.DisplayCondition = _condition;
_executed = true;
}
/// <inheritdoc />
public void Undo()
{
_profileElement.DisplayCondition = _oldCondition;
_executed = false;
}
/// <inheritdoc />
public void Dispose()
{
if (_executed)
_oldCondition?.Dispose();
else
_condition?.Dispose();
}
}

View File

@ -0,0 +1,40 @@
using Artemis.Core;
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
/// <summary>
/// Represents a profile editor command that can be used to enable or disable data bindings on a layer property.
/// </summary>
public class ChangeDataBindingEnabled : IProfileEditorCommand
{
private readonly bool _enabled;
private readonly ILayerProperty _layerProperty;
private readonly bool _originalEnabled;
/// <summary>
/// Creates a new instance of the <see cref="ChangeDataBindingEnabled" /> class.
/// </summary>
/// <param name="layerProperty">The layer property to enable or disable data bindings on.</param>
/// <param name="enabled">Whether to enable or disable data bindings.</param>
public ChangeDataBindingEnabled(ILayerProperty layerProperty, bool enabled)
{
_layerProperty = layerProperty;
_enabled = enabled;
_originalEnabled = _layerProperty.BaseDataBinding.IsEnabled;
}
/// <inheritdoc />
public string DisplayName => _enabled ? "Enable data binding" : "Disable data binding";
/// <inheritdoc />
public void Execute()
{
_layerProperty.BaseDataBinding.IsEnabled = _enabled;
}
/// <inheritdoc />
public void Undo()
{
_layerProperty.BaseDataBinding.IsEnabled = _originalEnabled;
}
}

View File

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Services.Interfaces;
using DynamicData; using DynamicData;
namespace Artemis.UI.Shared.Services.ProfileEditor; namespace Artemis.UI.Shared.Services.ProfileEditor;
@ -22,6 +21,11 @@ public interface IProfileEditorService : IArtemisSharedUIService
/// </summary> /// </summary>
IObservable<RenderProfileElement?> ProfileElement { get; } IObservable<RenderProfileElement?> ProfileElement { get; }
/// <summary>
/// Gets an observable of the currently selected layer property.
/// </summary>
IObservable<ILayerProperty?> LayerProperty { get; }
/// <summary> /// <summary>
/// Gets an observable of the current editor history. /// Gets an observable of the current editor history.
/// </summary> /// </summary>
@ -65,6 +69,12 @@ public interface IProfileEditorService : IArtemisSharedUIService
/// <param name="renderProfileElement">The profile element to select.</param> /// <param name="renderProfileElement">The profile element to select.</param>
void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement); void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement);
/// <summary>
/// Change the selected layer property.
/// </summary>
/// <param name="layerProperty">The layer property to select.</param>
void ChangeCurrentLayerProperty(ILayerProperty? layerProperty);
/// <summary> /// <summary>
/// Changes the current profile preview playback time. /// Changes the current profile preview playback time.
/// </summary> /// </summary>

View File

@ -1,16 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
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.Shared.Services.Interfaces;
using Artemis.UI.Shared.Services.ProfileEditor.Commands; using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using DynamicData; using DynamicData;
using DynamicData.Binding;
using ReactiveUI; using ReactiveUI;
using Serilog; using Serilog;
@ -18,6 +15,7 @@ namespace Artemis.UI.Shared.Services.ProfileEditor;
internal class ProfileEditorService : IProfileEditorService internal class ProfileEditorService : IProfileEditorService
{ {
private readonly BehaviorSubject<ILayerProperty?> _layerPropertySubject = new(null);
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IModuleService _moduleService; private readonly IModuleService _moduleService;
private readonly BehaviorSubject<int> _pixelsPerSecondSubject = new(120); private readonly BehaviorSubject<int> _pixelsPerSecondSubject = new(120);
@ -41,6 +39,7 @@ internal class ProfileEditorService : IProfileEditorService
ProfileConfiguration = _profileConfigurationSubject.AsObservable(); ProfileConfiguration = _profileConfigurationSubject.AsObservable();
ProfileElement = _profileElementSubject.AsObservable(); ProfileElement = _profileElementSubject.AsObservable();
LayerProperty = _layerPropertySubject.AsObservable();
History = Observable.Defer(() => Observable.Return(GetHistory(_profileConfigurationSubject.Value))).Concat(ProfileConfiguration.Select(GetHistory)); History = Observable.Defer(() => Observable.Return(GetHistory(_profileConfigurationSubject.Value))).Concat(ProfileConfiguration.Select(GetHistory));
Time = _timeSubject.AsObservable(); Time = _timeSubject.AsObservable();
Playing = _playingSubject.AsObservable(); Playing = _playingSubject.AsObservable();
@ -55,24 +54,15 @@ internal class ProfileEditorService : IProfileEditorService
// Disable all others if the changed one is selected and exclusive // Disable all others if the changed one is selected and exclusive
if (changed.IsSelected && changed.IsExclusive) if (changed.IsSelected && changed.IsExclusive)
{
Tools.Edit(list => Tools.Edit(list =>
{ {
foreach (IToolViewModel toolViewModel in list.Where(t => t.IsExclusive && t != changed)) foreach (IToolViewModel toolViewModel in list.Where(t => t.IsExclusive && t != changed))
toolViewModel.IsSelected = false; toolViewModel.IsSelected = false;
}); });
}
}); });
} }
public IObservable<bool> SuspendedEditing { get; } public IObservable<bool> SuspendedEditing { get; }
public IObservable<ProfileConfiguration?> ProfileConfiguration { get; }
public IObservable<RenderProfileElement?> ProfileElement { get; }
public IObservable<ProfileEditorHistory?> History { get; }
public IObservable<TimeSpan> Time { get; }
public IObservable<bool> Playing { get; }
public IObservable<int> PixelsPerSecond { get; }
public SourceList<IToolViewModel> Tools { get; }
private ProfileEditorHistory? GetHistory(ProfileConfiguration? profileConfiguration) private ProfileEditorHistory? GetHistory(ProfileConfiguration? profileConfiguration)
{ {
@ -114,6 +104,15 @@ internal class ProfileEditorService : IProfileEditorService
} }
} }
public IObservable<ProfileConfiguration?> ProfileConfiguration { get; }
public IObservable<RenderProfileElement?> ProfileElement { get; }
public IObservable<ILayerProperty?> LayerProperty { get; }
public IObservable<ProfileEditorHistory?> History { get; }
public IObservable<TimeSpan> Time { get; }
public IObservable<bool> Playing { get; }
public IObservable<int> PixelsPerSecond { get; }
public SourceList<IToolViewModel> Tools { get; }
public IObservable<IChangeSet<ILayerPropertyKeyframe>> ConnectToKeyframes() public IObservable<IChangeSet<ILayerPropertyKeyframe>> ConnectToKeyframes()
{ {
return _selectedKeyframes.Connect(); return _selectedKeyframes.Connect();
@ -167,6 +166,13 @@ internal class ProfileEditorService : IProfileEditorService
{ {
_selectedKeyframes.Clear(); _selectedKeyframes.Clear();
_profileElementSubject.OnNext(renderProfileElement); _profileElementSubject.OnNext(renderProfileElement);
ChangeCurrentLayerProperty(null);
}
/// <inheritdoc />
public void ChangeCurrentLayerProperty(ILayerProperty? layerProperty)
{
_layerPropertySubject.OnNext(layerProperty);
} }
public void ChangeTime(TimeSpan time) public void ChangeTime(TimeSpan time)

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Services.Interfaces;
namespace Artemis.UI.Shared.Services.PropertyInput; namespace Artemis.UI.Shared.Services.PropertyInput;

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia; using Avalonia;
using Avalonia.Layout; using Avalonia.Layout;

View File

@ -2,7 +2,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;

View File

@ -39,6 +39,11 @@
<Button Classes="title-bar-button"> <Button Classes="title-bar-button">
<avalonia:MaterialIcon Kind="WindowMinimize" /> <avalonia:MaterialIcon Kind="WindowMinimize" />
</Button> </Button>
<TextBlock Margin="0 5 0 0">ToggleButton.window-button</TextBlock>
<ToggleButton Classes="icon-button">
<avalonia:MaterialIcon Kind="BlockChain" />
</ToggleButton>
</StackPanel> </StackPanel>
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
@ -100,4 +105,28 @@
<Style Selector="Button.title-bar-button:pointerover"> <Style Selector="Button.title-bar-button:pointerover">
<Setter Property="Background" Value="Red"></Setter> <Setter Property="Background" Value="Red"></Setter>
</Style> </Style>
<Style Selector="ToggleButton:checked:pointerover /template/ Border#BorderElement">
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushCheckedPointerOver}" />
</Style>
<Style Selector="ToggleButton:checked:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedPointerOver}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedPointerOver}" />
</Style>
<Style Selector="ToggleButton:checked:pressed /template/ Border#BorderElement">
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushCheckedPressed}" />
</Style>
<Style Selector="ToggleButton:checked:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedPressed}" />
</Style>
<Style Selector="ToggleButton:checked:disabled /template/ Border#BorderElement">
<Setter Property="BorderBrush" Value="{DynamicResource ToggleButtonBorderBrushCheckedDisabled}" />
</Style>
<Style Selector="ToggleButton:checked:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ToggleButtonBackgroundCheckedDisabled}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ToggleButtonForegroundCheckedDisabled}" />
</Style>
</Styles> </Styles>

View File

@ -32,6 +32,8 @@
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
VerticalAlignment="{TemplateBinding VerticalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
HorizontalContentAlignment="Stretch"> HorizontalContentAlignment="Stretch">
<Grid ColumnDefinitions="*,Auto" HorizontalAlignment="Stretch"> <Grid ColumnDefinitions="*,Auto" HorizontalAlignment="Stretch">
<TextBlock Name="MainButtonLabel" <TextBlock Name="MainButtonLabel"

View File

@ -8,7 +8,7 @@ using System.Security.Principal;
using System.Threading; using System.Threading;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Artemis.UI.Windows.Utilities; using Artemis.UI.Windows.Utilities;
using Avalonia; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;

View File

@ -7,7 +7,7 @@ using Artemis.UI.Ninject;
using Artemis.UI.Screens.Root; using Artemis.UI.Screens.Root;
using Artemis.UI.Shared.Controls.DataModelPicker; using Artemis.UI.Shared.Controls.DataModelPicker;
using Artemis.UI.Shared.Ninject; using Artemis.UI.Shared.Ninject;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Artemis.VisualScripting.Ninject; using Artemis.VisualScripting.Ninject;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;

View File

@ -6,7 +6,7 @@ using Artemis.Core;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Screens.ProfileEditor.Properties.Tree.Dialogs; using Artemis.UI.Screens.ProfileEditor.Properties.Tree.Dialogs;
using Artemis.UI.Shared.Services.Interfaces; 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 Artemis.UI.Shared.Services.PropertyInput; using Artemis.UI.Shared.Services.PropertyInput;

View File

@ -0,0 +1,16 @@
using System;
using System.Reactive.Linq;
using Artemis.Core;
namespace Artemis.UI.Extensions;
public static class DataBindingExtensions
{
public static IObservable<IDataBinding> GetObservable(this IDataBinding dataBinding)
{
return Observable.FromEventPattern<DataBindingEventArgs>(x => dataBinding.DataBindingEnabled += x, x => dataBinding.DataBindingEnabled -= x)
.Merge(Observable.FromEventPattern<DataBindingEventArgs>(x => dataBinding.DataBindingDisabled += x, x => dataBinding.DataBindingDisabled -= x))
.Select(e => e.EventArgs.DataBinding)
.StartWith(dataBinding);
}
}

View File

@ -7,6 +7,7 @@ using Artemis.UI.Screens.Plugins;
using Artemis.UI.Screens.ProfileEditor; using Artemis.UI.Screens.ProfileEditor;
using Artemis.UI.Screens.ProfileEditor.ProfileTree; using Artemis.UI.Screens.ProfileEditor.ProfileTree;
using Artemis.UI.Screens.ProfileEditor.Properties; using Artemis.UI.Screens.ProfileEditor.Properties;
using Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline.Segments;
using Artemis.UI.Screens.ProfileEditor.Properties.Tree; using Artemis.UI.Screens.ProfileEditor.Properties.Tree;
@ -84,6 +85,11 @@ namespace Artemis.UI.Ninject.Factories
TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel); TimelineGroupViewModel TimelineGroupViewModel(PropertyGroupViewModel propertyGroupViewModel);
} }
public interface IDataBindingVmFactory : IVmFactory
{
DataBindingViewModel DataBindingViewModel();
}
public interface IPropertyVmFactory public interface IPropertyVmFactory
{ {
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel); ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);

View File

@ -9,7 +9,7 @@ using Artemis.Core.Modules;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.DataModelVisualization.Shared; using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Avalonia.Threading; using Avalonia.Threading;
using DynamicData; using DynamicData;
using ReactiveUI; using ReactiveUI;

View File

@ -5,8 +5,8 @@ using System.Reactive.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI; using ReactiveUI;
using RGB.NET.Core; using RGB.NET.Core;

View File

@ -5,7 +5,7 @@ using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Settings; using Artemis.UI.Screens.Settings;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Avalonia.Threading; using Avalonia.Threading;
using Humanizer; using Humanizer;
using ReactiveUI; using ReactiveUI;

View File

@ -1,7 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Avalonia; using Avalonia;
using RGB.NET.Core; using RGB.NET.Core;

View File

@ -6,8 +6,8 @@ using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI; using ReactiveUI;
using SkiaSharp; using SkiaSharp;

View File

@ -8,7 +8,7 @@ using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Plugins namespace Artemis.UI.Screens.Plugins

View File

@ -9,7 +9,7 @@ using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Plugins namespace Artemis.UI.Screens.Plugins

View File

@ -6,8 +6,8 @@ using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia; using Avalonia;
using Avalonia.Threading; using Avalonia.Threading;
using ReactiveUI; using ReactiveUI;

View File

@ -11,7 +11,7 @@ using Artemis.Core.Services;
using Artemis.UI.Exceptions; using Artemis.UI.Exceptions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Avalonia.Threading; using Avalonia.Threading;
using Ninject; using Ninject;
using ReactiveUI; using ReactiveUI;

View File

@ -0,0 +1,19 @@
using System;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition
{
public class ConditionTypeViewModel : ViewModelBase
{
public ConditionTypeViewModel(string name, string description, Type? conditionType)
{
Name = name;
Description = description;
ConditionType = conditionType;
}
public string Name { get; }
public string Description { get; }
public Type? ConditionType { get; }
}
}

View File

@ -4,28 +4,104 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:displayCondition="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayCondition" xmlns:displayCondition="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayCondition"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia" xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="650"
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.DisplayConditionScriptView" x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.DisplayConditionScriptView"
x:DataType="displayCondition:DisplayConditionScriptViewModel"> x:DataType="displayCondition:DisplayConditionScriptViewModel">
<Grid> <UserControl.Styles>
<Grid IsVisible="{CompiledBinding NodeScriptViewModel, Converter={x:Static ObjectConverters.IsNotNull}}"> <Style Selector="ComboBox.condition-type /template/ ContentControl#ContentPresenter">
<ContentControl Content="{CompiledBinding NodeScriptViewModel}" /> <Setter Property="ContentTemplate">
<Button Classes="icon-button" <Setter.Value>
ToolTip.Tip="Open editor" <DataTemplate DataType="{x:Type displayCondition:ConditionTypeViewModel}">
VerticalAlignment="Top" <TextBlock Text="{CompiledBinding Name}" VerticalAlignment="Center" />
HorizontalAlignment="Right" </DataTemplate>
Margin="4" </Setter.Value>
Command="{Binding OpenEditor}"> </Setter>
<avalonia:MaterialIcon Kind="OpenInNew"></avalonia:MaterialIcon> </Style>
</UserControl.Styles>
<ScrollViewer>
<DockPanel LastChildFill="False">
<DockPanel.Styles>
<Style Selector="DockPanel > TextBlock">
<Setter Property="Margin" Value="0 5"></Setter>
</Style>
</DockPanel.Styles>
<TextBlock DockPanel.Dock="Top">Condition type</TextBlock>
<ComboBox Name="ConditionType"
DockPanel.Dock="Top"
Classes="condition-type"
PlaceholderText="Select a condition type"
Items="{CompiledBinding ConditionTypeViewModels}"
SelectedItem="{CompiledBinding SelectedConditionTypeViewModel}"
HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type displayCondition:ConditionTypeViewModel}">
<StackPanel Spacing="5">
<TextBlock Text="{CompiledBinding Name}" TextWrapping="Wrap" MaxWidth="350" />
<TextBlock Text="{CompiledBinding Description}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" TextWrapping="Wrap" MaxWidth="350" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- Event options -->
<DockPanel IsVisible="{CompiledBinding ShowEventOptions}" DockPanel.Dock="Top" HorizontalAlignment="Stretch">
<TextBlock DockPanel.Dock="Top">Triggered by event</TextBlock>
<dataModelPicker:DataModelPickerButton Placeholder="Select an event"
DockPanel.Dock="Top"
HorizontalAlignment="Stretch" />
<TextBlock DockPanel.Dock="Top">When the event fires..</TextBlock>
<ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top">
<ComboBoxItem>
Play the timeline once
</ComboBoxItem>
<ComboBoxItem>
Toggle the element on or off
</ComboBoxItem>
</ComboBox>
<TextBlock DockPanel.Dock="Top">And if already playing..</TextBlock>
<ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top">
<ComboBoxItem>
Restart the timeline
</ComboBoxItem>
<ComboBoxItem>
Play a second copy
</ComboBoxItem>
<ComboBoxItem>
Do nothing
</ComboBoxItem>
</ComboBox>
</DockPanel>
<!-- Static options -->
<DockPanel IsVisible="{CompiledBinding ShowStaticOptions}" HorizontalAlignment="Stretch" DockPanel.Dock="Top">
<TextBlock DockPanel.Dock="Top">While condition is met..</TextBlock>
<ComboBox DockPanel.Dock="Top" PlaceholderText="Select a play mode" HorizontalAlignment="Stretch">
<ComboBoxItem>Play the main segment once</ComboBoxItem>
<ComboBoxItem>Repeat the main segment</ComboBoxItem>
</ComboBox>
<TextBlock DockPanel.Dock="Top">And when no longer met..</TextBlock>
<ComboBox DockPanel.Dock="Top" PlaceholderText="Select a stop mode" HorizontalAlignment="Stretch">
<ComboBoxItem>Finish the main segment</ComboBoxItem>
<ComboBoxItem>Skip forward to the end segment</ComboBoxItem>
</ComboBox>
<DockPanel DockPanel.Dock="Top" Margin="0 5">
<avalonia:MaterialIcon Kind="InfoCircle" Margin="5 0"></avalonia:MaterialIcon>
<TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap">
The start- and end-segments are always played once.
</TextBlock>
</DockPanel>
</DockPanel>
<Button DockPanel.Dock="Bottom" ToolTip.Tip="Open editor" Margin="0 15 0 5" Command="{Binding OpenEditor}" HorizontalAlignment="Stretch">
Edit condition script
</Button> </Button>
</Grid> </DockPanel>
</ScrollViewer>
<Button VerticalAlignment="Center"
HorizontalAlignment="Center"
Command="{Binding EnableConditions}"
IsVisible="{CompiledBinding NodeScriptViewModel, Converter={x:Static ObjectConverters.IsNull}}">
Enable display conditions
</Button>
</Grid>
</UserControl> </UserControl>

View File

@ -1,11 +1,13 @@
using System; using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.VisualScripting; using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; 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.Controls.Mixins; using Avalonia.Controls.Mixins;
@ -16,40 +18,77 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition;
public class DisplayConditionScriptViewModel : ActivatableViewModelBase public class DisplayConditionScriptViewModel : ActivatableViewModelBase
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly ObservableAsPropertyHelper<bool> _showEventOptions;
private readonly ObservableAsPropertyHelper<bool> _showStaticOptions;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<NodeScriptViewModel?>? _nodeScriptViewModel; private ObservableAsPropertyHelper<NodeScriptViewModel?>? _nodeScriptViewModel;
private RenderProfileElement? _profileElement; private RenderProfileElement? _profileElement;
private ObservableAsPropertyHelper<ConditionTypeViewModel?>? _selectedConditionTypeViewModel;
public DisplayConditionScriptViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService) public DisplayConditionScriptViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService)
{ {
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_windowService = windowService; _windowService = windowService;
ConditionTypeViewModels = new ObservableCollection<ConditionTypeViewModel>
{
new("None", "The element is always active.", null),
new("Regular", "The element is activated when the provided visual script ends in true.", typeof(StaticCondition)),
new("Event", "The element is activated when the selected event fires.\r\n" +
"Events that contain data can conditionally trigger the layer using a visual script.", typeof(EventCondition))
};
this.WhenActivated(d => this.WhenActivated(d =>
{ {
profileEditorService.ProfileElement.Subscribe(p => _profileElement = p).DisposeWith(d); profileEditorService.ProfileElement.Subscribe(p => _profileElement = p).DisposeWith(d);
_nodeScriptViewModel = profileEditorService.ProfileElement _nodeScriptViewModel = profileEditorService.ProfileElement
.Select(p => p?.WhenAnyValue(element => element.DisplayCondition) ?? Observable.Never<ICondition?>()) .Select(p => p?.WhenAnyValue(element => element.DisplayCondition) ?? Observable.Never<ICondition?>())
.Switch() .Switch()
.Select(c => c is StaticCondition staticCondition ? nodeVmFactory.NodeScriptViewModel(staticCondition.Script, true) : null) .Select(c => c is INodeScriptCondition {NodeScript: NodeScript nodeScript} ? nodeVmFactory.NodeScriptViewModel(nodeScript, true) : null)
.ToProperty(this, vm => vm.NodeScriptViewModel) .ToProperty(this, vm => vm.NodeScriptViewModel)
.DisposeWith(d); .DisposeWith(d);
_selectedConditionTypeViewModel = profileEditorService.ProfileElement
.Select(p => p?.WhenAnyValue(element => element.DisplayCondition) ?? Observable.Never<ICondition?>())
.Switch()
.Select(c => c != null ? ConditionTypeViewModels.FirstOrDefault(vm => vm.ConditionType == c.GetType()) : null)
.ToProperty(this, vm => vm.SelectedConditionTypeViewModel)
.DisposeWith(d);
}); });
_showStaticOptions = this.WhenAnyValue(vm => vm.SelectedConditionTypeViewModel)
.Select(c => c != null && c.ConditionType == typeof(StaticCondition))
.ToProperty(this, vm => vm.ShowStaticOptions);
_showEventOptions = this.WhenAnyValue(vm => vm.SelectedConditionTypeViewModel)
.Select(c => c != null && c.ConditionType == typeof(EventCondition))
.ToProperty(this, vm => vm.ShowEventOptions);
} }
public NodeScriptViewModel? NodeScriptViewModel => _nodeScriptViewModel?.Value; public NodeScriptViewModel? NodeScriptViewModel => _nodeScriptViewModel?.Value;
public ObservableCollection<ConditionTypeViewModel> ConditionTypeViewModels { get; }
public async Task EnableConditions() public ConditionTypeViewModel? SelectedConditionTypeViewModel
{ {
bool confirmed = await _windowService.ShowConfirmContentDialog( get => _selectedConditionTypeViewModel?.Value;
"Display conditions", set
"Do you want to enable display conditions for this element? \r\n" + {
"Using display conditions you can dynamically hide or show layers and folders depending on certain parameters." if (_profileElement == null)
); return;
if (confirmed && _profileElement != null) ICondition? condition = null;
_profileEditorService.ExecuteCommand(new ChangeElementDisplayCondition(_profileElement, new StaticCondition(_profileElement))); if (value?.ConditionType == typeof(StaticCondition))
condition = new StaticCondition(_profileElement);
else if (value?.ConditionType == typeof(EventCondition))
condition = new EventCondition(_profileElement);
_profileEditorService.ExecuteCommand(new ChangeConditionType(_profileElement, condition));
}
} }
public bool ShowStaticOptions => _showStaticOptions.Value;
public bool ShowEventOptions => _showEventOptions.Value;
public async Task OpenEditor() public async Task OpenEditor()
{ {
if (_profileElement?.DisplayCondition is StaticCondition staticCondition) if (_profileElement?.DisplayCondition is StaticCondition staticCondition)

View File

@ -1,7 +1,7 @@
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree namespace Artemis.UI.Screens.ProfileEditor.ProfileTree

View File

@ -1,7 +1,7 @@
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
namespace Artemis.UI.Screens.ProfileEditor.ProfileTree namespace Artemis.UI.Screens.ProfileEditor.ProfileTree

View File

@ -6,7 +6,7 @@ using System.Reactive.Disposables;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using ReactiveUI; using ReactiveUI;

View File

@ -10,7 +10,7 @@ using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; 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 ReactiveUI; using ReactiveUI;

View File

@ -0,0 +1,52 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:dataBinding="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.DataBinding"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.DataBinding.DataBindingView"
x:DataType="dataBinding:DataBindingViewModel">
<Grid RowDefinitions="48,*">
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,*">
<TextBlock Grid.Column="0"
Classes="h5"
VerticalAlignment="Center"
Text="{CompiledBinding LayerProperty.PropertyDescription.Name}"
Margin="10 0" />
<ToggleSwitch Grid.Column="1"
OnContent="Data binding enabled"
OffContent="Data binding disabled"
VerticalAlignment="Center"
IsChecked="{CompiledBinding DataBindingEnabled}" />
<Button Grid.Column="2"
Classes="icon-button"
ToolTip.Tip="Open editor"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Margin="10"
Command="{Binding OpenEditor}">
<avalonia:MaterialIcon Kind="OpenInNew"></avalonia:MaterialIcon>
</Button>
</Grid>
<ContentControl Grid.Row="1"
IsVisible="{CompiledBinding NodeScriptViewModel, Converter={x:Static ObjectConverters.IsNotNull}}"
Content="{CompiledBinding NodeScriptViewModel}" />
<StackPanel Grid.Row="1"
VerticalAlignment="Center"
IsVisible="{CompiledBinding NodeScriptViewModel, Converter={x:Static ObjectConverters.IsNull}}">
<TextBlock TextAlignment="Center" Classes="h5">To use data bindings enable them in with the toggle above.</TextBlock>
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center" Foreground="{DynamicResource TextFillColorSecondary}">
When you enable data bindings you can no longer use keyframes or normal values for this property.
</TextBlock>
<controls:HyperlinkButton HorizontalAlignment="Center"
NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/profiles/data-bindings"
Margin="0 10">
Learn more
</controls:HyperlinkButton>
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,18 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.DataBinding
{
public partial class DataBindingView : ReactiveUserControl<DataBindingViewModel>
{
public DataBindingView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,65 @@
using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Avalonia.Controls.Mixins;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
public class DataBindingViewModel : ActivatableViewModelBase
{
private readonly IProfileEditorService _profileEditorService;
private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<bool>? _dataBindingEnabled;
private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty;
private ObservableAsPropertyHelper<NodeScriptViewModel?>? _nodeScriptViewModel;
public DataBindingViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService)
{
_profileEditorService = profileEditorService;
_windowService = windowService;
this.WhenActivated(d =>
{
_layerProperty = profileEditorService.LayerProperty.ToProperty(this, vm => vm.LayerProperty).DisposeWith(d);
_nodeScriptViewModel = profileEditorService.LayerProperty
.Select(p => p != null ? p.BaseDataBinding.GetObservable() : Observable.Never<IDataBinding>())
.Switch()
.Select(b => b.IsEnabled ? nodeVmFactory.NodeScriptViewModel((NodeScript) b.Script, false) : null)
.ToProperty(this, vm => vm.NodeScriptViewModel)
.DisposeWith(d);
_dataBindingEnabled = profileEditorService.LayerProperty
.Select(p => p != null ? p.BaseDataBinding.GetObservable() : Observable.Never<IDataBinding>())
.Switch()
.Select(b => b.IsEnabled)
.ToProperty(this, vm => vm.DataBindingEnabled)
.DisposeWith(d);
});
}
public ILayerProperty? LayerProperty => _layerProperty?.Value;
public NodeScriptViewModel? NodeScriptViewModel => _nodeScriptViewModel?.Value;
public bool DataBindingEnabled
{
get => _dataBindingEnabled?.Value ?? false;
set
{
if (LayerProperty != null)
_profileEditorService.ExecuteCommand(new ChangeDataBindingEnabled(LayerProperty, value));
}
}
public async Task OpenEditor()
{
if (LayerProperty != null && LayerProperty.BaseDataBinding.IsEnabled)
await _windowService.ShowDialogAsync<NodeScriptWindowViewModel, bool>(("nodeScript", LayerProperty.BaseDataBinding.Script));
}
}

View File

@ -44,8 +44,15 @@
Background="Transparent" Background="Transparent"
Margin="0 0 -5 0" /> Margin="0 0 -5 0" />
<!-- Horizontal scrolling --> <ContentControl Grid.Column="2"
<ScrollViewer Grid.Column="2" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled"> IsVisible="{CompiledBinding DataBindingViewModel, Converter={x:Static ObjectConverters.IsNotNull}}"
Content="{CompiledBinding DataBindingViewModel}"/>
<!-- Horizontal scrolling -->
<ScrollViewer Grid.Column="2"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Disabled"
IsVisible="{CompiledBinding DataBindingViewModel, Converter={x:Static ObjectConverters.IsNull}}">
<Grid RowDefinitions="48,*"> <Grid RowDefinitions="48,*">
<!-- Timeline header body --> <!-- Timeline header body -->
<controls:TimelineHeader Grid.Row="0" <controls:TimelineHeader Grid.Row="0"
@ -136,12 +143,8 @@
<ContentControl Content="{CompiledBinding TimelineViewModel}" <ContentControl Content="{CompiledBinding TimelineViewModel}"
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}" /> Background="{DynamicResource CardStrokeColorDefaultSolidBrush}" />
</ScrollViewer> </ScrollViewer>
<!-- TODO: Databindings here -->
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@ -11,6 +11,7 @@ using Artemis.Core.LayerEffects;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.Playback; using Artemis.UI.Screens.ProfileEditor.Playback;
using Artemis.UI.Screens.ProfileEditor.Properties.DataBinding;
using Artemis.UI.Screens.ProfileEditor.Properties.Timeline; using Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
@ -20,67 +21,85 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties;
public class PropertiesViewModel : ActivatableViewModelBase public class PropertiesViewModel : ActivatableViewModelBase
{ {
private readonly Dictionary<LayerPropertyGroup, PropertyGroupViewModel> _cachedViewModels; private readonly Dictionary<LayerPropertyGroup, PropertyGroupViewModel> _cachedPropertyViewModels;
private readonly IDataBindingVmFactory _dataBindingVmFactory;
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory; private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private DataBindingViewModel? _backgroundDataBindingViewModel;
private DataBindingViewModel? _dataBindingViewModel;
private ObservableAsPropertyHelper<ILayerProperty?>? _layerProperty;
private ObservableAsPropertyHelper<int>? _pixelsPerSecond; private ObservableAsPropertyHelper<int>? _pixelsPerSecond;
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement; private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
/// <inheritdoc /> /// <inheritdoc />
public PropertiesViewModel(IProfileEditorService profileEditorService, ISettingsService settingsService, ILayerPropertyVmFactory layerPropertyVmFactory, PlaybackViewModel playbackViewModel) public PropertiesViewModel(IProfileEditorService profileEditorService,
ISettingsService settingsService,
ILayerPropertyVmFactory layerPropertyVmFactory,
IDataBindingVmFactory dataBindingVmFactory,
PlaybackViewModel playbackViewModel)
{ {
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_settingsService = settingsService; _settingsService = settingsService;
_layerPropertyVmFactory = layerPropertyVmFactory; _layerPropertyVmFactory = layerPropertyVmFactory;
_cachedViewModels = new Dictionary<LayerPropertyGroup, PropertyGroupViewModel>(); _dataBindingVmFactory = dataBindingVmFactory;
_cachedPropertyViewModels = new Dictionary<LayerPropertyGroup, PropertyGroupViewModel>();
PropertyGroupViewModels = new ObservableCollection<PropertyGroupViewModel>(); PropertyGroupViewModels = new ObservableCollection<PropertyGroupViewModel>();
PlaybackViewModel = playbackViewModel; PlaybackViewModel = playbackViewModel;
TimelineViewModel = layerPropertyVmFactory.TimelineViewModel(PropertyGroupViewModels); TimelineViewModel = layerPropertyVmFactory.TimelineViewModel(PropertyGroupViewModels);
// React to service profile element changes as long as the VM is active
this.WhenActivated(d =>
{
_profileElement = profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d);
_pixelsPerSecond = profileEditorService.PixelsPerSecond.ToProperty(this, vm => vm.PixelsPerSecond).DisposeWith(d);
_layerProperty = profileEditorService.LayerProperty.ToProperty(this, vm => vm.LayerProperty).DisposeWith(d);
Disposable.Create(() =>
{
_settingsService.SaveAllSettings();
foreach ((LayerPropertyGroup _, PropertyGroupViewModel value) in _cachedPropertyViewModels)
value.Dispose();
_cachedPropertyViewModels.Clear();
}).DisposeWith(d);
});
// Subscribe to events of the latest selected profile element - borrowed from https://stackoverflow.com/a/63950940 // Subscribe to events of the latest selected profile element - borrowed from https://stackoverflow.com/a/63950940
this.WhenAnyValue(vm => vm.ProfileElement) this.WhenAnyValue(vm => vm.ProfileElement)
.Select(p => p is Layer l .Select(p => p is Layer l
? Observable.FromEventPattern(x => l.LayerBrushUpdated += x, x => l.LayerBrushUpdated -= x) ? Observable.FromEventPattern(x => l.LayerBrushUpdated += x, x => l.LayerBrushUpdated -= x)
: Observable.Never<EventPattern<object>>()) : Observable.Never<EventPattern<object>>())
.Switch() .Switch()
.Subscribe(_ => UpdateGroups()); .Subscribe(_ => UpdatePropertyGroups());
this.WhenAnyValue(vm => vm.ProfileElement) this.WhenAnyValue(vm => vm.ProfileElement)
.Select(p => p != null .Select(p => p != null
? Observable.FromEventPattern(x => p.LayerEffectsUpdated += x, x => p.LayerEffectsUpdated -= x) ? Observable.FromEventPattern(x => p.LayerEffectsUpdated += x, x => p.LayerEffectsUpdated -= x)
: Observable.Never<EventPattern<object>>()) : Observable.Never<EventPattern<object>>())
.Switch() .Switch()
.Subscribe(_ => UpdateGroups()); .Subscribe(_ => UpdatePropertyGroups());
// React to service profile element changes as long as the VM is active this.WhenAnyValue(vm => vm.ProfileElement).Subscribe(_ => UpdatePropertyGroups());
this.WhenAnyValue(vm => vm.LayerProperty).Subscribe(_ => UpdateTimelineViewModel());
this.WhenActivated(d =>
{
_profileElement = profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d);
_pixelsPerSecond = profileEditorService.PixelsPerSecond.ToProperty(this, vm => vm.PixelsPerSecond).DisposeWith(d);
Disposable.Create(() =>
{
_settingsService.SaveAllSettings();
foreach ((LayerPropertyGroup _, PropertyGroupViewModel value) in _cachedViewModels)
value.Dispose();
_cachedViewModels.Clear();
}).DisposeWith(d);
});
this.WhenAnyValue(vm => vm.ProfileElement).Subscribe(_ => UpdateGroups());
} }
public ObservableCollection<PropertyGroupViewModel> PropertyGroupViewModels { get; } public ObservableCollection<PropertyGroupViewModel> PropertyGroupViewModels { get; }
public PlaybackViewModel PlaybackViewModel { get; } public PlaybackViewModel PlaybackViewModel { get; }
public TimelineViewModel TimelineViewModel { get; } public TimelineViewModel TimelineViewModel { get; }
public DataBindingViewModel? DataBindingViewModel
{
get => _dataBindingViewModel;
set => RaiseAndSetIfChanged(ref _dataBindingViewModel, value);
}
public RenderProfileElement? ProfileElement => _profileElement?.Value; public RenderProfileElement? ProfileElement => _profileElement?.Value;
public Layer? Layer => _profileElement?.Value as Layer; public Layer? Layer => _profileElement?.Value as Layer;
public ILayerProperty? LayerProperty => _layerProperty?.Value;
public int PixelsPerSecond => _pixelsPerSecond?.Value ?? 0; public int PixelsPerSecond => _pixelsPerSecond?.Value ?? 0;
public IObservable<bool> Playing => _profileEditorService.Playing; public IObservable<bool> Playing => _profileEditorService.Playing;
public PluginSetting<double> PropertiesTreeWidth => _settingsService.GetSetting("ProfileEditor.PropertiesTreeWidth", 500.0); public PluginSetting<double> PropertiesTreeWidth => _settingsService.GetSetting("ProfileEditor.PropertiesTreeWidth", 500.0);
private void UpdateGroups() private void UpdatePropertyGroups()
{ {
if (ProfileElement == null) if (ProfileElement == null)
{ {
@ -92,18 +111,20 @@ public class PropertiesViewModel : ActivatableViewModelBase
if (Layer != null) if (Layer != null)
{ {
// Add base VMs // Add base VMs
viewModels.Add(GetOrCreateViewModel(Layer.General, null, null)); viewModels.Add(GetOrCreatePropertyViewModel(Layer.General, null, null));
viewModels.Add(GetOrCreateViewModel(Layer.Transform, null, null)); viewModels.Add(GetOrCreatePropertyViewModel(Layer.Transform, null, null));
// Add brush VM if the brush has properties // Add brush VM if the brush has properties
if (Layer.LayerBrush?.BaseProperties != null) if (Layer.LayerBrush?.BaseProperties != null)
viewModels.Add(GetOrCreateViewModel(Layer.LayerBrush.BaseProperties, Layer.LayerBrush, null)); viewModels.Add(GetOrCreatePropertyViewModel(Layer.LayerBrush.BaseProperties, Layer.LayerBrush, null));
} }
// Add effect VMs // Add effect VMs
foreach (BaseLayerEffect layerEffect in ProfileElement.LayerEffects.OrderBy(e => e.Order)) foreach (BaseLayerEffect layerEffect in ProfileElement.LayerEffects.OrderBy(e => e.Order))
{
if (layerEffect.BaseProperties != null) if (layerEffect.BaseProperties != null)
viewModels.Add(GetOrCreateViewModel(layerEffect.BaseProperties, null, layerEffect)); viewModels.Add(GetOrCreatePropertyViewModel(layerEffect.BaseProperties, null, layerEffect));
}
// Map the most recent collection of VMs to the current list of VMs, making as little changes to the collection as possible // Map the most recent collection of VMs to the current list of VMs, making as little changes to the collection as possible
for (int index = 0; index < viewModels.Count; index++) for (int index = 0; index < viewModels.Count; index++)
@ -119,9 +140,9 @@ public class PropertiesViewModel : ActivatableViewModelBase
PropertyGroupViewModels.RemoveAt(PropertyGroupViewModels.Count - 1); PropertyGroupViewModels.RemoveAt(PropertyGroupViewModels.Count - 1);
} }
private PropertyGroupViewModel GetOrCreateViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush? layerBrush, BaseLayerEffect? layerEffect) private PropertyGroupViewModel GetOrCreatePropertyViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush? layerBrush, BaseLayerEffect? layerEffect)
{ {
if (_cachedViewModels.TryGetValue(layerPropertyGroup, out PropertyGroupViewModel? cachedVm)) if (_cachedPropertyViewModels.TryGetValue(layerPropertyGroup, out PropertyGroupViewModel? cachedVm))
return cachedVm; return cachedVm;
PropertyGroupViewModel createdVm; PropertyGroupViewModel createdVm;
@ -132,7 +153,20 @@ public class PropertiesViewModel : ActivatableViewModelBase
else else
createdVm = _layerPropertyVmFactory.PropertyGroupViewModel(layerPropertyGroup); createdVm = _layerPropertyVmFactory.PropertyGroupViewModel(layerPropertyGroup);
_cachedViewModels[layerPropertyGroup] = createdVm; _cachedPropertyViewModels[layerPropertyGroup] = createdVm;
return createdVm; return createdVm;
} }
private void UpdateTimelineViewModel()
{
if (LayerProperty == null)
{
DataBindingViewModel = null;
}
else
{
_backgroundDataBindingViewModel ??= _dataBindingVmFactory.DataBindingViewModel();
DataBindingViewModel = _backgroundDataBindingViewModel;
}
}
} }

View File

@ -1,4 +1,5 @@
using Artemis.Core; using System.Reactive;
using Artemis.Core;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree; namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
@ -6,6 +7,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Tree;
public interface ITreePropertyViewModel : IReactiveObject public interface ITreePropertyViewModel : IReactiveObject
{ {
ILayerProperty BaseLayerProperty { get; } ILayerProperty BaseLayerProperty { get; }
bool HasDataBinding { get; } bool DataBindingEnabled { get; }
double GetDepth(); double GetDepth();
void ToggleCurrentLayerProperty();
} }

View File

@ -12,7 +12,7 @@ using Artemis.UI.Screens.ProfileEditor.Properties.Windows;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.LayerBrushes; using Artemis.UI.Shared.LayerBrushes;
using Artemis.UI.Shared.LayerEffects; using Artemis.UI.Shared.LayerEffects;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Ninject; using Ninject;
using Ninject.Parameters; using Ninject.Parameters;

View File

@ -5,8 +5,10 @@
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:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared" xmlns:sharedConverters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
xmlns:tree="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Tree"
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.TreePropertyView"> x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Tree.TreePropertyView"
x:DataType="tree:ITreePropertyViewModel">
<UserControl.Resources> <UserControl.Resources>
<converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20" /> <converters:PropertyTreeMarginConverter x:Key="PropertyTreeMarginConverter" Length="20" />
</UserControl.Resources> </UserControl.Resources>
@ -49,14 +51,17 @@
<avalonia:MaterialIcon Kind="BackupRestore" /> <avalonia:MaterialIcon Kind="BackupRestore" />
</Button> </Button>
<ToggleButton Grid.Column="4" Classes="icon-button" <ToggleButton Grid.Column="4"
Name="DataBindingToggleButton"
Classes="icon-button"
Margin="2 0 0 0" Margin="2 0 0 0"
ToolTip.Tip="Change the property's data binding" ToolTip.Tip="Change the property's data binding"
Width="24" Width="24"
Height="24" Height="24"
VerticalAlignment="Center" VerticalAlignment="Center"
IsEnabled="{Binding LayerProperty.DataBindingsSupported}" IsEnabled="{Binding LayerProperty.DataBindingsSupported}"
IsChecked="{Binding HasDataBinding, Mode=OneWay}"> IsChecked="{CompiledBinding DataBindingEnabled, Mode=OneWay}"
Click="DataBindingToggleButton_OnClick">
<avalonia:MaterialIcon Kind="VectorLink" /> <avalonia:MaterialIcon Kind="VectorLink" />
</ToggleButton> </ToggleButton>
</Grid> </Grid>

View File

@ -3,6 +3,8 @@ using System.Reactive.Linq;
using Artemis.Core; using Artemis.Core;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using ReactiveUI; using ReactiveUI;
@ -13,18 +15,23 @@ public class TreePropertyView : ReactiveUserControl<ITreePropertyViewModel>
{ {
public TreePropertyView() public TreePropertyView()
{ {
InitializeComponent();
this.WhenActivated(d => this.WhenActivated(d =>
{ {
if (ViewModel != null) Observable.FromEventPattern<LayerPropertyEventArgs>(e => ViewModel!.BaseLayerProperty.CurrentValueSet += e, e => ViewModel!.BaseLayerProperty.CurrentValueSet -= e)
Observable.FromEventPattern<LayerPropertyEventArgs>(e => ViewModel.BaseLayerProperty.CurrentValueSet += e, e => ViewModel.BaseLayerProperty.CurrentValueSet -= e) .Subscribe(_ => this.BringIntoView())
.Subscribe(_ => this.BringIntoView()) .DisposeWith(d);
.DisposeWith(d);
}); });
InitializeComponent();
} }
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private void DataBindingToggleButton_OnClick(object? sender, RoutedEventArgs e)
{
ViewModel?.ToggleCurrentLayerProperty();
this.Find<ToggleButton>("DataBindingToggleButton").IsChecked = !this.Find<ToggleButton>("DataBindingToggleButton").IsChecked;
}
} }

View File

@ -2,6 +2,7 @@
using System.Reactive; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Extensions;
using Artemis.UI.Shared; using Artemis.UI.Shared;
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;
@ -15,6 +16,8 @@ internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropert
{ {
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private TimeSpan _time; private TimeSpan _time;
private ObservableAsPropertyHelper<bool>? _isCurrentlySelected;
private ObservableAsPropertyHelper<bool>? _dataBindingEnabled;
public TreePropertyViewModel(LayerProperty<T> layerProperty, PropertyViewModel propertyViewModel, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService) public TreePropertyViewModel(LayerProperty<T> layerProperty, PropertyViewModel propertyViewModel, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService)
{ {
@ -27,17 +30,17 @@ internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropert
this.WhenActivated(d => this.WhenActivated(d =>
{ {
_profileEditorService.Time.Subscribe(t => _time = t).DisposeWith(d); _profileEditorService.Time.Subscribe(t => _time = t).DisposeWith(d);
_isCurrentlySelected = _profileEditorService.LayerProperty.Select(l => l == LayerProperty).ToProperty(this, vm => vm.IsCurrentlySelected).DisposeWith(d);
_dataBindingEnabled = LayerProperty.BaseDataBinding.GetObservable().Select(b => b.IsEnabled).ToProperty(this, vm => vm.DataBindingEnabled).DisposeWith(d);
this.WhenAnyValue(vm => vm.LayerProperty.KeyframesEnabled).Subscribe(_ => this.RaisePropertyChanged(nameof(KeyframesEnabled))).DisposeWith(d); this.WhenAnyValue(vm => vm.LayerProperty.KeyframesEnabled).Subscribe(_ => this.RaisePropertyChanged(nameof(KeyframesEnabled))).DisposeWith(d);
}); });
ResetToDefault = ReactiveCommand.Create(ExecuteResetToDefault, Observable.Return(LayerProperty.DefaultValue != null)); ResetToDefault = ReactiveCommand.Create(ExecuteResetToDefault, Observable.Return(LayerProperty.DefaultValue != null));
} }
private void ExecuteResetToDefault() public bool IsCurrentlySelected => _isCurrentlySelected?.Value ?? false;
{ public bool DataBindingEnabled => _dataBindingEnabled?.Value ?? false;
_profileEditorService.ExecuteCommand(new ResetLayerProperty<T>(LayerProperty));
}
public LayerProperty<T> LayerProperty { get; } public LayerProperty<T> LayerProperty { get; }
public PropertyViewModel PropertyViewModel { get; } public PropertyViewModel PropertyViewModel { get; }
public PropertyInputViewModel<T>? PropertyInputViewModel { get; } public PropertyInputViewModel<T>? PropertyInputViewModel { get; }
@ -57,8 +60,12 @@ internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropert
_profileEditorService.ExecuteCommand(new ToggleLayerPropertyKeyframes<T>(LayerProperty, value, _time)); _profileEditorService.ExecuteCommand(new ToggleLayerPropertyKeyframes<T>(LayerProperty, value, _time));
} }
private void ExecuteResetToDefault()
{
_profileEditorService.ExecuteCommand(new ResetLayerProperty<T>(LayerProperty));
}
public ILayerProperty BaseLayerProperty => LayerProperty; public ILayerProperty BaseLayerProperty => LayerProperty;
public bool HasDataBinding => LayerProperty.HasDataBinding;
public double GetDepth() public double GetDepth()
{ {
@ -72,5 +79,10 @@ internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropert
return depth; return depth;
} }
}
/// <inheritdoc />
public void ToggleCurrentLayerProperty()
{
_profileEditorService.ChangeCurrentLayerProperty(IsCurrentlySelected ? null : LayerProperty);
}
}

View File

@ -25,6 +25,7 @@ public class SelectionAddToolViewModel : ToolViewModel
{ {
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_rgbService = rgbService; _rgbService = rgbService;
// Not disposed when deactivated but when really disposed
_isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled); _isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled);
this.WhenActivated(d => profileEditorService.ProfileElement.Subscribe(p => _layer = p as Layer).DisposeWith(d)); this.WhenActivated(d => profileEditorService.ProfileElement.Subscribe(p => _layer = p as Layer).DisposeWith(d));

View File

@ -22,6 +22,7 @@ public class SelectionRemoveToolViewModel : ToolViewModel
public SelectionRemoveToolViewModel(IProfileEditorService profileEditorService) public SelectionRemoveToolViewModel(IProfileEditorService profileEditorService)
{ {
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
// Not disposed when deactivated but when really disposed
_isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled); _isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled);
this.WhenActivated(d => profileEditorService.ProfileElement.Subscribe(p => _layer = p as Layer).DisposeWith(d)); this.WhenActivated(d => profileEditorService.ProfileElement.Subscribe(p => _layer = p as Layer).DisposeWith(d));
} }

View File

@ -74,7 +74,6 @@ public class ProfileEditorViewModel : MainScreenViewModel
public PluginSetting<double> ConditionsHeight => _settingsService.GetSetting("ProfileEditor.ConditionsHeight", 300.0); public PluginSetting<double> ConditionsHeight => _settingsService.GetSetting("ProfileEditor.ConditionsHeight", 300.0);
public PluginSetting<double> PropertiesHeight => _settingsService.GetSetting("ProfileEditor.PropertiesHeight", 300.0); public PluginSetting<double> PropertiesHeight => _settingsService.GetSetting("ProfileEditor.PropertiesHeight", 300.0);
public void OpenUrl(string url) public void OpenUrl(string url)
{ {
Utilities.OpenUrl(url); Utilities.OpenUrl(url);

View File

@ -8,7 +8,7 @@ using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Sidebar; using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.MainWindow; using Artemis.UI.Shared.Services.MainWindow;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;

View File

@ -9,7 +9,7 @@ using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Device; using Artemis.UI.Screens.Device;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Avalonia.Threading; using Avalonia.Threading;
using DynamicData; using DynamicData;
using ReactiveUI; using ReactiveUI;

View File

@ -11,8 +11,8 @@ using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Plugins; using Artemis.UI.Screens.Plugins;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia.Threading; using Avalonia.Threading;
using ReactiveUI; using ReactiveUI;

View File

@ -7,7 +7,7 @@ using Artemis.Core;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Svg.Skia; using Avalonia.Svg.Skia;

View File

@ -7,8 +7,8 @@ using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
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.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using ReactiveUI; using ReactiveUI;

View File

@ -5,7 +5,7 @@ using System.Threading.Tasks;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Sidebar namespace Artemis.UI.Screens.Sidebar

View File

@ -12,8 +12,8 @@ using Artemis.UI.Screens.Settings;
using Artemis.UI.Screens.SurfaceEditor; using Artemis.UI.Screens.SurfaceEditor;
using Artemis.UI.Screens.Workshop; using Artemis.UI.Screens.Workshop;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Material.Icons; using Material.Icons;
using Ninject; using Ninject;

View File

@ -7,7 +7,7 @@ using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
using Avalonia.Input; using Avalonia.Input;
using ReactiveUI; using ReactiveUI;
using RGB.NET.Core; using RGB.NET.Core;

View File

@ -2,6 +2,7 @@ using System;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Avalonia.Controls.PanAndZoom;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
@ -16,7 +17,7 @@ public class NodePickerView : ReactiveUserControl<NodePickerViewModel>
InitializeComponent(); InitializeComponent();
this.WhenActivated(d => this.WhenActivated(d =>
{ {
ViewModel?.WhenAnyValue(vm => vm.IsVisible).Where(visible => !visible).Subscribe(_ => this.FindLogicalAncestorOfType<Grid>()?.ContextFlyout?.Hide()).DisposeWith(d); ViewModel?.WhenAnyValue(vm => vm.IsVisible).Where(visible => visible == false).Subscribe(_ => this.FindLogicalAncestorOfType<ZoomBorder>()?.ContextFlyout?.Hide()).DisposeWith(d);
this.Get<TextBox>("SearchBox").SelectAll(); this.Get<TextBox>("SearchBox").SelectAll();
}); });
} }

View File

@ -37,12 +37,12 @@
MaxZoomY="1" MaxZoomY="1"
EnableConstrains="True" EnableConstrains="True"
PointerReleased="ZoomBorder_OnPointerReleased"> PointerReleased="ZoomBorder_OnPointerReleased">
<Grid Name="ContainerGrid" Background="Transparent" ClipToBounds="False"> <paz:ZoomBorder.ContextFlyout>
<Grid.ContextFlyout> <Flyout FlyoutPresenterClasses="node-picker-flyout">
<Flyout FlyoutPresenterClasses="node-picker-flyout"> <ContentControl Content="{CompiledBinding NodePickerViewModel}" />
<ContentControl Content="{CompiledBinding NodePickerViewModel}" /> </Flyout>
</Flyout> </paz:ZoomBorder.ContextFlyout>
</Grid.ContextFlyout> <Grid Name="ContainerGrid" Background="Transparent" ClipToBounds="False" Focusable="True">
<Grid.Transitions> <Grid.Transitions>
<Transitions> <Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut" /> <TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut" />

View File

@ -25,10 +25,10 @@ namespace Artemis.UI.Screens.VisualScripting;
public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel> public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
{ {
private readonly Grid _grid;
private readonly ItemsControl _nodesContainer; private readonly ItemsControl _nodesContainer;
private readonly SelectionRectangle _selectionRectangle; private readonly SelectionRectangle _selectionRectangle;
private readonly ZoomBorder _zoomBorder; private readonly ZoomBorder _zoomBorder;
private readonly Grid _grid;
public NodeScriptView() public NodeScriptView()
{ {
@ -41,8 +41,8 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged; _zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
UpdateZoomBorderBackground(); UpdateZoomBorderBackground();
_grid.AddHandler(PointerReleasedEvent, CanvasOnPointerReleased, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true); _zoomBorder.AddHandler(PointerReleasedEvent, CanvasOnPointerReleased, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
_zoomBorder.AddHandler(PointerWheelChangedEvent, ZoomOnPointerWheelChanged, RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble, true);
this.WhenActivated(d => this.WhenActivated(d =>
{ {
ViewModel!.PickerPositionSubject.Subscribe(ShowPickerAt).DisposeWith(d); ViewModel!.PickerPositionSubject.Subscribe(ShowPickerAt).DisposeWith(d);
@ -56,6 +56,13 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
}); });
} }
private void ZoomOnPointerWheelChanged(object? sender, PointerWheelEventArgs e)
{
// If scroll events aren't handled here the ZoomBorder does some random panning when at the zoom limit
if (e.Delta.Y > 0 && _zoomBorder.ZoomX >= 1)
e.Handled = true;
}
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
AutoFitIfPreview(); AutoFitIfPreview();
@ -67,7 +74,7 @@ public class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
if (ViewModel == null) if (ViewModel == null)
return; return;
ViewModel.NodePickerViewModel.Position = point; ViewModel.NodePickerViewModel.Position = point;
_grid?.ContextFlyout?.ShowAt(_grid, true); _zoomBorder?.ContextFlyout?.ShowAt(_zoomBorder, true);
} }
private void AutoFitIfPreview() private void AutoFitIfPreview()

View File

@ -6,8 +6,8 @@ using System.Reactive.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.VisualScripting; using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Artemis.VisualScripting.Nodes; using Artemis.VisualScripting.Nodes;
using Artemis.VisualScripting.Nodes.Operators; using Artemis.VisualScripting.Nodes.Operators;
using Avalonia.Input; using Avalonia.Input;

View File

@ -1,6 +1,6 @@
using Artemis.UI.Screens.Debugger; using Artemis.UI.Screens.Debugger;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services;
namespace Artemis.UI.Services namespace Artemis.UI.Services
{ {

View File

@ -6,7 +6,9 @@ using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.DefaultTypes.PropertyInput; using Artemis.UI.DefaultTypes.PropertyInput;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.DefaultTypes.DataModel.Display;
using Artemis.UI.Shared.Providers; using Artemis.UI.Shared.Providers;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.PropertyInput; using Artemis.UI.Shared.Services.PropertyInput;
using Artemis.VisualScripting.Nodes; using Artemis.VisualScripting.Nodes;
@ -24,14 +26,22 @@ public class RegistrationService : IRegistrationService
private readonly IInputService _inputService; private readonly IInputService _inputService;
private readonly IPropertyInputService _propertyInputService; private readonly IPropertyInputService _propertyInputService;
private readonly INodeService _nodeService; private readonly INodeService _nodeService;
private readonly IDataModelUIService _dataModelUIService;
private bool _registeredBuiltInPropertyEditors; private bool _registeredBuiltInPropertyEditors;
public RegistrationService(IKernel kernel, IInputService inputService, IPropertyInputService propertyInputService, IProfileEditorService profileEditorService, INodeService nodeService, IEnumerable<IToolViewModel> toolViewModels) public RegistrationService(IKernel kernel,
IInputService inputService,
IPropertyInputService propertyInputService,
IProfileEditorService profileEditorService,
INodeService nodeService,
IDataModelUIService dataModelUIService,
IEnumerable<IToolViewModel> toolViewModels)
{ {
_kernel = kernel; _kernel = kernel;
_inputService = inputService; _inputService = inputService;
_propertyInputService = propertyInputService; _propertyInputService = propertyInputService;
_nodeService = nodeService; _nodeService = nodeService;
_dataModelUIService = dataModelUIService;
profileEditorService.Tools.AddRange(toolViewModels); profileEditorService.Tools.AddRange(toolViewModels);
CreateCursorResources(); CreateCursorResources();
@ -50,6 +60,7 @@ public class RegistrationService : IRegistrationService
public void RegisterBuiltInDataModelDisplays() public void RegisterBuiltInDataModelDisplays()
{ {
_dataModelUIService.RegisterDataModelDisplay<SKColorDataModelDisplayViewModel>(Constants.CorePlugin);
} }
public void RegisterBuiltInDataModelInputs() public void RegisterBuiltInDataModelInputs()

View File

@ -6,7 +6,7 @@ using Artemis.VisualScripting.Nodes.DataModel.Screens;
namespace Artemis.VisualScripting.Nodes.DataModel; namespace Artemis.VisualScripting.Nodes.DataModel;
[Node("Data Model-Event", "Responds to a data model event trigger", "Data Model", OutputType = typeof(bool))] [Node("Data Model-Event", "Responds to a data model event trigger", "Data Model", OutputType = typeof(object))]
public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCustomViewModel>, IDisposable public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCustomViewModel>, IDisposable
{ {
private int _currentIndex; private int _currentIndex;

View File

@ -1,12 +1,11 @@
using System.ComponentModel; using Artemis.Core;
using Artemis.Core;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Artemis.VisualScripting.Nodes.DataModel.Screens; using Artemis.VisualScripting.Nodes.DataModel.Screens;
using Avalonia.Threading; using Avalonia.Threading;
namespace Artemis.VisualScripting.Nodes.DataModel; namespace Artemis.VisualScripting.Nodes.DataModel;
[Node("Data Model-Value", "Outputs a selectable data model value.", "Data Model")] [Node("Data Model-Value", "Outputs a selectable data model value.", "Data Model", OutputType = typeof(object))]
public class DataModelNode : Node<DataModelPathEntity, DataModelNodeCustomViewModel>, IDisposable public class DataModelNode : Node<DataModelPathEntity, DataModelNodeCustomViewModel>, IDisposable
{ {
private DataModelPath? _dataModelPath; private DataModelPath? _dataModelPath;
@ -30,17 +29,6 @@ public class DataModelNode : Node<DataModelPathEntity, DataModelNodeCustomViewMo
UpdateDataModelPath(); UpdateDataModelPath();
} }
private void UpdateDataModelPath()
{
DataModelPath? old = _dataModelPath;
old?.Dispose();
_dataModelPath = Storage != null ? new DataModelPath(Storage) : null;
if (_dataModelPath != null)
_dataModelPath.PathValidated += DataModelPathOnPathValidated;
UpdateOutputPin();
}
public override void Evaluate() public override void Evaluate()
{ {
if (_dataModelPath == null || !_dataModelPath.IsValid) if (_dataModelPath == null || !_dataModelPath.IsValid)
@ -69,6 +57,17 @@ public class DataModelNode : Node<DataModelPathEntity, DataModelNodeCustomViewMo
Output.ChangeType(type); Output.ChangeType(type);
} }
private void UpdateDataModelPath()
{
DataModelPath? old = _dataModelPath;
old?.Dispose();
_dataModelPath = Storage != null ? new DataModelPath(Storage) : null;
if (_dataModelPath != null)
_dataModelPath.PathValidated += DataModelPathOnPathValidated;
UpdateOutputPin();
}
private void DataModelPathOnPathValidated(object? sender, EventArgs e) private void DataModelPathOnPathValidated(object? sender, EventArgs e)
{ {
Dispatcher.UIThread.InvokeAsync(UpdateOutputPin); Dispatcher.UIThread.InvokeAsync(UpdateOutputPin);

View File

@ -11,8 +11,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI", "Artemis.UI\Ar
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Shared", "Artemis.UI.Shared\Artemis.UI.Shared.csproj", "{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Shared", "Artemis.UI.Shared\Artemis.UI.Shared.csproj", "{05A5AB0F-A303-4404-9623-4DB1C9AA1DA0}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Avalonia", "Avalonia", "{960CAAC5-AA73-49F5-BF2F-DF2C789DF042}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Windows", "Artemis.UI.Windows\Artemis.UI.Windows.csproj", "{DE45A288-9320-461F-BE2A-26DFE3817216}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Windows", "Artemis.UI.Windows\Artemis.UI.Windows.csproj", "{DE45A288-9320-461F-BE2A-26DFE3817216}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Linux", "Artemis.UI.Linux\Artemis.UI.Linux.csproj", "{9012C8E2-3BEC-42F5-8270-7352A5922B04}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Artemis.UI.Linux", "Artemis.UI.Linux\Artemis.UI.Linux.csproj", "{9012C8E2-3BEC-42F5-8270-7352A5922B04}"