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

Node editor - Added VM structure

Debugger - Run updates on UI thread
This commit is contained in:
Robert 2022-03-11 17:22:29 +01:00
parent 4c274a1749
commit c99224ab2d
32 changed files with 825 additions and 232 deletions

View File

@ -6,7 +6,7 @@ namespace Artemis.UI.Shared
/// <summary> /// <summary>
/// Represents a view model for a plugin configuration window /// Represents a view model for a plugin configuration window
/// </summary> /// </summary>
public abstract class PluginConfigurationViewModel : ViewModelBase, IPluginConfigurationViewModel public abstract class PluginConfigurationViewModel : ViewModelValidationBase, IPluginConfigurationViewModel
{ {
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="PluginConfigurationViewModel" /> class /// Creates a new instance of the <see cref="PluginConfigurationViewModel" /> class

View File

@ -60,6 +60,33 @@ public abstract class ViewModelValidationBase : ReactiveValidationObject
get => _displayName; get => _displayName;
set => this.RaiseAndSetIfChanged(ref _displayName, value); set => this.RaiseAndSetIfChanged(ref _displayName, value);
} }
/// <summary>
/// RaiseAndSetIfChanged fully implements a Setter for a read-write property on a ReactiveObject, using
/// CallerMemberName to raise the notification and the ref to the backing field to set the property.
/// </summary>
/// <typeparam name="TRet">The type of the return value.</typeparam>
/// <param name="backingField">A Reference to the backing field for this property.</param>
/// <param name="newValue">The new value.</param>
/// <param name="propertyName">
/// The name of the property, usually automatically provided through the CallerMemberName
/// attribute.
/// </param>
/// <returns>The newly set value, normally discarded.</returns>
[NotifyPropertyChangedInvocator]
public TRet RaiseAndSetIfChanged<TRet>(ref TRet backingField, TRet newValue, [CallerMemberName] string? propertyName = null)
{
if (propertyName is null)
throw new ArgumentNullException(nameof(propertyName));
if (EqualityComparer<TRet>.Default.Equals(backingField, newValue))
return newValue;
this.RaisePropertyChanging(propertyName);
backingField = newValue;
this.RaisePropertyChanged(propertyName);
return newValue;
}
} }
/// <summary> /// <summary>

View File

@ -90,6 +90,11 @@
"Splat": "14.1.45" "Splat": "14.1.45"
} }
}, },
"ArtemisRGB.Plugins.BuildTask": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "B8A5NkUEzTUgc5M/QACfyjIF5M23EUzSx8A8l/owJtB0bgmij6y/MQW1i/PcS3EDFQRothBOUuC3BCPY5UoRRQ=="
},
"Avalonia.Angle.Windows.Natives": { "Avalonia.Angle.Windows.Natives": {
"type": "Transitive", "type": "Transitive",
"resolved": "2.1.0.2020091801", "resolved": "2.1.0.2020091801",
@ -560,6 +565,11 @@
"Ninject": "3.3.3" "Ninject": "3.3.3"
} }
}, },
"OpenRGB.NET": {
"type": "Transitive",
"resolved": "1.7.0",
"contentHash": "12pMEUaeoG8mN707QRO9hdT529+UnqUpwMW1H/gDTMsJrerhJve6Yt5Dnheu1isQB4PWP1wu3IDVbHCchznkiw=="
},
"ReactiveUI.Validation": { "ReactiveUI.Validation": {
"type": "Transitive", "type": "Transitive",
"resolved": "2.2.1", "resolved": "2.2.1",
@ -1762,6 +1772,21 @@
"System.ValueTuple": "4.5.0" "System.ValueTuple": "4.5.0"
} }
}, },
"artemis.plugins.devices.openrgb": {
"type": "Project",
"dependencies": {
"ArtemisRGB.Plugins.BuildTask": "1.1.0",
"Avalonia": "0.10.11",
"Avalonia.Controls.DataGrid": "0.10.11",
"Avalonia.ReactiveUI": "0.10.11",
"Material.Icons.Avalonia": "1.0.2",
"OpenRGB.NET": "1.7.0",
"RGB.NET.Core": "1.0.0-prerelease7",
"ReactiveUI": "16.3.10",
"Serilog": "2.10.0",
"SkiaSharp": "2.88.0-preview.178"
}
},
"artemis.storage": { "artemis.storage": {
"type": "Project", "type": "Project",
"dependencies": { "dependencies": {

View File

@ -14,6 +14,8 @@ using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
using Artemis.UI.Screens.Settings; using Artemis.UI.Screens.Settings;
using Artemis.UI.Screens.Sidebar; using Artemis.UI.Screens.Sidebar;
using Artemis.UI.Screens.SurfaceEditor; using Artemis.UI.Screens.SurfaceEditor;
using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Screens.VisualScripting.Pins;
using Artemis.UI.Services; using Artemis.UI.Services;
using DynamicData.Binding; using DynamicData.Binding;
using ReactiveUI; using ReactiveUI;
@ -87,4 +89,15 @@ namespace Artemis.UI.Ninject.Factories
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel); ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel); ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, PropertyViewModel propertyViewModel);
} }
public interface INodeVmFactory
{
NodeScriptViewModel NodeScriptViewModel(NodeScript nodeScript);
NodePickerViewModel NodePickerViewModel(NodeScript nodeScript);
NodeViewModel NodeViewModel(INode node);
InputPinCollectionViewModel<T> InputPinCollectionViewModel<T>(InputPinCollection<T> inputPinCollection);
InputPinViewModel<T> InputPinViewModel<T>(InputPin<T> inputPin);
OutputPinCollectionViewModel<T> OutputPinCollectionViewModel<T>(OutputPinCollection<T> outputPinCollection);
OutputPinViewModel<T> OutputPinViewModel<T>(OutputPin<T> outputPin);
}
} }

