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

Data model picker - Implemented selecting

This commit is contained in:
Robert 2022-03-23 23:58:09 +01:00
parent 75a0be0c98
commit 30ec28a9a9
19 changed files with 644 additions and 446 deletions

View File

@ -9,9 +9,13 @@ using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Events;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Material.Icons;
using Material.Icons.Avalonia;
using ReactiveUI;
using SkiaSharp;
namespace Artemis.UI.Shared.Controls.DataModelPicker;
@ -61,9 +65,15 @@ public class DataModelPicker : TemplatedControl
public static readonly StyledProperty<ObservableCollection<Type>?> FilterTypesProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<Type>?>(nameof(FilterTypes), new ObservableCollection<Type>());
private MaterialIcon? _currentPathIcon;
private TextBlock? _currentPathDisplay;
private TextBlock? _currentPathDescription;
private TreeView? _dataModelTreeView;
static DataModelPicker()
{
ModulesProperty.Changed.Subscribe(ModulesChanged);
DataModelPathProperty.Changed.Subscribe(DataModelPathPropertyChanged);
DataModelViewModelProperty.Changed.Subscribe(DataModelViewModelPropertyChanged);
ExtraDataModelViewModelsProperty.Changed.Subscribe(ExtraDataModelViewModelsChanged);
}
@ -114,7 +124,7 @@ public class DataModelPicker : TemplatedControl
public DataModelPropertiesViewModel? DataModelViewModel
{
get => GetValue(DataModelViewModelProperty);
set => SetValue(DataModelViewModelProperty, value);
private set => SetValue(DataModelViewModelProperty, value);
}
/// <summary>
@ -154,9 +164,35 @@ public class DataModelPicker : TemplatedControl
/// <inheritdoc />
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
GetDataModel();
if (_dataModelTreeView != null)
_dataModelTreeView.SelectionChanged -= DataModelTreeViewOnSelectionChanged;
_currentPathIcon = e.NameScope.Find<MaterialIcon>("CurrentPathIcon");
_currentPathDisplay = e.NameScope.Find<TextBlock>("CurrentPathDisplay");
_currentPathDescription = e.NameScope.Find<TextBlock>("CurrentPathDescription");
_dataModelTreeView = e.NameScope.Find<TreeView>("DataModelTreeView");
if (_dataModelTreeView != null)
_dataModelTreeView.SelectionChanged += DataModelTreeViewOnSelectionChanged;
}
#region Overrides of Visual
/// <inheritdoc />
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
GetDataModel();
UpdateCurrentPath(true);
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DataModelViewModel?.Dispose();
}
#endregion
#endregion
private static void ModulesChanged(AvaloniaPropertyChangedEventArgs<ObservableCollection<Module>?> e)
@ -165,6 +201,12 @@ public class DataModelPicker : TemplatedControl
dataModelPicker.GetDataModel();
}
private static void DataModelPathPropertyChanged(AvaloniaPropertyChangedEventArgs<DataModelPath?> e)
{
if (e.Sender is DataModelPicker dataModelPicker)
dataModelPicker.UpdateCurrentPath(false);
}
private static void DataModelViewModelPropertyChanged(AvaloniaPropertyChangedEventArgs<DataModelPropertiesViewModel?> e)
{
if (e.Sender is DataModelPicker && e.OldValue.Value != null)
@ -215,4 +257,59 @@ public class DataModelPicker : TemplatedControl
foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels)
extraDataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
}
private void DataModelTreeViewOnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
// Multi-select isn't a think so grab the first one
object? selected = _dataModelTreeView?.SelectedItems[0];
if (selected == null)
return;
if (selected is DataModelPropertyViewModel property && property.DataModelPath != null)
DataModelPath = new DataModelPath(property.DataModelPath);
if (selected is DataModelListViewModel list && list.DataModelPath != null)
DataModelPath = new DataModelPath(list.DataModelPath);
}
private void UpdateCurrentPath(bool selectCurrentPath)
{
if (DataModelPath == null)
return;
if (_dataModelTreeView != null && selectCurrentPath)
{
// Expand the path
DataModel? start = DataModelPath.Target;
DataModelVisualizationViewModel? root = DataModelViewModel?.Children.FirstOrDefault(c => c.DataModel == start);
if (root != null)
{
root.ExpandToPath(DataModelPath);
_dataModelTreeView.SelectedItem = root.GetViewModelForPath(DataModelPath);
}
}
if (_currentPathDisplay != null)
_currentPathDisplay.Text = string.Join(" ", DataModelPath.Segments.Where(s => s.GetPropertyDescription() != null).Select(s => s.GetPropertyDescription()!.Name));
if (_currentPathDescription != null)
_currentPathDescription.Text = DataModelPath.GetPropertyDescription()?.Description;
if (_currentPathIcon != null)
{
Type? type = DataModelPath.GetPropertyType();
if (type == null)
_currentPathIcon.Kind = MaterialIconKind.QuestionMarkCircle;
else if (type.TypeIsNumber())
_currentPathIcon.Kind = MaterialIconKind.CalculatorVariantOutline;
else if (type.IsEnum)
_currentPathIcon.Kind = MaterialIconKind.FormatListBulletedSquare;
else if (type == typeof(bool))
_currentPathIcon.Kind = MaterialIconKind.CircleHalfFull;
else if (type == typeof(string))
_currentPathIcon.Kind = MaterialIconKind.Text;
else if (type == typeof(SKColor))
_currentPathIcon.Kind = MaterialIconKind.Palette;
else
_currentPathIcon.Kind = MaterialIconKind.Matrix;
}
}
}

