using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Timers; using System.Windows.Media; using Artemis.Core; using Artemis.Core.Modules; using Artemis.Core.Services; using Artemis.UI.Shared.Services; using MaterialDesignColors.ColorManipulation; using Stylet; namespace Artemis.UI.Shared.Input { /// /// Represents a view model that allows selecting a data model property used by boolean operations on a certain data /// model property /// public class DataModelDynamicViewModel : PropertyChangedBase, IDisposable { private readonly List _modules; private readonly IDataModelUIService _dataModelUIService; private readonly Timer _updateTimer; private SolidColorBrush _buttonBrush = new(Color.FromRgb(171, 71, 188)); private DataModelPath? _dataModelPath; private DataModelPropertiesViewModel? _dataModelViewModel; private bool _displaySwitchButton; private Type[] _filterTypes = Array.Empty(); private bool _isDataModelViewModelOpen; private bool _isEnabled = true; private string _placeholder = "Select a property"; private readonly PluginSetting _showFullPath; internal DataModelDynamicViewModel(List modules, ISettingsService settingsService, IDataModelUIService dataModelUIService) { _modules = modules; _dataModelUIService = dataModelUIService; _updateTimer = new Timer(500); _showFullPath = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true); ExtraDataModelViewModels = new BindableCollection(); ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues"); SelectPropertyCommand = new DelegateCommand(ExecuteSelectPropertyCommand); Initialize(); } /// /// Gets or sets the brush to use for the input button /// public SolidColorBrush ButtonBrush { get => _buttonBrush; set { if (!SetAndNotify(ref _buttonBrush, value)) return; NotifyOfPropertyChange(nameof(SwitchButtonBrush)); } } /// /// Gets the brush to use for the switch button /// public SolidColorBrush SwitchButtonBrush => new(ButtonBrush.Color.Darken()); /// /// Gets or sets the placeholder text when no value is entered /// public string Placeholder { get => _placeholder; set => SetAndNotify(ref _placeholder, value); } /// /// Gets or sets the enabled state of the input /// public bool IsEnabled { get => _isEnabled; set => SetAndNotify(ref _isEnabled, value); } /// /// Gets or sets whether the switch button should be displayed /// public bool DisplaySwitchButton { get => _displaySwitchButton; set => SetAndNotify(ref _displaySwitchButton, value); } /// /// Gets or sets the types of properties this view model will allow to be selected /// public Type[] FilterTypes { get => _filterTypes; set { if (!SetAndNotify(ref _filterTypes, value)) return; DataModelViewModel?.ApplyTypeFilter(true, FilterTypes); } } /// /// Gets a bindable collection of extra data model view models to show /// public BindableCollection ExtraDataModelViewModels { get; } /// /// Gets a boolean indicating whether there are any modules providing data models /// public bool HasNoModules => (DataModelViewModel == null || !DataModelViewModel.Children.Any()) && !HasExtraDataModels; /// /// Gets a boolean indicating whether there are any extra data models /// public bool HasExtraDataModels => ExtraDataModelViewModels.Any(); /// /// Command used by view /// public DelegateCommand SelectPropertyCommand { get; } /// /// Setting used by view /// public PluginSetting ShowDataModelValues { get; } public PluginSetting ShowFullPath { get; } /// /// Gets or sets root the data model view model /// public DataModelPropertiesViewModel? DataModelViewModel { get => _dataModelViewModel; private set => SetAndNotify(ref _dataModelViewModel, value); } /// /// Gets or sets a boolean indicating whether the data model is open /// public bool IsDataModelViewModelOpen { get => _isDataModelViewModelOpen; set { if (!SetAndNotify(ref _isDataModelViewModelOpen, value)) return; if (value) { UpdateDataModelVisualization(); if (DataModelViewModel != null) OpenSelectedValue(DataModelViewModel); } } } /// /// Gets or sets the data model path of the currently selected data model property /// public DataModelPath? DataModelPath { get => _dataModelPath; private set { if (!SetAndNotify(ref _dataModelPath, value)) return; NotifyOfPropertyChange(nameof(IsValid)); NotifyOfPropertyChange(nameof(DisplayValue)); NotifyOfPropertyChange(nameof(DisplayPath)); } } /// /// Gets a boolean indicating whether the current selection is valid /// public bool IsValid => DataModelPath?.IsValid ?? true; /// /// Gets the display name of the currently selected property /// public string? DisplayValue { get { if (_showFullPath.Value) return DisplayPath; return DataModelPath?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier; } } /// /// Gets the human readable path of the currently selected property /// public string DisplayPath { get { if (DataModelPath == null) return "Click to select a property"; if (!DataModelPath.IsValid) return "Invalid path"; return string.Join(" › ", DataModelPath.Segments.Where(s => s.GetPropertyDescription()!= null).Select(s => s.GetPropertyDescription()!.Name)); } } /// /// Gets or sets a boolean indicating whether event child VMs should be loaded, defaults to /// public bool LoadEventChildren { get; set; } = true; /// /// Changes the root data model VM stored in to the provided /// /// /// The data model VM to set the new root data model to public void ChangeDataModel(DataModelPropertiesViewModel dataModel) { if (DataModelViewModel != null) DataModelViewModel.UpdateRequested -= DataModelOnUpdateRequested; DataModelViewModel = dataModel; if (DataModelViewModel != null) DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested; } /// /// Changes the currently selected property by its path /// /// The path of the property to set the selection to public void ChangeDataModelPath(DataModelPath? dataModelPath) { DataModelPath?.Dispose(); DataModelPath = dataModelPath != null ? new DataModelPath(dataModelPath) : null; } /// /// Requests switching the input type to static using a /// public void SwitchToStatic() { ChangeDataModelPath(null); OnPropertySelected(new DataModelInputDynamicEventArgs(null)); OnSwitchToStaticRequested(); } private void Initialize() { // Get the data models DataModelViewModel = _dataModelUIService.GetPluginDataModelVisualization(_modules, true); if (DataModelViewModel != null) DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested; ExtraDataModelViewModels.CollectionChanged += ExtraDataModelViewModelsOnCollectionChanged; _updateTimer.Start(); _updateTimer.Elapsed += OnUpdateTimerOnElapsed; _showFullPath.SettingChanged += ShowFullPathOnSettingChanged; } private void ExecuteSelectPropertyCommand(object? context) { if (context is not DataModelVisualizationViewModel selected) return; ChangeDataModelPath(selected.DataModelPath); OnPropertySelected(new DataModelInputDynamicEventArgs(DataModelPath)); } #region IDisposable /// /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// /// to release both managed and unmanaged resources; /// to release only unmanaged resources. /// protected virtual void Dispose(bool disposing) { if (disposing) { _updateTimer.Stop(); _updateTimer.Dispose(); _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; _showFullPath.SettingChanged -= ShowFullPathOnSettingChanged; DataModelViewModel?.Dispose(); DataModelPath?.Dispose(); } } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion #region Event handlers private void DataModelOnUpdateRequested(object? sender, EventArgs e) { DataModelViewModel?.ApplyTypeFilter(true, FilterTypes); foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels) extraDataModelViewModel.ApplyTypeFilter(true, FilterTypes); } private void ExtraDataModelViewModelsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { NotifyOfPropertyChange(nameof(HasExtraDataModels)); } private void OnUpdateTimerOnElapsed(object? sender, ElapsedEventArgs e) { if (!IsDataModelViewModelOpen) return; UpdateDataModelVisualization(); } private void UpdateDataModelVisualization() { DataModelViewModel?.Update(_dataModelUIService, new DataModelUpdateConfiguration(LoadEventChildren)); foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels) extraDataModelViewModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(LoadEventChildren)); } private void OpenSelectedValue(DataModelVisualizationViewModel dataModelPropertiesViewModel) { if (DataModelPath == null) return; if (dataModelPropertiesViewModel.Children.Any(c => c.DataModelPath != null && DataModelPath.Path.StartsWith(c.DataModelPath.Path))) { dataModelPropertiesViewModel.IsVisualizationExpanded = true; foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in dataModelPropertiesViewModel.Children) { OpenSelectedValue(dataModelVisualizationViewModel); } } } private void ShowFullPathOnSettingChanged(object? sender, EventArgs e) { NotifyOfPropertyChange(nameof(DisplayValue)); } #endregion #region Events /// /// Occurs when anew property has been selected /// public event EventHandler? PropertySelected; /// /// Occurs when a switch to static input has been requested /// public event EventHandler? SwitchToStaticRequested; /// /// Invokes the event /// /// protected virtual void OnPropertySelected(DataModelInputDynamicEventArgs e) { PropertySelected?.Invoke(this, e); } /// /// Invokes the event /// protected virtual void OnSwitchToStaticRequested() { SwitchToStaticRequested?.Invoke(this, EventArgs.Empty); } #endregion } }