View File

@ -4,144 +4,145 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Timers;
using Artemis.Core; 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.DataModelVisualization.Shared; using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.Interfaces;
using Avalonia.Threading;
using DynamicData; using DynamicData;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Debugger.DataModel namespace Artemis.UI.Screens.Debugger.DataModel;
public class DataModelDebugViewModel : ActivatableViewModelBase, IRoutableViewModel
{ {
public class DataModelDebugViewModel : ActivatableViewModelBase, IRoutableViewModel private readonly IDataModelUIService _dataModelUIService;
private readonly IPluginManagementService _pluginManagementService;
private readonly DispatcherTimer _updateTimer;
private bool _isModuleFilterEnabled;
private DataModelPropertiesViewModel? _mainDataModel;
private string? _propertySearch;
private Module? _selectedModule;
private bool _slowUpdates;
public DataModelDebugViewModel(IScreen hostScreen, IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService)
{ {
private readonly IDataModelUIService _dataModelUIService; _dataModelUIService = dataModelUIService;
private readonly IPluginManagementService _pluginManagementService; _pluginManagementService = pluginManagementService;
private readonly Timer _updateTimer; _updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(25), DispatcherPriority.Normal, (_, _) => Update());
private bool _isModuleFilterEnabled; HostScreen = hostScreen;
private DataModelPropertiesViewModel? _mainDataModel; Modules = new ObservableCollection<Module>();
private string? _propertySearch;
private Module? _selectedModule; this.WhenActivated(disposables =>
private bool _slowUpdates;
public DataModelDebugViewModel(IScreen hostScreen, IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService)
{ {
_dataModelUIService = dataModelUIService; Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x)
_pluginManagementService = pluginManagementService; .Subscribe(d => PluginFeatureToggled(d.EventArgs.PluginFeature))
_updateTimer = new Timer(25); .DisposeWith(disposables);
_updateTimer.Elapsed += UpdateTimerOnElapsed; Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x)
.Subscribe(d => PluginFeatureToggled(d.EventArgs.PluginFeature))
.DisposeWith(disposables);
HostScreen = hostScreen; GetDataModel();
Modules = new ObservableCollection<Module>(); _updateTimer.Start();
Disposable.Create(() => _updateTimer.Stop()).DisposeWith(disposables);
});
}
this.WhenActivated(disposables => public DataModelPropertiesViewModel? MainDataModel
{ {
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x) get => _mainDataModel;
.Subscribe(d => PluginFeatureToggled(d.EventArgs.PluginFeature)) set => RaiseAndSetIfChanged(ref _mainDataModel, value);
.DisposeWith(disposables); }
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x)
.Subscribe(d => PluginFeatureToggled(d.EventArgs.PluginFeature))
.DisposeWith(disposables);
GetDataModel(); public string? PropertySearch
_updateTimer.Start(); {
Disposable.Create(() => _updateTimer.Stop()).DisposeWith(disposables); get => _propertySearch;
}); set => RaiseAndSetIfChanged(ref _propertySearch, value);
} }
public string UrlPathSegment => "data-model"; public bool SlowUpdates
public IScreen HostScreen { get; } {
get => _slowUpdates;
public DataModelPropertiesViewModel? MainDataModel set
{ {
get => _mainDataModel; RaiseAndSetIfChanged(ref _slowUpdates, value);
set => RaiseAndSetIfChanged(ref _mainDataModel, value); _updateTimer.Interval = TimeSpan.FromMilliseconds(_slowUpdates ? 500 : 25);
}
public string? PropertySearch
{
get => _propertySearch;
set => RaiseAndSetIfChanged(ref _propertySearch, value);
}
public bool SlowUpdates
{
get => _slowUpdates;
set
{
RaiseAndSetIfChanged(ref _slowUpdates, value);
_updateTimer.Interval = _slowUpdates ? 500 : 25;
}
}
public ObservableCollection<Module> Modules { get; }
public Module? SelectedModule
{
get => _selectedModule;
set
{
RaiseAndSetIfChanged(ref _selectedModule, value);
GetDataModel();
}
}
public bool IsModuleFilterEnabled
{
get => _isModuleFilterEnabled;
set
{
RaiseAndSetIfChanged(ref _isModuleFilterEnabled, value);
if (!IsModuleFilterEnabled)
SelectedModule = null;
else
GetDataModel();
}
}
private void UpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
{
if (MainDataModel == null)
return;
lock (MainDataModel)
{
MainDataModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(true));
}
}
private void PluginFeatureToggled(PluginFeature pluginFeature)
{
if (pluginFeature is Module)
PopulateModules();
}
private void GetDataModel()
{
MainDataModel = SelectedModule != null
? _dataModelUIService.GetPluginDataModelVisualization(new List<Module>() { SelectedModule }, false)
: _dataModelUIService.GetMainDataModelVisualization();
}
private void PopulateModules()
{
Modules.Clear();
Modules.AddRange(_pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled).OrderBy(m => m.Info.Name));
if (MainDataModel == null)
return;
if (SelectedModule == null)
{
if (MainDataModel != null)
_dataModelUIService.UpdateModules(MainDataModel);
}
else if (!SelectedModule.IsEnabled)
SelectedModule = null;
} }
} }
public ObservableCollection<Module> Modules { get; }
public Module? SelectedModule
{
get => _selectedModule;
set
{
RaiseAndSetIfChanged(ref _selectedModule, value);
GetDataModel();
}
}
public bool IsModuleFilterEnabled
{
get => _isModuleFilterEnabled;
set
{
RaiseAndSetIfChanged(ref _isModuleFilterEnabled, value);
if (!IsModuleFilterEnabled)
SelectedModule = null;
else
GetDataModel();
}
}
private void Update()
{
if (MainDataModel == null)
return;
lock (MainDataModel)
{
MainDataModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(true));
}
}
private void PluginFeatureToggled(PluginFeature pluginFeature)
{
if (pluginFeature is Module)
PopulateModules();
}
private void GetDataModel()
{
MainDataModel = SelectedModule != null
? _dataModelUIService.GetPluginDataModelVisualization(new List<Module> {SelectedModule}, false)
: _dataModelUIService.GetMainDataModelVisualization();
}
private void PopulateModules()
{
Modules.Clear();
Modules.AddRange(_pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled).OrderBy(m => m.Info.Name));
if (MainDataModel == null)
return;
if (SelectedModule == null)
{
if (MainDataModel != null)
_dataModelUIService.UpdateModules(MainDataModel);
}
else if (!SelectedModule.IsEnabled)
{
SelectedModule = null;
}
}
public string UrlPathSegment => "data-model";
public IScreen HostScreen { get; }
} }

