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 }