using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.UI.Shared.DataModelVisualization.Shared;
using Artemis.UI.Shared.Events;
using Artemis.UI.Shared.Extensions;
using Artemis.UI.Shared.Services;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Threading;
using Material.Icons.Avalonia;
using ReactiveUI;
namespace Artemis.UI.Shared.DataModelPicker;
///
/// Represents a data model picker picker that can be used to select a data model path.
///
public class DataModelPicker : TemplatedControl
{
///
/// The data model UI service this picker should use.
///
public static IDataModelUIService? DataModelUIService;
///
/// Gets or sets data model path.
///
public static readonly StyledProperty DataModelPathProperty =
AvaloniaProperty.Register(nameof(DataModelPath), defaultBindingMode: BindingMode.TwoWay);
///
/// Gets or sets a boolean indicating whether the data model picker should show current values when selecting a path.
///
public static readonly StyledProperty ShowDataModelValuesProperty =
AvaloniaProperty.Register(nameof(ShowDataModelValues));
///
/// A list of extra modules to show data models of.
///
public static readonly StyledProperty?> ModulesProperty =
AvaloniaProperty.Register?>(nameof(Modules), new ObservableCollection());
///
/// The data model view model to show, if not provided one will be retrieved by the control.
///
public static readonly StyledProperty DataModelViewModelProperty =
AvaloniaProperty.Register(nameof(DataModelViewModel));
///
/// A list of types to filter the selectable paths on.
///
public static readonly StyledProperty?> FilterTypesProperty =
AvaloniaProperty.Register?>(nameof(FilterTypes), new ObservableCollection());
///
/// Gets or sets a boolean indicating whether the picker is in event picker mode.
/// When event children aren't selectable and non-events are described as "{PropertyName}
/// changed".
///
public static readonly StyledProperty IsEventPickerProperty =
AvaloniaProperty.Register(nameof(IsEventPicker));
private TextBlock? _currentPathDescription;
private TextBlock? _currentPathDisplay;
private MaterialIcon? _currentPathIcon;
private TreeView? _dataModelTreeView;
private DispatcherTimer? _updateTimer;
static DataModelPicker()
{
ModulesProperty.Changed.Subscribe(ModulesChanged);
DataModelPathProperty.Changed.Subscribe(DataModelPathPropertyChanged);
DataModelViewModelProperty.Changed.Subscribe(DataModelViewModelPropertyChanged);
}
///
/// Creates a new instance of the class.
///
public DataModelPicker()
{
SelectPropertyCommand = ReactiveCommand.Create(selected => ExecuteSelectPropertyCommand(selected));
}
///
/// Gets a command that selects the path by it's view model.
///
public ReactiveCommand SelectPropertyCommand { get; }
///
/// Gets or sets data model path.
///
public DataModelPath? DataModelPath
{
get => GetValue(DataModelPathProperty);
set => SetValue(DataModelPathProperty, value);
}
///
/// Gets or sets a boolean indicating whether the data model picker should show current values when selecting a path.
///
public bool ShowDataModelValues
{
get => GetValue(ShowDataModelValuesProperty);
set => SetValue(ShowDataModelValuesProperty, value);
}
///
/// A list of extra modules to show data models of.
///
public ObservableCollection? Modules
{
get => GetValue(ModulesProperty);
set => SetValue(ModulesProperty, value);
}
///
/// The data model view model to show, if not provided one will be retrieved by the control.
///
public DataModelPropertiesViewModel? DataModelViewModel
{
get => GetValue(DataModelViewModelProperty);
private set => SetValue(DataModelViewModelProperty, value);
}
///
/// A list of types to filter the selectable paths on.
///
public ObservableCollection? FilterTypes
{
get => GetValue(FilterTypesProperty);
set => SetValue(FilterTypesProperty, value);
}
///
/// Gets or sets a boolean indicating whether the picker is in event picker mode.
/// When event children aren't selectable and non-events are described as "{PropertyName}
/// changed".
///
public bool IsEventPicker
{
get => GetValue(IsEventPickerProperty);
set => SetValue(IsEventPickerProperty, value);
}
///
/// Occurs when a new path has been selected
///
public event EventHandler? DataModelPathSelected;
///
/// Invokes the event
///
///
protected virtual void OnDataModelPathSelected(DataModelSelectedEventArgs e)
{
DataModelPathSelected?.Invoke(this, e);
}
private static void ModulesChanged(AvaloniaPropertyChangedEventArgs?> e)
{
if (e.Sender is DataModelPicker dataModelPicker)
dataModelPicker.GetDataModel();
}
private static void DataModelPathPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is DataModelPicker dataModelPicker)
dataModelPicker.UpdateCurrentPath(false);
}
private static void DataModelViewModelPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is DataModelPicker && e.OldValue.Value != null)
e.OldValue.Value.Dispose();
}
private void ExecuteSelectPropertyCommand(DataModelVisualizationViewModel selected)
{
if (selected.DataModelPath == null)
return;
if (selected.DataModelPath.Equals(DataModelPath))
return;
DataModelPath = new DataModelPath(selected.DataModelPath);
OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath));
}
private void Update(object? sender, EventArgs e)
{
if (DataModelUIService == null)
return;
DataModelViewModel?.Update(DataModelUIService, new DataModelUpdateConfiguration(!IsEventPicker));
}
private void GetDataModel()
{
if (DataModelUIService == null)
return;
ChangeDataModel(DataModelUIService.GetPluginDataModelVisualization(Modules?.ToList() ?? new List(), true));
}
private void ChangeDataModel(DataModelPropertiesViewModel? dataModel)
{
if (DataModelViewModel != null)
{
DataModelViewModel.Dispose();
DataModelViewModel.UpdateRequested -= DataModelOnUpdateRequested;
}
DataModelViewModel = dataModel;
if (DataModelViewModel != null)
DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested;
}
private void DataModelOnUpdateRequested(object? sender, EventArgs e)
{
if (DataModelUIService == null || DataModelViewModel == null)
return;
DataModelViewModel.Update(DataModelUIService, new DataModelUpdateConfiguration(IsEventPicker));
DataModelViewModel.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);
else if (selected is DataModelListViewModel list && list.DataModelPath != null)
DataModelPath = new DataModelPath(list.DataModelPath);
else if (selected is DataModelEventViewModel dataModelEvent && dataModelEvent.DataModelPath != null)
DataModelPath = new DataModelPath(dataModelEvent.DataModelPath);
}
private void DataModelTreeViewOnDoubleTapped(object? sender, RoutedEventArgs e)
{
TreeViewItem? treeViewItem = (e.Source as ILogical)?.FindLogicalAncestorOfType();
if (treeViewItem != null)
treeViewItem.IsExpanded = !treeViewItem.IsExpanded;
}
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)
_currentPathIcon.Kind = DataModelPath.GetPropertyType().GetTypeIcon();
}
#region Overrides of TemplatedControl
///
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_dataModelTreeView != null)
_dataModelTreeView.SelectionChanged -= DataModelTreeViewOnSelectionChanged;
if (_dataModelTreeView != null)
_dataModelTreeView.DoubleTapped -= DataModelTreeViewOnDoubleTapped;
_currentPathIcon = e.NameScope.Find("CurrentPathIcon");
_currentPathDisplay = e.NameScope.Find("CurrentPathDisplay");
_currentPathDescription = e.NameScope.Find("CurrentPathDescription");
_dataModelTreeView = e.NameScope.Find("DataModelTreeView");
if (_dataModelTreeView != null)
_dataModelTreeView.SelectionChanged += DataModelTreeViewOnSelectionChanged;
if (_dataModelTreeView != null)
_dataModelTreeView.DoubleTapped += DataModelTreeViewOnDoubleTapped;
}
#region Overrides of Visual
///
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
GetDataModel();
UpdateCurrentPath(true);
_updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(200), DispatcherPriority.Background, Update);
_updateTimer.Start();
}
///
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
DataModelViewModel?.Dispose();
_updateTimer?.Stop();
_updateTimer = null;
}
#endregion
#endregion
}