View File

@ -3,129 +3,128 @@ using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Timers;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Avalonia.Threading;
using ReactiveUI; using ReactiveUI;
using SkiaSharp; using SkiaSharp;
namespace Artemis.UI.Screens.Debugger.Performance namespace Artemis.UI.Screens.Debugger.Performance;
public class PerformanceDebugViewModel : ActivatableViewModelBase, IRoutableViewModel
{ {
public class PerformanceDebugViewModel : ActivatableViewModelBase, IRoutableViewModel private readonly ICoreService _coreService;
private readonly IPluginManagementService _pluginManagementService;
private readonly DispatcherTimer _updateTimer;
private double _currentFps;
private string? _renderer;
private int _renderHeight;
private int _renderWidth;
public PerformanceDebugViewModel(IScreen hostScreen, ICoreService coreService, IPluginManagementService pluginManagementService)
{ {
private readonly ICoreService _coreService; HostScreen = hostScreen;
private readonly IPluginManagementService _pluginManagementService; _coreService = coreService;
private double _currentFps; _pluginManagementService = pluginManagementService;
private string? _renderer; _updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(500), DispatcherPriority.Normal, (_, _) => Update());
private int _renderHeight;
private int _renderWidth;
public PerformanceDebugViewModel(IScreen hostScreen, ICoreService coreService, IPluginManagementService pluginManagementService) this.WhenActivated(disposables =>
{ {
HostScreen = hostScreen; Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x)
_coreService = coreService; .Subscribe(_ => Repopulate())
_pluginManagementService = pluginManagementService; .DisposeWith(disposables);
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x)
.Subscribe(_ => Repopulate())
.DisposeWith(disposables);
Observable.FromEventPattern<PluginEventArgs>(x => pluginManagementService.PluginEnabled += x, x => pluginManagementService.PluginEnabled -= x)
.Subscribe(_ => Repopulate())
.DisposeWith(disposables);
Observable.FromEventPattern<PluginEventArgs>(x => pluginManagementService.PluginDisabled += x, x => pluginManagementService.PluginDisabled -= x)
.Subscribe(_ => Repopulate())
.DisposeWith(disposables);
Timer updateTimer = new(500); HandleActivation();
updateTimer.Elapsed += UpdateTimerOnElapsed;
this.WhenActivated(disposables =>
{
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x)
.Subscribe(_ => Repopulate())
.DisposeWith(disposables);
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x)
.Subscribe(_ => Repopulate())
.DisposeWith(disposables);
Observable.FromEventPattern<PluginEventArgs>(x => pluginManagementService.PluginEnabled += x, x => pluginManagementService.PluginEnabled -= x)
.Subscribe(_ => Repopulate())
.DisposeWith(disposables);
Observable.FromEventPattern<PluginEventArgs>(x => pluginManagementService.PluginDisabled += x, x => pluginManagementService.PluginDisabled -= x)
.Subscribe(_ => Repopulate())
.DisposeWith(disposables);
HandleActivation();
PopulateItems();
updateTimer.Start();
Disposable.Create(() =>
{
updateTimer.Stop();
Items.Clear();
HandleDeactivation();
}).DisposeWith(disposables);
});
}
public string UrlPathSegment => "performance";
public IScreen HostScreen { get; }
public ObservableCollection<PerformanceDebugPluginViewModel> Items { get; } = new();
public double CurrentFps
{
get => _currentFps;
set => RaiseAndSetIfChanged(ref _currentFps, value);
}
public int RenderWidth
{
get => _renderWidth;
set => RaiseAndSetIfChanged(ref _renderWidth, value);
}
public int RenderHeight
{
get => _renderHeight;
set => RaiseAndSetIfChanged(ref _renderHeight, value);
}
public string? Renderer
{
get => _renderer;
set => RaiseAndSetIfChanged(ref _renderer, value);
}
private void HandleActivation()
{
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
_coreService.FrameRendered += CoreServiceOnFrameRendered;
}
private void HandleDeactivation()
{
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
}
private void PopulateItems()
{
foreach (PerformanceDebugPluginViewModel performanceDebugPluginViewModel in _pluginManagementService.GetAllPlugins()
.Where(p => p.IsEnabled && p.Profilers.Any(pr => pr.Measurements.Any()))
.OrderBy(p => p.Info.Name)
.Select(p => new PerformanceDebugPluginViewModel(p)))
Items.Add(performanceDebugPluginViewModel);
}
private void Repopulate()
{
Items.Clear();
PopulateItems(); PopulateItems();
} _updateTimer.Start();
private void UpdateTimerOnElapsed(object? sender, ElapsedEventArgs e) Disposable.Create(() =>
{ {
foreach (PerformanceDebugPluginViewModel viewModel in Items) _updateTimer.Stop();
viewModel.Update(); Items.Clear();
} HandleDeactivation();
}).DisposeWith(disposables);
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e) });
{
CurrentFps = _coreService.FrameRate;
SKImageInfo bitmapInfo = e.Texture.ImageInfo;
RenderHeight = bitmapInfo.Height;
RenderWidth = bitmapInfo.Width;
}
} }
public ObservableCollection<PerformanceDebugPluginViewModel> Items { get; } = new();
public double CurrentFps
{
get => _currentFps;
set => RaiseAndSetIfChanged(ref _currentFps, value);
}
public int RenderWidth
{
get => _renderWidth;
set => RaiseAndSetIfChanged(ref _renderWidth, value);
}
public int RenderHeight
{
get => _renderHeight;
set => RaiseAndSetIfChanged(ref _renderHeight, value);
}
public string? Renderer
{
get => _renderer;
set => RaiseAndSetIfChanged(ref _renderer, value);
}
private void HandleActivation()
{
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
_coreService.FrameRendered += CoreServiceOnFrameRendered;
}
private void HandleDeactivation()
{
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
}
private void PopulateItems()
{
foreach (PerformanceDebugPluginViewModel performanceDebugPluginViewModel in _pluginManagementService.GetAllPlugins()
.Where(p => p.IsEnabled && p.Profilers.Any(pr => pr.Measurements.Any()))
.OrderBy(p => p.Info.Name)
.Select(p => new PerformanceDebugPluginViewModel(p)))
Items.Add(performanceDebugPluginViewModel);
}
private void Repopulate()
{
Items.Clear();
PopulateItems();
}
private void Update()
{
foreach (PerformanceDebugPluginViewModel viewModel in Items)
viewModel.Update();
}
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
{
CurrentFps = _coreService.FrameRate;
SKImageInfo bitmapInfo = e.Texture.ImageInfo;
RenderHeight = bitmapInfo.Height;
RenderWidth = bitmapInfo.Width;
}
public string UrlPathSegment => "performance";
public IScreen HostScreen { get; }
} }

