diff --git a/src/Avalonia/Artemis.UI.Shared/Plugins/PluginConfigurationViewModel.cs b/src/Avalonia/Artemis.UI.Shared/Plugins/PluginConfigurationViewModel.cs index dbed50afa..4fcfaa4fd 100644 --- a/src/Avalonia/Artemis.UI.Shared/Plugins/PluginConfigurationViewModel.cs +++ b/src/Avalonia/Artemis.UI.Shared/Plugins/PluginConfigurationViewModel.cs @@ -6,7 +6,7 @@ namespace Artemis.UI.Shared /// /// Represents a view model for a plugin configuration window /// - public abstract class PluginConfigurationViewModel : ViewModelBase, IPluginConfigurationViewModel + public abstract class PluginConfigurationViewModel : ViewModelValidationBase, IPluginConfigurationViewModel { /// /// Creates a new instance of the class diff --git a/src/Avalonia/Artemis.UI.Shared/ViewModelBase.cs b/src/Avalonia/Artemis.UI.Shared/ViewModelBase.cs index e89b30258..d28200871 100644 --- a/src/Avalonia/Artemis.UI.Shared/ViewModelBase.cs +++ b/src/Avalonia/Artemis.UI.Shared/ViewModelBase.cs @@ -60,6 +60,33 @@ public abstract class ViewModelValidationBase : ReactiveValidationObject get => _displayName; set => this.RaiseAndSetIfChanged(ref _displayName, value); } + + /// + /// 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. + /// + /// The type of the return value. + /// A Reference to the backing field for this property. + /// The new value. + /// + /// The name of the property, usually automatically provided through the CallerMemberName + /// attribute. + /// + /// The newly set value, normally discarded. + [NotifyPropertyChangedInvocator] + public TRet RaiseAndSetIfChanged(ref TRet backingField, TRet newValue, [CallerMemberName] string? propertyName = null) + { + if (propertyName is null) + throw new ArgumentNullException(nameof(propertyName)); + + if (EqualityComparer.Default.Equals(backingField, newValue)) + return newValue; + + this.RaisePropertyChanging(propertyName); + backingField = newValue; + this.RaisePropertyChanged(propertyName); + return newValue; + } } /// diff --git a/src/Avalonia/Artemis.UI.Windows/packages.lock.json b/src/Avalonia/Artemis.UI.Windows/packages.lock.json index b7a683c5b..8ce041f51 100644 --- a/src/Avalonia/Artemis.UI.Windows/packages.lock.json +++ b/src/Avalonia/Artemis.UI.Windows/packages.lock.json @@ -90,6 +90,11 @@ "Splat": "14.1.45" } }, + "ArtemisRGB.Plugins.BuildTask": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "B8A5NkUEzTUgc5M/QACfyjIF5M23EUzSx8A8l/owJtB0bgmij6y/MQW1i/PcS3EDFQRothBOUuC3BCPY5UoRRQ==" + }, "Avalonia.Angle.Windows.Natives": { "type": "Transitive", "resolved": "2.1.0.2020091801", @@ -560,6 +565,11 @@ "Ninject": "3.3.3" } }, + "OpenRGB.NET": { + "type": "Transitive", + "resolved": "1.7.0", + "contentHash": "12pMEUaeoG8mN707QRO9hdT529+UnqUpwMW1H/gDTMsJrerhJve6Yt5Dnheu1isQB4PWP1wu3IDVbHCchznkiw==" + }, "ReactiveUI.Validation": { "type": "Transitive", "resolved": "2.2.1", @@ -1762,6 +1772,21 @@ "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": { "type": "Project", "dependencies": { diff --git a/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs index b7163bcd3..d575bfbc3 100644 --- a/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Avalonia/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -14,6 +14,8 @@ using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers; using Artemis.UI.Screens.Settings; using Artemis.UI.Screens.Sidebar; using Artemis.UI.Screens.SurfaceEditor; +using Artemis.UI.Screens.VisualScripting; +using Artemis.UI.Screens.VisualScripting.Pins; using Artemis.UI.Services; using DynamicData.Binding; using ReactiveUI; @@ -87,4 +89,15 @@ namespace Artemis.UI.Ninject.Factories ITreePropertyViewModel TreePropertyViewModel(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 InputPinCollectionViewModel(InputPinCollection inputPinCollection); + InputPinViewModel InputPinViewModel(InputPin inputPin); + OutputPinCollectionViewModel OutputPinCollectionViewModel(OutputPinCollection outputPinCollection); + OutputPinViewModel OutputPinViewModel(OutputPin outputPin); + } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/DataModel/DataModelDebugViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/DataModel/DataModelDebugViewModel.cs index 9c62836b8..874e5c75e 100644 --- a/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/DataModel/DataModelDebugViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/DataModel/DataModelDebugViewModel.cs @@ -4,144 +4,145 @@ using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Timers; using Artemis.Core; using Artemis.Core.Modules; using Artemis.Core.Services; using Artemis.UI.Shared; using Artemis.UI.Shared.DataModelVisualization.Shared; using Artemis.UI.Shared.Services.Interfaces; +using Avalonia.Threading; using DynamicData; 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; - private readonly IPluginManagementService _pluginManagementService; - private readonly Timer _updateTimer; + _dataModelUIService = dataModelUIService; + _pluginManagementService = pluginManagementService; + _updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(25), DispatcherPriority.Normal, (_, _) => Update()); - private bool _isModuleFilterEnabled; - private DataModelPropertiesViewModel? _mainDataModel; - private string? _propertySearch; - private Module? _selectedModule; - private bool _slowUpdates; - - public DataModelDebugViewModel(IScreen hostScreen, IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService) + HostScreen = hostScreen; + Modules = new ObservableCollection(); + + this.WhenActivated(disposables => { - _dataModelUIService = dataModelUIService; - _pluginManagementService = pluginManagementService; - _updateTimer = new Timer(25); - _updateTimer.Elapsed += UpdateTimerOnElapsed; + Observable.FromEventPattern(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x) + .Subscribe(d => PluginFeatureToggled(d.EventArgs.PluginFeature)) + .DisposeWith(disposables); + Observable.FromEventPattern(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x) + .Subscribe(d => PluginFeatureToggled(d.EventArgs.PluginFeature)) + .DisposeWith(disposables); - HostScreen = hostScreen; - Modules = new ObservableCollection(); + GetDataModel(); + _updateTimer.Start(); + Disposable.Create(() => _updateTimer.Stop()).DisposeWith(disposables); + }); + } - this.WhenActivated(disposables => - { - Observable.FromEventPattern(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x) - .Subscribe(d => PluginFeatureToggled(d.EventArgs.PluginFeature)) - .DisposeWith(disposables); - Observable.FromEventPattern(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x) - .Subscribe(d => PluginFeatureToggled(d.EventArgs.PluginFeature)) - .DisposeWith(disposables); + public DataModelPropertiesViewModel? MainDataModel + { + get => _mainDataModel; + set => RaiseAndSetIfChanged(ref _mainDataModel, value); + } - GetDataModel(); - _updateTimer.Start(); - Disposable.Create(() => _updateTimer.Stop()).DisposeWith(disposables); - }); - } + public string? PropertySearch + { + get => _propertySearch; + set => RaiseAndSetIfChanged(ref _propertySearch, value); + } - public string UrlPathSegment => "data-model"; - public IScreen HostScreen { get; } - - public DataModelPropertiesViewModel? MainDataModel + public bool SlowUpdates + { + get => _slowUpdates; + set { - get => _mainDataModel; - set => RaiseAndSetIfChanged(ref _mainDataModel, value); - } - - 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 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() { SelectedModule }, false) - : _dataModelUIService.GetMainDataModelVisualization(); - } - - private void PopulateModules() - { - Modules.Clear(); - Modules.AddRange(_pluginManagementService.GetFeaturesOfType().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; + RaiseAndSetIfChanged(ref _slowUpdates, value); + _updateTimer.Interval = TimeSpan.FromMilliseconds(_slowUpdates ? 500 : 25); } } + + public ObservableCollection 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 {SelectedModule}, false) + : _dataModelUIService.GetMainDataModelVisualization(); + } + + private void PopulateModules() + { + Modules.Clear(); + Modules.AddRange(_pluginManagementService.GetFeaturesOfType().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; } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs index d4e81011b..a3b4c6ead 100644 --- a/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs @@ -3,129 +3,128 @@ using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Timers; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Shared; +using Avalonia.Threading; using ReactiveUI; 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; - private readonly IPluginManagementService _pluginManagementService; - private double _currentFps; - private string? _renderer; - private int _renderHeight; - private int _renderWidth; + HostScreen = hostScreen; + _coreService = coreService; + _pluginManagementService = pluginManagementService; + _updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(500), DispatcherPriority.Normal, (_, _) => Update()); - public PerformanceDebugViewModel(IScreen hostScreen, ICoreService coreService, IPluginManagementService pluginManagementService) + this.WhenActivated(disposables => { - HostScreen = hostScreen; - _coreService = coreService; - _pluginManagementService = pluginManagementService; + Observable.FromEventPattern(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x) + .Subscribe(_ => Repopulate()) + .DisposeWith(disposables); + Observable.FromEventPattern(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x) + .Subscribe(_ => Repopulate()) + .DisposeWith(disposables); + Observable.FromEventPattern(x => pluginManagementService.PluginEnabled += x, x => pluginManagementService.PluginEnabled -= x) + .Subscribe(_ => Repopulate()) + .DisposeWith(disposables); + Observable.FromEventPattern(x => pluginManagementService.PluginDisabled += x, x => pluginManagementService.PluginDisabled -= x) + .Subscribe(_ => Repopulate()) + .DisposeWith(disposables); - Timer updateTimer = new(500); - updateTimer.Elapsed += UpdateTimerOnElapsed; - - this.WhenActivated(disposables => - { - Observable.FromEventPattern(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x) - .Subscribe(_ => Repopulate()) - .DisposeWith(disposables); - Observable.FromEventPattern(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x) - .Subscribe(_ => Repopulate()) - .DisposeWith(disposables); - Observable.FromEventPattern(x => pluginManagementService.PluginEnabled += x, x => pluginManagementService.PluginEnabled -= x) - .Subscribe(_ => Repopulate()) - .DisposeWith(disposables); - Observable.FromEventPattern(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 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(); + HandleActivation(); PopulateItems(); - } + _updateTimer.Start(); - private void UpdateTimerOnElapsed(object? sender, ElapsedEventArgs e) - { - 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; - } + Disposable.Create(() => + { + _updateTimer.Stop(); + Items.Clear(); + HandleDeactivation(); + }).DisposeWith(disposables); + }); } + + public ObservableCollection 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; } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/CableView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/CableView.axaml new file mode 100644 index 000000000..12b214fb4 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/CableView.axaml @@ -0,0 +1,8 @@ + + Welcome to Avalonia! + diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/CableView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/CableView.axaml.cs new file mode 100644 index 000000000..b4fa8c6a4 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/CableView.axaml.cs @@ -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 + { + public CableView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/CableViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/CableViewModel.cs new file mode 100644 index 000000000..9d14d845d --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/CableViewModel.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml new file mode 100644 index 000000000..329c64a18 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml @@ -0,0 +1,45 @@ + + + + + + + + Test + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml.cs new file mode 100644 index 000000000..921da0c83 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerView.axaml.cs @@ -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 + { + public NodePickerView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs new file mode 100644 index 000000000..231463b3b --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodePickerViewModel.cs @@ -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; + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml new file mode 100644 index 000000000..2f0c9114b --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs new file mode 100644 index 000000000..8f5e65282 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptView.axaml.cs @@ -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 + { + 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(); + } + } +} diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs new file mode 100644 index 000000000..e584407bc --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeScriptViewModel.cs @@ -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>(x => NodeScript.NodeAdded += x, x => NodeScript.NodeAdded -= x) + .Subscribe(e => HandleNodeAdded(e.EventArgs)) + .DisposeWith(d); + Observable.FromEventPattern>(x => NodeScript.NodeRemoved += x, x => NodeScript.NodeRemoved -= x) + .Subscribe(e => HandleNodeRemoved(e.EventArgs)) + .DisposeWith(d); + }); + + NodeViewModels = new ObservableCollection(); + foreach (INode nodeScriptNode in NodeScript.Nodes) + NodeViewModels.Add(_nodeVmFactory.NodeViewModel(nodeScriptNode)); + } + + public NodeScript NodeScript { get; } + public ObservableCollection NodeViewModels { get; } + public NodePickerViewModel NodePickerViewModel { get; } + + + private void HandleNodeAdded(SingleValueEventArgs eventArgs) + { + NodeViewModels.Add(_nodeVmFactory.NodeViewModel(eventArgs.Value)); + } + + private void HandleNodeRemoved(SingleValueEventArgs 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(); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml new file mode 100644 index 000000000..37cfb4264 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml @@ -0,0 +1,8 @@ + + Welcome to Avalonia! + diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml.cs new file mode 100644 index 000000000..7d8e70086 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeView.axaml.cs @@ -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 + { + public NodeView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs new file mode 100644 index 000000000..e38eed9ed --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/NodeViewModel.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinCollectionView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinCollectionView.axaml new file mode 100644 index 000000000..3bf8ae477 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinCollectionView.axaml @@ -0,0 +1,8 @@ + + Welcome to Avalonia! + diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinCollectionView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinCollectionView.axaml.cs new file mode 100644 index 000000000..bbde24ba6 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinCollectionView.axaml.cs @@ -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 + { + public InputPinCollectionView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinCollectionViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinCollectionViewModel.cs new file mode 100644 index 000000000..75488b75e --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinCollectionViewModel.cs @@ -0,0 +1,13 @@ +using Artemis.Core; + +namespace Artemis.UI.Screens.VisualScripting.Pins; + +public class InputPinCollectionViewModel : PinCollectionViewModel +{ + public InputPinCollection InputPinCollection { get; } + + public InputPinCollectionViewModel(InputPinCollection inputPinCollection) : base(inputPinCollection) + { + InputPinCollection = inputPinCollection; + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinView.axaml new file mode 100644 index 000000000..ae1b6724d --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinView.axaml @@ -0,0 +1,8 @@ + + Welcome to Avalonia! + diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinView.axaml.cs new file mode 100644 index 000000000..44a65692c --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinView.axaml.cs @@ -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 + { + public InputPinView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinViewModel.cs new file mode 100644 index 000000000..ed148b458 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/InputPinViewModel.cs @@ -0,0 +1,13 @@ +using Artemis.Core; + +namespace Artemis.UI.Screens.VisualScripting.Pins; + +public class InputPinViewModel : PinViewModel +{ + public InputPin InputPin { get; } + + public InputPinViewModel(InputPin inputPin) : base(inputPin) + { + InputPin = inputPin; + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinCollectionView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinCollectionView.axaml new file mode 100644 index 000000000..424369776 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinCollectionView.axaml @@ -0,0 +1,8 @@ + + Welcome to Avalonia! + diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinCollectionView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinCollectionView.axaml.cs new file mode 100644 index 000000000..ff6401ee8 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinCollectionView.axaml.cs @@ -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 + { + public OutputPinCollectionView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinCollectionViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinCollectionViewModel.cs new file mode 100644 index 000000000..7d8400019 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinCollectionViewModel.cs @@ -0,0 +1,13 @@ +using Artemis.Core; + +namespace Artemis.UI.Screens.VisualScripting.Pins; + +public class OutputPinCollectionViewModel : PinCollectionViewModel +{ + public OutputPinCollection OutputPinCollection { get; } + + public OutputPinCollectionViewModel(OutputPinCollection outputPinCollection) : base(outputPinCollection) + { + OutputPinCollection = outputPinCollection; + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinView.axaml b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinView.axaml new file mode 100644 index 000000000..893ff48e2 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinView.axaml @@ -0,0 +1,8 @@ + + Welcome to Avalonia! + diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinView.axaml.cs new file mode 100644 index 000000000..175d9bd0d --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinView.axaml.cs @@ -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 + { + public OutputPinView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinViewModel.cs new file mode 100644 index 000000000..0719fdcab --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/OutputPinViewModel.cs @@ -0,0 +1,13 @@ +using Artemis.Core; + +namespace Artemis.UI.Screens.VisualScripting.Pins; + +public class OutputPinViewModel : PinViewModel +{ + public OutputPin OutputPin { get; } + + public OutputPinViewModel(OutputPin outputPin) : base(outputPin) + { + OutputPin = outputPin; + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinCollectionViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinCollectionViewModel.cs new file mode 100644 index 000000000..42140ec4f --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinCollectionViewModel.cs @@ -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; + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinViewModel.cs b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinViewModel.cs new file mode 100644 index 000000000..ec321632a --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/VisualScripting/Pins/PinViewModel.cs @@ -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; } +} \ No newline at end of file