View File

@ -62,12 +62,6 @@ public class DataModelPickerButton : TemplatedControl
public static readonly StyledProperty<ObservableCollection<Module>?> ModulesProperty =
AvaloniaProperty.Register<DataModelPicker, ObservableCollection<Module>?>(nameof(Modules), new ObservableCollection<Module>());
/// <summary>
/// The data model view model to show, if not provided one will be retrieved by the control.
/// </summary>
public static readonly StyledProperty<DataModelPropertiesViewModel?> DataModelViewModelProperty =
AvaloniaProperty.Register<DataModelPicker, DataModelPropertiesViewModel?>(nameof(DataModelViewModel));
/// <summary>
/// A list of data model view models to show
/// </summary>
@ -84,7 +78,6 @@ public class DataModelPickerButton : TemplatedControl
private bool _flyoutActive;
private Button? _button;
private DataModelPickerFlyout? _flyout;
private IDisposable? _dataModelPathChanged;
static DataModelPickerButton()
{
@ -155,15 +148,6 @@ public class DataModelPickerButton : TemplatedControl
set => SetValue(ModulesProperty, value);
}
/// <summary>
/// The data model view model to show, if not provided one will be retrieved by the control.
/// </summary>
public DataModelPropertiesViewModel? DataModelViewModel
{
get => GetValue(DataModelViewModelProperty);
set => SetValue(DataModelViewModelProperty, value);
}
/// <summary>
/// A list of data model view models to show.
/// </summary>
@ -219,14 +203,6 @@ public class DataModelPickerButton : TemplatedControl
self.UpdateValueDisplay();
}
private void FlyoutDataModelPathChanged(AvaloniaPropertyChangedEventArgs<DataModelPath?> e)
{
if (!ReferenceEquals(e.Sender, _flyout?.DataModelPicker))
return;
DataModelPath = e.NewValue.Value;
}
private void PathValidationChanged(object? sender, EventArgs e)
{
Dispatcher.UIThread.InvokeAsync(UpdateValueDisplay, DispatcherPriority.DataBind);
@ -264,7 +240,6 @@ public class DataModelPickerButton : TemplatedControl
// Logic here is taken from Fluent Avalonia's ColorPicker which also reuses the same control since it's large
_flyout.DataModelPicker.DataModelPath = DataModelPath;
_flyout.DataModelPicker.DataModelViewModel = DataModelViewModel;
_flyout.DataModelPicker.ExtraDataModelViewModels = ExtraDataModelViewModels;
_flyout.DataModelPicker.FilterTypes = FilterTypes;
_flyout.DataModelPicker.Modules = Modules;
@ -274,22 +249,9 @@ public class DataModelPickerButton : TemplatedControl
_flyout.ShowAt(_button != null ? _button : this);
_flyoutActive = true;
_dataModelPathChanged = DataModelPicker.DataModelPathProperty.Changed.Subscribe(FlyoutDataModelPathChanged);
FlyoutOpened?.Invoke(this, EventArgs.Empty);
}
private void OnFlyoutClosed(object? sender, EventArgs e)
{
if (_flyoutActive)
{
FlyoutClosed?.Invoke(this, EventArgs.Empty);
_flyoutActive = false;
}
_dataModelPathChanged?.Dispose();
_dataModelPathChanged = null;
}
#region Overrides of TemplatedControl
/// <inheritdoc />
@ -314,15 +276,33 @@ public class DataModelPickerButton : TemplatedControl
DataModelPath.PathValidated += PathValidationChanged;
}
if (_flyout == null)
{
_flyout = new DataModelPickerFlyout();
_flyout.FlyoutPresenterClasses.Add("data-model-picker-presenter");
}
_flyout ??= new DataModelPickerFlyout();
_flyout.Confirmed += FlyoutOnConfirmed;
_flyout.Closed += OnFlyoutClosed;
}
private void FlyoutOnConfirmed(DataModelPickerFlyout sender, object args)
{
if (_flyoutActive)
{
FlyoutClosed?.Invoke(this, EventArgs.Empty);
_flyoutActive = false;
if (_flyout != null)
DataModelPath = _flyout.DataModelPicker.DataModelPath;
}
}
private void OnFlyoutClosed(object? sender, EventArgs e)
{
if (_flyoutActive)
{
FlyoutClosed?.Invoke(this, EventArgs.Empty);
_flyoutActive = false;
}
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
@ -332,8 +312,6 @@ public class DataModelPickerButton : TemplatedControl
DataModelPath.PathValidated -= PathValidationChanged;
}
DataModelViewModel?.Dispose();
if (_flyout != null)
_flyout.Closed -= OnFlyoutClosed;
}

