diff --git a/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker.axaml b/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker.axaml
deleted file mode 100644
index 56783710c..000000000
--- a/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker.axaml
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs b/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs
new file mode 100644
index 000000000..f9055c86e
--- /dev/null
+++ b/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPicker.cs
@@ -0,0 +1,218 @@
+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.Services.Interfaces;
+using Avalonia;
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
+using ReactiveUI;
+
+namespace Artemis.UI.Shared.Controls.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 data model view models to show
+ ///
+ public static readonly StyledProperty?> ExtraDataModelViewModelsProperty =
+ AvaloniaProperty.Register?>(nameof(ExtraDataModelViewModels), new ObservableCollection());
+
+ ///
+ /// A list of types to filter the selectable paths on.
+ ///
+ public static readonly StyledProperty?> FilterTypesProperty =
+ AvaloniaProperty.Register?>(nameof(FilterTypes), new ObservableCollection());
+
+ static DataModelPicker()
+ {
+ ModulesProperty.Changed.Subscribe(ModulesChanged);
+ DataModelViewModelProperty.Changed.Subscribe(DataModelViewModelPropertyChanged);
+ ExtraDataModelViewModelsProperty.Changed.Subscribe(ExtraDataModelViewModelsChanged);
+ }
+
+ ///
+ /// 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);
+ set => SetValue(DataModelViewModelProperty, value);
+ }
+
+ ///
+ /// A list of data model view models to show.
+ ///
+ public ObservableCollection? ExtraDataModelViewModels
+ {
+ get => GetValue(ExtraDataModelViewModelsProperty);
+ set => SetValue(ExtraDataModelViewModelsProperty, value);
+ }
+
+ ///
+ /// A list of types to filter the selectable paths on.
+ ///
+ public ObservableCollection? FilterTypes
+ {
+ get => GetValue(FilterTypesProperty);
+ set => SetValue(FilterTypesProperty, 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);
+ }
+
+ #region Overrides of TemplatedControl
+
+ ///
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ GetDataModel();
+ }
+
+ #endregion
+
+ private static void ModulesChanged(AvaloniaPropertyChangedEventArgs?> e)
+ {
+ if (e.Sender is DataModelPicker dataModelPicker)
+ dataModelPicker.GetDataModel();
+ }
+
+ private static void DataModelViewModelPropertyChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.Sender is DataModelPicker && e.OldValue.Value != null)
+ e.OldValue.Value.Dispose();
+ }
+
+ private static void ExtraDataModelViewModelsChanged(AvaloniaPropertyChangedEventArgs?> e)
+ {
+ // TODO, the original did nothing here either and I can't remember why
+ }
+
+ 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 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)
+ {
+ DataModelViewModel?.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
+ if (ExtraDataModelViewModels == null) return;
+ foreach (DataModelPropertiesViewModel extraDataModelViewModel in ExtraDataModelViewModels)
+ extraDataModelViewModel.ApplyTypeFilter(true, FilterTypes?.ToArray() ?? Type.EmptyTypes);
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker.axaml.cs b/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPickerButton.cs
similarity index 56%
rename from src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker.axaml.cs
rename to src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPickerButton.cs
index b469d9058..8a2a2c898 100644
--- a/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker.axaml.cs
+++ b/src/Avalonia/Artemis.UI.Shared/Controls/DataModelPicker/DataModelPickerButton.cs
@@ -1,45 +1,31 @@
-using System;
-using System.Collections.Generic;
+using System;
using System.Collections.ObjectModel;
using System.Linq;
-using System.Reactive;
using Artemis.Core;
using Artemis.Core.Modules;
+using Artemis.UI.Shared.Controls.Flyouts;
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 Avalonia.Media;
+using Avalonia.Interactivity;
using Avalonia.Threading;
-using ReactiveUI;
+using FluentAvalonia.Core;
-namespace Artemis.UI.Shared.Controls;
+namespace Artemis.UI.Shared.Controls.DataModelPicker;
-public class DataModelPicker : TemplatedControl
+///
+/// Represents a button that can be used to pick a data model path in a flyout.
+///
+public class DataModelPickerButton : TemplatedControl
{
- private static IDataModelUIService? _dataModelUIService;
-
- ///
- /// Gets or sets data model path.
- ///
- public static readonly StyledProperty DataModelPathProperty =
- AvaloniaProperty.Register(nameof(DataModelPath), defaultBindingMode: BindingMode.TwoWay);
-
///
/// Gets or sets the placeholder to show when nothing is selected.
///
public static readonly StyledProperty PlaceholderProperty =
AvaloniaProperty.Register(nameof(Placeholder), "Click to select");
- ///
- /// 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));
-
///
/// Gets or sets a boolean indicating whether the data model picker should show the full path of the selected value.
///
@@ -47,10 +33,28 @@ public class DataModelPicker : TemplatedControl
AvaloniaProperty.Register(nameof(ShowFullPath));
///
- /// Gets or sets the brush to use when drawing the button.
+ /// Gets a boolean indicating whether the data model picker has a value.
///
- public static readonly StyledProperty ButtonBrushProperty =
- AvaloniaProperty.Register(nameof(ButtonBrush));
+ public static readonly StyledProperty HasValueProperty =
+ AvaloniaProperty.Register(nameof(HasValue));
+
+ ///
+ /// Gets or sets the desired flyout placement.
+ ///
+ public static readonly StyledProperty PlacementProperty =
+ AvaloniaProperty.Register(nameof(Placement));
+
+ ///
+ /// 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.
@@ -76,103 +80,16 @@ public class DataModelPicker : TemplatedControl
public static readonly StyledProperty?> FilterTypesProperty =
AvaloniaProperty.Register?>(nameof(FilterTypes), new ObservableCollection());
- ///
- /// Gets a boolean indicating whether the data model picker has a value.
- ///
- public static readonly StyledProperty HasValueProperty =
- AvaloniaProperty.Register(nameof(HasValue));
-
- private Button? _dataModelButton;
private bool _attached;
+ private bool _flyoutActive;
+ private Button? _button;
+ private DataModelPickerFlyout? _flyout;
+ private IDisposable? _dataModelPathChanged;
- static DataModelPicker()
+ static DataModelPickerButton()
{
- DataModelPathProperty.Changed.Subscribe(DataModelPathChanged);
ShowFullPathProperty.Changed.Subscribe(ShowFullPathChanged);
- ModulesProperty.Changed.Subscribe(ModulesChanged);
- DataModelViewModelProperty.Changed.Subscribe(DataModelViewModelPropertyChanged);
- ExtraDataModelViewModelsProperty.Changed.Subscribe(ExtraDataModelViewModelsChanged);
- }
-
- private static void DataModelPathChanged(AvaloniaPropertyChangedEventArgs e)
- {
- if (e.Sender is not DataModelPicker dataModelPicker)
- return;
-
- if (e.OldValue.Value != null)
- {
- e.OldValue.Value.PathInvalidated -= dataModelPicker.PathValidationChanged;
- e.OldValue.Value.PathValidated -= dataModelPicker.PathValidationChanged;
- e.OldValue.Value.Dispose();
- }
-
- if (!dataModelPicker._attached)
- return;
-
- dataModelPicker.UpdateValueDisplay();
- if (e.NewValue.Value != null)
- {
- e.NewValue.Value.PathInvalidated += dataModelPicker.PathValidationChanged;
- e.NewValue.Value.PathValidated += dataModelPicker.PathValidationChanged;
- }
- }
-
- private static void ShowFullPathChanged(AvaloniaPropertyChangedEventArgs e)
- {
- if (e.Sender is DataModelPicker dataModelPicker)
- dataModelPicker.UpdateValueDisplay();
- }
-
- private static void ModulesChanged(AvaloniaPropertyChangedEventArgs?> e)
- {
- if (e.Sender is DataModelPicker dataModelPicker)
- dataModelPicker.GetDataModel();
- }
-
- private static void DataModelViewModelPropertyChanged(AvaloniaPropertyChangedEventArgs e)
- {
- if (e.Sender is DataModelPicker && e.OldValue.Value != null)
- e.OldValue.Value.Dispose();
- }
-
- private static void ExtraDataModelViewModelsChanged(AvaloniaPropertyChangedEventArgs?> e)
- {
- // TODO, the original did nothing here either and I can't remember why
- }
-
- ///
- /// 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; }
-
- ///
- /// Internal, don't use.
- ///
- public static IDataModelUIService DataModelUIService
- {
- set
- {
- if (_dataModelUIService != null)
- throw new AccessViolationException("This is not for you to touch");
- _dataModelUIService = value;
- }
- }
-
- ///
- /// Gets or sets data model path.
- ///
- public DataModelPath? DataModelPath
- {
- get => GetValue(DataModelPathProperty);
- set => SetValue(DataModelPathProperty, value);
+ DataModelPathProperty.Changed.Subscribe(DataModelPathChanged);
}
///
@@ -193,6 +110,33 @@ public class DataModelPicker : TemplatedControl
set => SetValue(ShowFullPathProperty, value);
}
+ ///
+ /// Gets a boolean indicating whether the data model picker has a value.
+ ///
+ public bool HasValue
+ {
+ get => GetValue(HasValueProperty);
+ private set => SetValue(HasValueProperty, value);
+ }
+
+ ///
+ /// Gets or sets the desired flyout placement.
+ ///
+ public FlyoutPlacementMode Placement
+ {
+ get => GetValue(PlacementProperty);
+ set => SetValue(PlacementProperty, value);
+ }
+
+ ///
+ /// 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.
///
@@ -202,15 +146,6 @@ public class DataModelPicker : TemplatedControl
set => SetValue(ShowDataModelValuesProperty, value);
}
- ///
- /// Gets or sets the brush to use when drawing the button.
- ///
- public Brush ButtonBrush
- {
- get => GetValue(ButtonBrushProperty);
- set => SetValue(ButtonBrushProperty, value);
- }
-
///
/// A list of extra modules to show data models of.
///
@@ -248,58 +183,111 @@ public class DataModelPicker : TemplatedControl
}
///
- /// Gets a boolean indicating whether the data model picker has a value.
+ /// Raised when the flyout opens.
///
- public bool HasValue
- {
- get => GetValue(HasValueProperty);
- private set => SetValue(HasValueProperty, value);
- }
+ public event TypedEventHandler? FlyoutOpened;
///
- /// Occurs when a new path has been selected
+ /// Raised when the flyout closes.
///
- public event EventHandler? DataModelPathSelected;
+ public event TypedEventHandler? FlyoutClosed;
- ///
- /// Invokes the event
- ///
- ///
- protected virtual void OnDataModelPathSelected(DataModelSelectedEventArgs e)
+ private static void DataModelPathChanged(AvaloniaPropertyChangedEventArgs e)
{
- DataModelPathSelected?.Invoke(this, e);
- }
-
- private void ExecuteSelectPropertyCommand(DataModelVisualizationViewModel selected)
- {
- if (selected.DataModelPath == null)
- return;
- if (selected.DataModelPath.Equals(DataModelPath))
+ if (e.Sender is not DataModelPickerButton self || !self._attached)
return;
- DataModelPath = new DataModelPath(selected.DataModelPath);
- OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath));
- }
-
- private void GetDataModel()
- {
- if (_dataModelUIService == null)
- return;
-
- ChangeDataModel(_dataModelUIService.GetPluginDataModelVisualization(Modules?.ToList() ?? new List(), true));
- }
-
- private void ChangeDataModel(DataModelPropertiesViewModel? dataModel)
- {
- if (DataModelViewModel != null)
+ if (e.OldValue.Value != null)
{
- DataModelViewModel.Dispose();
- DataModelViewModel.UpdateRequested -= DataModelOnUpdateRequested;
+ e.OldValue.Value.PathInvalidated -= self.PathValidationChanged;
+ e.OldValue.Value.PathValidated -= self.PathValidationChanged;
+ e.OldValue.Value.Dispose();
}
- DataModelViewModel = dataModel;
- if (DataModelViewModel != null)
- DataModelViewModel.UpdateRequested += DataModelOnUpdateRequested;
+ if (e.NewValue.Value != null)
+ {
+ e.NewValue.Value.PathInvalidated += self.PathValidationChanged;
+ e.NewValue.Value.PathValidated += self.PathValidationChanged;
+ }
+
+ self.UpdateValueDisplay();
+ }
+
+ private static void ShowFullPathChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.Sender is DataModelPickerButton self)
+ self.UpdateValueDisplay();
+ }
+
+ private void FlyoutDataModelPathChanged(AvaloniaPropertyChangedEventArgs 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);
+ }
+
+ private void UpdateValueDisplay()
+ {
+ HasValue = DataModelPath != null && DataModelPath.IsValid;
+
+ if (_button == null)
+ return;
+
+ if (!HasValue)
+ {
+ ToolTip.SetTip(_button, null);
+ _button.Content = Placeholder;
+ }
+ else
+ {
+ string? formattedPath = null;
+ if (DataModelPath != null && DataModelPath.IsValid)
+ formattedPath = string.Join(" › ", DataModelPath.Segments.Where(s => s.GetPropertyDescription() != null).Select(s => s.GetPropertyDescription()!.Name));
+
+ ToolTip.SetTip(_button, formattedPath);
+ _button.Content = ShowFullPath
+ ? formattedPath
+ : DataModelPath?.Segments.LastOrDefault()?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier;
+ }
+ }
+
+ private void OnButtonClick(object? sender, RoutedEventArgs e)
+ {
+ if (_flyout == null)
+ return;
+
+ // 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;
+ _flyout.DataModelPicker.ShowDataModelValues = ShowDataModelValues;
+
+ _flyout.Placement = Placement;
+ _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
@@ -307,17 +295,15 @@ public class DataModelPicker : TemplatedControl
///
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
+ if (_button != null)
+ _button.Click -= OnButtonClick;
base.OnApplyTemplate(e);
- _dataModelButton = e.NameScope.Find