View File

@ -0,0 +1,8 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.CableView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,20 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting
{
public partial class CableView : ReactiveUserControl<CableViewModel>
{
public CableView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,28 @@
using Artemis.UI.Screens.VisualScripting.Pins;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.VisualScripting;
public class CableViewModel : ActivatableViewModelBase
{
private PinViewModel _from;
private PinViewModel _to;
public CableViewModel(PinViewModel from, PinViewModel to)
{
_from = from;
_to = to;
}
public PinViewModel From
{
get => _from;
set => RaiseAndSetIfChanged(ref _from, value);
}
public PinViewModel To
{
get => _to;
set => RaiseAndSetIfChanged(ref _to, value);
}
}

View File

@ -0,0 +1,45 @@
<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:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.NodePickerView"
x:DataType="visualScripting:NodePickerViewModel">
<UserControl.Styles>
<Style Selector="Border.picker-container">
</Style>
<Style Selector="Border.picker-container-hidden">
<Style.Animations>
<Animation Duration="0:0:1">
<KeyFrame Cue="0%">
<Setter Property="Opacity" Value="1.0" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Opacity" Value="0.0" />
<Setter Property="IsVisible" Value="False" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.picker-container-visible">
<Style.Animations>
<Animation Duration="0:0:1">
<KeyFrame Cue="0%">
<Setter Property="Opacity" Value="0.0" />
<Setter Property="IsVisible" Value="True" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Opacity" Value="1.0" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</UserControl.Styles>
<Border Classes="grid picker-container"
Classes.picker-container-hidden="{CompiledBinding !IsVisible}"
Classes.picker-container-visible="{CompiledBinding !IsVisible}">
<TextBlock>Test</TextBlock>
</Border>
</UserControl>

View File

@ -0,0 +1,20 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting
{
public partial class NodePickerView : ReactiveUserControl<NodePickerViewModel>
{
public NodePickerView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Avalonia;
namespace Artemis.UI.Screens.VisualScripting;
public class NodePickerViewModel : ActivatableViewModelBase
{
private readonly INodeService _nodeService;
private bool _isVisible;
private Point _position;
public bool IsVisible
{
get => _isVisible;
set => RaiseAndSetIfChanged(ref _isVisible, value);
}
public Point Position
{
get => _position;
set => RaiseAndSetIfChanged(ref _position, value);
}
public NodePickerViewModel(INodeService nodeService)
{
_nodeService = nodeService;
}
public void Show(Point position)
{
IsVisible = true;
Position = position;
}
public void Hide()
{
IsVisible = false;
}
}

View File

@ -0,0 +1,19 @@
<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:visualScripting="clr-namespace:Artemis.UI.Screens.VisualScripting"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.NodeScriptView"
x:DataType="visualScripting:NodeScriptViewModel">
<Canvas PointerReleased="InputElement_OnPointerReleased">
<!-- Cables -->
<!-- Nodes -->
<!-- Flyout -->
<ContentControl Content="{CompiledBinding NodePickerViewModel}"
Canvas.Left="{CompiledBinding NodePickerViewModel.Position.X}"
Canvas.Top="{CompiledBinding NodePickerViewModel.Position.Y}"/>
</Canvas>
</UserControl>

View File

@ -0,0 +1,29 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting
{
public partial class NodeScriptView : ReactiveUserControl<NodeScriptViewModel>
{
public NodeScriptView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (e.InitialPressMouseButton == MouseButton.Right)
ViewModel?.ShowNodePicker(e.GetCurrentPoint(this).Position);
if (e.InitialPressMouseButton == MouseButton.Left)
ViewModel?.HideNodePicker();
}
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using Artemis.Core;
using Artemis.Core.Events;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared;
using Avalonia;
using Avalonia.Controls.Mixins;
using ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting;
public class NodeScriptViewModel : ActivatableViewModelBase
{
private readonly INodeService _nodeService;
private readonly INodeVmFactory _nodeVmFactory;
public NodeScriptViewModel(NodeScript nodeScript, INodeVmFactory nodeVmFactory, INodeService nodeService)
{
_nodeVmFactory = nodeVmFactory;
_nodeService = nodeService;
NodeScript = nodeScript;
NodePickerViewModel = _nodeVmFactory.NodePickerViewModel(nodeScript);
this.WhenActivated(d =>
{
Observable.FromEventPattern<SingleValueEventArgs<INode>>(x => NodeScript.NodeAdded += x, x => NodeScript.NodeAdded -= x)
.Subscribe(e => HandleNodeAdded(e.EventArgs))
.DisposeWith(d);
Observable.FromEventPattern<SingleValueEventArgs<INode>>(x => NodeScript.NodeRemoved += x, x => NodeScript.NodeRemoved -= x)
.Subscribe(e => HandleNodeRemoved(e.EventArgs))
.DisposeWith(d);
});
NodeViewModels = new ObservableCollection<NodeViewModel>();
foreach (INode nodeScriptNode in NodeScript.Nodes)
NodeViewModels.Add(_nodeVmFactory.NodeViewModel(nodeScriptNode));
}
public NodeScript NodeScript { get; }
public ObservableCollection<NodeViewModel> NodeViewModels { get; }
public NodePickerViewModel NodePickerViewModel { get; }
private void HandleNodeAdded(SingleValueEventArgs<INode> eventArgs)
{
NodeViewModels.Add(_nodeVmFactory.NodeViewModel(eventArgs.Value));
}
private void HandleNodeRemoved(SingleValueEventArgs<INode> eventArgs)
{
NodeViewModel? toRemove = NodeViewModels.FirstOrDefault(vm => vm.Node == eventArgs.Value);
if (toRemove != null)
NodeViewModels.Remove(toRemove);
}
public void ShowNodePicker(Point position)
{
NodePickerViewModel.Show(position);
}
public void HideNodePicker()
{
NodePickerViewModel.Hide();
}
}

View File

@ -0,0 +1,8 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.NodeView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,20 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting
{
public partial class NodeView : ReactiveUserControl<NodeViewModel>
{
public NodeView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,23 @@
using Artemis.Core;
using Artemis.UI.Shared;
using Avalonia;
namespace Artemis.UI.Screens.VisualScripting;
public class NodeViewModel : ActivatableViewModelBase
{
private Point _position;
public NodeViewModel(INode node)
{
Node = node;
}
public INode Node { get; }
public Point Position
{
get => _position;
set => RaiseAndSetIfChanged(ref _position, value);
}
}

View File

@ -0,0 +1,8 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.Pins.InputPinCollectionView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,20 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting.Pins
{
public partial class InputPinCollectionView : ReactiveUserControl<PinCollectionViewModel>
{
public InputPinCollectionView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,13 @@
using Artemis.Core;
namespace Artemis.UI.Screens.VisualScripting.Pins;
public class InputPinCollectionViewModel<T> : PinCollectionViewModel
{
public InputPinCollection<T> InputPinCollection { get; }
public InputPinCollectionViewModel(InputPinCollection<T> inputPinCollection) : base(inputPinCollection)
{
InputPinCollection = inputPinCollection;
}
}

View File

@ -0,0 +1,8 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.Pins.InputPinView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,20 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting.Pins
{
public partial class InputPinView : ReactiveUserControl<PinViewModel>
{
public InputPinView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,13 @@
using Artemis.Core;
namespace Artemis.UI.Screens.VisualScripting.Pins;
public class InputPinViewModel<T> : PinViewModel
{
public InputPin<T> InputPin { get; }
public InputPinViewModel(InputPin<T> inputPin) : base(inputPin)
{
InputPin = inputPin;
}
}

View File

@ -0,0 +1,8 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.Pins.OutputPinCollectionView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,20 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting.Pins
{
public partial class OutputPinCollectionView : ReactiveUserControl<PinCollectionViewModel>
{
public OutputPinCollectionView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,13 @@
using Artemis.Core;
namespace Artemis.UI.Screens.VisualScripting.Pins;
public class OutputPinCollectionViewModel<T> : PinCollectionViewModel
{
public OutputPinCollection<T> OutputPinCollection { get; }
public OutputPinCollectionViewModel(OutputPinCollection<T> outputPinCollection) : base(outputPinCollection)
{
OutputPinCollection = outputPinCollection;
}
}

View File

@ -0,0 +1,8 @@
<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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.VisualScripting.Pins.OutputPinView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,20 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.VisualScripting.Pins
{
public partial class OutputPinView : ReactiveUserControl<PinViewModel>
{
public OutputPinView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View File

@ -0,0 +1,13 @@
using Artemis.Core;
namespace Artemis.UI.Screens.VisualScripting.Pins;
public class OutputPinViewModel<T> : PinViewModel
{
public OutputPin<T> OutputPin { get; }
public OutputPinViewModel(OutputPin<T> outputPin) : base(outputPin)
{
OutputPin = outputPin;
}
}

View File

@ -0,0 +1,14 @@
using Artemis.Core;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.VisualScripting.Pins;
public abstract class PinCollectionViewModel : ActivatableViewModelBase
{
public PinCollection PinCollection { get; }
protected PinCollectionViewModel(PinCollection pinCollection)
{
PinCollection = pinCollection;
}
}

View File

@ -0,0 +1,14 @@
using Artemis.Core;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.VisualScripting.Pins;
public abstract class PinViewModel : ActivatableViewModelBase
{
protected PinViewModel(IPin pin)
{
Pin = pin;
}
public IPin Pin { get; }
}