View File

@ -1,11 +1,16 @@
using Avalonia.Controls;
using System;
using System.ComponentModel;
using Avalonia.Controls;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Controls.Primitives;
namespace Artemis.UI.Shared.Controls.Flyouts;
/// <summary>
/// Defines a flyout that hosts a data model picker.
/// </summary>
public sealed class DataModelPickerFlyout : Flyout
public sealed class DataModelPickerFlyout : PickerFlyoutBase
{
private DataModelPicker.DataModelPicker? _picker;
@ -14,11 +19,49 @@ public sealed class DataModelPickerFlyout : Flyout
/// </summary>
public DataModelPicker.DataModelPicker DataModelPicker => _picker ??= new DataModelPicker.DataModelPicker();
/// <summary>
/// Raised when the Confirmed button is tapped indicating the new Color should be applied
/// </summary>
public event TypedEventHandler<DataModelPickerFlyout, object>? Confirmed;
/// <summary>
/// Raised when the Dismiss button is tapped, indicating the new color should not be applied
/// </summary>
public event TypedEventHandler<DataModelPickerFlyout, object>? Dismissed;
/// <inheritdoc />
protected override Control CreatePresenter()
{
_picker ??= new DataModelPicker.DataModelPicker();
FlyoutPresenter presenter = new() {Content = DataModelPicker};
PickerFlyoutPresenter presenter = new() {Content = DataModelPicker};
presenter.Confirmed += OnFlyoutConfirmed;
presenter.Dismissed += OnFlyoutDismissed;
return presenter;
}
/// <inheritdoc />
protected override void OnConfirmed()
{
Confirmed?.Invoke(this, EventArgs.Empty);
Hide();
}
/// <inheritdoc />
protected override void OnOpening(CancelEventArgs args)
{
base.OnOpening(args);
(Popup.Child as PickerFlyoutPresenter)?.Classes.Set(":acceptdismiss", true);
}
private void OnFlyoutDismissed(PickerFlyoutPresenter sender, object args)
{
Dismissed?.Invoke(this, EventArgs.Empty);
Hide();
}
private void OnFlyoutConfirmed(PickerFlyoutPresenter sender, object args)
{
OnConfirmed();
}
}

View File

@ -9,21 +9,21 @@ using Artemis.Core.Modules;
using Artemis.UI.Shared.Services.Interfaces;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelVisualization.Shared
namespace Artemis.UI.Shared.DataModelVisualization.Shared;
/// <summary>
/// Represents a base class for a view model that visualizes a part of the data model
/// </summary>
public abstract class DataModelVisualizationViewModel : ReactiveObject, IDisposable
{
/// <summary>
/// Represents a base class for a view model that visualizes a part of the data model
/// </summary>
public abstract class DataModelVisualizationViewModel : ReactiveObject, IDisposable
{
private const int MaxDepth = 4;
private ObservableCollection<DataModelVisualizationViewModel> _children;
private DataModel? _dataModel;
private bool _isMatchingFilteredTypes;
private bool _isVisualizationExpanded;
private DataModelVisualizationViewModel? _parent;
private DataModelPropertyAttribute? _propertyDescription;
private bool _populatedStaticChildren;
private DataModelPropertyAttribute? _propertyDescription;
internal DataModelVisualizationViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
{
@ -194,6 +194,66 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
IsMatchingFilteredTypes = filteredTypes.Any(t => t == type || t == typeof(Enum) && type.IsEnum);
}
/// <summary>
/// Occurs when an update to the property this view model visualizes is requested
/// </summary>
public event EventHandler? UpdateRequested;
/// <summary>
/// Expands this view model and any children to expose the provided <paramref name="dataModelPath" />.
/// </summary>
/// <param name="dataModelPath">The data model path to expose.</param>
public void ExpandToPath(DataModelPath dataModelPath)
{
if (dataModelPath.Target != DataModel)
throw new ArtemisSharedUIException("Can't expand to a path that doesn't belong to this data model.");
IsVisualizationExpanded = true;
DataModelPathSegment current = dataModelPath.Segments.Skip(1).First();
Children.FirstOrDefault(c => c.Path == current.Path)?.ExpandToPath(current.Next);
}
/// <summary>
/// Finds the view model that hosts the given path.
/// </summary>
/// <param name="dataModelPath">The path to find</param>
/// <returns>The matching view model, may be null if the path doesn't exist or isn't expanded</returns>
public DataModelVisualizationViewModel? GetViewModelForPath(DataModelPath dataModelPath)
{
if (dataModelPath.Target != DataModel)
throw new ArtemisSharedUIException("Can't expand to a path that doesn't belong to this data model.");
if (DataModelPath?.Path == dataModelPath.Path)
return this;
return Children.Select(c => c.GetViewModelForPath(dataModelPath)).FirstOrDefault(match => match != null);
}
/// <summary>
/// Invokes the <see cref="UpdateRequested" /> event
/// </summary>
protected virtual void OnUpdateRequested()
{
UpdateRequested?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
DataModelPath?.Dispose();
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Dispose(true);
}
}
internal virtual int GetChildDepth()
{
return 0;
@ -246,7 +306,6 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
// Add missing dynamic children
object? value = Parent == null || Parent.IsRootViewModel ? DataModel : DataModelPath?.GetValue();
if (value is DataModel dataModel)
{
foreach (string key in dataModel.DynamicChildren.Keys.ToList())
{
string childPath = AppendToPath(key);
@ -257,7 +316,6 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
if (child != null)
Children.Add(child);
}
}
// Remove dynamic children that have been removed from the data model
List<DataModelVisualizationViewModel> toRemoveDynamic = Children.Where(c => c.DataModelPath != null && !c.DataModelPath.IsValid).ToList();
@ -325,40 +383,13 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
OnUpdateRequested();
}
#region Events
/// <summary>
/// Occurs when an update to the property this view model visualizes is requested
/// </summary>
public event EventHandler? UpdateRequested;
/// <summary>
/// Invokes the <see cref="UpdateRequested" /> event
/// </summary>
protected virtual void OnUpdateRequested()
private void ExpandToPath(DataModelPathSegment? segment)
{
UpdateRequested?.Invoke(this, EventArgs.Empty);
}
if (segment == null)
return;
#endregion
#region IDisposable
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
DataModelPath?.Dispose();
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
dataModelVisualizationViewModel.Dispose(true);
}
IsVisualizationExpanded = true;
Children.FirstOrDefault(c => c.Path == segment.Path)?.ExpandToPath(segment.Next);
}
/// <inheritdoc />
@ -367,7 +398,4 @@ namespace Artemis.UI.Shared.DataModelVisualization.Shared
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@ -2,7 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia">
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
<Design.PreviewWith>
<dataModelPicker:DataModelPicker />
</Design.PreviewWith>
@ -10,19 +11,34 @@
<Style Selector="dataModelPicker|DataModelPicker">
<Setter Property="Template">
<ControlTemplate>
<Grid RowDefinitions="Auto,Auto,*" Width="600" Height="400">
<TextBox Grid.Row="0" Watermark="Search" Name="SearchBox"></TextBox>
<Grid RowDefinitions="Auto,Auto,*" Width="600" Height="400" Margin="10">
<TextBox Grid.Row="0" Watermark="Search - not yet implemented 😱" Name="SearchBox" IsEnabled="False" />
<Border Grid.Row="1" Classes="card card-condensed" Margin="0 15">
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,*,*">
<TextBlock Grid.Row="0" Grid.ColumnSpan="2" Classes="SubtitleTextBlockStyle">Current selection</TextBlock>
<avalonia:MaterialIcon Kind="CalculatorVariantOutline" Grid.Column="0" Grid.Row="1" Grid.RowSpan="2" Height="22" Width="22" Margin="5 0 15 0"></avalonia:MaterialIcon>
<TextBlock Grid.Row="1" Grid.Column="1" Classes="BodyStrongTextBlockStyle">Cursor Y-position</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="1" Classes="BodyTextBlockStyle" Foreground="{DynamicResource TextFillColorSecondary}">The current Y-position of the cursor in pixels</TextBlock>
<Border Grid.Row="1" Classes="card card-condensed" Margin="0 10">
<Panel>
<Grid ColumnDefinitions="Auto,*"
RowDefinitions="*"
MinHeight="38"
IsVisible="{Binding DataModelPath, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static ObjectConverters.IsNotNull}}">
<avalonia:MaterialIcon Grid.Column="0" Grid.Row="0" Name="CurrentPathIcon" Kind="QuestionMarkCircle" Height="22" Width="22" Margin="5 0 15 0" />
<StackPanel Grid.Column="1" Grid.Row="0" VerticalAlignment="Center">
<TextBlock Name="CurrentPathDisplay" Classes="BodyStrongTextBlockStyle" MaxHeight="50" />
<TextBlock Name="CurrentPathDescription" Classes="BodyTextBlockStyle" Foreground="{DynamicResource TextFillColorSecondary}" MaxHeight="50" />
</StackPanel>
</Grid>
<Grid MinHeight="38"
IsVisible="{Binding DataModelPath, RelativeSource={RelativeSource TemplatedParent}, Converter={x:Static ObjectConverters.IsNull}}" ColumnDefinitions="*,Auto"
RowDefinitions="*,*">
<TextBlock Grid.Column="0" Grid.Row="0" Classes="BodyStrongTextBlockStyle">Welcome to the data model picker</TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" Foreground="{DynamicResource TextFillColorSecondary}">Select a value from the data model below</TextBlock>
<controls:HyperlinkButton Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">Learn more</controls:HyperlinkButton>
</Grid>
</Panel>
</Border>
<TreeView Grid.Row="2" Items="{Binding DataModelViewModel.Children, RelativeSource={RelativeSource TemplatedParent}}">
<TreeView Grid.Row="2"
Name="DataModelTreeView"
Items="{Binding DataModelViewModel.Children, RelativeSource={RelativeSource TemplatedParent}}">
<TreeView.Styles>
<Style Selector="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />

View File

@ -97,13 +97,13 @@ namespace Artemis.UI.Ninject.Factories
NodeViewModel NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node);
CableViewModel CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to);
DragCableViewModel DragCableViewModel(PinViewModel pinViewModel);
InputPinViewModel InputPinViewModel(IPin inputPin);
OutputPinViewModel OutputPinViewModel(IPin outputPin);
}
public interface INodePinVmFactory
{
PinCollectionViewModel InputPinCollectionViewModel(IPinCollection inputPinCollection, NodeScriptViewModel nodeScriptViewModel);
PinViewModel InputPinViewModel(IPin inputPin);
PinCollectionViewModel OutputPinCollectionViewModel(IPinCollection outputPinCollection, NodeScriptViewModel nodeScriptViewModel);
PinViewModel OutputPinViewModel(IPin outputPin);
}
}

View File

@ -10,24 +10,15 @@ public class NodePinViewModelInstanceProvider : StandardInstanceProvider
{
protected override Type GetType(MethodInfo methodInfo, object[] arguments)
{
if (methodInfo.ReturnType != typeof(PinCollectionViewModel) && methodInfo.ReturnType != typeof(PinViewModel))
if (methodInfo.ReturnType != typeof(PinCollectionViewModel))
return base.GetType(methodInfo, arguments);
if (arguments[0] is IPin pin)
return CreatePinViewModelType(pin);
if (arguments[0] is IPinCollection pinCollection)
return CreatePinCollectionViewModelType(pinCollection);
return base.GetType(methodInfo, arguments);
}
private Type CreatePinViewModelType(IPin pin)
{
if (pin.Direction == PinDirection.Input)
return typeof(InputPinViewModel<>).MakeGenericType(pin.Type);
return typeof(OutputPinViewModel<>).MakeGenericType(pin.Type);
}
private Type CreatePinCollectionViewModelType(IPinCollection pinCollection)
{
if (pinCollection.Direction == PinDirection.Input)

View File

@ -27,7 +27,6 @@ namespace Artemis.UI.Screens.Root
private readonly IDebugService _debugService;
private readonly IClassicDesktopStyleApplicationLifetime _lifeTime;
private readonly ISettingsService _settingsService;
private readonly IRegistrationService _registrationService;
private readonly ISidebarVmFactory _sidebarVmFactory;
private readonly IWindowService _windowService;
private SidebarViewModel? _sidebarViewModel;
@ -49,7 +48,6 @@ namespace Artemis.UI.Screens.Root
_coreService = coreService;
_settingsService = settingsService;
_registrationService = registrationService;
_windowService = windowService;
_debugService = debugService;
_assetLoader = assetLoader;
@ -62,7 +60,14 @@ namespace Artemis.UI.Screens.Root
DisplayAccordingToSettings();
Router.CurrentViewModel.Subscribe(UpdateTitleBarViewModel);
Task.Run(coreService.Initialize);
Task.Run(() =>
{
coreService.Initialize();
registrationService.RegisterBuiltInDataModelDisplays();
registrationService.RegisterBuiltInDataModelInputs();
registrationService.RegisterBuiltInPropertyEditors();
registrationService.RegisterBuiltInNodeTypes();
});
}
private void UpdateTitleBarViewModel(IRoutableViewModel? viewModel)
@ -178,11 +183,6 @@ namespace Artemis.UI.Screens.Root
/// <inheritdoc />
public void OpenMainWindow()
{
_registrationService.RegisterBuiltInDataModelDisplays();
_registrationService.RegisterBuiltInDataModelInputs();
_registrationService.RegisterBuiltInPropertyEditors();
_registrationService.RegisterBuiltInNodeTypes();
if (_lifeTime.MainWindow == null)
{
SidebarViewModel = _sidebarVmFactory.SidebarViewModel(this);

View File

@ -33,7 +33,7 @@ public class NodeViewModel : ActivatableViewModelBase
private ObservableAsPropertyHelper<bool>? _hasInputPins;
private ObservableAsPropertyHelper<bool>? _hasOutputPins;
public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodePinVmFactory nodePinVmFactory, INodeEditorService nodeEditorService)
public NodeViewModel(NodeScriptViewModel nodeScriptViewModel, INode node, INodeVmFactory nodeVmFactory, INodePinVmFactory nodePinVmFactory, INodeEditorService nodeEditorService)
{
NodeScriptViewModel = nodeScriptViewModel;
_nodeEditorService = nodeEditorService;
@ -47,12 +47,12 @@ public class NodeViewModel : ActivatableViewModelBase
// Create observable collections split up by direction
nodePins.Connect()
.Filter(n => n.Direction == PinDirection.Input)
.Transform(nodePinVmFactory.InputPinViewModel)
.Transform(p => (PinViewModel) nodeVmFactory.InputPinViewModel(p))
.Bind(out ReadOnlyObservableCollection<PinViewModel> inputPins)
.Subscribe();
nodePins.Connect()
.Filter(n => n.Direction == PinDirection.Output)
.Transform(nodePinVmFactory.OutputPinViewModel)
.Transform(p => (PinViewModel) nodeVmFactory.OutputPinViewModel(p))
.Bind(out ReadOnlyObservableCollection<PinViewModel> outputPins)
.Subscribe();
InputPinViewModels = inputPins;

View File

@ -6,11 +6,20 @@ namespace Artemis.UI.Screens.VisualScripting.Pins;
public class InputPinCollectionViewModel<T> : PinCollectionViewModel
{
private readonly INodeVmFactory _nodeVmFactory;
public InputPinCollection<T> InputPinCollection { get; }
public InputPinCollectionViewModel(InputPinCollection<T> inputPinCollection, NodeScriptViewModel nodeScriptViewModel, INodePinVmFactory nodePinVmFactory, INodeEditorService nodeEditorService)
: base(inputPinCollection, nodeScriptViewModel, nodePinVmFactory, nodeEditorService)
public InputPinCollectionViewModel(InputPinCollection<T> inputPinCollection, NodeScriptViewModel nodeScriptViewModel, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
: base(inputPinCollection, nodeScriptViewModel, nodeEditorService)
{
_nodeVmFactory = nodeVmFactory;
InputPinCollection = inputPinCollection;
}
protected override PinViewModel CreatePinViewModel(IPin pin)
{
PinViewModel vm = _nodeVmFactory.InputPinViewModel(pin);
vm.RemovePin = RemovePin;
return vm;
}
}

View File

@ -3,12 +3,9 @@ using Artemis.Core.Services;
namespace Artemis.UI.Screens.VisualScripting.Pins;
public class InputPinViewModel<T> : PinViewModel
public class InputPinViewModel : PinViewModel
{
public InputPin<T> InputPin { get; }
public InputPinViewModel(InputPin<T> inputPin, INodeService nodeService) : base(inputPin, nodeService)
public InputPinViewModel(IPin inputPin, INodeService nodeService) : base(inputPin, nodeService)
{
InputPin = inputPin;
}
}

View File

@ -6,11 +6,20 @@ namespace Artemis.UI.Screens.VisualScripting.Pins;
public class OutputPinCollectionViewModel<T> : PinCollectionViewModel
{
private readonly INodeVmFactory _nodeVmFactory;
public OutputPinCollection<T> OutputPinCollection { get; }
public OutputPinCollectionViewModel(OutputPinCollection<T> outputPinCollection, NodeScriptViewModel nodeScriptViewModel, INodePinVmFactory nodePinVmFactory, INodeEditorService nodeEditorService)
: base(outputPinCollection, nodeScriptViewModel, nodePinVmFactory, nodeEditorService)
public OutputPinCollectionViewModel(OutputPinCollection<T> outputPinCollection, NodeScriptViewModel nodeScriptViewModel, INodeVmFactory nodeVmFactory, INodeEditorService nodeEditorService)
: base(outputPinCollection, nodeScriptViewModel, nodeEditorService)
{
_nodeVmFactory = nodeVmFactory;
OutputPinCollection = outputPinCollection;
}
protected override PinViewModel CreatePinViewModel(IPin pin)
{
PinViewModel vm = _nodeVmFactory.OutputPinViewModel(pin);
vm.RemovePin = RemovePin;
return vm;
}
}

View File

@ -3,12 +3,9 @@ using Artemis.Core.Services;
namespace Artemis.UI.Screens.VisualScripting.Pins;
public class OutputPinViewModel<T> : PinViewModel
public class OutputPinViewModel : PinViewModel
{
public OutputPin<T> OutputPin { get; }
public OutputPinViewModel(OutputPin<T> outputPin, INodeService nodeService) : base(outputPin, nodeService)
public OutputPinViewModel(IPin outputPin, INodeService nodeService) : base(outputPin, nodeService)
{
OutputPin = outputPin;
}
}

View File

@ -17,12 +17,8 @@ namespace Artemis.UI.Screens.VisualScripting.Pins;
public abstract class PinCollectionViewModel : ActivatableViewModelBase
{
private readonly INodePinVmFactory _nodePinVmFactory;
protected PinCollectionViewModel(IPinCollection pinCollection, NodeScriptViewModel nodeScriptViewModel, INodePinVmFactory nodePinVmFactory, INodeEditorService nodeEditorService)
protected PinCollectionViewModel(IPinCollection pinCollection, NodeScriptViewModel nodeScriptViewModel, INodeEditorService nodeEditorService)
{
_nodePinVmFactory = nodePinVmFactory;
PinCollection = pinCollection;
PinViewModels = new ObservableCollection<PinViewModel>();
@ -49,10 +45,5 @@ public abstract class PinCollectionViewModel : ActivatableViewModelBase
public ObservableCollection<PinViewModel> PinViewModels { get; }
private PinViewModel CreatePinViewModel(IPin pin)
{
PinViewModel vm = PinCollection.Direction == PinDirection.Input ? _nodePinVmFactory.InputPinViewModel(pin) : _nodePinVmFactory.OutputPinViewModel(pin);
vm.RemovePin = RemovePin;
return vm;
}
protected abstract PinViewModel CreatePinViewModel(IPin pin);
}

View File

@ -17,7 +17,8 @@
<Border Classes="card">
<StackPanel Spacing="5">
<TextBlock Classes="h4">Nodes tests</TextBlock>
<dataModelPicker:DataModelPickerButton Placement="BottomEdgeAlignedLeft"/>
<!-- <dataModelPicker:DataModelPickerButton Placement="BottomEdgeAlignedLeft"/> -->
<ContentControl Content="{CompiledBinding VisualEditorViewModel}" HorizontalAlignment="Stretch" Height="800"></ContentControl>
</StackPanel>
</Border>
<Border Classes="card">

View File

@ -75,6 +75,9 @@
<Compile Update="Nodes\CustomViews\StaticStringValueNodeCustomView.axaml.cs">
<DependentUpon>StaticStringValueNodeCustomView.axaml</DependentUpon>
</Compile>
<Compile Update="Nodes\DataModel\CustomViews\DataModelNodeCustomView.axaml.cs">
<DependentUpon>DataModelNodeCustomView.axaml</DependentUpon>
</Compile>
</ItemGroup>
</Project>

View File

@ -2,7 +2,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker;assembly=Artemis.UI.Shared"
xmlns:customViewModels="clr-namespace:Artemis.VisualScripting.Nodes.DataModel.CustomViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.VisualScripting.Nodes.DataModel.CustomViews.DataModelEventNodeCustomView">
x:Class="Artemis.VisualScripting.Nodes.DataModel.CustomViews.DataModelEventNodeCustomView"
x:DataType="customViewModels:DataModelEventNodeCustomViewModel">
<dataModelPicker:DataModelPickerButton DataModelPath="{CompiledBinding DataModelPath}"
Modules="{CompiledBinding Modules}"
ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}"
FilterTypes="{CompiledBinding FilterTypes}" />
</UserControl>

View File

@ -0,0 +1,13 @@
<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:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker;assembly=Artemis.UI.Shared"
xmlns:customViewModels="clr-namespace:Artemis.VisualScripting.Nodes.DataModel.CustomViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.VisualScripting.Nodes.DataModel.CustomViews.DataModelNodeCustomView"
x:DataType="customViewModels:DataModelNodeCustomViewModel">
<dataModelPicker:DataModelPickerButton DataModelPath="{CompiledBinding DataModelPath}"
Modules="{CompiledBinding Modules}"
ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}" />
</UserControl>

View File

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Artemis.VisualScripting.Nodes.DataModel.CustomViews
{
public partial class DataModelNodeCustomView : UserControl
{
public DataModelNodeCustomView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}