mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 03:48:56 +00:00
Code style - Use file scoped namespaces
Code style - Ran code cleanup
This commit is contained in:
1 parent
e68e16df4d
commit
f6090dc296
691 files changed
+37301
-38119
No files matched your search
@@ -28,10 +28,10 @@ public class TextBoxAssist : AvaloniaObject
|
||||
{
|
||||
element.SetValue(SuffixTextProperty, value);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(value) && !((TextBox)element).Classes.Contains("suffixed"))
|
||||
((TextBox)element).Classes.Add("suffixed");
|
||||
if (!string.IsNullOrWhiteSpace(value) && !((TextBox) element).Classes.Contains("suffixed"))
|
||||
((TextBox) element).Classes.Add("suffixed");
|
||||
else
|
||||
((TextBox)element).Classes.Remove("suffixed");
|
||||
((TextBox) element).Classes.Remove("suffixed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -49,10 +49,10 @@ public class TextBoxAssist : AvaloniaObject
|
||||
{
|
||||
element.SetValue(PrefixTextProperty, value);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(value) && !((TextBox)element).Classes.Contains("prefixed"))
|
||||
((TextBox)element).Classes.Add("prefixed");
|
||||
if (!string.IsNullOrWhiteSpace(value) && !((TextBox) element).Classes.Contains("prefixed"))
|
||||
((TextBox) element).Classes.Add("prefixed");
|
||||
else
|
||||
((TextBox)element).Classes.Remove("prefixed");
|
||||
((TextBox) element).Classes.Remove("prefixed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Artemis.UI.Shared.Behaviors;
|
||||
public class LostFocusNumberBoxBindingBehavior : Behavior<NumberBox>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the binding.
|
||||
/// Gets or sets the value of the binding.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> ValueProperty = AvaloniaProperty.Register<LostFocusTextBoxBindingBehavior, double>(
|
||||
nameof(Value), defaultBindingMode: BindingMode.TwoWay);
|
||||
@@ -24,7 +24,7 @@ public class LostFocusNumberBoxBindingBehavior : Behavior<NumberBox>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the binding.
|
||||
/// Gets or sets the value of the binding.
|
||||
/// </summary>
|
||||
public double Value
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Artemis.UI.Shared.Behaviors;
|
||||
public class LostFocusTextBoxBindingBehavior : Behavior<TextBox>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the binding.
|
||||
/// Gets or sets the value of the binding.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<LostFocusTextBoxBindingBehavior, string>(
|
||||
"Text", defaultBindingMode: BindingMode.TwoWay);
|
||||
@@ -24,7 +24,7 @@ public class LostFocusTextBoxBindingBehavior : Behavior<TextBox>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the binding.
|
||||
/// Gets or sets the value of the binding.
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@ using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Xaml.Interactivity;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
|
||||
namespace Artemis.UI.Shared.Behaviors;
|
||||
|
||||
@@ -14,7 +13,7 @@ namespace Artemis.UI.Shared.Behaviors;
|
||||
public class SliderPointerReleasedBindingBehavior : Behavior<Slider>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the binding.
|
||||
/// Gets or sets the value of the binding.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> ValueProperty = AvaloniaProperty.Register<LostFocusTextBoxBindingBehavior, double>(
|
||||
nameof(Value), defaultBindingMode: BindingMode.TwoWay);
|
||||
@@ -25,7 +24,7 @@ public class SliderPointerReleasedBindingBehavior : Behavior<Slider>
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the binding.
|
||||
/// Gets or sets the value of the binding.
|
||||
/// </summary>
|
||||
public double Value
|
||||
{
|
||||
|
||||
@@ -11,133 +11,135 @@ using Avalonia.Visuals.Media.Imaging;
|
||||
using Material.Icons;
|
||||
using Material.Icons.Avalonia;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a control that can display an arbitrary kind of icon.
|
||||
/// </summary>
|
||||
public class ArtemisIcon : UserControl
|
||||
{
|
||||
private static readonly Regex _imageRegex = new(@"[\/.](gif|jpg|jpeg|tiff|png)$", RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a control that can display an arbitrary kind of icon.
|
||||
/// Creates a new instance of the <see cref="ArtemisIcon" /> class.
|
||||
/// </summary>
|
||||
public class ArtemisIcon : UserControl
|
||||
public ArtemisIcon()
|
||||
{
|
||||
private static Regex _imageRegex = new(@"[\/.](gif|jpg|jpeg|tiff|png)$", RegexOptions.Compiled);
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ArtemisIcon" /> class.
|
||||
/// </summary>
|
||||
public ArtemisIcon()
|
||||
{
|
||||
InitializeComponent();
|
||||
DetachedFromLogicalTree += OnDetachedFromLogicalTree;
|
||||
LayoutUpdated += OnLayoutUpdated;
|
||||
}
|
||||
InitializeComponent();
|
||||
DetachedFromLogicalTree += OnDetachedFromLogicalTree;
|
||||
LayoutUpdated += OnLayoutUpdated;
|
||||
}
|
||||
|
||||
private static void IconChanging(IAvaloniaObject sender, bool before)
|
||||
{
|
||||
if (before)
|
||||
((ArtemisIcon) sender).Update();
|
||||
}
|
||||
private static void IconChanging(IAvaloniaObject sender, bool before)
|
||||
{
|
||||
if (before)
|
||||
((ArtemisIcon) sender).Update();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
private void Update()
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
// First look for an enum value instead of a string
|
||||
if (Icon is MaterialIconKind materialIcon)
|
||||
{
|
||||
// First look for an enum value instead of a string
|
||||
if (Icon is MaterialIconKind materialIcon)
|
||||
Content = new MaterialIcon {Kind = materialIcon, Width = Bounds.Width, Height = Bounds.Height};
|
||||
// If it's a string there are several options
|
||||
else if (Icon is string iconString)
|
||||
Content = new MaterialIcon {Kind = materialIcon, Width = Bounds.Width, Height = Bounds.Height};
|
||||
}
|
||||
// If it's a string there are several options
|
||||
else if (Icon is string iconString)
|
||||
{
|
||||
// An enum defined as a string
|
||||
if (Enum.TryParse(iconString, true, out MaterialIconKind parsedIcon))
|
||||
{
|
||||
// An enum defined as a string
|
||||
if (Enum.TryParse(iconString, true, out MaterialIconKind parsedIcon))
|
||||
Content = new MaterialIcon {Kind = parsedIcon, Width = Bounds.Width, Height = Bounds.Height};
|
||||
// An URI pointing to an image
|
||||
else if (_imageRegex.IsMatch(iconString))
|
||||
{
|
||||
if (!Fill)
|
||||
Content = new MaterialIcon {Kind = parsedIcon, Width = Bounds.Width, Height = Bounds.Height};
|
||||
}
|
||||
// An URI pointing to an image
|
||||
else if (_imageRegex.IsMatch(iconString))
|
||||
{
|
||||
if (!Fill)
|
||||
Content = new Image
|
||||
{
|
||||
Content = new Image
|
||||
{
|
||||
Source = new Bitmap(iconString),
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
Content = new Border
|
||||
{
|
||||
Background = TextBlock.GetForeground(this),
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
OpacityMask = new ImageBrush(new Bitmap(iconString)) {BitmapInterpolationMode = BitmapInterpolationMode.MediumQuality}
|
||||
};
|
||||
}
|
||||
}
|
||||
Source = new Bitmap(iconString),
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
};
|
||||
else
|
||||
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark, Width = Bounds.Width, Height = Bounds.Height};
|
||||
Content = new Border
|
||||
{
|
||||
Background = TextBlock.GetForeground(this),
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
OpacityMask = new ImageBrush(new Bitmap(iconString)) {BitmapInterpolationMode = BitmapInterpolationMode.MediumQuality}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark, Width = Bounds.Width, Height = Bounds.Height};
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark, Width = Bounds.Width, Height = Bounds.Height};
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLayoutUpdated(object? sender, EventArgs e)
|
||||
catch
|
||||
{
|
||||
if (Content is Control contentControl)
|
||||
{
|
||||
contentControl.Width = Bounds.Width;
|
||||
contentControl.Height = Bounds.Height;
|
||||
}
|
||||
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark, Width = Bounds.Width, Height = Bounds.Height};
|
||||
}
|
||||
|
||||
private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (Content is Image image && image.Source is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
|
||||
/// pointing to an SVG
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<object?> IconProperty =
|
||||
AvaloniaProperty.Register<ArtemisIcon, object?>(nameof(Icon), notifying: IconChanging);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
|
||||
/// pointing to an SVG
|
||||
/// </summary>
|
||||
public object? Icon
|
||||
{
|
||||
get => GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the
|
||||
/// theme
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> FillProperty =
|
||||
AvaloniaProperty.Register<ArtemisIcon, bool>(nameof(Icon), defaultValue: true, notifying: IconChanging);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the
|
||||
/// theme
|
||||
/// </summary>
|
||||
public bool Fill
|
||||
{
|
||||
get => GetValue(FillProperty);
|
||||
set => SetValue(FillProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
private void OnLayoutUpdated(object? sender, EventArgs e)
|
||||
{
|
||||
if (Content is Control contentControl)
|
||||
{
|
||||
contentControl.Width = Bounds.Width;
|
||||
contentControl.Height = Bounds.Height;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (Content is Image image && image.Source is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
|
||||
/// pointing to an SVG
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<object?> IconProperty =
|
||||
AvaloniaProperty.Register<ArtemisIcon, object?>(nameof(Icon), notifying: IconChanging);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
|
||||
/// pointing to an SVG
|
||||
/// </summary>
|
||||
public object? Icon
|
||||
{
|
||||
get => GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the
|
||||
/// theme
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> FillProperty =
|
||||
AvaloniaProperty.Register<ArtemisIcon, bool>(nameof(Icon), true, notifying: IconChanging);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether or not the icon should be filled in with the primary text color of the
|
||||
/// theme
|
||||
/// </summary>
|
||||
public bool Fill
|
||||
{
|
||||
get => GetValue(FillProperty);
|
||||
set => SetValue(FillProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -44,7 +44,7 @@ public class DataModelPickerButton : TemplatedControl
|
||||
AvaloniaProperty.Register<DataModelPicker, bool>(nameof(HasValue));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the desired flyout placement.
|
||||
/// Gets or sets the desired flyout placement.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<FlyoutPlacementMode> PlacementProperty =
|
||||
AvaloniaProperty.Register<FlyoutBase, FlyoutPlacementMode>(nameof(Placement));
|
||||
@@ -82,10 +82,10 @@ public class DataModelPickerButton : TemplatedControl
|
||||
AvaloniaProperty.Register<DataModelPicker, bool>(nameof(IsEventPicker));
|
||||
|
||||
private bool _attached;
|
||||
private bool _flyoutActive;
|
||||
private Button? _button;
|
||||
private TextBlock? _label;
|
||||
private DataModelPickerFlyout? _flyout;
|
||||
private bool _flyoutActive;
|
||||
private TextBlock? _label;
|
||||
|
||||
static DataModelPickerButton()
|
||||
{
|
||||
@@ -131,7 +131,7 @@ public class DataModelPickerButton : TemplatedControl
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the desired flyout placement.
|
||||
/// Gets or sets the desired flyout placement.
|
||||
/// </summary>
|
||||
public FlyoutPlacementMode Placement
|
||||
{
|
||||
|
||||
@@ -17,326 +17,325 @@ using Avalonia.Rendering;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.Visuals.Media.Imaging;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors
|
||||
/// </summary>
|
||||
public class DeviceVisualizer : Control
|
||||
{
|
||||
/// <summary>
|
||||
/// Visualizes an <see cref="ArtemisDevice" /> with optional per-LED colors
|
||||
/// </summary>
|
||||
public class DeviceVisualizer : Control
|
||||
private const double UpdateFrameRate = 25.0;
|
||||
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
|
||||
private readonly DispatcherTimer _timer;
|
||||
|
||||
private Rect _deviceBounds;
|
||||
private RenderTargetBitmap? _deviceImage;
|
||||
private List<DeviceVisualizerLed>? _dimmedLeds;
|
||||
private List<DeviceVisualizerLed>? _highlightedLeds;
|
||||
private ArtemisDevice? _oldDevice;
|
||||
|
||||
/// <inheritdoc />
|
||||
public DeviceVisualizer()
|
||||
{
|
||||
private const double UpdateFrameRate = 25.0;
|
||||
private readonly List<DeviceVisualizerLed> _deviceVisualizerLeds;
|
||||
private readonly DispatcherTimer _timer;
|
||||
_timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(1000.0 / UpdateFrameRate)};
|
||||
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
|
||||
|
||||
private Rect _deviceBounds;
|
||||
private RenderTargetBitmap? _deviceImage;
|
||||
private List<DeviceVisualizerLed>? _dimmedLeds;
|
||||
private List<DeviceVisualizerLed>? _highlightedLeds;
|
||||
private ArtemisDevice? _oldDevice;
|
||||
PointerReleased += OnPointerReleased;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public DeviceVisualizer()
|
||||
/// <inheritdoc />
|
||||
public override void Render(DrawingContext drawingContext)
|
||||
{
|
||||
if (Device == null)
|
||||
return;
|
||||
|
||||
// Determine the scale required to fit the desired size of the control
|
||||
double scale = Math.Min(Bounds.Width / _deviceBounds.Width, Bounds.Height / _deviceBounds.Height);
|
||||
|
||||
DrawingContext.PushedState? boundsPush = null;
|
||||
try
|
||||
{
|
||||
_timer = new DispatcherTimer(DispatcherPriority.Render) {Interval = TimeSpan.FromMilliseconds(1000.0 / UpdateFrameRate)};
|
||||
_deviceVisualizerLeds = new List<DeviceVisualizerLed>();
|
||||
// Scale the visualization in the desired bounding box
|
||||
if (Bounds.Width > 0 && Bounds.Height > 0)
|
||||
boundsPush = drawingContext.PushPreTransform(Matrix.CreateScale(scale, scale));
|
||||
|
||||
PointerReleased += OnPointerReleased;
|
||||
}
|
||||
// Apply device rotation
|
||||
using DrawingContext.PushedState translationPush = drawingContext.PushPreTransform(Matrix.CreateTranslation(0 - _deviceBounds.Left, 0 - _deviceBounds.Top));
|
||||
using DrawingContext.PushedState rotationPush = drawingContext.PushPreTransform(Matrix.CreateRotation(Matrix.ToRadians(Device.Rotation)));
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Render(DrawingContext drawingContext)
|
||||
{
|
||||
if (Device == null)
|
||||
// Render device and LED images
|
||||
if (_deviceImage != null)
|
||||
drawingContext.DrawImage(
|
||||
_deviceImage,
|
||||
new Rect(_deviceImage.Size),
|
||||
new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height),
|
||||
RenderOptions.GetBitmapInterpolationMode(this)
|
||||
);
|
||||
|
||||
if (!ShowColors)
|
||||
return;
|
||||
|
||||
// Determine the scale required to fit the desired size of the control
|
||||
double scale = Math.Min(Bounds.Width / _deviceBounds.Width, Bounds.Height / _deviceBounds.Height);
|
||||
lock (_deviceVisualizerLeds)
|
||||
{
|
||||
// Apply device scale
|
||||
using DrawingContext.PushedState scalePush = drawingContext.PushPreTransform(Matrix.CreateScale(Device.Scale, Device.Scale));
|
||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
||||
deviceVisualizerLed.RenderGeometry(drawingContext, false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
boundsPush?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a LED of the device has been clicked
|
||||
/// </summary>
|
||||
public event EventHandler<LedClickedEventArgs>? LedClicked;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the device was clicked but not on a LED.
|
||||
/// </summary>
|
||||
public event EventHandler<PointerReleasedEventArgs>? Clicked;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="LedClicked" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnLedClicked(LedClickedEventArgs e)
|
||||
{
|
||||
LedClicked?.Invoke(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="Clicked" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnClicked(PointerReleasedEventArgs e)
|
||||
{
|
||||
Clicked?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
private Rect MeasureDevice()
|
||||
{
|
||||
if (Device == null)
|
||||
return Rect.Empty;
|
||||
|
||||
Rect deviceRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
|
||||
Geometry geometry = new RectangleGeometry(deviceRect);
|
||||
geometry.Transform = new RotateTransform(Device.Rotation);
|
||||
|
||||
return geometry.Bounds;
|
||||
}
|
||||
|
||||
private void TimerOnTick(object? sender, EventArgs e)
|
||||
{
|
||||
if (ShowColors && IsVisible && Opacity > 0)
|
||||
Update();
|
||||
}
|
||||
|
||||
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
if (Device == null)
|
||||
return;
|
||||
|
||||
Point position = e.GetPosition(this);
|
||||
double x = position.X / Bounds.Width;
|
||||
double y = position.Y / Bounds.Height;
|
||||
|
||||
Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height);
|
||||
DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.HitTest(scaledPosition));
|
||||
if (deviceVisualizerLed != null)
|
||||
OnLedClicked(new LedClickedEventArgs(deviceVisualizerLed.Led.Device, deviceVisualizerLed.Led, e));
|
||||
else
|
||||
OnClicked(e);
|
||||
}
|
||||
|
||||
private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
Dispatcher.UIThread.Post(SetupForDevice, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
private void DeviceUpdated(object? sender, EventArgs e)
|
||||
{
|
||||
Dispatcher.UIThread.Post(SetupForDevice, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ArtemisDevice" /> to display
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<ArtemisDevice?> DeviceProperty =
|
||||
AvaloniaProperty.Register<DeviceVisualizer, ArtemisDevice?>(nameof(Device), notifying: DeviceUpdated);
|
||||
|
||||
private static void DeviceUpdated(IAvaloniaObject sender, bool before)
|
||||
{
|
||||
if (!before)
|
||||
((DeviceVisualizer) sender).SetupForDevice();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ArtemisDevice" /> to display
|
||||
/// </summary>
|
||||
public ArtemisDevice? Device
|
||||
{
|
||||
get => GetValue(DeviceProperty);
|
||||
set => SetValue(DeviceProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets boolean indicating whether or not to show per-LED colors
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> ShowColorsProperty =
|
||||
AvaloniaProperty.Register<DeviceVisualizer, bool>(nameof(ShowColors));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether or not to show per-LED colors
|
||||
/// </summary>
|
||||
public bool ShowColors
|
||||
{
|
||||
get => GetValue(ShowColorsProperty);
|
||||
set => SetValue(ShowColorsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of LEDs to highlight
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<ObservableCollection<ArtemisLed>?> HighlightedLedsProperty =
|
||||
AvaloniaProperty.Register<DeviceVisualizer, ObservableCollection<ArtemisLed>?>(nameof(HighlightedLeds));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of LEDs to highlight
|
||||
/// </summary>
|
||||
public ObservableCollection<ArtemisLed>? HighlightedLeds
|
||||
{
|
||||
get => GetValue(HighlightedLedsProperty);
|
||||
set => SetValue(HighlightedLedsProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifetime management
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
_deviceImage?.Dispose();
|
||||
_deviceImage = null;
|
||||
|
||||
if (Device != null)
|
||||
{
|
||||
Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
||||
Device.DeviceUpdated -= DeviceUpdated;
|
||||
}
|
||||
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
_timer.Start();
|
||||
_timer.Tick += TimerOnTick;
|
||||
base.OnAttachedToLogicalTree(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Tick -= TimerOnTick;
|
||||
base.OnDetachedFromLogicalTree(e);
|
||||
}
|
||||
|
||||
private void SetupForDevice()
|
||||
{
|
||||
_highlightedLeds = new List<DeviceVisualizerLed>();
|
||||
_dimmedLeds = new List<DeviceVisualizerLed>();
|
||||
|
||||
lock (_deviceVisualizerLeds)
|
||||
{
|
||||
_deviceVisualizerLeds.Clear();
|
||||
}
|
||||
|
||||
if (_oldDevice != null)
|
||||
{
|
||||
_oldDevice.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
||||
_oldDevice.DeviceUpdated -= DeviceUpdated;
|
||||
}
|
||||
|
||||
_oldDevice = Device;
|
||||
if (Device == null)
|
||||
return;
|
||||
|
||||
_deviceBounds = MeasureDevice();
|
||||
|
||||
Device.RgbDevice.PropertyChanged += DevicePropertyChanged;
|
||||
Device.DeviceUpdated += DeviceUpdated;
|
||||
|
||||
// Create all the LEDs
|
||||
lock (_deviceVisualizerLeds)
|
||||
{
|
||||
foreach (ArtemisLed artemisLed in Device.Leds)
|
||||
_deviceVisualizerLeds.Add(new DeviceVisualizerLed(artemisLed));
|
||||
}
|
||||
|
||||
// Load the device main image on a background thread
|
||||
ArtemisDevice? device = Device;
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (device.Layout?.Image == null || !File.Exists(device.Layout.Image.LocalPath))
|
||||
{
|
||||
_deviceImage?.Dispose();
|
||||
_deviceImage = null;
|
||||
return;
|
||||
}
|
||||
|
||||
DrawingContext.PushedState? boundsPush = null;
|
||||
try
|
||||
{
|
||||
// Scale the visualization in the desired bounding box
|
||||
if (Bounds.Width > 0 && Bounds.Height > 0)
|
||||
boundsPush = drawingContext.PushPreTransform(Matrix.CreateScale(scale, scale));
|
||||
// Create a bitmap that'll be used to render the device and LED images just once
|
||||
// Render 4 times the actual size of the device to make sure things look sharp when zoomed in
|
||||
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) device.RgbDevice.ActualSize.Width * 2, (int) device.RgbDevice.ActualSize.Height * 2));
|
||||
|
||||
// Apply device rotation
|
||||
using DrawingContext.PushedState translationPush = drawingContext.PushPreTransform(Matrix.CreateTranslation(0 - _deviceBounds.Left, 0 - _deviceBounds.Top));
|
||||
using DrawingContext.PushedState rotationPush = drawingContext.PushPreTransform(Matrix.CreateRotation(Matrix.ToRadians(Device.Rotation)));
|
||||
|
||||
// Render device and LED images
|
||||
if (_deviceImage != null)
|
||||
drawingContext.DrawImage(
|
||||
_deviceImage,
|
||||
new Rect(_deviceImage.Size),
|
||||
new Rect(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height),
|
||||
RenderOptions.GetBitmapInterpolationMode(this)
|
||||
);
|
||||
|
||||
if (!ShowColors)
|
||||
return;
|
||||
using IDrawingContextImpl context = renderTargetBitmap.CreateDrawingContext(new ImmediateRenderer(this));
|
||||
using Bitmap bitmap = new(device.Layout.Image.LocalPath);
|
||||
context.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), new Rect(renderTargetBitmap.Size), BitmapInterpolationMode.HighQuality);
|
||||
|
||||
lock (_deviceVisualizerLeds)
|
||||
{
|
||||
// Apply device scale
|
||||
using DrawingContext.PushedState scalePush = drawingContext.PushPreTransform(Matrix.CreateScale(Device.Scale, Device.Scale));
|
||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
||||
deviceVisualizerLed.RenderGeometry(drawingContext, false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
boundsPush?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a LED of the device has been clicked
|
||||
/// </summary>
|
||||
public event EventHandler<LedClickedEventArgs>? LedClicked;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the device was clicked but not on a LED.
|
||||
/// </summary>
|
||||
public event EventHandler<PointerReleasedEventArgs>? Clicked;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="LedClicked" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnLedClicked(LedClickedEventArgs e)
|
||||
{
|
||||
LedClicked?.Invoke(this, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="Clicked" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnClicked(PointerReleasedEventArgs e)
|
||||
{
|
||||
Clicked?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
private Rect MeasureDevice()
|
||||
{
|
||||
if (Device == null)
|
||||
return Rect.Empty;
|
||||
|
||||
Rect deviceRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
|
||||
Geometry geometry = new RectangleGeometry(deviceRect);
|
||||
geometry.Transform = new RotateTransform(Device.Rotation);
|
||||
|
||||
return geometry.Bounds;
|
||||
}
|
||||
|
||||
private void TimerOnTick(object? sender, EventArgs e)
|
||||
{
|
||||
if (ShowColors && IsVisible && Opacity > 0)
|
||||
Update();
|
||||
}
|
||||
|
||||
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
if (Device == null)
|
||||
return;
|
||||
|
||||
Point position = e.GetPosition(this);
|
||||
double x = position.X / Bounds.Width;
|
||||
double y = position.Y / Bounds.Height;
|
||||
|
||||
Point scaledPosition = new(x * Device.Rectangle.Width, y * Device.Rectangle.Height);
|
||||
DeviceVisualizerLed? deviceVisualizerLed = _deviceVisualizerLeds.FirstOrDefault(l => l.HitTest(scaledPosition));
|
||||
if (deviceVisualizerLed != null)
|
||||
OnLedClicked(new LedClickedEventArgs(deviceVisualizerLed.Led.Device, deviceVisualizerLed.Led, e));
|
||||
else
|
||||
OnClicked(e);
|
||||
}
|
||||
|
||||
private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
Dispatcher.UIThread.Post(SetupForDevice, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
private void DeviceUpdated(object? sender, EventArgs e)
|
||||
{
|
||||
Dispatcher.UIThread.Post(SetupForDevice, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ArtemisDevice" /> to display
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<ArtemisDevice?> DeviceProperty =
|
||||
AvaloniaProperty.Register<DeviceVisualizer, ArtemisDevice?>(nameof(Device), notifying: DeviceUpdated);
|
||||
|
||||
private static void DeviceUpdated(IAvaloniaObject sender, bool before)
|
||||
{
|
||||
if (!before)
|
||||
((DeviceVisualizer) sender).SetupForDevice();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ArtemisDevice" /> to display
|
||||
/// </summary>
|
||||
public ArtemisDevice? Device
|
||||
{
|
||||
get => GetValue(DeviceProperty);
|
||||
set => SetValue(DeviceProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets boolean indicating whether or not to show per-LED colors
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> ShowColorsProperty =
|
||||
AvaloniaProperty.Register<DeviceVisualizer, bool>(nameof(ShowColors));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether or not to show per-LED colors
|
||||
/// </summary>
|
||||
public bool ShowColors
|
||||
{
|
||||
get => GetValue(ShowColorsProperty);
|
||||
set => SetValue(ShowColorsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of LEDs to highlight
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<ObservableCollection<ArtemisLed>?> HighlightedLedsProperty =
|
||||
AvaloniaProperty.Register<DeviceVisualizer, ObservableCollection<ArtemisLed>?>(nameof(HighlightedLeds));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of LEDs to highlight
|
||||
/// </summary>
|
||||
public ObservableCollection<ArtemisLed>? HighlightedLeds
|
||||
{
|
||||
get => GetValue(HighlightedLedsProperty);
|
||||
set => SetValue(HighlightedLedsProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifetime management
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
_deviceImage?.Dispose();
|
||||
_deviceImage = null;
|
||||
|
||||
if (Device != null)
|
||||
{
|
||||
Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
||||
Device.DeviceUpdated -= DeviceUpdated;
|
||||
}
|
||||
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
_timer.Start();
|
||||
_timer.Tick += TimerOnTick;
|
||||
base.OnAttachedToLogicalTree(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Tick -= TimerOnTick;
|
||||
base.OnDetachedFromLogicalTree(e);
|
||||
}
|
||||
|
||||
private void SetupForDevice()
|
||||
{
|
||||
_highlightedLeds = new List<DeviceVisualizerLed>();
|
||||
_dimmedLeds = new List<DeviceVisualizerLed>();
|
||||
|
||||
lock (_deviceVisualizerLeds)
|
||||
{
|
||||
_deviceVisualizerLeds.Clear();
|
||||
}
|
||||
|
||||
if (_oldDevice != null)
|
||||
{
|
||||
_oldDevice.RgbDevice.PropertyChanged -= DevicePropertyChanged;
|
||||
_oldDevice.DeviceUpdated -= DeviceUpdated;
|
||||
}
|
||||
|
||||
_oldDevice = Device;
|
||||
if (Device == null)
|
||||
return;
|
||||
|
||||
_deviceBounds = MeasureDevice();
|
||||
|
||||
Device.RgbDevice.PropertyChanged += DevicePropertyChanged;
|
||||
Device.DeviceUpdated += DeviceUpdated;
|
||||
|
||||
// Create all the LEDs
|
||||
lock (_deviceVisualizerLeds)
|
||||
{
|
||||
foreach (ArtemisLed artemisLed in Device.Leds)
|
||||
_deviceVisualizerLeds.Add(new DeviceVisualizerLed(artemisLed));
|
||||
}
|
||||
|
||||
// Load the device main image on a background thread
|
||||
ArtemisDevice? device = Device;
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (device.Layout?.Image == null || !File.Exists(device.Layout.Image.LocalPath))
|
||||
{
|
||||
_deviceImage?.Dispose();
|
||||
_deviceImage = null;
|
||||
return;
|
||||
deviceVisualizerLed.DrawBitmap(context, 2 * Device.Scale);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Create a bitmap that'll be used to render the device and LED images just once
|
||||
// Render 4 times the actual size of the device to make sure things look sharp when zoomed in
|
||||
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) device.RgbDevice.ActualSize.Width * 2, (int) device.RgbDevice.ActualSize.Height * 2));
|
||||
_deviceImage?.Dispose();
|
||||
_deviceImage = renderTargetBitmap;
|
||||
|
||||
using IDrawingContextImpl context = renderTargetBitmap.CreateDrawingContext(new ImmediateRenderer(this));
|
||||
using Bitmap bitmap = new(device.Layout.Image.LocalPath);
|
||||
context.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), new Rect(renderTargetBitmap.Size), BitmapInterpolationMode.HighQuality);
|
||||
|
||||
lock (_deviceVisualizerLeds)
|
||||
{
|
||||
foreach (DeviceVisualizerLed deviceVisualizerLed in _deviceVisualizerLeds)
|
||||
deviceVisualizerLed.DrawBitmap(context, 2 * Device.Scale);
|
||||
}
|
||||
|
||||
_deviceImage?.Dispose();
|
||||
_deviceImage = renderTargetBitmap;
|
||||
|
||||
Dispatcher.UIThread.Post(InvalidateMeasure);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
if (_deviceBounds.Width <= 0 || _deviceBounds.Height <= 0)
|
||||
return new Size(0, 0);
|
||||
|
||||
double availableWidth = double.IsInfinity(availableSize.Width) ? _deviceBounds.Width : availableSize.Width;
|
||||
double availableHeight = double.IsInfinity(availableSize.Height) ? _deviceBounds.Height : availableSize.Height;
|
||||
double bestRatio = Math.Min(availableWidth / _deviceBounds.Width, availableHeight / _deviceBounds.Height);
|
||||
|
||||
return new Size(_deviceBounds.Width * bestRatio, _deviceBounds.Height * bestRatio);
|
||||
}
|
||||
|
||||
#endregion
|
||||
Dispatcher.UIThread.Post(InvalidateMeasure);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
if (_deviceBounds.Width <= 0 || _deviceBounds.Height <= 0)
|
||||
return new Size(0, 0);
|
||||
|
||||
double availableWidth = double.IsInfinity(availableSize.Width) ? _deviceBounds.Width : availableSize.Width;
|
||||
double availableHeight = double.IsInfinity(availableSize.Height) ? _deviceBounds.Height : availableSize.Height;
|
||||
double bestRatio = Math.Min(availableWidth / _deviceBounds.Width, availableHeight / _deviceBounds.Height);
|
||||
|
||||
return new Size(_deviceBounds.Width * bestRatio, _deviceBounds.Height * bestRatio);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -11,148 +11,147 @@ using Color = Avalonia.Media.Color;
|
||||
using Point = Avalonia.Point;
|
||||
using SolidColorBrush = Avalonia.Media.SolidColorBrush;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared;
|
||||
|
||||
internal class DeviceVisualizerLed
|
||||
{
|
||||
internal class DeviceVisualizerLed
|
||||
private readonly SolidColorBrush _fillBrush;
|
||||
private readonly Pen _pen;
|
||||
private readonly SolidColorBrush _penBrush;
|
||||
|
||||
public DeviceVisualizerLed(ArtemisLed led)
|
||||
{
|
||||
private readonly SolidColorBrush _penBrush;
|
||||
private readonly SolidColorBrush _fillBrush;
|
||||
private readonly Pen _pen;
|
||||
Led = led;
|
||||
LedRect = new Rect(
|
||||
Led.RgbLed.Location.X,
|
||||
Led.RgbLed.Location.Y,
|
||||
Led.RgbLed.Size.Width,
|
||||
Led.RgbLed.Size.Height
|
||||
);
|
||||
|
||||
public DeviceVisualizerLed(ArtemisLed led)
|
||||
_fillBrush = new SolidColorBrush();
|
||||
_penBrush = new SolidColorBrush();
|
||||
_pen = new Pen(_penBrush) {LineJoin = PenLineJoin.Round};
|
||||
|
||||
CreateLedGeometry();
|
||||
}
|
||||
|
||||
public ArtemisLed Led { get; }
|
||||
public Rect LedRect { get; set; }
|
||||
public Geometry? DisplayGeometry { get; private set; }
|
||||
|
||||
public void DrawBitmap(IDrawingContextImpl drawingContext, double scale)
|
||||
{
|
||||
if (Led.Layout?.Image == null || !File.Exists(Led.Layout.Image.LocalPath))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Led = led;
|
||||
LedRect = new Rect(
|
||||
Led.RgbLed.Location.X,
|
||||
Led.RgbLed.Location.Y,
|
||||
Led.RgbLed.Size.Width,
|
||||
Led.RgbLed.Size.Height
|
||||
using Bitmap bitmap = new(Led.Layout.Image.LocalPath);
|
||||
drawingContext.DrawBitmap(
|
||||
bitmap.PlatformImpl,
|
||||
1,
|
||||
new Rect(bitmap.Size),
|
||||
new Rect(Led.RgbLed.Location.X * scale, Led.RgbLed.Location.Y * scale, Led.RgbLed.Size.Width * scale, Led.RgbLed.Size.Height * scale),
|
||||
BitmapInterpolationMode.HighQuality
|
||||
);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
_fillBrush = new SolidColorBrush();
|
||||
_penBrush = new SolidColorBrush();
|
||||
_pen = new Pen(_penBrush) {LineJoin = PenLineJoin.Round};
|
||||
public void RenderGeometry(DrawingContext drawingContext, bool dimmed)
|
||||
{
|
||||
byte r = Led.RgbLed.Color.GetR();
|
||||
byte g = Led.RgbLed.Color.GetG();
|
||||
byte b = Led.RgbLed.Color.GetB();
|
||||
|
||||
CreateLedGeometry();
|
||||
if (dimmed)
|
||||
{
|
||||
_fillBrush.Color = new Color(50, r, g, b);
|
||||
_penBrush.Color = new Color(100, r, g, b);
|
||||
}
|
||||
else
|
||||
{
|
||||
_fillBrush.Color = new Color(100, r, g, b);
|
||||
_penBrush.Color = new Color(255, r, g, b);
|
||||
}
|
||||
|
||||
public ArtemisLed Led { get; }
|
||||
public Rect LedRect { get; set; }
|
||||
public Geometry? DisplayGeometry { get; private set; }
|
||||
// Render the LED geometry
|
||||
drawingContext.DrawGeometry(_fillBrush, _pen, DisplayGeometry);
|
||||
}
|
||||
|
||||
public void DrawBitmap(IDrawingContextImpl drawingContext, double scale)
|
||||
public bool HitTest(Point position)
|
||||
{
|
||||
return DisplayGeometry != null && DisplayGeometry.FillContains(position);
|
||||
}
|
||||
|
||||
private void CreateLedGeometry()
|
||||
{
|
||||
// The minimum required size for geometry to be created
|
||||
if (Led.RgbLed.Size.Width < 2 || Led.RgbLed.Size.Height < 2)
|
||||
return;
|
||||
|
||||
switch (Led.RgbLed.Shape)
|
||||
{
|
||||
if (Led.Layout?.Image == null || !File.Exists(Led.Layout.Image.LocalPath))
|
||||
return;
|
||||
case Shape.Custom:
|
||||
if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
|
||||
CreateCustomGeometry(2.0);
|
||||
else
|
||||
CreateCustomGeometry(1.0);
|
||||
break;
|
||||
case Shape.Rectangle:
|
||||
if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
|
||||
CreateKeyCapGeometry();
|
||||
else
|
||||
CreateRectangleGeometry();
|
||||
break;
|
||||
case Shape.Circle:
|
||||
CreateCircleGeometry();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
private void CreateRectangleGeometry()
|
||||
{
|
||||
DisplayGeometry = new RectangleGeometry(new Rect(Led.RgbLed.Location.X + 0.5, Led.RgbLed.Location.Y + 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
|
||||
}
|
||||
|
||||
private void CreateCircleGeometry()
|
||||
{
|
||||
DisplayGeometry = new EllipseGeometry(new Rect(Led.RgbLed.Location.X + 0.5, Led.RgbLed.Location.Y + 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
|
||||
}
|
||||
|
||||
private void CreateKeyCapGeometry()
|
||||
{
|
||||
DisplayGeometry = new RectangleGeometry(new Rect(Led.RgbLed.Location.X + 1, Led.RgbLed.Location.Y + 1, Led.RgbLed.Size.Width - 2, Led.RgbLed.Size.Height - 2));
|
||||
}
|
||||
|
||||
private void CreateCustomGeometry(double deflateAmount)
|
||||
{
|
||||
try
|
||||
{
|
||||
double width = Led.RgbLed.Size.Width - deflateAmount;
|
||||
double height = Led.RgbLed.Size.Height - deflateAmount;
|
||||
|
||||
Geometry geometry = Geometry.Parse(Led.RgbLed.ShapeData);
|
||||
geometry.Transform = new TransformGroup
|
||||
{
|
||||
using Bitmap bitmap = new(Led.Layout.Image.LocalPath);
|
||||
drawingContext.DrawBitmap(
|
||||
bitmap.PlatformImpl,
|
||||
1,
|
||||
new Rect(bitmap.Size),
|
||||
new Rect(Led.RgbLed.Location.X * scale, Led.RgbLed.Location.Y * scale, Led.RgbLed.Size.Width * scale, Led.RgbLed.Size.Height * scale),
|
||||
BitmapInterpolationMode.HighQuality
|
||||
);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderGeometry(DrawingContext drawingContext, bool dimmed)
|
||||
{
|
||||
byte r = Led.RgbLed.Color.GetR();
|
||||
byte g = Led.RgbLed.Color.GetG();
|
||||
byte b = Led.RgbLed.Color.GetB();
|
||||
|
||||
if (dimmed)
|
||||
{
|
||||
_fillBrush.Color = new Color(50, r, g, b);
|
||||
_penBrush.Color = new Color(100, r, g, b);
|
||||
}
|
||||
else
|
||||
{
|
||||
_fillBrush.Color = new Color(100, r, g, b);
|
||||
_penBrush.Color = new Color(255, r, g, b);
|
||||
}
|
||||
|
||||
// Render the LED geometry
|
||||
drawingContext.DrawGeometry(_fillBrush, _pen, DisplayGeometry);
|
||||
}
|
||||
|
||||
public bool HitTest(Point position)
|
||||
{
|
||||
return DisplayGeometry != null && DisplayGeometry.FillContains(position);
|
||||
}
|
||||
|
||||
private void CreateLedGeometry()
|
||||
{
|
||||
// The minimum required size for geometry to be created
|
||||
if (Led.RgbLed.Size.Width < 2 || Led.RgbLed.Size.Height < 2)
|
||||
return;
|
||||
|
||||
switch (Led.RgbLed.Shape)
|
||||
{
|
||||
case Shape.Custom:
|
||||
if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
|
||||
CreateCustomGeometry(2.0);
|
||||
else
|
||||
CreateCustomGeometry(1.0);
|
||||
break;
|
||||
case Shape.Rectangle:
|
||||
if (Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keyboard || Led.RgbLed.Device.DeviceInfo.DeviceType == RGBDeviceType.Keypad)
|
||||
CreateKeyCapGeometry();
|
||||
else
|
||||
CreateRectangleGeometry();
|
||||
break;
|
||||
case Shape.Circle:
|
||||
CreateCircleGeometry();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateRectangleGeometry()
|
||||
{
|
||||
DisplayGeometry = new RectangleGeometry(new Rect(Led.RgbLed.Location.X + 0.5, Led.RgbLed.Location.Y + 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
|
||||
}
|
||||
|
||||
private void CreateCircleGeometry()
|
||||
{
|
||||
DisplayGeometry = new EllipseGeometry(new Rect(Led.RgbLed.Location.X + 0.5, Led.RgbLed.Location.Y + 0.5, Led.RgbLed.Size.Width - 1, Led.RgbLed.Size.Height - 1));
|
||||
}
|
||||
|
||||
private void CreateKeyCapGeometry()
|
||||
{
|
||||
DisplayGeometry = new RectangleGeometry(new Rect(Led.RgbLed.Location.X + 1, Led.RgbLed.Location.Y + 1, Led.RgbLed.Size.Width - 2, Led.RgbLed.Size.Height - 2));
|
||||
}
|
||||
|
||||
private void CreateCustomGeometry(double deflateAmount)
|
||||
{
|
||||
try
|
||||
{
|
||||
double width = Led.RgbLed.Size.Width - deflateAmount;
|
||||
double height = Led.RgbLed.Size.Height - deflateAmount;
|
||||
|
||||
Geometry geometry = Geometry.Parse(Led.RgbLed.ShapeData);
|
||||
geometry.Transform = new TransformGroup
|
||||
Children = new Transforms
|
||||
{
|
||||
Children = new Transforms
|
||||
{
|
||||
new ScaleTransform(width, height),
|
||||
new TranslateTransform(Led.RgbLed.Location.X + deflateAmount / 2, Led.RgbLed.Location.Y + deflateAmount / 2)
|
||||
}
|
||||
};
|
||||
DisplayGeometry = geometry;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CreateRectangleGeometry();
|
||||
}
|
||||
new ScaleTransform(width, height),
|
||||
new TranslateTransform(Led.RgbLed.Location.X + deflateAmount / 2, Led.RgbLed.Location.Y + deflateAmount / 2)
|
||||
}
|
||||
};
|
||||
DisplayGeometry = geometry;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CreateRectangleGeometry();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,20 +21,6 @@ public class DraggableNumberBox : UserControl
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<double> ValueProperty = AvaloniaProperty.Register<DraggableNumberBox, double>(nameof(Value), defaultBindingMode: BindingMode.TwoWay, notifying: ValueChanged);
|
||||
|
||||
private static void ValueChanged(IAvaloniaObject sender, bool before)
|
||||
{
|
||||
if (before)
|
||||
return;
|
||||
|
||||
DraggableNumberBox draggable = (DraggableNumberBox) sender;
|
||||
if (!(Math.Abs(draggable._numberBox.Value - draggable.Value) > 0.00001))
|
||||
return;
|
||||
|
||||
draggable._updating = true;
|
||||
draggable._numberBox.Value = draggable.Value;
|
||||
draggable._updating = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Minimum" /> property.
|
||||
/// </summary>
|
||||
@@ -175,6 +161,20 @@ public class DraggableNumberBox : UserControl
|
||||
/// </summary>
|
||||
public event TypedEventHandler<DraggableNumberBox, EventArgs>? DragFinished;
|
||||
|
||||
private static void ValueChanged(IAvaloniaObject sender, bool before)
|
||||
{
|
||||
if (before)
|
||||
return;
|
||||
|
||||
DraggableNumberBox draggable = (DraggableNumberBox) sender;
|
||||
if (!(Math.Abs(draggable._numberBox.Value - draggable.Value) > 0.00001))
|
||||
return;
|
||||
|
||||
draggable._updating = true;
|
||||
draggable._numberBox.Value = draggable.Value;
|
||||
draggable._updating = false;
|
||||
}
|
||||
|
||||
private void HandleKeyUp(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter || e.Key == Key.Escape)
|
||||
|
||||
@@ -8,112 +8,111 @@ using Avalonia.Data;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a combobox that can display the values of an enum.
|
||||
/// </summary>
|
||||
public class EnumComboBox : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a combobox that can display the values of an enum.
|
||||
/// Gets or sets the currently selected value
|
||||
/// </summary>
|
||||
public class EnumComboBox : UserControl
|
||||
public static readonly StyledProperty<object?> ValueProperty =
|
||||
AvaloniaProperty.Register<EnumComboBox, object?>(nameof(Value), defaultBindingMode: BindingMode.TwoWay, notifying: ValueChanged);
|
||||
|
||||
private readonly ObservableCollection<(Enum, string)> _currentValues = new();
|
||||
private Type? _currentType;
|
||||
|
||||
private ComboBox? _enumComboBox;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="EnumComboBox" /> class.
|
||||
/// </summary>
|
||||
public EnumComboBox()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the currently selected value
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<object?> ValueProperty =
|
||||
AvaloniaProperty.Register<EnumComboBox, object?>(nameof(Value), defaultBindingMode: BindingMode.TwoWay, notifying: ValueChanged);
|
||||
|
||||
private readonly ObservableCollection<(Enum, string)> _currentValues = new();
|
||||
|
||||
private ComboBox? _enumComboBox;
|
||||
private Type? _currentType;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="EnumComboBox" /> class.
|
||||
/// </summary>
|
||||
public EnumComboBox()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently selected value
|
||||
/// </summary>
|
||||
public object? Value
|
||||
{
|
||||
get => GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
private static void ValueChanged(IAvaloniaObject sender, bool before)
|
||||
{
|
||||
if (sender is EnumComboBox enumCombo && !before)
|
||||
{
|
||||
enumCombo.UpdateValues();
|
||||
enumCombo.UpdateSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_enumComboBox == null || _enumComboBox.SelectedIndex == -1)
|
||||
return;
|
||||
|
||||
(Enum enumValue, _) = _currentValues[_enumComboBox.SelectedIndex];
|
||||
if (!Equals(Value, enumValue))
|
||||
Value = enumValue;
|
||||
}
|
||||
|
||||
private void UpdateValues()
|
||||
{
|
||||
Type? newType = Value?.GetType();
|
||||
if (_enumComboBox == null || newType == null || _currentType == newType)
|
||||
return;
|
||||
|
||||
_currentValues.Clear();
|
||||
foreach ((Enum, string) valueDesc in EnumUtilities.GetAllValuesAndDescriptions(newType))
|
||||
_currentValues.Add(valueDesc);
|
||||
|
||||
_currentType = newType;
|
||||
}
|
||||
|
||||
private void UpdateSelection()
|
||||
{
|
||||
if (_enumComboBox == null || Value is not Enum)
|
||||
return;
|
||||
|
||||
(Enum, string) value = _currentValues.FirstOrDefault(v => v.Item1.Equals(Value));
|
||||
if (!Equals(value.Item1, _enumComboBox.SelectedItem))
|
||||
_enumComboBox.SelectedItem = value;
|
||||
}
|
||||
|
||||
#region Overrides of TemplatedControl
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
_enumComboBox = this.Get<ComboBox>("EnumComboBox");
|
||||
_enumComboBox.Items = _currentValues;
|
||||
|
||||
UpdateValues();
|
||||
UpdateSelection();
|
||||
_enumComboBox.SelectionChanged += OnSelectionChanged;
|
||||
|
||||
base.OnAttachedToLogicalTree(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (_enumComboBox != null)
|
||||
_enumComboBox.SelectionChanged -= OnSelectionChanged;
|
||||
|
||||
base.OnDetachedFromLogicalTree(e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently selected value
|
||||
/// </summary>
|
||||
public object? Value
|
||||
{
|
||||
get => GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
private static void ValueChanged(IAvaloniaObject sender, bool before)
|
||||
{
|
||||
if (sender is EnumComboBox enumCombo && !before)
|
||||
{
|
||||
enumCombo.UpdateValues();
|
||||
enumCombo.UpdateSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_enumComboBox == null || _enumComboBox.SelectedIndex == -1)
|
||||
return;
|
||||
|
||||
(Enum enumValue, _) = _currentValues[_enumComboBox.SelectedIndex];
|
||||
if (!Equals(Value, enumValue))
|
||||
Value = enumValue;
|
||||
}
|
||||
|
||||
private void UpdateValues()
|
||||
{
|
||||
Type? newType = Value?.GetType();
|
||||
if (_enumComboBox == null || newType == null || _currentType == newType)
|
||||
return;
|
||||
|
||||
_currentValues.Clear();
|
||||
foreach ((Enum, string) valueDesc in EnumUtilities.GetAllValuesAndDescriptions(newType))
|
||||
_currentValues.Add(valueDesc);
|
||||
|
||||
_currentType = newType;
|
||||
}
|
||||
|
||||
private void UpdateSelection()
|
||||
{
|
||||
if (_enumComboBox == null || Value is not Enum)
|
||||
return;
|
||||
|
||||
(Enum, string) value = _currentValues.FirstOrDefault(v => v.Item1.Equals(Value));
|
||||
if (!Equals(value.Item1, _enumComboBox.SelectedItem))
|
||||
_enumComboBox.SelectedItem = value;
|
||||
}
|
||||
|
||||
#region Overrides of TemplatedControl
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
_enumComboBox = this.Get<ComboBox>("EnumComboBox");
|
||||
_enumComboBox.Items = _currentValues;
|
||||
|
||||
UpdateValues();
|
||||
UpdateSelection();
|
||||
_enumComboBox.SelectionChanged += OnSelectionChanged;
|
||||
|
||||
base.OnAttachedToLogicalTree(e);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (_enumComboBox != null)
|
||||
_enumComboBox.SelectionChanged -= OnSelectionChanged;
|
||||
|
||||
base.OnDetachedFromLogicalTree(e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -20,12 +20,12 @@ public sealed class DataModelPickerFlyout : PickerFlyoutBase
|
||||
public DataModelPicker.DataModelPicker DataModelPicker => _picker ??= new DataModelPicker.DataModelPicker();
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the Confirmed button is tapped indicating the new Color should be applied
|
||||
/// 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
|
||||
/// Raised when the Dismiss button is tapped, indicating the new color should not be applied
|
||||
/// </summary>
|
||||
public event TypedEventHandler<DataModelPickerFlyout, object>? Dismissed;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ using Button = Avalonia.Controls.Button;
|
||||
namespace Artemis.UI.Shared.Controls.GradientPicker;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a gradient picker that can be used to edit a gradient.
|
||||
/// Represents a gradient picker that can be used to edit a gradient.
|
||||
/// </summary>
|
||||
public class GradientPicker : TemplatedControl
|
||||
{
|
||||
@@ -60,18 +60,18 @@ public class GradientPicker : TemplatedControl
|
||||
AvaloniaProperty.RegisterDirect<GradientPicker, ICommand>(nameof(DeleteStop), g => g.DeleteStop);
|
||||
|
||||
private readonly ICommand _deleteStop;
|
||||
private bool _shiftDown;
|
||||
private ColorPicker? _colorPicker;
|
||||
private Button? _flipStops;
|
||||
private Border? _gradient;
|
||||
private ColorGradient? _lastColorGradient;
|
||||
private Button? _randomize;
|
||||
private Button? _rotateStops;
|
||||
private bool _shiftDown;
|
||||
private Button? _spreadStops;
|
||||
private Button? _toggleSeamless;
|
||||
private Button? _randomize;
|
||||
private ColorGradient? _lastColorGradient;
|
||||
private ColorPicker? _colorPicker;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="GradientPicker"/> class.
|
||||
/// Creates a new instance of the <see cref="GradientPicker" /> class.
|
||||
/// </summary>
|
||||
public GradientPicker()
|
||||
{
|
||||
|
||||
@@ -45,11 +45,12 @@ public class GradientPickerButton : TemplatedControl
|
||||
public static readonly DirectProperty<GradientPickerButton, LinearGradientBrush> LinearGradientBrushProperty =
|
||||
AvaloniaProperty.RegisterDirect<GradientPickerButton, LinearGradientBrush>(nameof(LinearGradientBrush), g => g.LinearGradientBrush);
|
||||
|
||||
private ColorGradient? _lastColorGradient;
|
||||
private Button? _button;
|
||||
private GradientPickerFlyout? _flyout;
|
||||
private bool _flyoutActive;
|
||||
|
||||
private ColorGradient? _lastColorGradient;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color gradient.
|
||||
/// </summary>
|
||||
@@ -178,10 +179,8 @@ public class GradientPickerButton : TemplatedControl
|
||||
// Update the display gradient
|
||||
GradientStops collection = new();
|
||||
if (ColorGradient != null)
|
||||
{
|
||||
foreach (ColorGradientStop c in ColorGradient.OrderBy(s => s.Position))
|
||||
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
|
||||
}
|
||||
|
||||
LinearGradientBrush.GradientStops = collection;
|
||||
}
|
||||
|
||||
@@ -18,19 +18,6 @@ internal class GradientPickerColorStop : TemplatedControl
|
||||
public static readonly StyledProperty<GradientPicker?> GradientPickerProperty =
|
||||
AvaloniaProperty.Register<GradientPickerColorStop, GradientPicker?>(nameof(GradientPicker), notifying: Notifying);
|
||||
|
||||
private static void Notifying(IAvaloniaObject sender, bool before)
|
||||
{
|
||||
if (sender is not GradientPickerColorStop self)
|
||||
return;
|
||||
|
||||
if (before && self.GradientPicker != null)
|
||||
self.GradientPicker.PropertyChanged -= self.GradientPickerOnPropertyChanged;
|
||||
else if (self.GradientPicker != null)
|
||||
self.GradientPicker.PropertyChanged += self.GradientPickerOnPropertyChanged;
|
||||
|
||||
self.IsSelected = ReferenceEquals(self.GradientPicker?.SelectedColorStop, self.ColorStop);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color stop.
|
||||
/// </summary>
|
||||
@@ -50,9 +37,10 @@ internal class GradientPickerColorStop : TemplatedControl
|
||||
public static readonly DirectProperty<GradientPickerColorStop, bool> IsSelectedProperty =
|
||||
AvaloniaProperty.RegisterDirect<GradientPickerColorStop, bool>(nameof(IsSelected), g => g.IsSelected);
|
||||
|
||||
private bool _isSelected;
|
||||
private double _dragOffset;
|
||||
|
||||
private bool _isSelected;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the gradient picker.
|
||||
/// </summary>
|
||||
@@ -97,12 +85,23 @@ internal class GradientPickerColorStop : TemplatedControl
|
||||
}
|
||||
}
|
||||
|
||||
private static void Notifying(IAvaloniaObject sender, bool before)
|
||||
{
|
||||
if (sender is not GradientPickerColorStop self)
|
||||
return;
|
||||
|
||||
if (before && self.GradientPicker != null)
|
||||
self.GradientPicker.PropertyChanged -= self.GradientPickerOnPropertyChanged;
|
||||
else if (self.GradientPicker != null)
|
||||
self.GradientPicker.PropertyChanged += self.GradientPickerOnPropertyChanged;
|
||||
|
||||
self.IsSelected = ReferenceEquals(self.GradientPicker?.SelectedColorStop, self.ColorStop);
|
||||
}
|
||||
|
||||
private void GradientPickerOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (GradientPicker != null && e.Property == GradientPicker.SelectedColorStopProperty)
|
||||
{
|
||||
IsSelected = GradientPicker.SelectedColorStop == ColorStop;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
|
||||
@@ -11,128 +11,127 @@ using Avalonia.Markup.Xaml;
|
||||
using Humanizer;
|
||||
using Material.Icons;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a control that can be used to display or edit <see cref="Core.Hotkey" /> instances.
|
||||
/// </summary>
|
||||
public class HotkeyBox : UserControl
|
||||
{
|
||||
private readonly TextBox _displayTextBox;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a control that can be used to display or edit <see cref="Core.Hotkey"/> instances.
|
||||
/// Creates a new instance of the <see cref="HotkeyBox" /> class
|
||||
/// </summary>
|
||||
public class HotkeyBox : UserControl
|
||||
public HotkeyBox()
|
||||
{
|
||||
private readonly TextBox _displayTextBox;
|
||||
InitializeComponent();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="HotkeyBox"/> class
|
||||
/// </summary>
|
||||
public HotkeyBox()
|
||||
{
|
||||
InitializeComponent();
|
||||
_displayTextBox = this.Find<TextBox>("DisplayTextBox");
|
||||
_displayTextBox.KeyDown += DisplayTextBoxOnKeyDown;
|
||||
_displayTextBox.KeyUp += DisplayTextBoxOnKeyUp;
|
||||
UpdateDisplayTextBox();
|
||||
}
|
||||
|
||||
_displayTextBox = this.Find<TextBox>("DisplayTextBox");
|
||||
_displayTextBox.KeyDown += DisplayTextBoxOnKeyDown;
|
||||
_displayTextBox.KeyUp += DisplayTextBoxOnKeyUp;
|
||||
UpdateDisplayTextBox();
|
||||
}
|
||||
private static void HotkeyChanging(IAvaloniaObject sender, bool before)
|
||||
{
|
||||
((HotkeyBox) sender).UpdateDisplayTextBox();
|
||||
}
|
||||
|
||||
private static void HotkeyChanging(IAvaloniaObject sender, bool before)
|
||||
{
|
||||
((HotkeyBox) sender).UpdateDisplayTextBox();
|
||||
}
|
||||
private void DisplayTextBoxOnKeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key >= Key.LeftShift && e.Key <= Key.RightAlt)
|
||||
return;
|
||||
|
||||
private void DisplayTextBoxOnKeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key >= Key.LeftShift && e.Key <= Key.RightAlt)
|
||||
return;
|
||||
Hotkey ??= new Hotkey();
|
||||
Hotkey.Key = (KeyboardKey?) e.Key;
|
||||
Hotkey.Modifiers = (KeyboardModifierKey?) e.KeyModifiers;
|
||||
UpdateDisplayTextBox();
|
||||
|
||||
Hotkey ??= new Hotkey();
|
||||
Hotkey.Key = (KeyboardKey?) e.Key;
|
||||
Hotkey.Modifiers = (KeyboardModifierKey?) e.KeyModifiers;
|
||||
UpdateDisplayTextBox();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void DisplayTextBoxOnKeyUp(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyModifiers == KeyModifiers.None)
|
||||
FocusManager.Instance?.Focus(null);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void UpdateDisplayTextBox()
|
||||
{
|
||||
string? display = null;
|
||||
if (Hotkey?.Modifiers != null)
|
||||
display = string.Join("+", Enum.GetValues<KeyboardModifierKey>().Skip(1).Where(m => Hotkey.Modifiers.Value.HasFlag(m)).Select(v => v.Humanize()));
|
||||
if (Hotkey?.Key != null)
|
||||
display = string.IsNullOrEmpty(display) ? Hotkey.Key.ToString() : $"{display}+{Hotkey.Key}";
|
||||
|
||||
_displayTextBox.Text = display;
|
||||
_displayTextBox.CaretIndex = _displayTextBox.Text?.Length ?? 0;
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void Button_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Hotkey = null;
|
||||
private void DisplayTextBoxOnKeyUp(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyModifiers == KeyModifiers.None)
|
||||
FocusManager.Instance?.Focus(null);
|
||||
|
||||
UpdateDisplayTextBox();
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
|
||||
/// pointing to an SVG
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<Hotkey?> HotkeyProperty =
|
||||
AvaloniaProperty.Register<HotkeyBox, Hotkey?>(nameof(Hotkey), defaultBindingMode: BindingMode.TwoWay, notifying: HotkeyChanging);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the watermark of the hotkey box when it is empty.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<string?> WatermarkProperty =
|
||||
AvaloniaProperty.Register<HotkeyBox, string?>(nameof(Watermark));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the watermark should float above the hotkey box when it is not empty.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
|
||||
AvaloniaProperty.Register<HotkeyBox, bool>(nameof(UseFloatingWatermark));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
|
||||
/// pointing to an SVG
|
||||
/// </summary>
|
||||
public Hotkey? Hotkey
|
||||
{
|
||||
get => GetValue(HotkeyProperty);
|
||||
set => SetValue(HotkeyProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the watermark of the hotkey box when it is empty.
|
||||
/// </summary>
|
||||
public string? Watermark
|
||||
{
|
||||
get => GetValue(WatermarkProperty);
|
||||
set => SetValue(WatermarkProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the watermark should float above the hotkey box when it is not empty.
|
||||
/// </summary>
|
||||
public bool UseFloatingWatermark
|
||||
{
|
||||
get => GetValue(UseFloatingWatermarkProperty);
|
||||
set => SetValue(UseFloatingWatermarkProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void UpdateDisplayTextBox()
|
||||
{
|
||||
string? display = null;
|
||||
if (Hotkey?.Modifiers != null)
|
||||
display = string.Join("+", Enum.GetValues<KeyboardModifierKey>().Skip(1).Where(m => Hotkey.Modifiers.Value.HasFlag(m)).Select(v => v.Humanize()));
|
||||
if (Hotkey?.Key != null)
|
||||
display = string.IsNullOrEmpty(display) ? Hotkey.Key.ToString() : $"{display}+{Hotkey.Key}";
|
||||
|
||||
_displayTextBox.Text = display;
|
||||
_displayTextBox.CaretIndex = _displayTextBox.Text?.Length ?? 0;
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void Button_OnClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Hotkey = null;
|
||||
FocusManager.Instance?.Focus(null);
|
||||
|
||||
UpdateDisplayTextBox();
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
|
||||
/// pointing to an SVG
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<Hotkey?> HotkeyProperty =
|
||||
AvaloniaProperty.Register<HotkeyBox, Hotkey?>(nameof(Hotkey), defaultBindingMode: BindingMode.TwoWay, notifying: HotkeyChanging);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the watermark of the hotkey box when it is empty.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<string?> WatermarkProperty =
|
||||
AvaloniaProperty.Register<HotkeyBox, string?>(nameof(Watermark));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the watermark should float above the hotkey box when it is not empty.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
|
||||
AvaloniaProperty.Register<HotkeyBox, bool>(nameof(UseFloatingWatermark));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the currently displayed icon as either a <see cref="MaterialIconKind" /> or an <see cref="Uri" />
|
||||
/// pointing to an SVG
|
||||
/// </summary>
|
||||
public Hotkey? Hotkey
|
||||
{
|
||||
get => GetValue(HotkeyProperty);
|
||||
set => SetValue(HotkeyProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the watermark of the hotkey box when it is empty.
|
||||
/// </summary>
|
||||
public string? Watermark
|
||||
{
|
||||
get => GetValue(WatermarkProperty);
|
||||
set => SetValue(WatermarkProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the watermark should float above the hotkey box when it is not empty.
|
||||
/// </summary>
|
||||
public bool UseFloatingWatermark
|
||||
{
|
||||
get => GetValue(UseFloatingWatermarkProperty);
|
||||
set => SetValue(UseFloatingWatermarkProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -3,22 +3,21 @@ using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared;
|
||||
|
||||
internal class NoInputTextBox : TextBox, IStyleable
|
||||
{
|
||||
internal class NoInputTextBox : TextBox, IStyleable
|
||||
/// <inheritdoc />
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
// Don't call the base method on purpose
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnKeyUp(KeyEventArgs e)
|
||||
{
|
||||
// Don't call the base method on purpose
|
||||
}
|
||||
|
||||
Type IStyleable.StyleKey => typeof(TextBox);
|
||||
// Don't call the base method on purpose
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnKeyUp(KeyEventArgs e)
|
||||
{
|
||||
// Don't call the base method on purpose
|
||||
}
|
||||
|
||||
Type IStyleable.StyleKey => typeof(TextBox);
|
||||
}
|
||||
@@ -13,133 +13,132 @@ using Avalonia.Visuals.Media.Imaging;
|
||||
using Material.Icons;
|
||||
using Material.Icons.Avalonia;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a control that can display the icon of a specific <see cref="ProfileConfiguration" />.
|
||||
/// </summary>
|
||||
public class ProfileConfigurationIcon : UserControl, IDisposable
|
||||
{
|
||||
private Stream? _stream;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a control that can display the icon of a specific <see cref="ProfileConfiguration"/>.
|
||||
/// Creates a new instance of the <see cref="ProfileConfigurationIcon" /> class.
|
||||
/// </summary>
|
||||
public class ProfileConfigurationIcon : UserControl, IDisposable
|
||||
public ProfileConfigurationIcon()
|
||||
{
|
||||
private Stream? _stream;
|
||||
InitializeComponent();
|
||||
DetachedFromLogicalTree += OnDetachedFromLogicalTree;
|
||||
PropertyChanged += OnPropertyChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ProfileConfigurationIcon" /> class.
|
||||
/// </summary>
|
||||
public ProfileConfigurationIcon()
|
||||
private void Update()
|
||||
{
|
||||
if (ConfigurationIcon == null)
|
||||
return;
|
||||
Dispose();
|
||||
|
||||
try
|
||||
{
|
||||
InitializeComponent();
|
||||
DetachedFromLogicalTree += OnDetachedFromLogicalTree;
|
||||
PropertyChanged += OnPropertyChanged;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (ConfigurationIcon == null)
|
||||
return;
|
||||
Dispose();
|
||||
|
||||
try
|
||||
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.MaterialIcon)
|
||||
{
|
||||
if (ConfigurationIcon.IconType == ProfileConfigurationIconType.MaterialIcon)
|
||||
{
|
||||
Content = Enum.TryParse(ConfigurationIcon.IconName, true, out MaterialIconKind parsedIcon)
|
||||
? new MaterialIcon {Kind = parsedIcon!}
|
||||
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||
}
|
||||
Content = Enum.TryParse(ConfigurationIcon.IconName, true, out MaterialIconKind parsedIcon)
|
||||
? new MaterialIcon {Kind = parsedIcon!}
|
||||
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||
}
|
||||
else
|
||||
{
|
||||
Stream? stream = ConfigurationIcon.GetIconStream();
|
||||
if (stream == null)
|
||||
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||
else
|
||||
{
|
||||
Stream? stream = ConfigurationIcon.GetIconStream();
|
||||
if (stream == null)
|
||||
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||
else
|
||||
LoadFromBitmap(ConfigurationIcon, stream);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||
LoadFromBitmap(ConfigurationIcon, stream);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadFromBitmap(Core.ProfileConfigurationIcon configurationIcon, Stream stream)
|
||||
catch (Exception)
|
||||
{
|
||||
_stream = stream;
|
||||
if (!configurationIcon.Fill)
|
||||
{
|
||||
Content = new Image {Source = new Bitmap(stream)};
|
||||
return;
|
||||
}
|
||||
|
||||
Content = new Border
|
||||
{
|
||||
Background = TextBlock.GetForeground(this),
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
OpacityMask = new ImageBrush(new Bitmap(stream)) {BitmapInterpolationMode = BitmapInterpolationMode.MediumQuality}
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (ConfigurationIcon != null)
|
||||
ConfigurationIcon.IconUpdated -= ConfigurationIconOnIconUpdated;
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property != ConfigurationIconProperty)
|
||||
return;
|
||||
|
||||
if (e.OldValue is Core.ProfileConfigurationIcon oldIcon)
|
||||
oldIcon.IconUpdated -= ConfigurationIconOnIconUpdated;
|
||||
if (e.NewValue is Core.ProfileConfigurationIcon newIcon)
|
||||
newIcon.IconUpdated += ConfigurationIconOnIconUpdated;
|
||||
|
||||
Dispatcher.UIThread.Post(Update, DispatcherPriority.ApplicationIdle);
|
||||
}
|
||||
|
||||
private void ConfigurationIconOnIconUpdated(object? sender, EventArgs e)
|
||||
{
|
||||
Dispatcher.UIThread.Post(Update, DispatcherPriority.ApplicationIdle);
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Core.ProfileConfigurationIcon" /> to display
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<Core.ProfileConfigurationIcon?> ConfigurationIconProperty =
|
||||
AvaloniaProperty.Register<ProfileConfigurationIcon, Core.ProfileConfigurationIcon?>(nameof(ConfigurationIcon));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Core.ProfileConfigurationIcon" /> to display
|
||||
/// </summary>
|
||||
public Core.ProfileConfigurationIcon? ConfigurationIcon
|
||||
{
|
||||
get => GetValue(ConfigurationIconProperty);
|
||||
set => SetValue(ConfigurationIconProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (Content is Image {Source: IDisposable d})
|
||||
{
|
||||
d.Dispose();
|
||||
Content = null;
|
||||
}
|
||||
|
||||
_stream?.Dispose();
|
||||
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadFromBitmap(Core.ProfileConfigurationIcon configurationIcon, Stream stream)
|
||||
{
|
||||
_stream = stream;
|
||||
if (!configurationIcon.Fill)
|
||||
{
|
||||
Content = new Image {Source = new Bitmap(stream)};
|
||||
return;
|
||||
}
|
||||
|
||||
Content = new Border
|
||||
{
|
||||
Background = TextBlock.GetForeground(this),
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
OpacityMask = new ImageBrush(new Bitmap(stream)) {BitmapInterpolationMode = BitmapInterpolationMode.MediumQuality}
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (ConfigurationIcon != null)
|
||||
ConfigurationIcon.IconUpdated -= ConfigurationIconOnIconUpdated;
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property != ConfigurationIconProperty)
|
||||
return;
|
||||
|
||||
if (e.OldValue is Core.ProfileConfigurationIcon oldIcon)
|
||||
oldIcon.IconUpdated -= ConfigurationIconOnIconUpdated;
|
||||
if (e.NewValue is Core.ProfileConfigurationIcon newIcon)
|
||||
newIcon.IconUpdated += ConfigurationIconOnIconUpdated;
|
||||
|
||||
Dispatcher.UIThread.Post(Update, DispatcherPriority.ApplicationIdle);
|
||||
}
|
||||
|
||||
private void ConfigurationIconOnIconUpdated(object? sender, EventArgs e)
|
||||
{
|
||||
Dispatcher.UIThread.Post(Update, DispatcherPriority.ApplicationIdle);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (Content is Image {Source: IDisposable d})
|
||||
{
|
||||
d.Dispose();
|
||||
Content = null;
|
||||
}
|
||||
|
||||
_stream?.Dispose();
|
||||
}
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Core.ProfileConfigurationIcon" /> to display
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<Core.ProfileConfigurationIcon?> ConfigurationIconProperty =
|
||||
AvaloniaProperty.Register<ProfileConfigurationIcon, Core.ProfileConfigurationIcon?>(nameof(ConfigurationIcon));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Core.ProfileConfigurationIcon" /> to display
|
||||
/// </summary>
|
||||
public Core.ProfileConfigurationIcon? ConfigurationIcon
|
||||
{
|
||||
get => GetValue(ConfigurationIconProperty);
|
||||
set => SetValue(ConfigurationIconProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -60,9 +60,9 @@ public class SelectionRectangle : Control
|
||||
|
||||
private Rect? _displayRect;
|
||||
private bool _isSelecting;
|
||||
private Point _lastPosition;
|
||||
private IControl? _oldInputElement;
|
||||
private Point _startPosition;
|
||||
private Point _lastPosition;
|
||||
|
||||
/// <inheritdoc />
|
||||
public SelectionRectangle()
|
||||
@@ -166,7 +166,7 @@ public class SelectionRectangle : Control
|
||||
{
|
||||
((SelectionRectangle) sender).SubscribeToInputElement();
|
||||
}
|
||||
|
||||
|
||||
private void ParentOnPointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
// Point moved seems to trigger when the element under the mouse changes?
|
||||
@@ -179,7 +179,7 @@ public class SelectionRectangle : Control
|
||||
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
return;
|
||||
|
||||
|
||||
// Capture the pointer and initialize dragging the first time it moves
|
||||
if (!ReferenceEquals(e.Pointer.Captured, this))
|
||||
{
|
||||
|
||||
@@ -5,34 +5,33 @@ using Avalonia.Media;
|
||||
using FluentAvalonia.UI.Media;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.Shared.Converters
|
||||
namespace Artemis.UI.Shared.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts <see cref="T:Avalonia.Media.Color" /> into <see cref="T:SkiaSharp.SKColor" />.
|
||||
/// </summary>
|
||||
public class ColorToSKColorConverter : IValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts <see cref="T:Avalonia.Media.Color" /> into <see cref="T:SkiaSharp.SKColor" />.
|
||||
/// </summary>
|
||||
public class ColorToSKColorConverter : IValueConverter
|
||||
/// <inheritdoc />
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is Color avaloniaColor)
|
||||
return new SKColor(avaloniaColor.R, avaloniaColor.G, avaloniaColor.B, avaloniaColor.A);
|
||||
if (value is Color2 fluentAvaloniaColor)
|
||||
return new SKColor(fluentAvaloniaColor.R, fluentAvaloniaColor.G, fluentAvaloniaColor.B, fluentAvaloniaColor.A);
|
||||
if (value is Color avaloniaColor)
|
||||
return new SKColor(avaloniaColor.R, avaloniaColor.G, avaloniaColor.B, avaloniaColor.A);
|
||||
if (value is Color2 fluentAvaloniaColor)
|
||||
return new SKColor(fluentAvaloniaColor.R, fluentAvaloniaColor.G, fluentAvaloniaColor.B, fluentAvaloniaColor.A);
|
||||
|
||||
return SKColor.Empty;
|
||||
}
|
||||
return SKColor.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
Color result = new(0, 0, 0, 0);
|
||||
if (value is SKColor skColor)
|
||||
result = new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue);
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
Color result = new(0, 0, 0, 0);
|
||||
if (value is SKColor skColor)
|
||||
result = new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue);
|
||||
|
||||
if (targetType == typeof(Color2))
|
||||
return (Color2) result;
|
||||
return result;
|
||||
}
|
||||
if (targetType == typeof(Color2))
|
||||
return (Color2) result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -3,23 +3,22 @@ using System.Globalization;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace Artemis.UI.Shared.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an enum into a boolean.
|
||||
/// </summary>
|
||||
public class EnumToBooleanConverter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return Equals(value, parameter);
|
||||
}
|
||||
namespace Artemis.UI.Shared.Converters;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value?.Equals(true) == true ? parameter : BindingOperations.DoNothing;
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts an enum into a boolean.
|
||||
/// </summary>
|
||||
public class EnumToBooleanConverter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return Equals(value, parameter);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value?.Equals(true) == true ? parameter : BindingOperations.DoNothing;
|
||||
}
|
||||
}
|
||||
@@ -5,34 +5,33 @@ using Avalonia.Media;
|
||||
using FluentAvalonia.UI.Media;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.Shared.Converters
|
||||
namespace Artemis.UI.Shared.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts <see cref="T:SkiaSharp.SKColor" /> into <see cref="T:Avalonia.Media.Color" />.
|
||||
/// </summary>
|
||||
public class SKColorToColorConverter : IValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts <see cref="T:SkiaSharp.SKColor" /> into <see cref="T:Avalonia.Media.Color" />.
|
||||
/// </summary>
|
||||
public class SKColorToColorConverter : IValueConverter
|
||||
/// <inheritdoc />
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
Color result = new(0, 0, 0, 0);
|
||||
if (value is SKColor skColor)
|
||||
result = new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue);
|
||||
Color result = new(0, 0, 0, 0);
|
||||
if (value is SKColor skColor)
|
||||
result = new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue);
|
||||
|
||||
if (targetType == typeof(Color2))
|
||||
return (Color2) result;
|
||||
return result;
|
||||
}
|
||||
if (targetType == typeof(Color2))
|
||||
return (Color2) result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is Color avaloniaColor)
|
||||
return new SKColor(avaloniaColor.R, avaloniaColor.G, avaloniaColor.B, avaloniaColor.A);
|
||||
if (value is Color2 fluentAvaloniaColor)
|
||||
return new SKColor(fluentAvaloniaColor.R, fluentAvaloniaColor.G, fluentAvaloniaColor.B, fluentAvaloniaColor.A);
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is Color avaloniaColor)
|
||||
return new SKColor(avaloniaColor.R, avaloniaColor.G, avaloniaColor.B, avaloniaColor.A);
|
||||
if (value is Color2 fluentAvaloniaColor)
|
||||
return new SKColor(fluentAvaloniaColor.R, fluentAvaloniaColor.G, fluentAvaloniaColor.B, fluentAvaloniaColor.A);
|
||||
|
||||
return SKColor.Empty;
|
||||
}
|
||||
return SKColor.Empty;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using Avalonia.Data.Converters;
|
||||
namespace Artemis.UI.Shared.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts any object to string by calling its ToString implementation, seems Avalonia doesn't do this
|
||||
/// Converts any object to string by calling its ToString implementation, seems Avalonia doesn't do this
|
||||
/// </summary>
|
||||
public class ToStringConverter : IValueConverter
|
||||
{
|
||||
|
||||
@@ -3,27 +3,26 @@ using System.Globalization;
|
||||
using Artemis.Core;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace Artemis.UI.Shared.Converters
|
||||
namespace Artemis.UI.Shared.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts <see cref="T:System.Type" /> into <see cref="T:System.String" />.
|
||||
/// </summary>
|
||||
public class TypeToStringConverter : IValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts <see cref="T:System.Type" /> into <see cref="T:System.String" />.
|
||||
/// </summary>
|
||||
public class TypeToStringConverter : IValueConverter
|
||||
/// <inheritdoc />
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
bool humanizeProvided = bool.TryParse(parameter?.ToString(), out bool humanize);
|
||||
if (value is Type type)
|
||||
return type.GetDisplayName(humanizeProvided && humanize);
|
||||
bool humanizeProvided = bool.TryParse(parameter?.ToString(), out bool humanize);
|
||||
if (value is Type type)
|
||||
return type.GetDisplayName(humanizeProvided && humanize);
|
||||
|
||||
return value?.ToString();
|
||||
}
|
||||
return value?.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace Artemis.UI.Shared.Converters;
|
||||
@@ -20,7 +19,7 @@ public class WidthNormalizedConverter : IMultiValueConverter
|
||||
object? first = values.FirstOrDefault();
|
||||
object? second = values.Skip(1).FirstOrDefault();
|
||||
if (first is float value && second is double totalWidth)
|
||||
return (totalWidth / 1.0) * value;
|
||||
return totalWidth / 1.0 * value;
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@@ -1,73 +1,71 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Artemis.Core.Modules;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared.DataModelVisualization
|
||||
namespace Artemis.UI.Shared.DataModelVisualization;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a <see cref="DataModel" /> display view model
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the data model</typeparam>
|
||||
public abstract class DataModelDisplayViewModel<T> : DataModelDisplayViewModel
|
||||
{
|
||||
[AllowNull] private T _displayValue;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a <see cref="DataModel" /> display view model
|
||||
/// Gets or sets value that the view model must display
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the data model</typeparam>
|
||||
public abstract class DataModelDisplayViewModel<T> : DataModelDisplayViewModel
|
||||
[AllowNull]
|
||||
public T DisplayValue
|
||||
{
|
||||
[AllowNull] private T _displayValue = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets value that the view model must display
|
||||
/// </summary>
|
||||
[AllowNull]
|
||||
public T DisplayValue
|
||||
{
|
||||
get => _displayValue;
|
||||
set
|
||||
{
|
||||
if (Equals(value, _displayValue)) return;
|
||||
RaiseAndSetIfChanged(ref _displayValue, value);
|
||||
OnDisplayValueUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
internal override object InternalGuard => new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void UpdateValue(object? model)
|
||||
{
|
||||
DisplayValue = model is T value ? value : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the display value is updated
|
||||
/// </summary>
|
||||
protected virtual void OnDisplayValueUpdated()
|
||||
get => _displayValue;
|
||||
set
|
||||
{
|
||||
if (Equals(value, _displayValue)) return;
|
||||
RaiseAndSetIfChanged(ref _displayValue, value);
|
||||
OnDisplayValueUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only, implement <see cref="DataModelDisplayViewModel{T}" /> instead.
|
||||
/// </summary>
|
||||
public abstract class DataModelDisplayViewModel : ViewModelBase
|
||||
internal override object InternalGuard => new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void UpdateValue(object? model)
|
||||
{
|
||||
private DataModelPropertyAttribute? _propertyDescription;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property description of this value
|
||||
/// </summary>
|
||||
public DataModelPropertyAttribute? PropertyDescription
|
||||
{
|
||||
get => _propertyDescription;
|
||||
internal set => RaiseAndSetIfChanged(ref _propertyDescription, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents this type being implemented directly, implement <see cref="DataModelDisplayViewModel{T}" /> instead.
|
||||
/// </summary>
|
||||
internal abstract object InternalGuard { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the display value
|
||||
/// </summary>
|
||||
/// <param name="model">The value to set</param>
|
||||
public abstract void UpdateValue(object? model);
|
||||
DisplayValue = model is T value ? value : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the display value is updated
|
||||
/// </summary>
|
||||
protected virtual void OnDisplayValueUpdated()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only, implement <see cref="DataModelDisplayViewModel{T}" /> instead.
|
||||
/// </summary>
|
||||
public abstract class DataModelDisplayViewModel : ViewModelBase
|
||||
{
|
||||
private DataModelPropertyAttribute? _propertyDescription;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property description of this value
|
||||
/// </summary>
|
||||
public DataModelPropertyAttribute? PropertyDescription
|
||||
{
|
||||
get => _propertyDescription;
|
||||
internal set => RaiseAndSetIfChanged(ref _propertyDescription, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents this type being implemented directly, implement <see cref="DataModelDisplayViewModel{T}" /> instead.
|
||||
/// </summary>
|
||||
internal abstract object InternalGuard { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Updates the display value
|
||||
/// </summary>
|
||||
/// <param name="model">The value to set</param>
|
||||
public abstract void UpdateValue(object? model);
|
||||
}
|
||||
@@ -4,111 +4,110 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Artemis.Core.Modules;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared.DataModelVisualization
|
||||
namespace Artemis.UI.Shared.DataModelVisualization;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a <see cref="DataModel" /> input view model
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the data model</typeparam>
|
||||
public abstract class DataModelInputViewModel<T> : DataModelInputViewModel
|
||||
{
|
||||
private bool _closed;
|
||||
[AllowNull] private T _inputValue;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelInputViewModel{T}" /> class
|
||||
/// </summary>
|
||||
/// <param name="targetDescription">The description of the property this input VM is representing</param>
|
||||
/// <param name="initialValue">The initial value to set the input value to</param>
|
||||
protected DataModelInputViewModel(DataModelPropertyAttribute targetDescription, T initialValue)
|
||||
{
|
||||
TargetDescription = targetDescription;
|
||||
InputValue = initialValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value shown in the input
|
||||
/// </summary>
|
||||
[AllowNull]
|
||||
public T InputValue
|
||||
{
|
||||
get => _inputValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _inputValue, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the property this input VM is representing
|
||||
/// </summary>
|
||||
public DataModelPropertyAttribute TargetDescription { get; }
|
||||
|
||||
internal override object InternalGuard { get; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override void Submit()
|
||||
{
|
||||
if (_closed)
|
||||
return;
|
||||
_closed = true;
|
||||
|
||||
OnSubmit();
|
||||
UpdateCallback(InputValue, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override void Cancel()
|
||||
{
|
||||
if (_closed)
|
||||
return;
|
||||
_closed = true;
|
||||
|
||||
OnCancel();
|
||||
UpdateCallback(InputValue, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only, implement <see cref="DataModelInputViewModel{T}" /> instead.
|
||||
/// </summary>
|
||||
public abstract class DataModelInputViewModel : ReactiveObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="DataModel" /> input view model
|
||||
/// Prevents this type being implemented directly, implement <see cref="DataModelInputViewModel{T}" /> instead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the data model</typeparam>
|
||||
public abstract class DataModelInputViewModel<T> : DataModelInputViewModel
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
internal abstract object InternalGuard { get; }
|
||||
|
||||
internal Action<object?, bool> UpdateCallback { get; set; } = null!; // Set right after construction
|
||||
|
||||
/// <summary>
|
||||
/// Gets the types this input view model can support through type conversion. This list is defined when registering the
|
||||
/// view model.
|
||||
/// </summary>
|
||||
internal IReadOnlyCollection<Type>? CompatibleConversionTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Submits the input value and removes this view model.
|
||||
/// <para>This is called automatically when the user presses enter or clicks outside the view</para>
|
||||
/// </summary>
|
||||
public abstract void Submit();
|
||||
|
||||
/// <summary>
|
||||
/// Discards changes to the input value and removes this view model.
|
||||
/// <para>This is called automatically when the user presses escape</para>
|
||||
/// </summary>
|
||||
public abstract void Cancel();
|
||||
|
||||
/// <summary>
|
||||
/// Called before the current value is submitted
|
||||
/// </summary>
|
||||
protected virtual void OnSubmit()
|
||||
{
|
||||
private bool _closed;
|
||||
[AllowNull] private T _inputValue = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelInputViewModel{T}" /> class
|
||||
/// </summary>
|
||||
/// <param name="targetDescription">The description of the property this input VM is representing</param>
|
||||
/// <param name="initialValue">The initial value to set the input value to</param>
|
||||
protected DataModelInputViewModel(DataModelPropertyAttribute targetDescription, T initialValue)
|
||||
{
|
||||
TargetDescription = targetDescription;
|
||||
InputValue = initialValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value shown in the input
|
||||
/// </summary>
|
||||
[AllowNull]
|
||||
public T InputValue
|
||||
{
|
||||
get => _inputValue;
|
||||
set => this.RaiseAndSetIfChanged(ref _inputValue, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the property this input VM is representing
|
||||
/// </summary>
|
||||
public DataModelPropertyAttribute TargetDescription { get; }
|
||||
|
||||
internal override object InternalGuard { get; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override void Submit()
|
||||
{
|
||||
if (_closed)
|
||||
return;
|
||||
_closed = true;
|
||||
|
||||
OnSubmit();
|
||||
UpdateCallback(InputValue, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public sealed override void Cancel()
|
||||
{
|
||||
if (_closed)
|
||||
return;
|
||||
_closed = true;
|
||||
|
||||
OnCancel();
|
||||
UpdateCallback(InputValue, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For internal use only, implement <see cref="DataModelInputViewModel{T}" /> instead.
|
||||
/// Called before the current value is discarded
|
||||
/// </summary>
|
||||
public abstract class DataModelInputViewModel : ReactiveObject
|
||||
protected virtual void OnCancel()
|
||||
{
|
||||
/// <summary>
|
||||
/// Prevents this type being implemented directly, implement <see cref="DataModelInputViewModel{T}" /> instead.
|
||||
/// </summary>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
internal abstract object InternalGuard { get; }
|
||||
|
||||
internal Action<object?, bool> UpdateCallback { get; set; } = null!; // Set right after construction
|
||||
|
||||
/// <summary>
|
||||
/// Gets the types this input view model can support through type conversion. This list is defined when registering the
|
||||
/// view model.
|
||||
/// </summary>
|
||||
internal IReadOnlyCollection<Type>? CompatibleConversionTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Submits the input value and removes this view model.
|
||||
/// <para>This is called automatically when the user presses enter or clicks outside the view</para>
|
||||
/// </summary>
|
||||
public abstract void Submit();
|
||||
|
||||
/// <summary>
|
||||
/// Discards changes to the input value and removes this view model.
|
||||
/// <para>This is called automatically when the user presses escape</para>
|
||||
/// </summary>
|
||||
public abstract void Cancel();
|
||||
|
||||
/// <summary>
|
||||
/// Called before the current value is submitted
|
||||
/// </summary>
|
||||
protected virtual void OnSubmit()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before the current value is discarded
|
||||
/// </summary>
|
||||
protected virtual void OnCancel()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
-76
@@ -3,86 +3,85 @@ using System.Collections.Generic;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Services;
|
||||
|
||||
namespace Artemis.UI.Shared.DataModelVisualization
|
||||
namespace Artemis.UI.Shared.DataModelVisualization;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a layer brush registered through
|
||||
/// <see cref="IDataModelUIService.RegisterDataModelInput{T}" /> or
|
||||
/// <see cref="IDataModelUIService.RegisterDataModelDisplay{T}" />
|
||||
/// </summary>
|
||||
public class DataModelVisualizationRegistration
|
||||
{
|
||||
private readonly IDataModelUIService _dataModelUIService;
|
||||
|
||||
internal DataModelVisualizationRegistration(IDataModelUIService dataModelUIService,
|
||||
RegistrationType registrationType,
|
||||
Plugin plugin,
|
||||
Type supportedType,
|
||||
Type viewModelType)
|
||||
{
|
||||
_dataModelUIService = dataModelUIService;
|
||||
RegistrationType = registrationType;
|
||||
Plugin = plugin;
|
||||
SupportedType = supportedType;
|
||||
ViewModelType = viewModelType;
|
||||
|
||||
if (Plugin != Constants.CorePlugin)
|
||||
Plugin.Disabled += InstanceOnDisabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of registration, either a display or an input
|
||||
/// </summary>
|
||||
public RegistrationType RegistrationType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin that registered the visualization
|
||||
/// </summary>
|
||||
public Plugin Plugin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type supported by the visualization
|
||||
/// </summary>
|
||||
public Type SupportedType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the view model type of the visualization
|
||||
/// </summary>
|
||||
public Type ViewModelType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only collection of types this visualization can convert to and from
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<Type>? CompatibleConversionTypes { get; internal set; }
|
||||
|
||||
internal void Unsubscribe()
|
||||
{
|
||||
if (Plugin != Constants.CorePlugin)
|
||||
Plugin.Disabled -= InstanceOnDisabled;
|
||||
}
|
||||
|
||||
private void InstanceOnDisabled(object? sender, EventArgs e)
|
||||
{
|
||||
if (RegistrationType == RegistrationType.Input)
|
||||
_dataModelUIService.RemoveDataModelInput(this);
|
||||
else if (RegistrationType == RegistrationType.Display)
|
||||
_dataModelUIService.RemoveDataModelDisplay(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a type of data model visualization registration
|
||||
/// </summary>
|
||||
public enum RegistrationType
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a layer brush registered through
|
||||
/// <see cref="IDataModelUIService.RegisterDataModelInput{T}" /> or
|
||||
/// <see cref="IDataModelUIService.RegisterDataModelDisplay{T}" />
|
||||
/// A visualization used for displaying values
|
||||
/// </summary>
|
||||
public class DataModelVisualizationRegistration
|
||||
{
|
||||
private readonly IDataModelUIService _dataModelUIService;
|
||||
|
||||
internal DataModelVisualizationRegistration(IDataModelUIService dataModelUIService,
|
||||
RegistrationType registrationType,
|
||||
Plugin plugin,
|
||||
Type supportedType,
|
||||
Type viewModelType)
|
||||
{
|
||||
_dataModelUIService = dataModelUIService;
|
||||
RegistrationType = registrationType;
|
||||
Plugin = plugin;
|
||||
SupportedType = supportedType;
|
||||
ViewModelType = viewModelType;
|
||||
|
||||
if (Plugin != Constants.CorePlugin)
|
||||
Plugin.Disabled += InstanceOnDisabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of registration, either a display or an input
|
||||
/// </summary>
|
||||
public RegistrationType RegistrationType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin that registered the visualization
|
||||
/// </summary>
|
||||
public Plugin Plugin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type supported by the visualization
|
||||
/// </summary>
|
||||
public Type SupportedType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the view model type of the visualization
|
||||
/// </summary>
|
||||
public Type ViewModelType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only collection of types this visualization can convert to and from
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<Type>? CompatibleConversionTypes { get; internal set; }
|
||||
|
||||
internal void Unsubscribe()
|
||||
{
|
||||
if (Plugin != Constants.CorePlugin)
|
||||
Plugin.Disabled -= InstanceOnDisabled;
|
||||
}
|
||||
|
||||
private void InstanceOnDisabled(object? sender, EventArgs e)
|
||||
{
|
||||
if (RegistrationType == RegistrationType.Input)
|
||||
_dataModelUIService.RemoveDataModelInput(this);
|
||||
else if (RegistrationType == RegistrationType.Display)
|
||||
_dataModelUIService.RemoveDataModelDisplay(this);
|
||||
}
|
||||
}
|
||||
Display,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a type of data model visualization registration
|
||||
/// A visualization used for inputting values
|
||||
/// </summary>
|
||||
public enum RegistrationType
|
||||
{
|
||||
/// <summary>
|
||||
/// A visualization used for displaying values
|
||||
/// </summary>
|
||||
Display,
|
||||
|
||||
/// <summary>
|
||||
/// A visualization used for inputting values
|
||||
/// </summary>
|
||||
Input
|
||||
}
|
||||
Input
|
||||
}
|
||||
@@ -5,66 +5,65 @@ using Artemis.Core.Modules;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a view model that visualizes an event data model property
|
||||
/// </summary>
|
||||
public class DataModelEventViewModel : DataModelVisualizationViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a view model that visualizes an event data model property
|
||||
/// </summary>
|
||||
public class DataModelEventViewModel : DataModelVisualizationViewModel
|
||||
private Type? _displayValueType;
|
||||
|
||||
internal DataModelEventViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath)
|
||||
{
|
||||
private Type? _displayValueType;
|
||||
}
|
||||
|
||||
internal DataModelEventViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath) : base(dataModel, parent, dataModelPath)
|
||||
/// <summary>
|
||||
/// Gets the type of event arguments this event triggers and that must be displayed as children
|
||||
/// </summary>
|
||||
public Type? DisplayValueType
|
||||
{
|
||||
get => _displayValueType;
|
||||
set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
|
||||
{
|
||||
DisplayValueType = DataModelPath?.GetPropertyType();
|
||||
|
||||
if (configuration != null)
|
||||
{
|
||||
if (configuration.CreateEventChildren)
|
||||
PopulateProperties(dataModelUIService, configuration);
|
||||
else if (Children.Any())
|
||||
Children.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of event arguments this event triggers and that must be displayed as children
|
||||
/// </summary>
|
||||
public Type? DisplayValueType
|
||||
{
|
||||
get => _displayValueType;
|
||||
set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||
}
|
||||
// Only update children if the parent is expanded
|
||||
if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded)
|
||||
return;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
|
||||
{
|
||||
DisplayValueType = DataModelPath?.GetPropertyType();
|
||||
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
|
||||
dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
|
||||
}
|
||||
|
||||
if (configuration != null)
|
||||
{
|
||||
if (configuration.CreateEventChildren)
|
||||
PopulateProperties(dataModelUIService, configuration);
|
||||
else if (Children.Any())
|
||||
Children.Clear();
|
||||
}
|
||||
|
||||
// Only update children if the parent is expanded
|
||||
if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded)
|
||||
return;
|
||||
/// <summary>
|
||||
/// Always returns <see langword="null" /> for data model events
|
||||
/// </summary>
|
||||
public override object? GetCurrentValue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
|
||||
dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override string? ToString()
|
||||
{
|
||||
return DisplayPath ?? Path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Always returns <see langword="null"/> for data model events
|
||||
/// </summary>
|
||||
public override object? GetCurrentValue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string? ToString()
|
||||
{
|
||||
return DisplayPath ?? Path;
|
||||
}
|
||||
|
||||
internal override int GetChildDepth()
|
||||
{
|
||||
return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1;
|
||||
}
|
||||
internal override int GetChildDepth()
|
||||
{
|
||||
return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1;
|
||||
}
|
||||
}
|
||||
@@ -2,90 +2,89 @@
|
||||
using Artemis.UI.Shared.Services;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a view model that visualizes a single data model property contained in a
|
||||
/// <see cref="DataModelListViewModel" />
|
||||
/// </summary>
|
||||
public class DataModelListItemViewModel : DataModelVisualizationViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a view model that visualizes a single data model property contained in a
|
||||
/// <see cref="DataModelListViewModel" />
|
||||
/// </summary>
|
||||
public class DataModelListItemViewModel : DataModelVisualizationViewModel
|
||||
private object? _displayValue;
|
||||
private DataModelDisplayViewModel? _displayViewModel;
|
||||
private int _index;
|
||||
private Type? _listType;
|
||||
|
||||
internal DataModelListItemViewModel(Type listType, DataModelDisplayViewModel displayViewModel, string? name) : base(null, null, null)
|
||||
{
|
||||
private int _index;
|
||||
private Type? _listType;
|
||||
private object? _displayValue;
|
||||
private DataModelDisplayViewModel? _displayViewModel;
|
||||
ListType = listType;
|
||||
DisplayViewModel = displayViewModel;
|
||||
}
|
||||
|
||||
internal DataModelListItemViewModel(Type listType, DataModelDisplayViewModel displayViewModel, string? name) : base(null, null, null)
|
||||
{
|
||||
ListType = listType;
|
||||
DisplayViewModel = displayViewModel;
|
||||
}
|
||||
internal DataModelListItemViewModel(Type listType, string? name) : base(null, null, null)
|
||||
{
|
||||
ListType = listType;
|
||||
}
|
||||
|
||||
internal DataModelListItemViewModel(Type listType, string? name) : base(null, null, null)
|
||||
{
|
||||
ListType = listType;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the view model used to display the display value
|
||||
/// </summary>
|
||||
public DataModelDisplayViewModel? DisplayViewModel
|
||||
{
|
||||
get => _displayViewModel;
|
||||
internal set => this.RaiseAndSetIfChanged(ref _displayViewModel, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the view model used to display the display value
|
||||
/// </summary>
|
||||
public DataModelDisplayViewModel? DisplayViewModel
|
||||
{
|
||||
get => _displayViewModel;
|
||||
internal set => this.RaiseAndSetIfChanged(ref _displayViewModel, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the index of the element within the list
|
||||
/// </summary>
|
||||
public int Index
|
||||
{
|
||||
get => _index;
|
||||
internal set => this.RaiseAndSetIfChanged(ref _index, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the element within the list
|
||||
/// </summary>
|
||||
public int Index
|
||||
{
|
||||
get => _index;
|
||||
internal set => this.RaiseAndSetIfChanged(ref _index, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the type of elements contained in the list
|
||||
/// </summary>
|
||||
public Type? ListType
|
||||
{
|
||||
get => _listType;
|
||||
private set => this.RaiseAndSetIfChanged(ref _listType, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of elements contained in the list
|
||||
/// </summary>
|
||||
public Type? ListType
|
||||
{
|
||||
get => _listType;
|
||||
private set => this.RaiseAndSetIfChanged(ref _listType, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the value of the property that is being visualized
|
||||
/// </summary>
|
||||
public object? DisplayValue
|
||||
{
|
||||
get => _displayValue;
|
||||
internal set => this.RaiseAndSetIfChanged(ref _displayValue, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the property that is being visualized
|
||||
/// </summary>
|
||||
public object? DisplayValue
|
||||
{
|
||||
get => _displayValue;
|
||||
internal set => this.RaiseAndSetIfChanged(ref _displayValue, value);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override object? GetCurrentValue()
|
||||
{
|
||||
return DisplayValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? GetCurrentValue()
|
||||
{
|
||||
return DisplayValue;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
|
||||
{
|
||||
// Display value gets updated by parent, don't do anything if it is null
|
||||
if (DisplayValue == null)
|
||||
return;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
|
||||
{
|
||||
// Display value gets updated by parent, don't do anything if it is null
|
||||
if (DisplayValue == null)
|
||||
return;
|
||||
if (DisplayViewModel == null)
|
||||
DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(DisplayValue.GetType(), PropertyDescription, true);
|
||||
|
||||
if (DisplayViewModel == null)
|
||||
DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(DisplayValue.GetType(), PropertyDescription, true);
|
||||
ListType = DisplayValue.GetType();
|
||||
DisplayViewModel?.UpdateValue(DisplayValue);
|
||||
}
|
||||
|
||||
ListType = DisplayValue.GetType();
|
||||
DisplayViewModel?.UpdateValue(DisplayValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[List item {Index}]";
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[List item {Index}]";
|
||||
}
|
||||
}
|
||||
@@ -6,131 +6,132 @@ using Artemis.Core.Modules;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a view model that visualizes a list data model property
|
||||
/// </summary>
|
||||
public class DataModelListViewModel : DataModelVisualizationViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a view model that visualizes a list data model property
|
||||
/// </summary>
|
||||
public class DataModelListViewModel : DataModelVisualizationViewModel
|
||||
private string _countDisplay;
|
||||
private Type? _displayValueType;
|
||||
private IEnumerable? _list;
|
||||
private ObservableCollection<DataModelVisualizationViewModel> _listChildren;
|
||||
private int _listCount;
|
||||
|
||||
internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath)
|
||||
: base(dataModel, parent, dataModelPath)
|
||||
{
|
||||
private string _countDisplay;
|
||||
private Type? _displayValueType;
|
||||
private IEnumerable? _list;
|
||||
private ObservableCollection<DataModelVisualizationViewModel> _listChildren;
|
||||
private int _listCount;
|
||||
_countDisplay = "0 items";
|
||||
_listChildren = new ObservableCollection<DataModelVisualizationViewModel>();
|
||||
}
|
||||
|
||||
internal DataModelListViewModel(DataModel dataModel, DataModelVisualizationViewModel parent, DataModelPath dataModelPath)
|
||||
: base(dataModel, parent, dataModelPath)
|
||||
/// <summary>
|
||||
/// Gets the instance of the list that is being visualized
|
||||
/// </summary>
|
||||
public IEnumerable? List
|
||||
{
|
||||
get => _list;
|
||||
private set => this.RaiseAndSetIfChanged(ref _list, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets amount of elements in the list that is being visualized
|
||||
/// </summary>
|
||||
public int ListCount
|
||||
{
|
||||
get => _listCount;
|
||||
private set => this.RaiseAndSetIfChanged(ref _listCount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of elements this list contains and that must be displayed as children
|
||||
/// </summary>
|
||||
public Type? DisplayValueType
|
||||
{
|
||||
get => _displayValueType;
|
||||
set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a human readable display count
|
||||
/// </summary>
|
||||
public string CountDisplay
|
||||
{
|
||||
get => _countDisplay;
|
||||
set => this.RaiseAndSetIfChanged(ref _countDisplay, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of child view models that visualize the elements in the list
|
||||
/// </summary>
|
||||
public ObservableCollection<DataModelVisualizationViewModel> ListChildren
|
||||
{
|
||||
get => _listChildren;
|
||||
private set => this.RaiseAndSetIfChanged(ref _listChildren, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
|
||||
{
|
||||
if (Parent != null && !Parent.IsVisualizationExpanded)
|
||||
return;
|
||||
|
||||
List = GetCurrentValue() as IEnumerable;
|
||||
DisplayValueType = List?.GetType();
|
||||
if (List == null)
|
||||
return;
|
||||
|
||||
int index = 0;
|
||||
foreach (object item in List)
|
||||
{
|
||||
_countDisplay = "0 items";
|
||||
_listChildren = new ObservableCollection<DataModelVisualizationViewModel>();
|
||||
}
|
||||
if (item == null)
|
||||
continue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance of the list that is being visualized
|
||||
/// </summary>
|
||||
public IEnumerable? List
|
||||
{
|
||||
get => _list;
|
||||
private set => this.RaiseAndSetIfChanged(ref _list, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets amount of elements in the list that is being visualized
|
||||
/// </summary>
|
||||
public int ListCount
|
||||
{
|
||||
get => _listCount;
|
||||
private set => this.RaiseAndSetIfChanged(ref _listCount, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of elements this list contains and that must be displayed as children
|
||||
/// </summary>
|
||||
public Type? DisplayValueType
|
||||
{
|
||||
get => _displayValueType;
|
||||
set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a human readable display count
|
||||
/// </summary>
|
||||
public string CountDisplay
|
||||
{
|
||||
get => _countDisplay;
|
||||
set => this.RaiseAndSetIfChanged(ref _countDisplay, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of child view models that visualize the elements in the list
|
||||
/// </summary>
|
||||
public ObservableCollection<DataModelVisualizationViewModel> ListChildren
|
||||
{
|
||||
get => _listChildren;
|
||||
private set => this.RaiseAndSetIfChanged(ref _listChildren, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
|
||||
{
|
||||
if (Parent != null && !Parent.IsVisualizationExpanded)
|
||||
return;
|
||||
|
||||
List = GetCurrentValue() as IEnumerable;
|
||||
DisplayValueType = List?.GetType();
|
||||
if (List == null)
|
||||
return;
|
||||
|
||||
int index = 0;
|
||||
foreach (object item in List)
|
||||
DataModelVisualizationViewModel? child;
|
||||
if (ListChildren.Count <= index)
|
||||
{
|
||||
if (item == null)
|
||||
child = CreateListChild(dataModelUIService, item.GetType(), DataModelPath?.GetPropertyDescription()?.ListItemName);
|
||||
if (child == null)
|
||||
continue;
|
||||
|
||||
DataModelVisualizationViewModel? child;
|
||||
if (ListChildren.Count <= index)
|
||||
{
|
||||
child = CreateListChild(dataModelUIService, item.GetType(), DataModelPath?.GetPropertyDescription()?.ListItemName);
|
||||
if (child == null)
|
||||
continue;
|
||||
ListChildren.Add(child);
|
||||
}
|
||||
else
|
||||
child = ListChildren[index];
|
||||
|
||||
if (child is DataModelListItemViewModel dataModelListPropertyViewModel)
|
||||
{
|
||||
dataModelListPropertyViewModel.DisplayValue = item;
|
||||
dataModelListPropertyViewModel.Index = index;
|
||||
dataModelListPropertyViewModel.Update(dataModelUIService, configuration);
|
||||
}
|
||||
|
||||
index++;
|
||||
ListChildren.Add(child);
|
||||
}
|
||||
else
|
||||
{
|
||||
child = ListChildren[index];
|
||||
}
|
||||
|
||||
ListCount = index;
|
||||
if (child is DataModelListItemViewModel dataModelListPropertyViewModel)
|
||||
{
|
||||
dataModelListPropertyViewModel.DisplayValue = item;
|
||||
dataModelListPropertyViewModel.Index = index;
|
||||
dataModelListPropertyViewModel.Update(dataModelUIService, configuration);
|
||||
}
|
||||
|
||||
while (ListChildren.Count > ListCount)
|
||||
ListChildren.RemoveAt(ListChildren.Count - 1);
|
||||
|
||||
CountDisplay = $"{ListChildren.Count} {(ListChildren.Count == 1 ? "item" : "items")}";
|
||||
index++;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[List] {DisplayPath ?? Path} - {ListCount} item(s)";
|
||||
}
|
||||
ListCount = index;
|
||||
|
||||
private DataModelVisualizationViewModel? CreateListChild(IDataModelUIService dataModelUIService, Type listType, string? name)
|
||||
{
|
||||
// If a display VM was found, prefer to use that in any case
|
||||
DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(listType, PropertyDescription);
|
||||
while (ListChildren.Count > ListCount)
|
||||
ListChildren.RemoveAt(ListChildren.Count - 1);
|
||||
|
||||
return typeViewModel != null
|
||||
? new DataModelListItemViewModel(listType, typeViewModel, name)
|
||||
: new DataModelListItemViewModel(listType, name);
|
||||
}
|
||||
CountDisplay = $"{ListChildren.Count} {(ListChildren.Count == 1 ? "item" : "items")}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[List] {DisplayPath ?? Path} - {ListCount} item(s)";
|
||||
}
|
||||
|
||||
private DataModelVisualizationViewModel? CreateListChild(IDataModelUIService dataModelUIService, Type listType, string? name)
|
||||
{
|
||||
// If a display VM was found, prefer to use that in any case
|
||||
DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(listType, PropertyDescription);
|
||||
|
||||
return typeViewModel != null
|
||||
? new DataModelListItemViewModel(listType, typeViewModel, name)
|
||||
: new DataModelListItemViewModel(listType, name);
|
||||
}
|
||||
}
|
||||
+61
-62
@@ -4,79 +4,78 @@ using Artemis.Core.Modules;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a view model that visualizes a class (POCO) data model property containing child properties
|
||||
/// </summary>
|
||||
public class DataModelPropertiesViewModel : DataModelVisualizationViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a view model that visualizes a class (POCO) data model property containing child properties
|
||||
/// </summary>
|
||||
public class DataModelPropertiesViewModel : DataModelVisualizationViewModel
|
||||
private object? _displayValue;
|
||||
private Type? _displayValueType;
|
||||
|
||||
internal DataModelPropertiesViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
|
||||
: base(dataModel, parent, dataModelPath)
|
||||
{
|
||||
private object? _displayValue;
|
||||
private Type? _displayValueType;
|
||||
}
|
||||
|
||||
internal DataModelPropertiesViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
|
||||
: base(dataModel, parent, dataModelPath)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the type of the property that is being visualized
|
||||
/// </summary>
|
||||
public Type? DisplayValueType
|
||||
{
|
||||
get => _displayValueType;
|
||||
private set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the property that is being visualized
|
||||
/// </summary>
|
||||
public Type? DisplayValueType
|
||||
{
|
||||
get => _displayValueType;
|
||||
private set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the value of the property that is being visualized
|
||||
/// </summary>
|
||||
public object? DisplayValue
|
||||
{
|
||||
get => _displayValue;
|
||||
private set => this.RaiseAndSetIfChanged(ref _displayValue, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the property that is being visualized
|
||||
/// </summary>
|
||||
public object? DisplayValue
|
||||
{
|
||||
get => _displayValue;
|
||||
private set => this.RaiseAndSetIfChanged(ref _displayValue, value);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
|
||||
{
|
||||
DisplayValueType = DataModelPath?.GetPropertyType();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
|
||||
{
|
||||
DisplayValueType = DataModelPath?.GetPropertyType();
|
||||
// Only set a display value if ToString returns useful information and not just the type name
|
||||
object? currentValue = GetCurrentValue();
|
||||
if (currentValue != null && currentValue.ToString() != currentValue.GetType().ToString())
|
||||
DisplayValue = currentValue.ToString();
|
||||
else
|
||||
DisplayValue = null;
|
||||
|
||||
// Only set a display value if ToString returns useful information and not just the type name
|
||||
object? currentValue = GetCurrentValue();
|
||||
if (currentValue != null && currentValue.ToString() != currentValue.GetType().ToString())
|
||||
DisplayValue = currentValue.ToString();
|
||||
else
|
||||
DisplayValue = null;
|
||||
// Always populate properties
|
||||
PopulateProperties(dataModelUIService, configuration);
|
||||
|
||||
// Always populate properties
|
||||
PopulateProperties(dataModelUIService, configuration);
|
||||
// Only update children if the parent is expanded
|
||||
if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded)
|
||||
return;
|
||||
|
||||
// Only update children if the parent is expanded
|
||||
if (Parent != null && !Parent.IsRootViewModel && !Parent.IsVisualizationExpanded)
|
||||
return;
|
||||
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
|
||||
dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
|
||||
}
|
||||
|
||||
foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children)
|
||||
dataModelVisualizationViewModel.Update(dataModelUIService, configuration);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override object? GetCurrentValue()
|
||||
{
|
||||
if (Parent == null || Parent.IsRootViewModel || IsRootViewModel)
|
||||
return DataModel;
|
||||
return base.GetCurrentValue();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object? GetCurrentValue()
|
||||
{
|
||||
if (Parent == null || Parent.IsRootViewModel || IsRootViewModel)
|
||||
return DataModel;
|
||||
return base.GetCurrentValue();
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override string? ToString()
|
||||
{
|
||||
return DisplayPath ?? Path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string? ToString()
|
||||
{
|
||||
return DisplayPath ?? Path;
|
||||
}
|
||||
|
||||
internal override int GetChildDepth()
|
||||
{
|
||||
return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1;
|
||||
}
|
||||
internal override int GetChildDepth()
|
||||
{
|
||||
return PropertyDescription != null && !PropertyDescription.ResetsDepth ? Depth + 1 : 1;
|
||||
}
|
||||
}
|
||||
@@ -4,79 +4,78 @@ using Artemis.Core.Modules;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a view model that visualizes a single data model property contained in a
|
||||
/// <see cref="DataModelPropertiesViewModel" />
|
||||
/// </summary>
|
||||
public class DataModelPropertyViewModel : DataModelVisualizationViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a view model that visualizes a single data model property contained in a
|
||||
/// <see cref="DataModelPropertiesViewModel" />
|
||||
/// </summary>
|
||||
public class DataModelPropertyViewModel : DataModelVisualizationViewModel
|
||||
private object? _displayValue;
|
||||
private Type? _displayValueType;
|
||||
private DataModelDisplayViewModel? _displayViewModel;
|
||||
|
||||
internal DataModelPropertyViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
|
||||
: base(dataModel, parent, dataModelPath)
|
||||
{
|
||||
private object? _displayValue;
|
||||
private Type? _displayValueType;
|
||||
private DataModelDisplayViewModel? _displayViewModel;
|
||||
}
|
||||
|
||||
internal DataModelPropertyViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath)
|
||||
: base(dataModel, parent, dataModelPath)
|
||||
/// <summary>
|
||||
/// Gets the value of the property that is being visualized
|
||||
/// </summary>
|
||||
public object? DisplayValue
|
||||
{
|
||||
get => _displayValue;
|
||||
internal set => this.RaiseAndSetIfChanged(ref _displayValue, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the property that is being visualized
|
||||
/// </summary>
|
||||
public Type? DisplayValueType
|
||||
{
|
||||
get => _displayValueType;
|
||||
protected set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the view model used to display the display value
|
||||
/// </summary>
|
||||
public DataModelDisplayViewModel? DisplayViewModel
|
||||
{
|
||||
get => _displayViewModel;
|
||||
internal set => this.RaiseAndSetIfChanged(ref _displayViewModel, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
|
||||
{
|
||||
if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel)
|
||||
return;
|
||||
|
||||
if (DisplayViewModel == null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the property that is being visualized
|
||||
/// </summary>
|
||||
public object? DisplayValue
|
||||
{
|
||||
get => _displayValue;
|
||||
internal set => this.RaiseAndSetIfChanged(ref _displayValue, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the property that is being visualized
|
||||
/// </summary>
|
||||
public Type? DisplayValueType
|
||||
{
|
||||
get => _displayValueType;
|
||||
protected set => this.RaiseAndSetIfChanged(ref _displayValueType, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the view model used to display the display value
|
||||
/// </summary>
|
||||
public DataModelDisplayViewModel? DisplayViewModel
|
||||
{
|
||||
get => _displayViewModel;
|
||||
internal set => this.RaiseAndSetIfChanged(ref _displayViewModel, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(IDataModelUIService dataModelUIService, DataModelUpdateConfiguration? configuration)
|
||||
{
|
||||
if (Parent != null && !Parent.IsVisualizationExpanded && !Parent.IsRootViewModel)
|
||||
return;
|
||||
|
||||
if (DisplayViewModel == null)
|
||||
Type? propertyType = DataModelPath?.GetPropertyType();
|
||||
if (propertyType != null)
|
||||
{
|
||||
Type? propertyType = DataModelPath?.GetPropertyType();
|
||||
if (propertyType != null)
|
||||
{
|
||||
DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType, PropertyDescription, true);
|
||||
if (DisplayViewModel != null)
|
||||
DisplayViewModel.PropertyDescription = DataModelPath?.GetPropertyDescription();
|
||||
}
|
||||
DisplayViewModel = dataModelUIService.GetDataModelDisplayViewModel(propertyType, PropertyDescription, true);
|
||||
if (DisplayViewModel != null)
|
||||
DisplayViewModel.PropertyDescription = DataModelPath?.GetPropertyDescription();
|
||||
}
|
||||
|
||||
DisplayValue = GetCurrentValue();
|
||||
DisplayValueType = DisplayValue != null ? DisplayValue.GetType() : DataModelPath?.GetPropertyType();
|
||||
|
||||
DisplayViewModel?.UpdateValue(DisplayValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
if (DisplayValueType != null)
|
||||
return $"[{DisplayValueType.Name}] {DisplayPath ?? Path} - {DisplayValue}";
|
||||
return $"{DisplayPath ?? Path} - {DisplayValue}";
|
||||
}
|
||||
DisplayValue = GetCurrentValue();
|
||||
DisplayValueType = DisplayValue != null ? DisplayValue.GetType() : DataModelPath?.GetPropertyType();
|
||||
|
||||
DisplayViewModel?.UpdateValue(DisplayValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
if (DisplayValueType != null)
|
||||
return $"[{DisplayValueType.Name}] {DisplayPath ?? Path} - {DisplayValue}";
|
||||
return $"{DisplayPath ?? Path} - {DisplayValue}";
|
||||
}
|
||||
}
|
||||
+15
-16
@@ -1,22 +1,21 @@
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a configuration to use while updating a <see cref="DataModelVisualizationViewModel" />
|
||||
/// </summary>
|
||||
public class DataModelUpdateConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a configuration to use while updating a <see cref="DataModelVisualizationViewModel" />
|
||||
/// Creates a new instance of the <see cref="DataModelUpdateConfiguration" /> class
|
||||
/// </summary>
|
||||
public class DataModelUpdateConfiguration
|
||||
/// <param name="createEventChildren">A boolean indicating whether or not event children should be created</param>
|
||||
public DataModelUpdateConfiguration(bool createEventChildren)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DataModelUpdateConfiguration" /> class
|
||||
/// </summary>
|
||||
/// <param name="createEventChildren">A boolean indicating whether or not event children should be created</param>
|
||||
public DataModelUpdateConfiguration(bool createEventChildren)
|
||||
{
|
||||
CreateEventChildren = createEventChildren;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether or not event children should be created
|
||||
/// </summary>
|
||||
public bool CreateEventChildren { get; }
|
||||
CreateEventChildren = createEventChildren;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether or not event children should be created
|
||||
/// </summary>
|
||||
public bool CreateEventChildren { get; }
|
||||
}
|
||||
+10
-10
@@ -5,10 +5,10 @@ using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Avalonia;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared.DataModelVisualization.Shared;
|
||||
@@ -37,8 +37,8 @@ public abstract class DataModelVisualizationViewModel : ReactiveObject, IDisposa
|
||||
|
||||
CopyPath = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
if (Avalonia.Application.Current?.Clipboard != null && Path != null)
|
||||
await Avalonia.Application.Current.Clipboard.SetTextAsync(Path);
|
||||
if (Application.Current?.Clipboard != null && Path != null)
|
||||
await Application.Current.Clipboard.SetTextAsync(Path);
|
||||
});
|
||||
|
||||
if (parent == null)
|
||||
@@ -47,7 +47,7 @@ public abstract class DataModelVisualizationViewModel : ReactiveObject, IDisposa
|
||||
PropertyDescription = DataModelPath?.GetPropertyDescription() ?? DataModel?.DataModelDescription;
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit,Unit> CopyPath { get; }
|
||||
public ReactiveCommand<Unit, Unit> CopyPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether this view model is at the root of the data model
|
||||
@@ -197,11 +197,11 @@ public abstract class DataModelVisualizationViewModel : ReactiveObject, IDisposa
|
||||
|
||||
if (looseMatch)
|
||||
IsMatchingFilteredTypes = filteredTypes.Any(t => t.IsCastableFrom(type) ||
|
||||
t == typeof(Enum) && type.IsEnum ||
|
||||
t == typeof(IEnumerable<>) && type.IsGenericEnumerable() ||
|
||||
type.IsGenericType && t == type.GetGenericTypeDefinition());
|
||||
(t == typeof(Enum) && type.IsEnum) ||
|
||||
(t == typeof(IEnumerable<>) && type.IsGenericEnumerable()) ||
|
||||
(type.IsGenericType && t == type.GetGenericTypeDefinition()));
|
||||
else
|
||||
IsMatchingFilteredTypes = filteredTypes.Any(t => t == type || t == typeof(Enum) && type.IsEnum);
|
||||
IsMatchingFilteredTypes = filteredTypes.Any(t => t == type || (t == typeof(Enum) && type.IsEnum));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -362,11 +362,11 @@ public abstract class DataModelVisualizationViewModel : ReactiveObject, IDisposa
|
||||
if (typeViewModel != null)
|
||||
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {DisplayViewModel = typeViewModel, Depth = depth};
|
||||
// For primitives, create a property view model, it may be null that is fine
|
||||
if (propertyType.IsPrimitive || propertyType.IsEnum || propertyType == typeof(string) || propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
if (propertyType.IsPrimitive || propertyType.IsEnum || propertyType == typeof(string) || (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)))
|
||||
return new DataModelPropertyViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
||||
if (propertyType.IsGenericEnumerable())
|
||||
return new DataModelListViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
||||
if (propertyType == typeof(DataModelEvent) || propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(DataModelEvent<>))
|
||||
if (propertyType == typeof(DataModelEvent) || (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(DataModelEvent<>)))
|
||||
return new DataModelEventViewModel(DataModel, this, dataModelPath) {Depth = depth};
|
||||
// For other value types create a child view model
|
||||
if (propertyType.IsClass || propertyType.IsStruct())
|
||||
|
||||
+13
-14
@@ -1,24 +1,23 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
|
||||
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a default data model display view.
|
||||
/// </summary>
|
||||
public class DefaultDataModelDisplayView : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a default data model display view.
|
||||
/// Creates a new instance of the <see cref="DefaultDataModelDisplayView" /> class.
|
||||
/// </summary>
|
||||
public class DefaultDataModelDisplayView : UserControl
|
||||
public DefaultDataModelDisplayView()
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DefaultDataModelDisplayView" /> class.
|
||||
/// </summary>
|
||||
public DefaultDataModelDisplayView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
+31
-33
@@ -2,43 +2,41 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.DataModelVisualization;
|
||||
using Newtonsoft.Json;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
|
||||
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the default data model display view model that is used when no display viewmodel specific for the type
|
||||
/// is registered
|
||||
/// </summary>
|
||||
internal class DefaultDataModelDisplayViewModel : DataModelDisplayViewModel<object>
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the default data model display view model that is used when no display viewmodel specific for the type
|
||||
/// is registered
|
||||
/// </summary>
|
||||
internal class DefaultDataModelDisplayViewModel : DataModelDisplayViewModel<object>
|
||||
private readonly JsonSerializerSettings _serializerSettings;
|
||||
private string _display;
|
||||
|
||||
public DefaultDataModelDisplayViewModel()
|
||||
{
|
||||
private readonly JsonSerializerSettings _serializerSettings;
|
||||
private string _display;
|
||||
_serializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
||||
PreserveReferencesHandling = PreserveReferencesHandling.None
|
||||
};
|
||||
_display = "null";
|
||||
}
|
||||
|
||||
public DefaultDataModelDisplayViewModel()
|
||||
{
|
||||
_serializerSettings = new JsonSerializerSettings()
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
||||
PreserveReferencesHandling = PreserveReferencesHandling.None
|
||||
};
|
||||
_display = "null";
|
||||
}
|
||||
public string Display
|
||||
{
|
||||
get => _display;
|
||||
set => RaiseAndSetIfChanged(ref _display, value);
|
||||
}
|
||||
|
||||
public string Display
|
||||
{
|
||||
get => _display;
|
||||
set => RaiseAndSetIfChanged(ref _display, value);
|
||||
}
|
||||
|
||||
protected override void OnDisplayValueUpdated()
|
||||
{
|
||||
if (DisplayValue is Enum enumDisplayValue)
|
||||
Display = EnumUtilities.HumanizeValue(enumDisplayValue);
|
||||
else if (DisplayValue is not string)
|
||||
Display = JsonConvert.SerializeObject(DisplayValue, _serializerSettings);
|
||||
else
|
||||
Display = DisplayValue?.ToString() ?? "null";
|
||||
}
|
||||
protected override void OnDisplayValueUpdated()
|
||||
{
|
||||
if (DisplayValue is Enum enumDisplayValue)
|
||||
Display = EnumUtilities.HumanizeValue(enumDisplayValue);
|
||||
else if (DisplayValue is not string)
|
||||
Display = JsonConvert.SerializeObject(DisplayValue, _serializerSettings);
|
||||
else
|
||||
Display = DisplayValue?.ToString() ?? "null";
|
||||
}
|
||||
}
|
||||
+15
-17
@@ -1,26 +1,24 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
|
||||
namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a data model display view used to display <see cref="SKColor" /> values.
|
||||
/// </summary>
|
||||
public class SKColorDataModelDisplayView : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a data model display view used to display <see cref="SKColor" /> values.
|
||||
/// Creates a new instance of the <see cref="SKColorDataModelDisplayView" /> class.
|
||||
/// </summary>
|
||||
public partial class SKColorDataModelDisplayView : UserControl
|
||||
public SKColorDataModelDisplayView()
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="SKColorDataModelDisplayView"/> class.
|
||||
/// </summary>
|
||||
public SKColorDataModelDisplayView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,20 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data about selection events raised by <see cref="DataModelPicker" />
|
||||
/// </summary>
|
||||
public class DataModelSelectedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the data model path that was selected
|
||||
/// </summary>
|
||||
public DataModelPath? Path { get; }
|
||||
namespace Artemis.UI.Shared.Events;
|
||||
|
||||
internal DataModelSelectedEventArgs(DataModelPath? path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
/// <summary>
|
||||
/// Provides data about selection events raised by <see cref="DataModelPicker" />
|
||||
/// </summary>
|
||||
public class DataModelSelectedEventArgs : EventArgs
|
||||
{
|
||||
internal DataModelSelectedEventArgs(DataModelPath? path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data model path that was selected
|
||||
/// </summary>
|
||||
public DataModelPath? Path { get; }
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.UI.Shared.Events
|
||||
{
|
||||
internal class DialogClosedEventArgs<TResult> : EventArgs
|
||||
{
|
||||
public TResult Result { get; }
|
||||
namespace Artemis.UI.Shared.Events;
|
||||
|
||||
public DialogClosedEventArgs(TResult result)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
internal class DialogClosedEventArgs<TResult> : EventArgs
|
||||
{
|
||||
public DialogClosedEventArgs(TResult result)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
}
|
||||
|
||||
public TResult Result { get; }
|
||||
}
|
||||
@@ -1,32 +1,31 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared.Events
|
||||
namespace Artemis.UI.Shared.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Provides data on profile related events raised by the profile editor
|
||||
/// </summary>
|
||||
public class ProfileConfigurationEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data on profile related events raised by the profile editor
|
||||
/// </summary>
|
||||
public class ProfileConfigurationEventArgs : EventArgs
|
||||
internal ProfileConfigurationEventArgs(ProfileConfiguration? profileConfiguration)
|
||||
{
|
||||
internal ProfileConfigurationEventArgs(ProfileConfiguration? profileConfiguration)
|
||||
{
|
||||
ProfileConfiguration = profileConfiguration;
|
||||
}
|
||||
|
||||
internal ProfileConfigurationEventArgs(ProfileConfiguration? profileConfiguration, ProfileConfiguration? previousProfileConfiguration)
|
||||
{
|
||||
ProfileConfiguration = profileConfiguration;
|
||||
PreviousProfileConfiguration = previousProfileConfiguration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile the event was raised for
|
||||
/// </summary>
|
||||
public ProfileConfiguration? ProfileConfiguration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If applicable, the previous active profile before the event was raised
|
||||
/// </summary>
|
||||
public ProfileConfiguration? PreviousProfileConfiguration { get; }
|
||||
ProfileConfiguration = profileConfiguration;
|
||||
}
|
||||
|
||||
internal ProfileConfigurationEventArgs(ProfileConfiguration? profileConfiguration, ProfileConfiguration? previousProfileConfiguration)
|
||||
{
|
||||
ProfileConfiguration = profileConfiguration;
|
||||
PreviousProfileConfiguration = previousProfileConfiguration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile the event was raised for
|
||||
/// </summary>
|
||||
public ProfileConfiguration? ProfileConfiguration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If applicable, the previous active profile before the event was raised
|
||||
/// </summary>
|
||||
public ProfileConfiguration? PreviousProfileConfiguration { get; }
|
||||
}
|
||||
@@ -1,32 +1,31 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared.Events
|
||||
namespace Artemis.UI.Shared.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Provides data on profile element related events raised by the profile editor
|
||||
/// </summary>
|
||||
public class RenderProfileElementEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data on profile element related events raised by the profile editor
|
||||
/// </summary>
|
||||
public class RenderProfileElementEventArgs : EventArgs
|
||||
internal RenderProfileElementEventArgs(RenderProfileElement? renderProfileElement)
|
||||
{
|
||||
internal RenderProfileElementEventArgs(RenderProfileElement? renderProfileElement)
|
||||
{
|
||||
RenderProfileElement = renderProfileElement;
|
||||
}
|
||||
|
||||
internal RenderProfileElementEventArgs(RenderProfileElement? renderProfileElement, RenderProfileElement? previousRenderProfileElement)
|
||||
{
|
||||
RenderProfileElement = renderProfileElement;
|
||||
PreviousRenderProfileElement = previousRenderProfileElement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile element the event was raised for
|
||||
/// </summary>
|
||||
public RenderProfileElement? RenderProfileElement { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If applicable, the previous active profile element before the event was raised
|
||||
/// </summary>
|
||||
public RenderProfileElement? PreviousRenderProfileElement { get; }
|
||||
RenderProfileElement = renderProfileElement;
|
||||
}
|
||||
|
||||
internal RenderProfileElementEventArgs(RenderProfileElement? renderProfileElement, RenderProfileElement? previousRenderProfileElement)
|
||||
{
|
||||
RenderProfileElement = renderProfileElement;
|
||||
PreviousRenderProfileElement = previousRenderProfileElement;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile element the event was raised for
|
||||
/// </summary>
|
||||
public RenderProfileElement? RenderProfileElement { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If applicable, the previous active profile element before the event was raised
|
||||
/// </summary>
|
||||
public RenderProfileElement? PreviousRenderProfileElement { get; }
|
||||
}
|
||||
@@ -2,36 +2,35 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Input;
|
||||
|
||||
namespace Artemis.UI.Shared.Events
|
||||
namespace Artemis.UI.Shared.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Provides data on selection events raised by the <see cref="SelectionRectangle" />.
|
||||
/// </summary>
|
||||
public class SelectionRectangleEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data on selection events raised by the <see cref="SelectionRectangle" />.
|
||||
/// Creates a new instance of the <see cref="SelectionRectangleEventArgs" /> class.
|
||||
/// </summary>
|
||||
public class SelectionRectangleEventArgs : EventArgs
|
||||
public SelectionRectangleEventArgs(Rect rectangle, Rect absoluteRectangle, KeyModifiers keyModifiers)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="SelectionRectangleEventArgs" /> class.
|
||||
/// </summary>
|
||||
public SelectionRectangleEventArgs(Rect rectangle, Rect absoluteRectangle, KeyModifiers keyModifiers)
|
||||
{
|
||||
KeyModifiers = keyModifiers;
|
||||
Rectangle = rectangle;
|
||||
AbsoluteRectangle = absoluteRectangle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rectangle relative to the parent that was selected when the event occurred.
|
||||
/// </summary>
|
||||
public Rect Rectangle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rectangle relative to the window that was selected when the event occurred.
|
||||
/// </summary>
|
||||
public Rect AbsoluteRectangle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key modifiers that where pressed when the event occurred.
|
||||
/// </summary>
|
||||
public KeyModifiers KeyModifiers { get; }
|
||||
KeyModifiers = keyModifiers;
|
||||
Rectangle = rectangle;
|
||||
AbsoluteRectangle = absoluteRectangle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rectangle relative to the parent that was selected when the event occurred.
|
||||
/// </summary>
|
||||
public Rect Rectangle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rectangle relative to the window that was selected when the event occurred.
|
||||
/// </summary>
|
||||
public Rect AbsoluteRectangle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the key modifiers that where pressed when the event occurred.
|
||||
/// </summary>
|
||||
public KeyModifiers KeyModifiers { get; }
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents errors that occur within the Artemis Shared UI library
|
||||
/// </summary>
|
||||
public class ArtemisSharedUIException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents errors that occur within the Artemis Shared UI library
|
||||
/// </summary>
|
||||
public class ArtemisSharedUIException : Exception
|
||||
internal ArtemisSharedUIException()
|
||||
{
|
||||
internal ArtemisSharedUIException()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal ArtemisSharedUIException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
internal ArtemisSharedUIException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
internal ArtemisSharedUIException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
internal ArtemisSharedUIException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,24 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.LogicalTree;
|
||||
|
||||
namespace Artemis.UI.Shared.Extensions
|
||||
namespace Artemis.UI.Shared.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for Avalonia's <see cref="Control" /> type
|
||||
/// </summary>
|
||||
public static class ControlExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for Avalonia's <see cref="Control" /> type
|
||||
/// Clears all data validation errors on the given control and any of it's logical siblings
|
||||
/// </summary>
|
||||
public static class ControlExtensions
|
||||
/// <param name="target">The target control</param>
|
||||
public static void ClearAllDataValidationErrors(this Control target)
|
||||
{
|
||||
/// <summary>
|
||||
/// Clears all data validation errors on the given control and any of it's logical siblings
|
||||
/// </summary>
|
||||
/// <param name="target">The target control</param>
|
||||
public static void ClearAllDataValidationErrors(this Control target)
|
||||
DataValidationErrors.ClearErrors(target);
|
||||
foreach (ILogical logicalChild in target.GetLogicalChildren())
|
||||
{
|
||||
DataValidationErrors.ClearErrors(target);
|
||||
foreach (ILogical logicalChild in target.GetLogicalChildren())
|
||||
{
|
||||
if (logicalChild is Control childControl)
|
||||
childControl.ClearAllDataValidationErrors();
|
||||
}
|
||||
if (logicalChild is Control childControl)
|
||||
childControl.ClearAllDataValidationErrors();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace Artemis.UI.Shared.Extensions;
|
||||
public static class LayerPropertyExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns an observable sequence of layer property values starting with the current value.
|
||||
/// Returns an observable sequence of layer property values starting with the current value.
|
||||
/// </summary>
|
||||
/// <param name="layerProperty">The layer property to create the sequence of.</param>
|
||||
/// <typeparam name="T">The value type of the layer property.</typeparam>
|
||||
|
||||
@@ -4,12 +4,12 @@ using SkiaSharp;
|
||||
namespace Artemis.UI.Shared.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for the <see cref="SKColor"/> type.
|
||||
/// Provides extension methods for the <see cref="SKColor" /> type.
|
||||
/// </summary>
|
||||
public static class SKColorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a SkiaSharp <see cref="SKColor"/> to an Avalonia <see cref="Color"/>.
|
||||
/// Converts a SkiaSharp <see cref="SKColor" /> to an Avalonia <see cref="Color" />.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to convert.</param>
|
||||
/// <returns>The resulting color.</returns>
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
using Artemis.Core;
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
using Material.Icons;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
|
||||
namespace Artemis.UI.Shared.Extensions
|
||||
namespace Artemis.UI.Shared.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides utilities when working with types in UI elements.
|
||||
/// </summary>
|
||||
public static class TypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utilities when working with types in UI elements.
|
||||
/// Finds an appropriate Material Design icon for the given <paramref name="type" />.
|
||||
/// </summary>
|
||||
public static class TypeExtensions
|
||||
/// <param name="type">The type to retrieve an icon for.</param>
|
||||
/// <returns>An appropriate Material Design icon for the given <paramref name="type" />.</returns>
|
||||
public static MaterialIconKind GetTypeIcon(this Type? type)
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds an appropriate Material Design icon for the given <paramref name="type"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to retrieve an icon for.</param>
|
||||
/// <returns>An appropriate Material Design icon for the given <paramref name="type"/>.</returns>
|
||||
public static MaterialIconKind GetTypeIcon(this Type? type)
|
||||
{
|
||||
if (type == null)
|
||||
return MaterialIconKind.QuestionMarkCircle;
|
||||
if (type.TypeIsNumber())
|
||||
return MaterialIconKind.CalculatorVariantOutline;
|
||||
if (type.IsEnum)
|
||||
return MaterialIconKind.FormatListBulletedSquare;
|
||||
if (type == typeof(bool))
|
||||
return MaterialIconKind.CircleHalfFull;
|
||||
if (type == typeof(string))
|
||||
return MaterialIconKind.Text;
|
||||
if (type == typeof(SKColor))
|
||||
return MaterialIconKind.Palette;
|
||||
if (type.IsAssignableTo(typeof(IDataModelEvent)))
|
||||
return MaterialIconKind.LightningBolt;
|
||||
return MaterialIconKind.Matrix;
|
||||
}
|
||||
if (type == null)
|
||||
return MaterialIconKind.QuestionMarkCircle;
|
||||
if (type.TypeIsNumber())
|
||||
return MaterialIconKind.CalculatorVariantOutline;
|
||||
if (type.IsEnum)
|
||||
return MaterialIconKind.FormatListBulletedSquare;
|
||||
if (type == typeof(bool))
|
||||
return MaterialIconKind.CircleHalfFull;
|
||||
if (type == typeof(string))
|
||||
return MaterialIconKind.Text;
|
||||
if (type == typeof(SKColor))
|
||||
return MaterialIconKind.Palette;
|
||||
if (type.IsAssignableTo(typeof(IDataModelEvent)))
|
||||
return MaterialIconKind.LightningBolt;
|
||||
return MaterialIconKind.Matrix;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,29 +3,28 @@ using Artemis.UI.Shared.Services;
|
||||
using Ninject.Extensions.Conventions;
|
||||
using Ninject.Modules;
|
||||
|
||||
namespace Artemis.UI.Shared.Ninject
|
||||
{
|
||||
/// <summary>
|
||||
/// The main <see cref="NinjectModule" /> of the Artemis Shared UI toolkit that binds all services
|
||||
/// </summary>
|
||||
public class SharedUIModule : NinjectModule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Load()
|
||||
{
|
||||
if (Kernel == null)
|
||||
throw new ArgumentNullException("Kernel shouldn't be null here.");
|
||||
namespace Artemis.UI.Shared.Ninject;
|
||||
|
||||
// Bind all shared UI services as singletons
|
||||
Kernel.Bind(x =>
|
||||
{
|
||||
x.FromAssemblyContaining<IArtemisSharedUIService>()
|
||||
.IncludingNonPublicTypes()
|
||||
.SelectAllClasses()
|
||||
.InheritedFrom<IArtemisSharedUIService>()
|
||||
.BindAllInterfaces()
|
||||
.Configure(c => c.InSingletonScope());
|
||||
});
|
||||
}
|
||||
/// <summary>
|
||||
/// The main <see cref="NinjectModule" /> of the Artemis Shared UI toolkit that binds all services
|
||||
/// </summary>
|
||||
public class SharedUIModule : NinjectModule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Load()
|
||||
{
|
||||
if (Kernel == null)
|
||||
throw new ArgumentNullException("Kernel shouldn't be null here.");
|
||||
|
||||
// Bind all shared UI services as singletons
|
||||
Kernel.Bind(x =>
|
||||
{
|
||||
x.FromAssemblyContaining<IArtemisSharedUIService>()
|
||||
.IncludingNonPublicTypes()
|
||||
.SelectAllClasses()
|
||||
.InheritedFrom<IArtemisSharedUIService>()
|
||||
.BindAllInterfaces()
|
||||
.Configure(c => c.InSingletonScope());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,56 +2,55 @@
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
|
||||
namespace Artemis.UI.Shared.LayerBrushes
|
||||
namespace Artemis.UI.Shared.LayerBrushes;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a view model for a brush configuration window
|
||||
/// </summary>
|
||||
public abstract class BrushConfigurationViewModel : ValidatableViewModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a view model for a brush configuration window
|
||||
/// Creates a new instance of the <see cref="BrushConfigurationViewModel" /> class
|
||||
/// </summary>
|
||||
public abstract class BrushConfigurationViewModel : ValidatableViewModelBase
|
||||
/// <param name="layerBrush"></param>
|
||||
protected BrushConfigurationViewModel(BaseLayerBrush layerBrush)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="BrushConfigurationViewModel" /> class
|
||||
/// </summary>
|
||||
/// <param name="layerBrush"></param>
|
||||
protected BrushConfigurationViewModel(BaseLayerBrush layerBrush)
|
||||
{
|
||||
LayerBrush = layerBrush;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer brush this view model is associated with
|
||||
/// </summary>
|
||||
public BaseLayerBrush LayerBrush { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Closes the dialog
|
||||
/// </summary>
|
||||
public void RequestClose()
|
||||
{
|
||||
CloseRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the window wants to close, returning <see langword="false" /> will cause the window to stay open.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true" /> if the window may close; otherwise <see langword="false" />.</returns>
|
||||
public virtual bool CanClose()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the window wants to close, returning <see langword="false" /> will cause the window to stay open.
|
||||
/// </summary>
|
||||
/// <returns>A task <see langword="true" /> if the window may close; otherwise <see langword="false" />.</returns>
|
||||
public virtual Task<bool> CanCloseAsync()
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a close was requested
|
||||
/// </summary>
|
||||
public event EventHandler? CloseRequested;
|
||||
LayerBrush = layerBrush;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layer brush this view model is associated with
|
||||
/// </summary>
|
||||
public BaseLayerBrush LayerBrush { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Closes the dialog
|
||||
/// </summary>
|
||||
public void RequestClose()
|
||||
{
|
||||
CloseRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the window wants to close, returning <see langword="false" /> will cause the window to stay open.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true" /> if the window may close; otherwise <see langword="false" />.</returns>
|
||||
public virtual bool CanClose()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the window wants to close, returning <see langword="false" /> will cause the window to stay open.
|
||||
/// </summary>
|
||||
/// <returns>A task <see langword="true" /> if the window may close; otherwise <see langword="false" />.</returns>
|
||||
public virtual Task<bool> CanCloseAsync()
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a close was requested
|
||||
/// </summary>
|
||||
public event EventHandler? CloseRequested;
|
||||
}
|
||||
@@ -1,45 +1,44 @@
|
||||
using System;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
|
||||
namespace Artemis.UI.Shared.LayerBrushes
|
||||
namespace Artemis.UI.Shared.LayerBrushes;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class LayerBrushConfigurationDialog<T> : LayerBrushConfigurationDialog where T : BrushConfigurationViewModel
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class LayerBrushConfigurationDialog<T> : LayerBrushConfigurationDialog where T : BrushConfigurationViewModel
|
||||
public LayerBrushConfigurationDialog()
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public LayerBrushConfigurationDialog()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public LayerBrushConfigurationDialog(int dialogWidth, int dialogHeight)
|
||||
{
|
||||
DialogWidth = dialogWidth;
|
||||
DialogHeight = dialogHeight;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type Type => typeof(T);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public LayerBrushConfigurationDialog(int dialogWidth, int dialogHeight)
|
||||
{
|
||||
DialogWidth = dialogWidth;
|
||||
DialogHeight = dialogHeight;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type Type => typeof(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a UI tab for a layer brush
|
||||
/// </summary>
|
||||
public abstract class LayerBrushConfigurationDialog : ILayerBrushConfigurationDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// The default width of the dialog
|
||||
/// </summary>
|
||||
public int DialogWidth { get; set; } = 800;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a UI tab for a layer brush
|
||||
/// The default height of the dialog
|
||||
/// </summary>
|
||||
public abstract class LayerBrushConfigurationDialog : ILayerBrushConfigurationDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// The default width of the dialog
|
||||
/// </summary>
|
||||
public int DialogWidth { get; set; } = 800;
|
||||
public int DialogHeight { get; set; } = 800;
|
||||
|
||||
/// <summary>
|
||||
/// The default height of the dialog
|
||||
/// </summary>
|
||||
public int DialogHeight { get; set; } = 800;
|
||||
|
||||
/// <summary>
|
||||
/// The type of view model the dialog contains
|
||||
/// </summary>
|
||||
public abstract Type Type { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// The type of view model the dialog contains
|
||||
/// </summary>
|
||||
public abstract Type Type { get; }
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace Artemis.UI.Shared.LayerEffects;
|
||||
|
||||
|
||||
@@ -1,46 +1,44 @@
|
||||
using System;
|
||||
using Artemis.Core.LayerEffects;
|
||||
|
||||
namespace Artemis.UI.Shared.LayerEffects
|
||||
namespace Artemis.UI.Shared.LayerEffects;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class LayerEffectConfigurationDialog<T> : LayerEffectConfigurationDialog where T : EffectConfigurationViewModel
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class LayerEffectConfigurationDialog<T> : LayerEffectConfigurationDialog where T : EffectConfigurationViewModel
|
||||
public LayerEffectConfigurationDialog()
|
||||
{
|
||||
|
||||
/// <inheritdoc />
|
||||
public LayerEffectConfigurationDialog()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public LayerEffectConfigurationDialog(int dialogWidth, int dialogHeight)
|
||||
{
|
||||
DialogWidth = dialogWidth;
|
||||
DialogHeight = dialogHeight;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type Type => typeof(T);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public LayerEffectConfigurationDialog(int dialogWidth, int dialogHeight)
|
||||
{
|
||||
DialogWidth = dialogWidth;
|
||||
DialogHeight = dialogHeight;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Type Type => typeof(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a UI tab for a specific layer effect
|
||||
/// </summary>
|
||||
public abstract class LayerEffectConfigurationDialog : ILayerEffectConfigurationDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// The default width of the dialog
|
||||
/// </summary>
|
||||
public int DialogWidth { get; set; } = 800;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a UI tab for a specific layer effect
|
||||
/// The default height of the dialog
|
||||
/// </summary>
|
||||
public abstract class LayerEffectConfigurationDialog : ILayerEffectConfigurationDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// The default width of the dialog
|
||||
/// </summary>
|
||||
public int DialogWidth { get; set; } = 800;
|
||||
public int DialogHeight { get; set; } = 800;
|
||||
|
||||
/// <summary>
|
||||
/// The default height of the dialog
|
||||
/// </summary>
|
||||
public int DialogHeight { get; set; } = 800;
|
||||
|
||||
/// <summary>
|
||||
/// The type of view model the dialog contains
|
||||
/// </summary>
|
||||
public abstract Type Type { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// The type of view model the dialog contains
|
||||
/// </summary>
|
||||
public abstract Type Type { get; }
|
||||
}
|
||||
@@ -1,21 +1,20 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class PluginConfigurationDialog<T> : PluginConfigurationDialog where T : PluginConfigurationViewModel
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class PluginConfigurationDialog<T> : PluginConfigurationDialog where T : PluginConfigurationViewModel
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Type Type => typeof(T);
|
||||
}
|
||||
public override Type Type => typeof(T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes a configuration dialog for a specific plugin
|
||||
/// </summary>
|
||||
public abstract class PluginConfigurationDialog : IPluginConfigurationDialog
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public abstract Type Type { get; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Describes a configuration dialog for a specific plugin
|
||||
/// </summary>
|
||||
public abstract class PluginConfigurationDialog : IPluginConfigurationDialog
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public abstract Type Type { get; }
|
||||
}
|
||||
@@ -1,46 +1,45 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a view model for a plugin configuration window
|
||||
/// </summary>
|
||||
public abstract class PluginConfigurationViewModel : ValidatableViewModelBase, IPluginConfigurationViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a view model for a plugin configuration window
|
||||
/// Creates a new instance of the <see cref="PluginConfigurationViewModel" /> class
|
||||
/// </summary>
|
||||
public abstract class PluginConfigurationViewModel : ValidatableViewModelBase, IPluginConfigurationViewModel
|
||||
/// <param name="plugin"></param>
|
||||
protected PluginConfigurationViewModel(Plugin plugin)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="PluginConfigurationViewModel" /> class
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
protected PluginConfigurationViewModel(Plugin plugin)
|
||||
{
|
||||
Plugin = plugin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin this configuration view model is associated with
|
||||
/// </summary>
|
||||
public Plugin Plugin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Closes the window hosting the view model
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
CloseRequested?.Invoke(this, EventArgs.Empty);
|
||||
OnCloseRequested();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the the window hosting the view model should close
|
||||
/// </summary>
|
||||
public virtual void OnCloseRequested()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the the window hosting the view model should close
|
||||
/// </summary>
|
||||
public event EventHandler? CloseRequested;
|
||||
Plugin = plugin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin this configuration view model is associated with
|
||||
/// </summary>
|
||||
public Plugin Plugin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Closes the window hosting the view model
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
CloseRequested?.Invoke(this, EventArgs.Empty);
|
||||
OnCloseRequested();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the the window hosting the view model should close
|
||||
/// </summary>
|
||||
public virtual void OnCloseRequested()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the the window hosting the view model should close
|
||||
/// </summary>
|
||||
public event EventHandler? CloseRequested;
|
||||
}
|
||||
@@ -1,55 +1,54 @@
|
||||
using Artemis.Core.ScriptingProviders;
|
||||
|
||||
namespace Artemis.UI.Shared.ScriptingProviders
|
||||
namespace Artemis.UI.Shared.ScriptingProviders;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Stylet view model containing a script editor
|
||||
/// </summary>
|
||||
public class ScriptEditorViewModel : ActivatableViewModelBase, IScriptEditorViewModel
|
||||
{
|
||||
private Script? _script;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Stylet view model containing a script editor
|
||||
/// Creates a new instance of <see cref="ScriptEditorViewModel" />
|
||||
/// </summary>
|
||||
public class ScriptEditorViewModel : ActivatableViewModelBase, IScriptEditorViewModel
|
||||
/// <param name="scriptType">The script type this view model was created for</param>
|
||||
public ScriptEditorViewModel(ScriptType scriptType)
|
||||
{
|
||||
private Script? _script;
|
||||
ScriptType = scriptType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ScriptEditorViewModel" />
|
||||
/// </summary>
|
||||
/// <param name="scriptType">The script type this view model was created for</param>
|
||||
public ScriptEditorViewModel(ScriptType scriptType)
|
||||
{
|
||||
ScriptType = scriptType;
|
||||
}
|
||||
/// <summary>
|
||||
/// Called just before the script is changed to a different one
|
||||
/// </summary>
|
||||
/// <param name="script">The script to display or <see langword="null" /> if no script is to be displayed</param>
|
||||
protected virtual void OnScriptChanging(Script? script)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called just before the script is changed to a different one
|
||||
/// </summary>
|
||||
/// <param name="script">The script to display or <see langword="null" /> if no script is to be displayed</param>
|
||||
protected virtual void OnScriptChanging(Script? script)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Called after the script was changed to a different one
|
||||
/// </summary>
|
||||
/// <param name="script">The script to display or <see langword="null" /> if no script is to be displayed</param>
|
||||
protected virtual void OnScriptChanged(Script? script)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the script was changed to a different one
|
||||
/// </summary>
|
||||
/// <param name="script">The script to display or <see langword="null" /> if no script is to be displayed</param>
|
||||
protected virtual void OnScriptChanged(Script? script)
|
||||
{
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public ScriptType ScriptType { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ScriptType ScriptType { get; }
|
||||
/// <inheritdoc />
|
||||
public Script? Script
|
||||
{
|
||||
get => _script;
|
||||
internal set => RaiseAndSetIfChanged(ref _script, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Script? Script
|
||||
{
|
||||
get => _script;
|
||||
internal set => RaiseAndSetIfChanged(ref _script, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeScript(Script? script)
|
||||
{
|
||||
OnScriptChanging(script);
|
||||
Script = script;
|
||||
OnScriptChanged(script);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public void ChangeScript(Script? script)
|
||||
{
|
||||
OnScriptChanging(script);
|
||||
Script = script;
|
||||
OnScriptChanged(script);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,10 @@ public interface IAutoRunProvider
|
||||
/// <summary>
|
||||
/// Asynchronously enables auto-run.
|
||||
/// </summary>
|
||||
/// <param name="recreate">A boolean indicating whether the auto-run configuration should be recreated (the auto run delay changed)</param>
|
||||
/// <param name="recreate">
|
||||
/// A boolean indicating whether the auto-run configuration should be recreated (the auto run delay
|
||||
/// changed)
|
||||
/// </param>
|
||||
/// <param name="autoRunDelay">The delay in seconds before the application should start (if supported)</param>
|
||||
Task EnableAutoRun(bool recreate, int autoRunDelay);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public interface IUpdateProvider
|
||||
Task<bool> CheckForUpdate(string channel);
|
||||
|
||||
/// <summary>
|
||||
/// Applies any available updates.
|
||||
/// Applies any available updates.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel to use when checking updates (i.e. master or development)</param>
|
||||
/// <param name="silent">Whether or not to update silently.</param>
|
||||
|
||||
@@ -8,79 +8,78 @@ using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Media;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
namespace Artemis.UI.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// A ReactiveUI <see cref="Window" /> that implements the <see cref="IViewFor{TViewModel}" /> interface and will
|
||||
/// activate your ViewModel automatically if the view model implements <see cref="IActivatableViewModel" />. When
|
||||
/// the DataContext property changes, this class will update the ViewModel property with the new DataContext value,
|
||||
/// and vice versa.
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
|
||||
public class ReactiveCoreWindow<TViewModel> : CoreWindow, IViewFor<TViewModel> where TViewModel : class
|
||||
{
|
||||
public static readonly StyledProperty<TViewModel?> ViewModelProperty = AvaloniaProperty
|
||||
.Register<ReactiveCoreWindow<TViewModel>, TViewModel?>(nameof(ViewModel));
|
||||
|
||||
/// <summary>
|
||||
/// A ReactiveUI <see cref="Window" /> that implements the <see cref="IViewFor{TViewModel}" /> interface and will
|
||||
/// activate your ViewModel automatically if the view model implements <see cref="IActivatableViewModel" />. When
|
||||
/// the DataContext property changes, this class will update the ViewModel property with the new DataContext value,
|
||||
/// and vice versa.
|
||||
/// Initializes a new instance of the <see cref="ReactiveCoreWindow{TViewModel}" /> class.
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
|
||||
public class ReactiveCoreWindow<TViewModel> : CoreWindow, IViewFor<TViewModel> where TViewModel : class
|
||||
public ReactiveCoreWindow()
|
||||
{
|
||||
public static readonly StyledProperty<TViewModel?> ViewModelProperty = AvaloniaProperty
|
||||
.Register<ReactiveCoreWindow<TViewModel>, TViewModel?>(nameof(ViewModel));
|
||||
// This WhenActivated block calls ViewModel's WhenActivated
|
||||
// block if the ViewModel implements IActivatableViewModel.
|
||||
this.WhenActivated(disposables => { });
|
||||
this.GetObservable(DataContextProperty).Subscribe(OnDataContextChanged);
|
||||
this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReactiveCoreWindow{TViewModel}" /> class.
|
||||
/// </summary>
|
||||
public ReactiveCoreWindow()
|
||||
{
|
||||
// This WhenActivated block calls ViewModel's WhenActivated
|
||||
// block if the ViewModel implements IActivatableViewModel.
|
||||
this.WhenActivated(disposables => { });
|
||||
this.GetObservable(DataContextProperty).Subscribe(OnDataContextChanged);
|
||||
this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
protected override void OnOpened(EventArgs e)
|
||||
{
|
||||
// TODO: Move to a style and remove opacity on focus loss
|
||||
base.OnOpened(e);
|
||||
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !IsWindows11)
|
||||
return;
|
||||
|
||||
// Enable Mica on Windows 11, based on the FluentAvalonia sample application
|
||||
TransparencyBackgroundFallback = Brushes.Transparent;
|
||||
TransparencyLevelHint = WindowTransparencyLevel.Mica;
|
||||
|
||||
Color2 color = this.TryFindResource("SolidBackgroundFillColorBase", out object? value) ? (Color) value! : new Color2(32, 32, 32);
|
||||
color = color.LightenPercent(-0.5f);
|
||||
Background = new ImmutableSolidColorBrush(color, 0.82);
|
||||
}
|
||||
|
||||
private void OnDataContextChanged(object? value)
|
||||
{
|
||||
if (value is TViewModel viewModel)
|
||||
ViewModel = viewModel;
|
||||
else
|
||||
ViewModel = null;
|
||||
}
|
||||
|
||||
private void OnViewModelChanged(object? value)
|
||||
{
|
||||
if (value == null)
|
||||
ClearValue(DataContextProperty);
|
||||
else if (DataContext != value) DataContext = value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The ViewModel.
|
||||
/// </summary>
|
||||
public TViewModel? ViewModel
|
||||
{
|
||||
get => GetValue(ViewModelProperty);
|
||||
set => SetValue(ViewModelProperty, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// The ViewModel.
|
||||
/// </summary>
|
||||
public TViewModel? ViewModel
|
||||
{
|
||||
get => GetValue(ViewModelProperty);
|
||||
set => SetValue(ViewModelProperty, value);
|
||||
}
|
||||
|
||||
object? IViewFor.ViewModel
|
||||
{
|
||||
get => ViewModel;
|
||||
set => ViewModel = (TViewModel?) value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnOpened(EventArgs e)
|
||||
{
|
||||
// TODO: Move to a style and remove opacity on focus loss
|
||||
base.OnOpened(e);
|
||||
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !IsWindows11)
|
||||
return;
|
||||
|
||||
// Enable Mica on Windows 11, based on the FluentAvalonia sample application
|
||||
TransparencyBackgroundFallback = Brushes.Transparent;
|
||||
TransparencyLevelHint = WindowTransparencyLevel.Mica;
|
||||
|
||||
Color2 color = this.TryFindResource("SolidBackgroundFillColorBase", out object? value) ? (Color) value! : new Color2(32, 32, 32);
|
||||
color = color.LightenPercent(-0.5f);
|
||||
Background = new ImmutableSolidColorBrush(color, 0.82);
|
||||
}
|
||||
|
||||
private void OnDataContextChanged(object? value)
|
||||
{
|
||||
if (value is TViewModel viewModel)
|
||||
ViewModel = viewModel;
|
||||
else
|
||||
ViewModel = null;
|
||||
}
|
||||
|
||||
private void OnViewModelChanged(object? value)
|
||||
{
|
||||
if (value == null)
|
||||
ClearValue(DataContextProperty);
|
||||
else if (DataContext != value) DataContext = value;
|
||||
}
|
||||
object? IViewFor.ViewModel
|
||||
{
|
||||
get => ViewModel;
|
||||
set => ViewModel = (TViewModel?) value;
|
||||
}
|
||||
}
|
||||
@@ -8,261 +8,260 @@ using Ninject;
|
||||
using Ninject.Parameters;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.Builders
|
||||
namespace Artemis.UI.Shared.Services.Builders;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a builder that can be used to create Fluent UI dialogs.
|
||||
/// </summary>
|
||||
public class ContentDialogBuilder
|
||||
{
|
||||
private readonly ContentDialog _contentDialog;
|
||||
private readonly IKernel _kernel;
|
||||
private readonly Window _parent;
|
||||
private ContentDialogViewModelBase? _viewModel;
|
||||
|
||||
internal ContentDialogBuilder(IKernel kernel, Window parent)
|
||||
{
|
||||
_kernel = kernel;
|
||||
_parent = parent;
|
||||
_contentDialog = new ContentDialog
|
||||
{
|
||||
CloseButtonText = "Close"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the title of the dialog.
|
||||
/// </summary>
|
||||
/// <param name="title">The new title.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder WithTitle(string? title)
|
||||
{
|
||||
_contentDialog.Title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the content of the dialog.
|
||||
/// </summary>
|
||||
/// <param name="content">The new content.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder WithContent(string? content)
|
||||
{
|
||||
_contentDialog.Content = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the default button of the dialog that is pressed on enter.
|
||||
/// </summary>
|
||||
/// <param name="defaultButton">The default button.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder WithDefaultButton(ContentDialogButton defaultButton)
|
||||
{
|
||||
_contentDialog.DefaultButton = (FluentAvalonia.UI.Controls.ContentDialogButton) defaultButton;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the primary button of the dialog.
|
||||
/// </summary>
|
||||
/// <param name="configure">An action to configure the button.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder HavingPrimaryButton(Action<ContentDialogButtonBuilder> configure)
|
||||
{
|
||||
ContentDialogButtonBuilder builder = new();
|
||||
configure(builder);
|
||||
|
||||
_contentDialog.IsPrimaryButtonEnabled = true;
|
||||
_contentDialog.PrimaryButtonText = builder.Text;
|
||||
_contentDialog.PrimaryButtonCommand = builder.Command;
|
||||
_contentDialog.PrimaryButtonCommandParameter = builder.CommandParameter;
|
||||
|
||||
// I feel like this isn't my responsibility...
|
||||
if (builder.Command != null)
|
||||
{
|
||||
_contentDialog.IsPrimaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
|
||||
builder.Command.CanExecuteChanged += (_, _) => _contentDialog.IsPrimaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the secondary button of the dialog.
|
||||
/// </summary>
|
||||
/// <param name="configure">An action to configure the button.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder HavingSecondaryButton(Action<ContentDialogButtonBuilder> configure)
|
||||
{
|
||||
ContentDialogButtonBuilder builder = new();
|
||||
configure(builder);
|
||||
|
||||
_contentDialog.IsSecondaryButtonEnabled = true;
|
||||
_contentDialog.SecondaryButtonText = builder.Text;
|
||||
_contentDialog.SecondaryButtonCommand = builder.Command;
|
||||
_contentDialog.SecondaryButtonCommandParameter = builder.CommandParameter;
|
||||
|
||||
// I feel like this isn't my responsibility...
|
||||
if (builder.Command != null)
|
||||
{
|
||||
_contentDialog.IsSecondaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
|
||||
builder.Command.CanExecuteChanged += (_, _) => _contentDialog.IsSecondaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the text of the close button of the dialog.
|
||||
/// </summary>
|
||||
/// <param name="text">The new text.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder WithCloseButtonText(string? text)
|
||||
{
|
||||
_contentDialog.CloseButtonText = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the view model of the content dialog, hosting it inside the dialog.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the view model to host.</typeparam>
|
||||
/// <param name="viewModel">The resulting view model.</param>
|
||||
/// <param name="parameters">Optional parameters to pass to the constructor of the view model, case and order sensitive.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder WithViewModel<T>(out T viewModel, params (string name, object? value)[] parameters) where T : ContentDialogViewModelBase
|
||||
{
|
||||
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
|
||||
viewModel = _kernel.Get<T>(paramsArray);
|
||||
viewModel.ContentDialog = _contentDialog;
|
||||
_contentDialog.Content = viewModel;
|
||||
|
||||
_viewModel = viewModel;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the dialog to take the full height of the window it's being hosted in.
|
||||
/// </summary>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder WithFullSize()
|
||||
{
|
||||
_contentDialog.FullSizeDesired = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously shows the content dialog.
|
||||
/// </summary>
|
||||
/// <returns>A task containing the result of the content dialog.</returns>
|
||||
/// <exception cref="ArtemisSharedUIException">Thrown when the parent window does not contain a panel at its root.</exception>
|
||||
public async Task<ContentDialogResult> ShowAsync()
|
||||
{
|
||||
if (_parent.Content is not Panel panel)
|
||||
throw new ArtemisSharedUIException($"The parent window {_parent.GetType().FullName} should contain a panel at its root");
|
||||
|
||||
try
|
||||
{
|
||||
panel.Children.Add(_contentDialog);
|
||||
ContentDialogResult result = await _contentDialog.ShowAsync();
|
||||
|
||||
// Take the dialog away from the VM in case it's going to try to hide it again or whatever...
|
||||
if (_viewModel != null)
|
||||
_viewModel.ContentDialog = null;
|
||||
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
panel.Children.Remove(_contentDialog);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a content dialog button.
|
||||
/// </summary>
|
||||
public enum ContentDialogButton
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a builder that can be used to create Fluent UI dialogs.
|
||||
/// No button.
|
||||
/// </summary>
|
||||
public class ContentDialogBuilder
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// The primary button.
|
||||
/// </summary>
|
||||
Primary,
|
||||
|
||||
/// <summary>
|
||||
/// The secondary button.
|
||||
/// </summary>
|
||||
Secondary,
|
||||
|
||||
/// <summary>
|
||||
/// The close button.
|
||||
/// </summary>
|
||||
Close
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a builder that can be used to create buttons inside content dialogs.
|
||||
/// </summary>
|
||||
public class ContentDialogButtonBuilder
|
||||
{
|
||||
internal ContentDialogButtonBuilder()
|
||||
{
|
||||
private readonly ContentDialog _contentDialog;
|
||||
private readonly IKernel _kernel;
|
||||
private readonly Window _parent;
|
||||
private ContentDialogViewModelBase? _viewModel;
|
||||
}
|
||||
|
||||
internal ContentDialogBuilder(IKernel kernel, Window parent)
|
||||
{
|
||||
_kernel = kernel;
|
||||
_parent = parent;
|
||||
_contentDialog = new ContentDialog
|
||||
{
|
||||
CloseButtonText = "Close"
|
||||
};
|
||||
}
|
||||
internal string? Text { get; set; }
|
||||
internal ICommand? Command { get; set; }
|
||||
internal Action? Action { get; set; }
|
||||
internal object? CommandParameter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Changes the title of the dialog.
|
||||
/// </summary>
|
||||
/// <param name="title">The new title.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder WithTitle(string? title)
|
||||
{
|
||||
_contentDialog.Title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the content of the dialog.
|
||||
/// </summary>
|
||||
/// <param name="content">The new content.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder WithContent(string? content)
|
||||
{
|
||||
_contentDialog.Content = content;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the default button of the dialog that is pressed on enter.
|
||||
/// </summary>
|
||||
/// <param name="defaultButton">The default button.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder WithDefaultButton(ContentDialogButton defaultButton)
|
||||
{
|
||||
_contentDialog.DefaultButton = (FluentAvalonia.UI.Controls.ContentDialogButton) defaultButton;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the primary button of the dialog.
|
||||
/// </summary>
|
||||
/// <param name="configure">An action to configure the button.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder HavingPrimaryButton(Action<ContentDialogButtonBuilder> configure)
|
||||
{
|
||||
ContentDialogButtonBuilder builder = new();
|
||||
configure(builder);
|
||||
|
||||
_contentDialog.IsPrimaryButtonEnabled = true;
|
||||
_contentDialog.PrimaryButtonText = builder.Text;
|
||||
_contentDialog.PrimaryButtonCommand = builder.Command;
|
||||
_contentDialog.PrimaryButtonCommandParameter = builder.CommandParameter;
|
||||
|
||||
// I feel like this isn't my responsibility...
|
||||
if (builder.Command != null)
|
||||
{
|
||||
_contentDialog.IsPrimaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
|
||||
builder.Command.CanExecuteChanged += (_, _) => _contentDialog.IsPrimaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the secondary button of the dialog.
|
||||
/// </summary>
|
||||
/// <param name="configure">An action to configure the button.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder HavingSecondaryButton(Action<ContentDialogButtonBuilder> configure)
|
||||
{
|
||||
ContentDialogButtonBuilder builder = new();
|
||||
configure(builder);
|
||||
|
||||
_contentDialog.IsSecondaryButtonEnabled = true;
|
||||
_contentDialog.SecondaryButtonText = builder.Text;
|
||||
_contentDialog.SecondaryButtonCommand = builder.Command;
|
||||
_contentDialog.SecondaryButtonCommandParameter = builder.CommandParameter;
|
||||
|
||||
// I feel like this isn't my responsibility...
|
||||
if (builder.Command != null)
|
||||
{
|
||||
_contentDialog.IsSecondaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
|
||||
builder.Command.CanExecuteChanged += (_, _) => _contentDialog.IsSecondaryButtonEnabled = builder.Command.CanExecute(builder.CommandParameter);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the text of the close button of the dialog.
|
||||
/// </summary>
|
||||
/// <param name="text">The new text.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder WithCloseButtonText(string? text)
|
||||
{
|
||||
_contentDialog.CloseButtonText = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the view model of the content dialog, hosting it inside the dialog.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the view model to host.</typeparam>
|
||||
/// <param name="viewModel">The resulting view model.</param>
|
||||
/// <param name="parameters">Optional parameters to pass to the constructor of the view model, case and order sensitive.</param>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder WithViewModel<T>(out T viewModel, params (string name, object? value)[] parameters) where T : ContentDialogViewModelBase
|
||||
{
|
||||
IParameter[] paramsArray = parameters.Select(kv => new ConstructorArgument(kv.name, kv.value)).Cast<IParameter>().ToArray();
|
||||
viewModel = _kernel.Get<T>(paramsArray);
|
||||
viewModel.ContentDialog = _contentDialog;
|
||||
_contentDialog.Content = viewModel;
|
||||
|
||||
_viewModel = viewModel;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the dialog to take the full height of the window it's being hosted in.
|
||||
/// </summary>
|
||||
/// <returns>The builder that can be used to further build the dialog.</returns>
|
||||
public ContentDialogBuilder WithFullSize()
|
||||
{
|
||||
_contentDialog.FullSizeDesired = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously shows the content dialog.
|
||||
/// </summary>
|
||||
/// <returns>A task containing the result of the content dialog.</returns>
|
||||
/// <exception cref="ArtemisSharedUIException">Thrown when the parent window does not contain a panel at its root.</exception>
|
||||
public async Task<ContentDialogResult> ShowAsync()
|
||||
{
|
||||
if (_parent.Content is not Panel panel)
|
||||
throw new ArtemisSharedUIException($"The parent window {_parent.GetType().FullName} should contain a panel at its root");
|
||||
|
||||
try
|
||||
{
|
||||
panel.Children.Add(_contentDialog);
|
||||
ContentDialogResult result = await _contentDialog.ShowAsync();
|
||||
|
||||
// Take the dialog away from the VM in case it's going to try to hide it again or whatever...
|
||||
if (_viewModel != null)
|
||||
_viewModel.ContentDialog = null;
|
||||
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
panel.Children.Remove(_contentDialog);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Changes text message of the button.
|
||||
/// </summary>
|
||||
/// <param name="text">The new text.</param>
|
||||
/// <returns>The notification builder that can be used to further build the button.</returns>
|
||||
public ContentDialogButtonBuilder WithText(string? text)
|
||||
{
|
||||
Text = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a content dialog button.
|
||||
/// Changes action that is called when the button is clicked.
|
||||
/// </summary>
|
||||
public enum ContentDialogButton
|
||||
/// <param name="action">The action to call when the button is clicked.</param>
|
||||
/// <returns>The builder that can be used to further build the button.</returns>
|
||||
public ContentDialogButtonBuilder WithAction(Action action)
|
||||
{
|
||||
/// <summary>
|
||||
/// No button.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// The primary button.
|
||||
/// </summary>
|
||||
Primary,
|
||||
|
||||
/// <summary>
|
||||
/// The secondary button.
|
||||
/// </summary>
|
||||
Secondary,
|
||||
|
||||
/// <summary>
|
||||
/// The close button.
|
||||
/// </summary>
|
||||
Close,
|
||||
Action = action;
|
||||
Command = ReactiveCommand.Create(() => Action());
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a builder that can be used to create buttons inside content dialogs.
|
||||
/// Changes command that is called when the button is clicked.
|
||||
/// </summary>
|
||||
public class ContentDialogButtonBuilder
|
||||
/// <param name="command">The command to call when the button is clicked.</param>
|
||||
/// <returns>The builder that can be used to further build the button.</returns>
|
||||
public ContentDialogButtonBuilder WithCommand(ICommand? command)
|
||||
{
|
||||
internal ContentDialogButtonBuilder()
|
||||
{
|
||||
}
|
||||
Command = command;
|
||||
return this;
|
||||
}
|
||||
|
||||
internal string? Text { get; set; }
|
||||
internal ICommand? Command { get; set; }
|
||||
internal Action? Action { get; set; }
|
||||
internal object? CommandParameter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Changes text message of the button.
|
||||
/// </summary>
|
||||
/// <param name="text">The new text.</param>
|
||||
/// <returns>The notification builder that can be used to further build the button.</returns>
|
||||
public ContentDialogButtonBuilder WithText(string? text)
|
||||
{
|
||||
Text = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes action that is called when the button is clicked.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to call when the button is clicked.</param>
|
||||
/// <returns>The builder that can be used to further build the button.</returns>
|
||||
public ContentDialogButtonBuilder WithAction(Action action)
|
||||
{
|
||||
Action = action;
|
||||
Command = ReactiveCommand.Create(() => Action());
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes command that is called when the button is clicked.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to call when the button is clicked.</param>
|
||||
/// <returns>The builder that can be used to further build the button.</returns>
|
||||
public ContentDialogButtonBuilder WithCommand(ICommand? command)
|
||||
{
|
||||
Command = command;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes parameter of the command that is called when the button is clicked.
|
||||
/// </summary>
|
||||
/// <param name="commandParameter">The parameter of the command to call when the button is clicked.</param>
|
||||
/// <returns>The builder that can be used to further build the button.</returns>
|
||||
public ContentDialogButtonBuilder WithCommandParameter(object? commandParameter)
|
||||
{
|
||||
CommandParameter = commandParameter;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Changes parameter of the command that is called when the button is clicked.
|
||||
/// </summary>
|
||||
/// <param name="commandParameter">The parameter of the command to call when the button is clicked.</param>
|
||||
/// <returns>The builder that can be used to further build the button.</returns>
|
||||
public ContentDialogButtonBuilder WithCommandParameter(object? commandParameter)
|
||||
{
|
||||
CommandParameter = commandParameter;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,39 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.Builders
|
||||
namespace Artemis.UI.Shared.Services.Builders;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a builder that can create a <see cref="FileDialogFilter" />.
|
||||
/// </summary>
|
||||
public class FileDialogFilterBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a builder that can create a <see cref="FileDialogFilter" />.
|
||||
/// </summary>
|
||||
public class FileDialogFilterBuilder
|
||||
private readonly FileDialogFilter _filter;
|
||||
|
||||
internal FileDialogFilterBuilder()
|
||||
{
|
||||
private readonly FileDialogFilter _filter;
|
||||
_filter = new FileDialogFilter();
|
||||
}
|
||||
|
||||
internal FileDialogFilterBuilder()
|
||||
{
|
||||
_filter = new FileDialogFilter();
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets the name of the filter
|
||||
/// </summary>
|
||||
public FileDialogFilterBuilder WithName(string name)
|
||||
{
|
||||
_filter.Name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the name of the filter
|
||||
/// </summary>
|
||||
public FileDialogFilterBuilder WithName(string name)
|
||||
{
|
||||
_filter.Name = name;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds the provided extension to the filter
|
||||
/// </summary>
|
||||
public FileDialogFilterBuilder WithExtension(string extension)
|
||||
{
|
||||
_filter.Extensions.Add(extension);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the provided extension to the filter
|
||||
/// </summary>
|
||||
public FileDialogFilterBuilder WithExtension(string extension)
|
||||
{
|
||||
_filter.Extensions.Add(extension);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal FileDialogFilter Build()
|
||||
{
|
||||
return _filter;
|
||||
}
|
||||
internal FileDialogFilter Build()
|
||||
{
|
||||
return _filter;
|
||||
}
|
||||
}
|
||||
@@ -3,86 +3,85 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.Builders
|
||||
namespace Artemis.UI.Shared.Services.Builders;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a builder that can create a <see cref="OpenFileDialog" />.
|
||||
/// </summary>
|
||||
public class OpenFileDialogBuilder
|
||||
{
|
||||
private readonly OpenFileDialog _openFileDialog;
|
||||
private readonly Window _parent;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a builder that can create a <see cref="OpenFileDialog" />.
|
||||
/// Creates a new instance of the <see cref="OpenFileDialogBuilder" /> class.
|
||||
/// </summary>
|
||||
public class OpenFileDialogBuilder
|
||||
/// <param name="parent">The parent window that will host the dialog.</param>
|
||||
internal OpenFileDialogBuilder(Window parent)
|
||||
{
|
||||
private readonly OpenFileDialog _openFileDialog;
|
||||
private readonly Window _parent;
|
||||
_parent = parent;
|
||||
_openFileDialog = new OpenFileDialog();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="OpenFileDialogBuilder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent window that will host the dialog.</param>
|
||||
internal OpenFileDialogBuilder(Window parent)
|
||||
{
|
||||
_parent = parent;
|
||||
_openFileDialog = new OpenFileDialog();
|
||||
}
|
||||
/// <summary>
|
||||
/// Indicate that the user can select multiple files.
|
||||
/// </summary>
|
||||
public OpenFileDialogBuilder WithAllowMultiple()
|
||||
{
|
||||
_openFileDialog.AllowMultiple = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicate that the user can select multiple files.
|
||||
/// </summary>
|
||||
public OpenFileDialogBuilder WithAllowMultiple()
|
||||
{
|
||||
_openFileDialog.AllowMultiple = true;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Set the title of the dialog
|
||||
/// </summary>
|
||||
public OpenFileDialogBuilder WithTitle(string? title)
|
||||
{
|
||||
_openFileDialog.Title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the title of the dialog
|
||||
/// </summary>
|
||||
public OpenFileDialogBuilder WithTitle(string? title)
|
||||
{
|
||||
_openFileDialog.Title = title;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Set the initial directory of the dialog
|
||||
/// </summary>
|
||||
public OpenFileDialogBuilder WithDirectory(string? directory)
|
||||
{
|
||||
_openFileDialog.Directory = directory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the initial directory of the dialog
|
||||
/// </summary>
|
||||
public OpenFileDialogBuilder WithDirectory(string? directory)
|
||||
{
|
||||
_openFileDialog.Directory = directory;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Set the initial file name of the dialog
|
||||
/// </summary>
|
||||
public OpenFileDialogBuilder WithInitialFileName(string? initialFileName)
|
||||
{
|
||||
_openFileDialog.InitialFileName = initialFileName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the initial file name of the dialog
|
||||
/// </summary>
|
||||
public OpenFileDialogBuilder WithInitialFileName(string? initialFileName)
|
||||
{
|
||||
_openFileDialog.InitialFileName = initialFileName;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Add a filter to the dialog
|
||||
/// </summary>
|
||||
public OpenFileDialogBuilder HavingFilter(Action<FileDialogFilterBuilder> configure)
|
||||
{
|
||||
FileDialogFilterBuilder builder = new();
|
||||
configure(builder);
|
||||
|
||||
/// <summary>
|
||||
/// Add a filter to the dialog
|
||||
/// </summary>
|
||||
public OpenFileDialogBuilder HavingFilter(Action<FileDialogFilterBuilder> configure)
|
||||
{
|
||||
FileDialogFilterBuilder builder = new();
|
||||
configure(builder);
|
||||
|
||||
_openFileDialog.Filters ??= new List<FileDialogFilter>();
|
||||
_openFileDialog.Filters.Add(builder.Build());
|
||||
_openFileDialog.Filters ??= new List<FileDialogFilter>();
|
||||
_openFileDialog.Filters.Add(builder.Build());
|
||||
|
||||
return this;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously shows the file dialog.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A task that on completion returns an array containing the full path to the selected
|
||||
/// files, or null if the dialog was canceled.
|
||||
/// </returns>
|
||||
public async Task<string[]?> ShowAsync()
|
||||
{
|
||||
return await _openFileDialog.ShowAsync(_parent);
|
||||
}
|
||||
/// <summary>
|
||||
/// Asynchronously shows the file dialog.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A task that on completion returns an array containing the full path to the selected
|
||||
/// files, or null if the dialog was canceled.
|
||||
/// </returns>
|
||||
public async Task<string[]?> ShowAsync()
|
||||
{
|
||||
return await _openFileDialog.ShowAsync(_parent);
|
||||
}
|
||||
}
|
||||
@@ -3,86 +3,85 @@ using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.Builders
|
||||
namespace Artemis.UI.Shared.Services.Builders;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a builder that can create a <see cref="SaveFileDialog" />.
|
||||
/// </summary>
|
||||
public class SaveFileDialogBuilder
|
||||
{
|
||||
private readonly Window _parent;
|
||||
private readonly SaveFileDialog _saveFileDialog;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a builder that can create a <see cref="SaveFileDialog" />.
|
||||
/// Creates a new instance of the <see cref="SaveFileDialogBuilder" /> class.
|
||||
/// </summary>
|
||||
public class SaveFileDialogBuilder
|
||||
/// <param name="parent">The parent window that will host the notification.</param>
|
||||
internal SaveFileDialogBuilder(Window parent)
|
||||
{
|
||||
private readonly Window _parent;
|
||||
private readonly SaveFileDialog _saveFileDialog;
|
||||
_parent = parent;
|
||||
_saveFileDialog = new SaveFileDialog();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="SaveFileDialogBuilder" /> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent window that will host the notification.</param>
|
||||
internal SaveFileDialogBuilder(Window parent)
|
||||
{
|
||||
_parent = parent;
|
||||
_saveFileDialog = new SaveFileDialog();
|
||||
}
|
||||
/// <summary>
|
||||
/// Set the title of the dialog
|
||||
/// </summary>
|
||||
public SaveFileDialogBuilder WithTitle(string? title)
|
||||
{
|
||||
_saveFileDialog.Title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the title of the dialog
|
||||
/// </summary>
|
||||
public SaveFileDialogBuilder WithTitle(string? title)
|
||||
{
|
||||
_saveFileDialog.Title = title;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Set the initial directory of the dialog
|
||||
/// </summary>
|
||||
public SaveFileDialogBuilder WithDirectory(string? directory)
|
||||
{
|
||||
_saveFileDialog.Directory = directory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the initial directory of the dialog
|
||||
/// </summary>
|
||||
public SaveFileDialogBuilder WithDirectory(string? directory)
|
||||
{
|
||||
_saveFileDialog.Directory = directory;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Set the initial file name of the dialog
|
||||
/// </summary>
|
||||
public SaveFileDialogBuilder WithInitialFileName(string? initialFileName)
|
||||
{
|
||||
_saveFileDialog.InitialFileName = initialFileName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the initial file name of the dialog
|
||||
/// </summary>
|
||||
public SaveFileDialogBuilder WithInitialFileName(string? initialFileName)
|
||||
{
|
||||
_saveFileDialog.InitialFileName = initialFileName;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Set the default extension of the dialog
|
||||
/// </summary>
|
||||
public SaveFileDialogBuilder WithDefaultExtension(string? defaultExtension)
|
||||
{
|
||||
_saveFileDialog.DefaultExtension = defaultExtension;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the default extension of the dialog
|
||||
/// </summary>
|
||||
public SaveFileDialogBuilder WithDefaultExtension(string? defaultExtension)
|
||||
{
|
||||
_saveFileDialog.DefaultExtension = defaultExtension;
|
||||
return this;
|
||||
}
|
||||
/// <summary>
|
||||
/// Add a filter to the dialog
|
||||
/// </summary>
|
||||
public SaveFileDialogBuilder HavingFilter(Action<FileDialogFilterBuilder> configure)
|
||||
{
|
||||
FileDialogFilterBuilder builder = new();
|
||||
configure(builder);
|
||||
|
||||
/// <summary>
|
||||
/// Add a filter to the dialog
|
||||
/// </summary>
|
||||
public SaveFileDialogBuilder HavingFilter(Action<FileDialogFilterBuilder> configure)
|
||||
{
|
||||
FileDialogFilterBuilder builder = new();
|
||||
configure(builder);
|
||||
|
||||
_saveFileDialog.Filters ??= new List<FileDialogFilter>();
|
||||
_saveFileDialog.Filters.Add(builder.Build());
|
||||
_saveFileDialog.Filters ??= new List<FileDialogFilter>();
|
||||
_saveFileDialog.Filters.Add(builder.Build());
|
||||
|
||||
return this;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously shows the save file dialog.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A task that on completion contains the full path of the save location, or null if the
|
||||
/// dialog was canceled.
|
||||
/// </returns>
|
||||
public async Task<string?> ShowAsync()
|
||||
{
|
||||
return await _saveFileDialog.ShowAsync(_parent);
|
||||
}
|
||||
/// <summary>
|
||||
/// Asynchronously shows the save file dialog.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A task that on completion contains the full path of the save location, or null if the
|
||||
/// dialog was canceled.
|
||||
/// </returns>
|
||||
public async Task<string?> ShowAsync()
|
||||
{
|
||||
return await _saveFileDialog.ShowAsync(_parent);
|
||||
}
|
||||
}
|
||||
@@ -11,241 +11,244 @@ using Artemis.UI.Shared.DefaultTypes.DataModel.Display;
|
||||
using Ninject;
|
||||
using Ninject.Parameters;
|
||||
|
||||
namespace Artemis.UI.Shared.Services
|
||||
namespace Artemis.UI.Shared.Services;
|
||||
|
||||
internal class DataModelUIService : IDataModelUIService
|
||||
{
|
||||
internal class DataModelUIService : IDataModelUIService
|
||||
private readonly IDataModelService _dataModelService;
|
||||
private readonly IKernel _kernel;
|
||||
private readonly List<DataModelVisualizationRegistration> _registeredDataModelDisplays;
|
||||
private readonly List<DataModelVisualizationRegistration> _registeredDataModelEditors;
|
||||
|
||||
public DataModelUIService(IDataModelService dataModelService, IKernel kernel)
|
||||
{
|
||||
private readonly IDataModelService _dataModelService;
|
||||
private readonly IKernel _kernel;
|
||||
private readonly List<DataModelVisualizationRegistration> _registeredDataModelDisplays;
|
||||
private readonly List<DataModelVisualizationRegistration> _registeredDataModelEditors;
|
||||
_dataModelService = dataModelService;
|
||||
_kernel = kernel;
|
||||
_registeredDataModelEditors = new List<DataModelVisualizationRegistration>();
|
||||
_registeredDataModelDisplays = new List<DataModelVisualizationRegistration>();
|
||||
|
||||
public DataModelUIService(IDataModelService dataModelService, IKernel kernel)
|
||||
RegisteredDataModelEditors = new ReadOnlyCollection<DataModelVisualizationRegistration>(_registeredDataModelEditors);
|
||||
RegisteredDataModelDisplays = new ReadOnlyCollection<DataModelVisualizationRegistration>(_registeredDataModelDisplays);
|
||||
}
|
||||
|
||||
private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute? description, object? initialValue)
|
||||
{
|
||||
// This assumes the type can be converted, that has been checked when the VM was created
|
||||
if (initialValue != null && initialValue.GetType() != registration.SupportedType)
|
||||
initialValue = Convert.ChangeType(initialValue, registration.SupportedType);
|
||||
|
||||
IParameter[] parameters =
|
||||
{
|
||||
_dataModelService = dataModelService;
|
||||
_kernel = kernel;
|
||||
_registeredDataModelEditors = new List<DataModelVisualizationRegistration>();
|
||||
_registeredDataModelDisplays = new List<DataModelVisualizationRegistration>();
|
||||
new ConstructorArgument("targetDescription", description),
|
||||
new ConstructorArgument("initialValue", initialValue)
|
||||
};
|
||||
|
||||
RegisteredDataModelEditors = new ReadOnlyCollection<DataModelVisualizationRegistration>(_registeredDataModelEditors);
|
||||
RegisteredDataModelDisplays = new ReadOnlyCollection<DataModelVisualizationRegistration>(_registeredDataModelDisplays);
|
||||
// If this ever happens something is likely wrong with the plugin unload detection
|
||||
if (registration.Plugin.Kernel == null)
|
||||
throw new ArtemisSharedUIException("Cannot InstantiateDataModelInputViewModel for a registration by an uninitialized plugin");
|
||||
|
||||
DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.Plugin.Kernel.Get(registration.ViewModelType, parameters);
|
||||
viewModel.CompatibleConversionTypes = registration.CompatibleConversionTypes;
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors { get; }
|
||||
public IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelDisplays { get; }
|
||||
|
||||
public DataModelPropertiesViewModel GetMainDataModelVisualization()
|
||||
{
|
||||
DataModelPropertiesViewModel viewModel = new(null, null, null);
|
||||
foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().Where(d => d.IsExpansion || d.Module.IsActivated).OrderBy(d => d.DataModelDescription.Name))
|
||||
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion)));
|
||||
|
||||
// Update to populate children
|
||||
viewModel.Update(this, null);
|
||||
viewModel.UpdateRequested += (sender, args) => viewModel.Update(this, null);
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
public void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization)
|
||||
{
|
||||
List<DataModelVisualizationViewModel> disabledChildren = mainDataModelVisualization.Children
|
||||
.Where(d => d.DataModel != null && !d.DataModel.Module.IsEnabled)
|
||||
.ToList();
|
||||
foreach (DataModelVisualizationViewModel child in disabledChildren)
|
||||
mainDataModelVisualization.Children.Remove(child);
|
||||
|
||||
foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().OrderBy(d => d.DataModelDescription.Name))
|
||||
{
|
||||
if (mainDataModelVisualization.Children.All(c => c.DataModel != dataModelExpansion))
|
||||
mainDataModelVisualization.Children.Add(
|
||||
new DataModelPropertiesViewModel(dataModelExpansion, mainDataModelVisualization, new DataModelPath(dataModelExpansion))
|
||||
);
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors { get; }
|
||||
public IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelDisplays { get; }
|
||||
mainDataModelVisualization.Update(this, null);
|
||||
}
|
||||
|
||||
public DataModelPropertiesViewModel GetMainDataModelVisualization()
|
||||
public DataModelPropertiesViewModel? GetPluginDataModelVisualization(List<Module> modules, bool includeMainDataModel)
|
||||
{
|
||||
DataModelPropertiesViewModel root;
|
||||
// This will contain any modules that are always available
|
||||
if (includeMainDataModel)
|
||||
{
|
||||
DataModelPropertiesViewModel viewModel = new(null, null, null);
|
||||
foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().Where(d => d.IsExpansion || d.Module.IsActivated).OrderBy(d => d.DataModelDescription.Name))
|
||||
viewModel.Children.Add(new DataModelPropertiesViewModel(dataModelExpansion, viewModel, new DataModelPath(dataModelExpansion)));
|
||||
|
||||
// Update to populate children
|
||||
viewModel.Update(this, null);
|
||||
viewModel.UpdateRequested += (sender, args) => viewModel.Update(this, null);
|
||||
return viewModel;
|
||||
root = GetMainDataModelVisualization();
|
||||
}
|
||||
else
|
||||
{
|
||||
root = new DataModelPropertiesViewModel(null, null, null);
|
||||
root.UpdateRequested += (sender, args) => root.Update(this, null);
|
||||
}
|
||||
|
||||
public void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization)
|
||||
foreach (Module module in modules)
|
||||
{
|
||||
List<DataModelVisualizationViewModel> disabledChildren = mainDataModelVisualization.Children
|
||||
.Where(d => d.DataModel != null && !d.DataModel.Module.IsEnabled)
|
||||
.ToList();
|
||||
foreach (DataModelVisualizationViewModel child in disabledChildren)
|
||||
mainDataModelVisualization.Children.Remove(child);
|
||||
DataModel? dataModel = _dataModelService.GetPluginDataModel(module);
|
||||
if (dataModel == null)
|
||||
continue;
|
||||
|
||||
foreach (DataModel dataModelExpansion in _dataModelService.GetDataModels().OrderBy(d => d.DataModelDescription.Name))
|
||||
root.Children.Add(new DataModelPropertiesViewModel(dataModel, root, new DataModelPath(dataModel)));
|
||||
}
|
||||
|
||||
if (!root.Children.Any())
|
||||
return null;
|
||||
|
||||
// Update to populate children
|
||||
root.Update(this, null);
|
||||
return root;
|
||||
}
|
||||
|
||||
public DataModelVisualizationRegistration RegisterDataModelInput<T>(Plugin plugin, IReadOnlyCollection<Type>? compatibleConversionTypes = null) where T : DataModelInputViewModel
|
||||
{
|
||||
compatibleConversionTypes ??= new List<Type>();
|
||||
Type viewModelType = typeof(T);
|
||||
lock (_registeredDataModelEditors)
|
||||
{
|
||||
Type supportedType = viewModelType.BaseType!.GetGenericArguments()[0];
|
||||
DataModelVisualizationRegistration? existing = _registeredDataModelEditors.FirstOrDefault(r => r.SupportedType == supportedType);
|
||||
if (existing != null)
|
||||
{
|
||||
if (mainDataModelVisualization.Children.All(c => c.DataModel != dataModelExpansion))
|
||||
{
|
||||
mainDataModelVisualization.Children.Add(
|
||||
new DataModelPropertiesViewModel(dataModelExpansion, mainDataModelVisualization, new DataModelPath(dataModelExpansion))
|
||||
);
|
||||
}
|
||||
if (existing.Plugin != plugin)
|
||||
throw new ArtemisSharedUIException($"Cannot register data model input for type {supportedType.Name} because an editor was already" +
|
||||
$" registered by {existing.Plugin}");
|
||||
return existing;
|
||||
}
|
||||
|
||||
mainDataModelVisualization.Update(this, null);
|
||||
}
|
||||
_kernel.Bind(viewModelType).ToSelf();
|
||||
|
||||
public DataModelPropertiesViewModel? GetPluginDataModelVisualization(List<Module> modules, bool includeMainDataModel)
|
||||
{
|
||||
DataModelPropertiesViewModel root;
|
||||
// This will contain any modules that are always available
|
||||
if (includeMainDataModel)
|
||||
root = GetMainDataModelVisualization();
|
||||
else
|
||||
// Create the registration
|
||||
DataModelVisualizationRegistration registration = new(this, RegistrationType.Input, plugin, supportedType, viewModelType)
|
||||
{
|
||||
root = new DataModelPropertiesViewModel(null, null, null);
|
||||
root.UpdateRequested += (sender, args) => root.Update(this, null);
|
||||
}
|
||||
|
||||
foreach (Module module in modules)
|
||||
{
|
||||
DataModel? dataModel = _dataModelService.GetPluginDataModel(module);
|
||||
if (dataModel == null)
|
||||
continue;
|
||||
|
||||
root.Children.Add(new DataModelPropertiesViewModel(dataModel, root, new DataModelPath(dataModel)));
|
||||
}
|
||||
|
||||
if (!root.Children.Any())
|
||||
return null;
|
||||
|
||||
// Update to populate children
|
||||
root.Update(this, null);
|
||||
return root;
|
||||
}
|
||||
|
||||
public DataModelVisualizationRegistration RegisterDataModelInput<T>(Plugin plugin, IReadOnlyCollection<Type>? compatibleConversionTypes = null) where T : DataModelInputViewModel
|
||||
{
|
||||
compatibleConversionTypes ??= new List<Type>();
|
||||
Type viewModelType = typeof(T);
|
||||
lock (_registeredDataModelEditors)
|
||||
{
|
||||
Type supportedType = viewModelType.BaseType!.GetGenericArguments()[0];
|
||||
DataModelVisualizationRegistration? existing = _registeredDataModelEditors.FirstOrDefault(r => r.SupportedType == supportedType);
|
||||
if (existing != null)
|
||||
{
|
||||
if (existing.Plugin != plugin)
|
||||
throw new ArtemisSharedUIException($"Cannot register data model input for type {supportedType.Name} because an editor was already" +
|
||||
$" registered by {existing.Plugin}");
|
||||
return existing;
|
||||
}
|
||||
|
||||
_kernel.Bind(viewModelType).ToSelf();
|
||||
|
||||
// Create the registration
|
||||
DataModelVisualizationRegistration registration = new(this, RegistrationType.Input, plugin, supportedType, viewModelType)
|
||||
{
|
||||
// Apply the compatible conversion types to the registration
|
||||
CompatibleConversionTypes = compatibleConversionTypes
|
||||
};
|
||||
|
||||
_registeredDataModelEditors.Add(registration);
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
|
||||
public DataModelVisualizationRegistration RegisterDataModelDisplay<T>(Plugin plugin) where T : DataModelDisplayViewModel
|
||||
{
|
||||
Type viewModelType = typeof(T);
|
||||
lock (_registeredDataModelDisplays)
|
||||
{
|
||||
Type supportedType = viewModelType.BaseType!.GetGenericArguments()[0];
|
||||
DataModelVisualizationRegistration? existing = _registeredDataModelDisplays.FirstOrDefault(r => r.SupportedType == supportedType);
|
||||
if (existing != null)
|
||||
{
|
||||
if (existing.Plugin != plugin)
|
||||
throw new ArtemisSharedUIException($"Cannot register data model display for type {supportedType.Name} because an editor was already" +
|
||||
$" registered by {existing.Plugin}");
|
||||
return existing;
|
||||
}
|
||||
|
||||
_kernel.Bind(viewModelType).ToSelf();
|
||||
DataModelVisualizationRegistration registration = new(this, RegistrationType.Display, plugin, supportedType, viewModelType);
|
||||
_registeredDataModelDisplays.Add(registration);
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveDataModelInput(DataModelVisualizationRegistration registration)
|
||||
{
|
||||
lock (_registeredDataModelEditors)
|
||||
{
|
||||
if (_registeredDataModelEditors.Contains(registration))
|
||||
{
|
||||
registration.Unsubscribe();
|
||||
_registeredDataModelEditors.Remove(registration);
|
||||
|
||||
_kernel.Unbind(registration.ViewModelType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveDataModelDisplay(DataModelVisualizationRegistration registration)
|
||||
{
|
||||
lock (_registeredDataModelDisplays)
|
||||
{
|
||||
if (_registeredDataModelDisplays.Contains(registration))
|
||||
{
|
||||
registration.Unsubscribe();
|
||||
_registeredDataModelDisplays.Remove(registration);
|
||||
|
||||
_kernel.Unbind(registration.ViewModelType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DataModelDisplayViewModel? GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute? description, bool fallBackToDefault)
|
||||
{
|
||||
lock (_registeredDataModelDisplays)
|
||||
{
|
||||
DataModelDisplayViewModel? result;
|
||||
|
||||
DataModelVisualizationRegistration? match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType);
|
||||
if (match != null)
|
||||
{
|
||||
// If this ever happens something is likely wrong with the plugin unload detection
|
||||
if (match.Plugin.Kernel == null)
|
||||
throw new ArtemisSharedUIException("Cannot GetDataModelDisplayViewModel for a registration by an uninitialized plugin");
|
||||
|
||||
result = (DataModelDisplayViewModel) match.Plugin.Kernel.Get(match.ViewModelType);
|
||||
}
|
||||
else if (!fallBackToDefault)
|
||||
result = null;
|
||||
else
|
||||
result = _kernel.Get<DefaultDataModelDisplayViewModel>();
|
||||
|
||||
if (result != null)
|
||||
result.PropertyDescription = description;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action<object?, bool> updateCallback)
|
||||
{
|
||||
lock (_registeredDataModelEditors)
|
||||
{
|
||||
// Prefer a VM that natively supports the type
|
||||
DataModelVisualizationRegistration? match = _registeredDataModelEditors.FirstOrDefault(d => d.SupportedType == propertyType);
|
||||
// Fall back on a VM that supports the type through conversion
|
||||
if (match == null)
|
||||
match = _registeredDataModelEditors.FirstOrDefault(d => d.CompatibleConversionTypes != null && d.CompatibleConversionTypes.Contains(propertyType));
|
||||
// Lastly try getting an enum VM if the provided type is an enum
|
||||
if (match == null && propertyType.IsEnum)
|
||||
match = _registeredDataModelEditors.FirstOrDefault(d => d.SupportedType == typeof(Enum));
|
||||
|
||||
if (match != null)
|
||||
{
|
||||
DataModelInputViewModel viewModel = InstantiateDataModelInputViewModel(match, description, initialValue);
|
||||
viewModel.UpdateCallback = updateCallback;
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private DataModelInputViewModel InstantiateDataModelInputViewModel(DataModelVisualizationRegistration registration, DataModelPropertyAttribute? description, object? initialValue)
|
||||
{
|
||||
// This assumes the type can be converted, that has been checked when the VM was created
|
||||
if (initialValue != null && initialValue.GetType() != registration.SupportedType)
|
||||
initialValue = Convert.ChangeType(initialValue, registration.SupportedType);
|
||||
|
||||
IParameter[] parameters =
|
||||
{
|
||||
new ConstructorArgument("targetDescription", description),
|
||||
new ConstructorArgument("initialValue", initialValue)
|
||||
// Apply the compatible conversion types to the registration
|
||||
CompatibleConversionTypes = compatibleConversionTypes
|
||||
};
|
||||
|
||||
// If this ever happens something is likely wrong with the plugin unload detection
|
||||
if (registration.Plugin.Kernel == null)
|
||||
throw new ArtemisSharedUIException("Cannot InstantiateDataModelInputViewModel for a registration by an uninitialized plugin");
|
||||
_registeredDataModelEditors.Add(registration);
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
|
||||
DataModelInputViewModel viewModel = (DataModelInputViewModel) registration.Plugin.Kernel.Get(registration.ViewModelType, parameters);
|
||||
viewModel.CompatibleConversionTypes = registration.CompatibleConversionTypes;
|
||||
return viewModel;
|
||||
public DataModelVisualizationRegistration RegisterDataModelDisplay<T>(Plugin plugin) where T : DataModelDisplayViewModel
|
||||
{
|
||||
Type viewModelType = typeof(T);
|
||||
lock (_registeredDataModelDisplays)
|
||||
{
|
||||
Type supportedType = viewModelType.BaseType!.GetGenericArguments()[0];
|
||||
DataModelVisualizationRegistration? existing = _registeredDataModelDisplays.FirstOrDefault(r => r.SupportedType == supportedType);
|
||||
if (existing != null)
|
||||
{
|
||||
if (existing.Plugin != plugin)
|
||||
throw new ArtemisSharedUIException($"Cannot register data model display for type {supportedType.Name} because an editor was already" +
|
||||
$" registered by {existing.Plugin}");
|
||||
return existing;
|
||||
}
|
||||
|
||||
_kernel.Bind(viewModelType).ToSelf();
|
||||
DataModelVisualizationRegistration registration = new(this, RegistrationType.Display, plugin, supportedType, viewModelType);
|
||||
_registeredDataModelDisplays.Add(registration);
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveDataModelInput(DataModelVisualizationRegistration registration)
|
||||
{
|
||||
lock (_registeredDataModelEditors)
|
||||
{
|
||||
if (_registeredDataModelEditors.Contains(registration))
|
||||
{
|
||||
registration.Unsubscribe();
|
||||
_registeredDataModelEditors.Remove(registration);
|
||||
|
||||
_kernel.Unbind(registration.ViewModelType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveDataModelDisplay(DataModelVisualizationRegistration registration)
|
||||
{
|
||||
lock (_registeredDataModelDisplays)
|
||||
{
|
||||
if (_registeredDataModelDisplays.Contains(registration))
|
||||
{
|
||||
registration.Unsubscribe();
|
||||
_registeredDataModelDisplays.Remove(registration);
|
||||
|
||||
_kernel.Unbind(registration.ViewModelType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DataModelDisplayViewModel? GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute? description, bool fallBackToDefault)
|
||||
{
|
||||
lock (_registeredDataModelDisplays)
|
||||
{
|
||||
DataModelDisplayViewModel? result;
|
||||
|
||||
DataModelVisualizationRegistration? match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType);
|
||||
if (match != null)
|
||||
{
|
||||
// If this ever happens something is likely wrong with the plugin unload detection
|
||||
if (match.Plugin.Kernel == null)
|
||||
throw new ArtemisSharedUIException("Cannot GetDataModelDisplayViewModel for a registration by an uninitialized plugin");
|
||||
|
||||
result = (DataModelDisplayViewModel) match.Plugin.Kernel.Get(match.ViewModelType);
|
||||
}
|
||||
else if (!fallBackToDefault)
|
||||
{
|
||||
result = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = _kernel.Get<DefaultDataModelDisplayViewModel>();
|
||||
}
|
||||
|
||||
if (result != null)
|
||||
result.PropertyDescription = description;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action<object?, bool> updateCallback)
|
||||
{
|
||||
lock (_registeredDataModelEditors)
|
||||
{
|
||||
// Prefer a VM that natively supports the type
|
||||
DataModelVisualizationRegistration? match = _registeredDataModelEditors.FirstOrDefault(d => d.SupportedType == propertyType);
|
||||
// Fall back on a VM that supports the type through conversion
|
||||
if (match == null)
|
||||
match = _registeredDataModelEditors.FirstOrDefault(d => d.CompatibleConversionTypes != null && d.CompatibleConversionTypes.Contains(propertyType));
|
||||
// Lastly try getting an enum VM if the provided type is an enum
|
||||
if (match == null && propertyType.IsEnum)
|
||||
match = _registeredDataModelEditors.FirstOrDefault(d => d.SupportedType == typeof(Enum));
|
||||
|
||||
if (match != null)
|
||||
{
|
||||
DataModelInputViewModel viewModel = InstantiateDataModelInputViewModel(match, description, initialValue);
|
||||
viewModel.UpdateCallback = updateCallback;
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
namespace Artemis.UI.Shared.Services
|
||||
namespace Artemis.UI.Shared.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a service provided by the Artemis Shared UI library
|
||||
/// </summary>
|
||||
public interface IArtemisSharedUIService
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a service provided by the Artemis Shared UI library
|
||||
/// </summary>
|
||||
public interface IArtemisSharedUIService
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -5,104 +5,103 @@ using Artemis.Core.Modules;
|
||||
using Artemis.UI.Shared.DataModelVisualization;
|
||||
using Artemis.UI.Shared.DataModelVisualization.Shared;
|
||||
|
||||
namespace Artemis.UI.Shared.Services
|
||||
namespace Artemis.UI.Shared.Services;
|
||||
|
||||
/// <summary>
|
||||
/// A service for UI related data model tasks
|
||||
/// </summary>
|
||||
public interface IDataModelUIService : IArtemisSharedUIService
|
||||
{
|
||||
/// <summary>
|
||||
/// A service for UI related data model tasks
|
||||
/// Gets a read-only list of all registered data model editors
|
||||
/// </summary>
|
||||
public interface IDataModelUIService : IArtemisSharedUIService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all registered data model editors
|
||||
/// </summary>
|
||||
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors { get; }
|
||||
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelEditors { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all registered data model displays
|
||||
/// </summary>
|
||||
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelDisplays { get; }
|
||||
/// <summary>
|
||||
/// Gets a read-only list of all registered data model displays
|
||||
/// </summary>
|
||||
IReadOnlyCollection<DataModelVisualizationRegistration> RegisteredDataModelDisplays { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a data model visualization view model for the main data model
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A data model visualization view model containing all data model expansions and modules that expand the main
|
||||
/// data model
|
||||
/// </returns>
|
||||
DataModelPropertiesViewModel GetMainDataModelVisualization();
|
||||
/// <summary>
|
||||
/// Creates a data model visualization view model for the main data model
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A data model visualization view model containing all data model expansions and modules that expand the main
|
||||
/// data model
|
||||
/// </returns>
|
||||
DataModelPropertiesViewModel GetMainDataModelVisualization();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a data model visualization view model for the data model of the provided plugin feature
|
||||
/// </summary>
|
||||
/// <param name="modules">The modules to create the data model visualization view model for</param>
|
||||
/// <param name="includeMainDataModel">
|
||||
/// Whether or not also to include the main data model (and therefore any modules marked
|
||||
/// as <see cref="Module.IsAlwaysAvailable" />)
|
||||
/// </param>
|
||||
/// <returns>A data model visualization view model containing the data model of the provided feature</returns>
|
||||
DataModelPropertiesViewModel? GetPluginDataModelVisualization(List<Module> modules, bool includeMainDataModel);
|
||||
/// <summary>
|
||||
/// Creates a data model visualization view model for the data model of the provided plugin feature
|
||||
/// </summary>
|
||||
/// <param name="modules">The modules to create the data model visualization view model for</param>
|
||||
/// <param name="includeMainDataModel">
|
||||
/// Whether or not also to include the main data model (and therefore any modules marked
|
||||
/// as <see cref="Module.IsAlwaysAvailable" />)
|
||||
/// </param>
|
||||
/// <returns>A data model visualization view model containing the data model of the provided feature</returns>
|
||||
DataModelPropertiesViewModel? GetPluginDataModelVisualization(List<Module> modules, bool includeMainDataModel);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the children of the provided main data model visualization, removing disabled children and adding newly
|
||||
/// enabled children
|
||||
/// </summary>
|
||||
void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization);
|
||||
/// <summary>
|
||||
/// Updates the children of the provided main data model visualization, removing disabled children and adding newly
|
||||
/// enabled children
|
||||
/// </summary>
|
||||
void UpdateModules(DataModelPropertiesViewModel mainDataModelVisualization);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new data model editor
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the editor</typeparam>
|
||||
/// <param name="plugin">The plugin this editor belongs to</param>
|
||||
/// <param name="compatibleConversionTypes">A collection of extra types this editor supports</param>
|
||||
/// <returns>A registration that can be used to remove the editor</returns>
|
||||
DataModelVisualizationRegistration RegisterDataModelInput<T>(Plugin plugin, IReadOnlyCollection<Type> compatibleConversionTypes) where T : DataModelInputViewModel;
|
||||
/// <summary>
|
||||
/// Registers a new data model editor
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the editor</typeparam>
|
||||
/// <param name="plugin">The plugin this editor belongs to</param>
|
||||
/// <param name="compatibleConversionTypes">A collection of extra types this editor supports</param>
|
||||
/// <returns>A registration that can be used to remove the editor</returns>
|
||||
DataModelVisualizationRegistration RegisterDataModelInput<T>(Plugin plugin, IReadOnlyCollection<Type> compatibleConversionTypes) where T : DataModelInputViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new data model display
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the display</typeparam>
|
||||
/// <param name="plugin">The plugin this display belongs to</param>
|
||||
/// <returns>A registration that can be used to remove the display</returns>
|
||||
DataModelVisualizationRegistration RegisterDataModelDisplay<T>(Plugin plugin) where T : DataModelDisplayViewModel;
|
||||
/// <summary>
|
||||
/// Registers a new data model display
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the display</typeparam>
|
||||
/// <param name="plugin">The plugin this display belongs to</param>
|
||||
/// <returns>A registration that can be used to remove the display</returns>
|
||||
DataModelVisualizationRegistration RegisterDataModelDisplay<T>(Plugin plugin) where T : DataModelDisplayViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// Removes a data model editor
|
||||
/// </summary>
|
||||
/// <param name="registration">
|
||||
/// The registration of the editor as returned by <see cref="RegisterDataModelInput{T}" />
|
||||
/// </param>
|
||||
void RemoveDataModelInput(DataModelVisualizationRegistration registration);
|
||||
/// <summary>
|
||||
/// Removes a data model editor
|
||||
/// </summary>
|
||||
/// <param name="registration">
|
||||
/// The registration of the editor as returned by <see cref="RegisterDataModelInput{T}" />
|
||||
/// </param>
|
||||
void RemoveDataModelInput(DataModelVisualizationRegistration registration);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a data model display
|
||||
/// </summary>
|
||||
/// <param name="registration">
|
||||
/// The registration of the display as returned by <see cref="RegisterDataModelDisplay{T}" />
|
||||
/// </param>
|
||||
void RemoveDataModelDisplay(DataModelVisualizationRegistration registration);
|
||||
/// <summary>
|
||||
/// Removes a data model display
|
||||
/// </summary>
|
||||
/// <param name="registration">
|
||||
/// The registration of the display as returned by <see cref="RegisterDataModelDisplay{T}" />
|
||||
/// </param>
|
||||
void RemoveDataModelDisplay(DataModelVisualizationRegistration registration);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the most appropriate display view model for the provided <paramref name="propertyType" /> that can display
|
||||
/// a value
|
||||
/// </summary>
|
||||
/// <param name="propertyType">The type of data model property to find a display view model for</param>
|
||||
/// <param name="description">The description of the data model property</param>
|
||||
/// <param name="fallBackToDefault">
|
||||
/// If <see langword="true"></see>, a simple .ToString() display view model will be
|
||||
/// returned if nothing else is found
|
||||
/// </param>
|
||||
/// <returns>The most appropriate display view model for the provided <paramref name="propertyType"></paramref></returns>
|
||||
DataModelDisplayViewModel? GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute? description, bool fallBackToDefault = false);
|
||||
/// <summary>
|
||||
/// Creates the most appropriate display view model for the provided <paramref name="propertyType" /> that can display
|
||||
/// a value
|
||||
/// </summary>
|
||||
/// <param name="propertyType">The type of data model property to find a display view model for</param>
|
||||
/// <param name="description">The description of the data model property</param>
|
||||
/// <param name="fallBackToDefault">
|
||||
/// If <see langword="true"></see>, a simple .ToString() display view model will be
|
||||
/// returned if nothing else is found
|
||||
/// </param>
|
||||
/// <returns>The most appropriate display view model for the provided <paramref name="propertyType"></paramref></returns>
|
||||
DataModelDisplayViewModel? GetDataModelDisplayViewModel(Type propertyType, DataModelPropertyAttribute? description, bool fallBackToDefault = false);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the most appropriate input view model for the provided <paramref name="propertyType" /> that allows
|
||||
/// inputting a value
|
||||
/// </summary>
|
||||
/// <param name="propertyType">The type of data model property to find a display view model for</param>
|
||||
/// <param name="description">The description of the data model property</param>
|
||||
/// <param name="initialValue">The initial value to show in the input</param>
|
||||
/// <param name="updateCallback">A function to call whenever the input was updated (submitted or not)</param>
|
||||
/// <returns>The most appropriate input view model for the provided <paramref name="propertyType" /></returns>
|
||||
DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action<object?, bool> updateCallback);
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates the most appropriate input view model for the provided <paramref name="propertyType" /> that allows
|
||||
/// inputting a value
|
||||
/// </summary>
|
||||
/// <param name="propertyType">The type of data model property to find a display view model for</param>
|
||||
/// <param name="description">The description of the data model property</param>
|
||||
/// <param name="initialValue">The initial value to show in the input</param>
|
||||
/// <param name="updateCallback">A function to call whenever the input was updated (submitted or not)</param>
|
||||
/// <returns>The most appropriate input view model for the provided <paramref name="propertyType" /></returns>
|
||||
DataModelInputViewModel? GetDataModelInputViewModel(Type propertyType, DataModelPropertyAttribute? description, object? initialValue, Action<object?, bool> updateCallback);
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
|
||||
namespace Artemis.UI.Shared.Services
|
||||
namespace Artemis.UI.Shared.Services;
|
||||
|
||||
/// <summary>
|
||||
/// A service that can be used to create notifications in either the application or on the desktop.
|
||||
/// </summary>
|
||||
public interface INotificationService : IArtemisSharedUIService
|
||||
{
|
||||
/// <summary>
|
||||
/// A service that can be used to create notifications in either the application or on the desktop.
|
||||
/// Creates an in-app notification using a builder.
|
||||
/// </summary>
|
||||
public interface INotificationService : IArtemisSharedUIService
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an in-app notification using a builder.
|
||||
/// </summary>
|
||||
/// <returns>A builder used to configure and show the notification.</returns>
|
||||
NotificationBuilder CreateNotification();
|
||||
}
|
||||
/// <returns>A builder used to configure and show the notification.</returns>
|
||||
NotificationBuilder CreateNotification();
|
||||
}
|
||||
@@ -3,106 +3,105 @@ using System.Threading.Tasks;
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Artemis.UI.Shared.Services
|
||||
namespace Artemis.UI.Shared.Services;
|
||||
|
||||
/// <summary>
|
||||
/// A service that can be used to show windows and dialogs.
|
||||
/// </summary>
|
||||
public interface IWindowService : IArtemisSharedUIService
|
||||
{
|
||||
/// <summary>
|
||||
/// A service that can be used to show windows and dialogs.
|
||||
/// Creates a view model instance of type <typeparamref name="TViewModel" /> and shows its corresponding View as a
|
||||
/// window
|
||||
/// </summary>
|
||||
public interface IWindowService : IArtemisSharedUIService
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a view model instance of type <typeparamref name="TViewModel" /> and shows its corresponding View as a
|
||||
/// window
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel">The type of view model to create</typeparam>
|
||||
/// <returns>The created view model</returns>
|
||||
TViewModel ShowWindow<TViewModel>(params (string name, object value)[] parameters);
|
||||
/// <typeparam name="TViewModel">The type of view model to create</typeparam>
|
||||
/// <returns>The created view model</returns>
|
||||
TViewModel ShowWindow<TViewModel>(params (string name, object value)[] parameters);
|
||||
|
||||
/// <summary>
|
||||
/// Given a ViewModel, show its corresponding View as a window
|
||||
/// </summary>
|
||||
/// <param name="viewModel">ViewModel to show the View for</param>
|
||||
Window ShowWindow(object viewModel);
|
||||
/// <summary>
|
||||
/// Given a ViewModel, show its corresponding View as a window
|
||||
/// </summary>
|
||||
/// <param name="viewModel">ViewModel to show the View for</param>
|
||||
Window ShowWindow(object viewModel);
|
||||
|
||||
/// <summary>
|
||||
/// Shows a dialog displaying the given exception
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the dialog</param>
|
||||
/// <param name="exception">The exception to display</param>
|
||||
void ShowExceptionDialog(string title, Exception exception);
|
||||
/// <summary>
|
||||
/// Shows a dialog displaying the given exception
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the dialog</param>
|
||||
/// <param name="exception">The exception to display</param>
|
||||
void ShowExceptionDialog(string title, Exception exception);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a view model instance of type <typeparamref name="TViewModel" /> and shows its corresponding View as a
|
||||
/// dialog
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel">The type of view model to create</typeparam>
|
||||
/// <returns>The created view model</returns>
|
||||
Task<TViewModel> ShowDialogAsync<TViewModel>(params (string name, object value)[] parameters);
|
||||
/// <summary>
|
||||
/// Creates a view model instance of type <typeparamref name="TViewModel" /> and shows its corresponding View as a
|
||||
/// dialog
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel">The type of view model to create</typeparam>
|
||||
/// <returns>The created view model</returns>
|
||||
Task<TViewModel> ShowDialogAsync<TViewModel>(params (string name, object value)[] parameters);
|
||||
|
||||
/// <summary>
|
||||
/// Given a ViewModel, show its corresponding View as a dialog
|
||||
/// </summary>
|
||||
/// <param name="viewModel">ViewModel to show the View for</param>
|
||||
Task ShowDialogAsync(object viewModel);
|
||||
|
||||
/// <summary>
|
||||
/// Given an existing ViewModel, show its corresponding View as a Dialog
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The return type</typeparam>
|
||||
/// <param name="viewModel">ViewModel to show the View for</param>
|
||||
/// <returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
|
||||
Task<TResult> ShowDialogAsync<TResult>(DialogViewModelBase<TResult> viewModel);
|
||||
/// <summary>
|
||||
/// Given a ViewModel, show its corresponding View as a dialog
|
||||
/// </summary>
|
||||
/// <param name="viewModel">ViewModel to show the View for</param>
|
||||
Task ShowDialogAsync(object viewModel);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a view model instance of type <typeparamref name="TViewModel" /> and shows its corresponding View as a
|
||||
/// Dialog
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel">The view model type</typeparam>
|
||||
/// <typeparam name="TResult">The return type</typeparam>
|
||||
/// <returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
|
||||
Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object? value)[] parameters) where TViewModel : DialogViewModelBase<TResult>;
|
||||
/// <summary>
|
||||
/// Given an existing ViewModel, show its corresponding View as a Dialog
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The return type</typeparam>
|
||||
/// <param name="viewModel">ViewModel to show the View for</param>
|
||||
/// <returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
|
||||
Task<TResult> ShowDialogAsync<TResult>(DialogViewModelBase<TResult> viewModel);
|
||||
|
||||
/// <summary>
|
||||
/// Shows a content dialog asking the user to confirm an action
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the dialog</param>
|
||||
/// <param name="message">The message of the dialog</param>
|
||||
/// <param name="confirm">The text of the confirm button</param>
|
||||
/// <param name="cancel">The text of the cancel button, if <see langword="null" /> the cancel button will not be shown</param>
|
||||
/// <returns>
|
||||
/// A task containing the result of the dialog, <see langword="true" /> if confirmed; otherwise
|
||||
/// <see langword="false" />
|
||||
/// </returns>
|
||||
Task<bool> ShowConfirmContentDialog(string title, string message, string confirm = "Confirm", string? cancel = "Cancel");
|
||||
/// <summary>
|
||||
/// Creates a view model instance of type <typeparamref name="TViewModel" /> and shows its corresponding View as a
|
||||
/// Dialog
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel">The view model type</typeparam>
|
||||
/// <typeparam name="TResult">The return type</typeparam>
|
||||
/// <returns>A task containing the return value of type <typeparamref name="TResult" /></returns>
|
||||
Task<TResult> ShowDialogAsync<TViewModel, TResult>(params (string name, object? value)[] parameters) where TViewModel : DialogViewModelBase<TResult>;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an open folder dialog, use the fluent API to configure it
|
||||
/// </summary>
|
||||
/// <returns>The builder that can be used to configure the dialog</returns>
|
||||
OpenFolderDialogBuilder CreateOpenFolderDialog();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an open file dialog, use the fluent API to configure it
|
||||
/// </summary>
|
||||
/// <returns>The builder that can be used to configure the dialog</returns>
|
||||
OpenFileDialogBuilder CreateOpenFileDialog();
|
||||
/// <summary>
|
||||
/// Shows a content dialog asking the user to confirm an action
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the dialog</param>
|
||||
/// <param name="message">The message of the dialog</param>
|
||||
/// <param name="confirm">The text of the confirm button</param>
|
||||
/// <param name="cancel">The text of the cancel button, if <see langword="null" /> the cancel button will not be shown</param>
|
||||
/// <returns>
|
||||
/// A task containing the result of the dialog, <see langword="true" /> if confirmed; otherwise
|
||||
/// <see langword="false" />
|
||||
/// </returns>
|
||||
Task<bool> ShowConfirmContentDialog(string title, string message, string confirm = "Confirm", string? cancel = "Cancel");
|
||||
|
||||
/// <summary>
|
||||
/// Creates a save file dialog, use the fluent API to configure it
|
||||
/// </summary>
|
||||
/// <returns>The builder that can be used to configure the dialog</returns>
|
||||
SaveFileDialogBuilder CreateSaveFileDialog();
|
||||
/// <summary>
|
||||
/// Creates an open folder dialog, use the fluent API to configure it
|
||||
/// </summary>
|
||||
/// <returns>The builder that can be used to configure the dialog</returns>
|
||||
OpenFolderDialogBuilder CreateOpenFolderDialog();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a content dialog, use the fluent API to configure it
|
||||
/// </summary>
|
||||
/// <returns>The builder that can be used to configure the dialog</returns>
|
||||
ContentDialogBuilder CreateContentDialog();
|
||||
/// <summary>
|
||||
/// Creates an open file dialog, use the fluent API to configure it
|
||||
/// </summary>
|
||||
/// <returns>The builder that can be used to configure the dialog</returns>
|
||||
OpenFileDialogBuilder CreateOpenFileDialog();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current window of the application
|
||||
/// </summary>
|
||||
/// <returns>The current window of the application</returns>
|
||||
Window? GetCurrentWindow();
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a save file dialog, use the fluent API to configure it
|
||||
/// </summary>
|
||||
/// <returns>The builder that can be used to configure the dialog</returns>
|
||||
SaveFileDialogBuilder CreateSaveFileDialog();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a content dialog, use the fluent API to configure it
|
||||
/// </summary>
|
||||
/// <returns>The builder that can be used to configure the dialog</returns>
|
||||
ContentDialogBuilder CreateContentDialog();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current window of the application
|
||||
/// </summary>
|
||||
/// <returns>The current window of the application</returns>
|
||||
Window? GetCurrentWindow();
|
||||
}
|
||||
@@ -1,36 +1,36 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.MainWindow
|
||||
namespace Artemis.UI.Shared.Services.MainWindow;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a class that provides the main window, so that <see cref="IMainWindowService" /> can control the state
|
||||
/// of
|
||||
/// the main window.
|
||||
/// </summary>
|
||||
public interface IMainWindowProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a class that provides the main window, so that <see cref="IMainWindowService" /> can control the state of
|
||||
/// the main window.
|
||||
/// Gets a boolean indicating whether the main window is currently open
|
||||
/// </summary>
|
||||
public interface IMainWindowProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the main window is currently open
|
||||
/// </summary>
|
||||
bool IsMainWindowOpen { get; }
|
||||
bool IsMainWindowOpen { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Opens the main window
|
||||
/// </summary>
|
||||
void OpenMainWindow();
|
||||
/// <summary>
|
||||
/// Opens the main window
|
||||
/// </summary>
|
||||
void OpenMainWindow();
|
||||
|
||||
/// <summary>
|
||||
/// Closes the main window
|
||||
/// </summary>
|
||||
void CloseMainWindow();
|
||||
/// <summary>
|
||||
/// Closes the main window
|
||||
/// </summary>
|
||||
void CloseMainWindow();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the main window has been opened
|
||||
/// </summary>
|
||||
public event EventHandler? MainWindowOpened;
|
||||
/// <summary>
|
||||
/// Occurs when the main window has been opened
|
||||
/// </summary>
|
||||
public event EventHandler? MainWindowOpened;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the main window has been closed
|
||||
/// </summary>
|
||||
public event EventHandler? MainWindowClosed;
|
||||
}
|
||||
/// <summary>
|
||||
/// Occurs when the main window has been closed
|
||||
/// </summary>
|
||||
public event EventHandler? MainWindowClosed;
|
||||
}
|
||||
@@ -1,41 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.MainWindow
|
||||
namespace Artemis.UI.Shared.Services.MainWindow;
|
||||
|
||||
/// <summary>
|
||||
/// A service that can be used to manage the state of the main window.
|
||||
/// </summary>
|
||||
public interface IMainWindowService : IArtemisSharedUIService
|
||||
{
|
||||
/// <summary>
|
||||
/// A service that can be used to manage the state of the main window.
|
||||
/// Gets a boolean indicating whether the main window is currently open
|
||||
/// </summary>
|
||||
public interface IMainWindowService : IArtemisSharedUIService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the main window is currently open
|
||||
/// </summary>
|
||||
bool IsMainWindowOpen { get; }
|
||||
bool IsMainWindowOpen { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the main window provider that controls the state of the main window
|
||||
/// </summary>
|
||||
/// <param name="mainWindowProvider">The main window provider to use to control the state of the main window</param>
|
||||
void ConfigureMainWindowProvider(IMainWindowProvider mainWindowProvider);
|
||||
/// <summary>
|
||||
/// Sets up the main window provider that controls the state of the main window
|
||||
/// </summary>
|
||||
/// <param name="mainWindowProvider">The main window provider to use to control the state of the main window</param>
|
||||
void ConfigureMainWindowProvider(IMainWindowProvider mainWindowProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the main window if it is not already open
|
||||
/// </summary>
|
||||
void OpenMainWindow();
|
||||
/// <summary>
|
||||
/// Opens the main window if it is not already open
|
||||
/// </summary>
|
||||
void OpenMainWindow();
|
||||
|
||||
/// <summary>
|
||||
/// Closes the main window if it is not already closed
|
||||
/// </summary>
|
||||
void CloseMainWindow();
|
||||
/// <summary>
|
||||
/// Closes the main window if it is not already closed
|
||||
/// </summary>
|
||||
void CloseMainWindow();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the main window has been opened
|
||||
/// </summary>
|
||||
public event EventHandler? MainWindowOpened;
|
||||
/// <summary>
|
||||
/// Occurs when the main window has been opened
|
||||
/// </summary>
|
||||
public event EventHandler? MainWindowOpened;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the main window has been closed
|
||||
/// </summary>
|
||||
public event EventHandler? MainWindowClosed;
|
||||
}
|
||||
/// <summary>
|
||||
/// Occurs when the main window has been closed
|
||||
/// </summary>
|
||||
public event EventHandler? MainWindowClosed;
|
||||
}
|
||||
@@ -1,80 +1,79 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.MainWindow
|
||||
namespace Artemis.UI.Shared.Services.MainWindow;
|
||||
|
||||
internal class MainWindowService : IMainWindowService
|
||||
{
|
||||
internal class MainWindowService : IMainWindowService
|
||||
private IMainWindowProvider? _mainWindowManager;
|
||||
|
||||
protected virtual void OnMainWindowOpened()
|
||||
{
|
||||
private IMainWindowProvider? _mainWindowManager;
|
||||
|
||||
protected virtual void OnMainWindowOpened()
|
||||
{
|
||||
MainWindowOpened?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnMainWindowClosed()
|
||||
{
|
||||
MainWindowClosed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void SyncWithManager()
|
||||
{
|
||||
if (_mainWindowManager == null)
|
||||
return;
|
||||
|
||||
if (IsMainWindowOpen && !_mainWindowManager.IsMainWindowOpen)
|
||||
{
|
||||
IsMainWindowOpen = false;
|
||||
OnMainWindowClosed();
|
||||
}
|
||||
|
||||
if (!IsMainWindowOpen && _mainWindowManager.IsMainWindowOpen)
|
||||
{
|
||||
IsMainWindowOpen = true;
|
||||
OnMainWindowOpened();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleMainWindowOpened(object? sender, EventArgs e)
|
||||
{
|
||||
SyncWithManager();
|
||||
}
|
||||
|
||||
private void HandleMainWindowClosed(object? sender, EventArgs e)
|
||||
{
|
||||
SyncWithManager();
|
||||
}
|
||||
|
||||
public bool IsMainWindowOpen { get; private set; }
|
||||
|
||||
public void ConfigureMainWindowProvider(IMainWindowProvider mainWindowProvider)
|
||||
{
|
||||
if (mainWindowProvider == null) throw new ArgumentNullException(nameof(mainWindowProvider));
|
||||
|
||||
if (_mainWindowManager != null)
|
||||
{
|
||||
_mainWindowManager.MainWindowOpened -= HandleMainWindowOpened;
|
||||
_mainWindowManager.MainWindowClosed -= HandleMainWindowClosed;
|
||||
}
|
||||
|
||||
_mainWindowManager = mainWindowProvider;
|
||||
_mainWindowManager.MainWindowOpened += HandleMainWindowOpened;
|
||||
_mainWindowManager.MainWindowClosed += HandleMainWindowClosed;
|
||||
|
||||
// Sync up with the new manager's state
|
||||
SyncWithManager();
|
||||
}
|
||||
|
||||
public void OpenMainWindow()
|
||||
{
|
||||
_mainWindowManager?.OpenMainWindow();
|
||||
}
|
||||
|
||||
public void CloseMainWindow()
|
||||
{
|
||||
_mainWindowManager?.CloseMainWindow();
|
||||
}
|
||||
|
||||
public event EventHandler? MainWindowOpened;
|
||||
public event EventHandler? MainWindowClosed;
|
||||
MainWindowOpened?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
protected virtual void OnMainWindowClosed()
|
||||
{
|
||||
MainWindowClosed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void SyncWithManager()
|
||||
{
|
||||
if (_mainWindowManager == null)
|
||||
return;
|
||||
|
||||
if (IsMainWindowOpen && !_mainWindowManager.IsMainWindowOpen)
|
||||
{
|
||||
IsMainWindowOpen = false;
|
||||
OnMainWindowClosed();
|
||||
}
|
||||
|
||||
if (!IsMainWindowOpen && _mainWindowManager.IsMainWindowOpen)
|
||||
{
|
||||
IsMainWindowOpen = true;
|
||||
OnMainWindowOpened();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleMainWindowOpened(object? sender, EventArgs e)
|
||||
{
|
||||
SyncWithManager();
|
||||
}
|
||||
|
||||
private void HandleMainWindowClosed(object? sender, EventArgs e)
|
||||
{
|
||||
SyncWithManager();
|
||||
}
|
||||
|
||||
public bool IsMainWindowOpen { get; private set; }
|
||||
|
||||
public void ConfigureMainWindowProvider(IMainWindowProvider mainWindowProvider)
|
||||
{
|
||||
if (mainWindowProvider == null) throw new ArgumentNullException(nameof(mainWindowProvider));
|
||||
|
||||
if (_mainWindowManager != null)
|
||||
{
|
||||
_mainWindowManager.MainWindowOpened -= HandleMainWindowOpened;
|
||||
_mainWindowManager.MainWindowClosed -= HandleMainWindowClosed;
|
||||
}
|
||||
|
||||
_mainWindowManager = mainWindowProvider;
|
||||
_mainWindowManager.MainWindowOpened += HandleMainWindowOpened;
|
||||
_mainWindowManager.MainWindowClosed += HandleMainWindowClosed;
|
||||
|
||||
// Sync up with the new manager's state
|
||||
SyncWithManager();
|
||||
}
|
||||
|
||||
public void OpenMainWindow()
|
||||
{
|
||||
_mainWindowManager?.OpenMainWindow();
|
||||
}
|
||||
|
||||
public void CloseMainWindow()
|
||||
{
|
||||
_mainWindowManager?.CloseMainWindow();
|
||||
}
|
||||
|
||||
public event EventHandler? MainWindowOpened;
|
||||
public event EventHandler? MainWindowClosed;
|
||||
}
|
||||
@@ -8,8 +8,8 @@ namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||
/// </summary>
|
||||
public class AddNode : INodeEditorCommand, IDisposable
|
||||
{
|
||||
private readonly INodeScript _nodeScript;
|
||||
private readonly INode _node;
|
||||
private readonly INodeScript _nodeScript;
|
||||
private bool _isRemoved;
|
||||
|
||||
/// <summary>
|
||||
@@ -23,6 +23,13 @@ public class AddNode : INodeEditorCommand, IDisposable
|
||||
_node = node;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isRemoved && _node is IDisposable disposableNode)
|
||||
disposableNode.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => $"Add '{_node.Name}' node";
|
||||
|
||||
@@ -30,7 +37,7 @@ public class AddNode : INodeEditorCommand, IDisposable
|
||||
public void Execute()
|
||||
{
|
||||
_nodeScript.AddNode(_node);
|
||||
_isRemoved = false;
|
||||
_isRemoved = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -39,15 +46,4 @@ public class AddNode : INodeEditorCommand, IDisposable
|
||||
_nodeScript.RemoveNode(_node);
|
||||
_isRemoved = true;
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isRemoved && _node is IDisposable disposableNode)
|
||||
disposableNode.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -9,8 +9,8 @@ namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||
/// </summary>
|
||||
public class CompositeCommand : INodeEditorCommand, IDisposable
|
||||
{
|
||||
private bool _ignoreNextExecute;
|
||||
private readonly List<INodeEditorCommand> _commands;
|
||||
private bool _ignoreNextExecute;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CompositeCommand" /> class.
|
||||
@@ -45,8 +45,10 @@ public class CompositeCommand : INodeEditorCommand, IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (INodeEditorCommand NodeEditorCommand in _commands)
|
||||
{
|
||||
if (NodeEditorCommand is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#region Implementation of INodeEditorCommand
|
||||
|
||||
@@ -8,9 +8,9 @@ namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||
/// </summary>
|
||||
public class ConnectPins : INodeEditorCommand
|
||||
{
|
||||
private readonly List<IPin>? _originalConnections;
|
||||
private readonly IPin _source;
|
||||
private readonly IPin _target;
|
||||
private readonly List<IPin>? _originalConnections;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ConnectPins" /> class.
|
||||
@@ -42,7 +42,7 @@ public class ConnectPins : INodeEditorCommand
|
||||
public void Undo()
|
||||
{
|
||||
_target.DisconnectFrom(_source);
|
||||
|
||||
|
||||
if (_originalConnections == null)
|
||||
return;
|
||||
foreach (IPin pin in _originalConnections)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||
@@ -9,9 +8,9 @@ namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||
/// </summary>
|
||||
public class DeleteNode : INodeEditorCommand, IDisposable
|
||||
{
|
||||
private readonly NodeConnectionStore _connections;
|
||||
private readonly INode _node;
|
||||
private readonly INodeScript _nodeScript;
|
||||
private readonly NodeConnectionStore _connections;
|
||||
private bool _isRemoved;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -8,8 +8,8 @@ namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||
/// </summary>
|
||||
public class DisconnectPins : INodeEditorCommand
|
||||
{
|
||||
private readonly IPin _pin;
|
||||
private readonly List<IPin> _originalConnections;
|
||||
private readonly IPin _pin;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DisconnectPins" /> class.
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using FluentAvalonia.Core;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||
|
||||
@@ -12,12 +11,12 @@ namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||
/// </summary>
|
||||
public class DuplicateNode : INodeEditorCommand, IDisposable
|
||||
{
|
||||
private readonly INode _node;
|
||||
private readonly bool _copyIncomingConnections;
|
||||
private readonly INodeService _nodeService;
|
||||
private readonly INode _node;
|
||||
private readonly INodeScript _nodeScript;
|
||||
private INode? _copy;
|
||||
private readonly INodeService _nodeService;
|
||||
private NodeConnectionStore? _connections;
|
||||
private INode? _copy;
|
||||
|
||||
private bool _executed;
|
||||
|
||||
@@ -36,45 +35,6 @@ public class DuplicateNode : INodeEditorCommand, IDisposable
|
||||
_nodeService = nodeService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_executed && _copy is IDisposable disposableNode)
|
||||
disposableNode.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => $"Duplicate '{_node.Name}' node";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Execute()
|
||||
{
|
||||
if (_copy == null)
|
||||
{
|
||||
_copy = CreateCopy();
|
||||
_nodeScript.AddNode(_copy);
|
||||
}
|
||||
else if (_connections != null)
|
||||
{
|
||||
_nodeScript.AddNode(_copy);
|
||||
_connections.Restore();
|
||||
}
|
||||
|
||||
_executed = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
if (_copy != null && _connections != null)
|
||||
{
|
||||
_connections.Store();
|
||||
_nodeScript.RemoveNode(_copy);
|
||||
}
|
||||
|
||||
_executed = false;
|
||||
}
|
||||
|
||||
private INode CreateCopy()
|
||||
{
|
||||
NodeData? nodeData = _nodeService.AvailableNodes.FirstOrDefault(d => d.Type == _node.GetType());
|
||||
@@ -123,4 +83,43 @@ public class DuplicateNode : INodeEditorCommand, IDisposable
|
||||
_connections = new NodeConnectionStore(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_executed && _copy is IDisposable disposableNode)
|
||||
disposableNode.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => $"Duplicate '{_node.Name}' node";
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Execute()
|
||||
{
|
||||
if (_copy == null)
|
||||
{
|
||||
_copy = CreateCopy();
|
||||
_nodeScript.AddNode(_copy);
|
||||
}
|
||||
else if (_connections != null)
|
||||
{
|
||||
_nodeScript.AddNode(_copy);
|
||||
_connections.Restore();
|
||||
}
|
||||
|
||||
_executed = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
if (_copy != null && _connections != null)
|
||||
{
|
||||
_connections.Store();
|
||||
_nodeScript.RemoveNode(_copy);
|
||||
}
|
||||
|
||||
_executed = false;
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,9 @@ namespace Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||
/// </summary>
|
||||
public class RemovePin : INodeEditorCommand
|
||||
{
|
||||
private readonly IPinCollection _pinCollection;
|
||||
private readonly IPin _pin;
|
||||
private readonly List<IPin> _originalConnections;
|
||||
private readonly IPin _pin;
|
||||
private readonly IPinCollection _pinCollection;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="RemovePin" /> class.
|
||||
|
||||
@@ -30,6 +30,21 @@ public class UpdateStorage<TStorage> : INodeEditorCommand, IDisposable
|
||||
_originalValue = _node.Storage;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_updatedValue)
|
||||
{
|
||||
if (_originalValue is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_value is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#region Implementation of INodeEditorCommand
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -50,23 +65,4 @@ public class UpdateStorage<TStorage> : INodeEditorCommand, IDisposable
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_updatedValue)
|
||||
{
|
||||
if (_originalValue is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_value is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,23 +1,22 @@
|
||||
namespace Artemis.UI.Shared.Services.NodeEditor
|
||||
namespace Artemis.UI.Shared.Services.NodeEditor;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a command that can be executed and if needed, undone
|
||||
/// </summary>
|
||||
public interface INodeEditorCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a command that can be executed and if needed, undone
|
||||
/// Gets the name of the command
|
||||
/// </summary>
|
||||
public interface INodeEditorCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the command
|
||||
/// </summary>
|
||||
string DisplayName { get; }
|
||||
string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Executes the command
|
||||
/// </summary>
|
||||
void Execute();
|
||||
/// <summary>
|
||||
/// Executes the command
|
||||
/// </summary>
|
||||
void Execute();
|
||||
|
||||
/// <summary>
|
||||
/// Undoes the command
|
||||
/// </summary>
|
||||
void Undo();
|
||||
}
|
||||
/// <summary>
|
||||
/// Undoes the command
|
||||
/// </summary>
|
||||
void Undo();
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||
using Artemis.Core;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.NodeEditor;
|
||||
|
||||
|
||||
@@ -33,30 +33,31 @@ public class NodeEditorHistory
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the node script the history relates to.
|
||||
/// Gets the node script the history relates to.
|
||||
/// </summary>
|
||||
public INodeScript NodeScript { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets an observable sequence containing a boolean value indicating whether history can be undone.
|
||||
/// </summary>
|
||||
public IObservable<bool> CanUndo => _canUndo.AsObservable().DistinctUntilChanged();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets an observable sequence containing a boolean value indicating whether history can be redone.
|
||||
/// </summary>
|
||||
public IObservable<bool> CanRedo => _canRedo.AsObservable().DistinctUntilChanged();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reactive command that can be executed to execute an instance of a <see cref="INodeEditorCommand" /> and puts it in history.
|
||||
/// Gets a reactive command that can be executed to execute an instance of a <see cref="INodeEditorCommand" /> and puts
|
||||
/// it in history.
|
||||
/// </summary>
|
||||
public ReactiveCommand<INodeEditorCommand, Unit> Execute { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reactive command that can be executed to undo history.
|
||||
/// </summary>
|
||||
public ReactiveCommand<Unit, INodeEditorCommand?> Undo { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reactive command that can be executed to redo history.
|
||||
/// </summary>
|
||||
@@ -88,8 +89,10 @@ public class NodeEditorHistory
|
||||
private void ClearRedo()
|
||||
{
|
||||
foreach (INodeEditorCommand nodeEditorCommand in _redoCommands)
|
||||
{
|
||||
if (nodeEditorCommand is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
_redoCommands.Clear();
|
||||
}
|
||||
@@ -97,8 +100,10 @@ public class NodeEditorHistory
|
||||
private void ClearUndo()
|
||||
{
|
||||
foreach (INodeEditorCommand nodeEditorCommand in _undoCommands)
|
||||
{
|
||||
if (nodeEditorCommand is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
_undoCommands.Clear();
|
||||
}
|
||||
|
||||
@@ -5,21 +5,34 @@ using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.NodeEditor;
|
||||
|
||||
/// <inheritdoc cref="INodeEditorService"/>
|
||||
/// <inheritdoc cref="INodeEditorService" />
|
||||
public class NodeEditorService : INodeEditorService
|
||||
{
|
||||
private readonly Dictionary<INodeScript, NodeEditorCommandScope> _nodeEditorCommandScopes = new();
|
||||
|
||||
private readonly Dictionary<INodeScript, NodeEditorHistory> _nodeEditorHistories = new();
|
||||
private readonly IWindowService _windowService;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="NodeEditorService"/> class.
|
||||
/// Creates a new instance of the <see cref="NodeEditorService" /> class.
|
||||
/// </summary>
|
||||
public NodeEditorService(IWindowService windowService)
|
||||
{
|
||||
_windowService = windowService;
|
||||
}
|
||||
|
||||
private readonly Dictionary<INodeScript, NodeEditorHistory> _nodeEditorHistories = new();
|
||||
private readonly Dictionary<INodeScript, NodeEditorCommandScope> _nodeEditorCommandScopes = new();
|
||||
internal void StopCommandScope(INodeScript nodeScript)
|
||||
{
|
||||
// This might happen if the scope is disposed twice, it's no biggie
|
||||
if (!_nodeEditorCommandScopes.TryGetValue(nodeScript, out NodeEditorCommandScope? scope))
|
||||
return;
|
||||
|
||||
_nodeEditorCommandScopes.Remove(nodeScript);
|
||||
|
||||
// Executing the composite command won't do anything the first time (see last ctor variable)
|
||||
// commands were already executed each time they were added to the scope
|
||||
ExecuteCommand(nodeScript, new CompositeCommand(scope.NodeEditorCommands, scope.Name, true));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NodeEditorHistory GetHistory(INodeScript nodeScript)
|
||||
@@ -38,7 +51,7 @@ public class NodeEditorService : INodeEditorService
|
||||
try
|
||||
{
|
||||
NodeEditorHistory history = GetHistory(nodeScript);
|
||||
|
||||
|
||||
// If a scope is active add the command to it, the scope will execute it immediately
|
||||
_nodeEditorCommandScopes.TryGetValue(nodeScript, out NodeEditorCommandScope? scope);
|
||||
if (scope != null)
|
||||
@@ -66,17 +79,4 @@ public class NodeEditorService : INodeEditorService
|
||||
_nodeEditorCommandScopes.Add(nodeScript, newScope);
|
||||
return newScope;
|
||||
}
|
||||
|
||||
internal void StopCommandScope(INodeScript nodeScript)
|
||||
{
|
||||
// This might happen if the scope is disposed twice, it's no biggie
|
||||
if (!_nodeEditorCommandScopes.TryGetValue(nodeScript, out NodeEditorCommandScope? scope))
|
||||
return;
|
||||
|
||||
_nodeEditorCommandScopes.Remove(nodeScript);
|
||||
|
||||
// Executing the composite command won't do anything the first time (see last ctor variable)
|
||||
// commands were already executed each time they were added to the scope
|
||||
ExecuteCommand(nodeScript, new CompositeCommand(scope.NodeEditorCommands, scope.Name, true));
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,23 @@
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace Artemis.UI.Shared.Services
|
||||
namespace Artemis.UI.Shared.Services;
|
||||
|
||||
internal class NotificationService : INotificationService
|
||||
{
|
||||
internal class NotificationService : INotificationService
|
||||
private readonly IWindowService _windowService;
|
||||
|
||||
public NotificationService(IWindowService windowService)
|
||||
{
|
||||
private readonly IWindowService _windowService;
|
||||
_windowService = windowService;
|
||||
}
|
||||
|
||||
public NotificationService(IWindowService windowService)
|
||||
{
|
||||
_windowService = windowService;
|
||||
}
|
||||
public NotificationBuilder CreateNotification()
|
||||
{
|
||||
Window? currentWindow = _windowService.GetCurrentWindow();
|
||||
if (currentWindow == null)
|
||||
throw new ArtemisSharedUIException("Can't show an in-app notification without any windows being shown.");
|
||||
|
||||
public NotificationBuilder CreateNotification()
|
||||
{
|
||||
Window? currentWindow = _windowService.GetCurrentWindow();
|
||||
if (currentWindow == null)
|
||||
throw new ArtemisSharedUIException("Can't show an in-app notification without any windows being shown.");
|
||||
|
||||
return new NotificationBuilder(currentWindow);
|
||||
}
|
||||
return new NotificationBuilder(currentWindow);
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,16 @@ using Artemis.Core.LayerEffects;
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profile editor command that can be used to add a layer effect to a profile element.
|
||||
/// Represents a profile editor command that can be used to add a layer effect to a profile element.
|
||||
/// </summary>
|
||||
public class AddLayerEffect : IProfileEditorCommand, IDisposable
|
||||
{
|
||||
private readonly RenderProfileElement _renderProfileElement;
|
||||
private readonly BaseLayerEffect _layerEffect;
|
||||
private readonly RenderProfileElement _renderProfileElement;
|
||||
private bool _executed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="AddLayerEffect"/> class.
|
||||
/// Creates a new instance of the <see cref="AddLayerEffect" /> class.
|
||||
/// </summary>
|
||||
public AddLayerEffect(RenderProfileElement renderProfileElement, BaseLayerEffect layerEffect)
|
||||
{
|
||||
@@ -22,6 +22,13 @@ public class AddLayerEffect : IProfileEditorCommand, IDisposable
|
||||
_layerEffect = layerEffect;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_executed)
|
||||
_layerEffect.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "Add layer effect";
|
||||
|
||||
@@ -38,11 +45,4 @@ public class AddLayerEffect : IProfileEditorCommand, IDisposable
|
||||
_renderProfileElement.RemoveLayerEffect(_layerEffect);
|
||||
_executed = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_executed)
|
||||
_layerEffect.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using Artemis.Core;
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profile editor command that can be used to add a profile element.
|
||||
/// Represents a profile editor command that can be used to add a profile element.
|
||||
/// </summary>
|
||||
public class AddProfileElement : IProfileEditorCommand, IDisposable
|
||||
{
|
||||
@@ -14,7 +14,7 @@ public class AddProfileElement : IProfileEditorCommand, IDisposable
|
||||
private bool _isAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="AddProfileElement"/> class.
|
||||
/// Creates a new instance of the <see cref="AddProfileElement" /> class.
|
||||
/// </summary>
|
||||
public AddProfileElement(RenderProfileElement subject, ProfileElement target, int index)
|
||||
{
|
||||
|
||||
@@ -8,9 +8,9 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
/// </summary>
|
||||
public class ChangeConditionType : IProfileEditorCommand, IDisposable
|
||||
{
|
||||
private readonly RenderProfileElement _profileElement;
|
||||
private readonly ICondition _condition;
|
||||
private readonly ICondition _oldCondition;
|
||||
private readonly RenderProfileElement _profileElement;
|
||||
private bool _executed;
|
||||
|
||||
/// <summary>
|
||||
@@ -24,7 +24,16 @@ public class ChangeConditionType : IProfileEditorCommand, IDisposable
|
||||
_condition = condition;
|
||||
_oldCondition = _profileElement.DisplayCondition;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_executed)
|
||||
_oldCondition?.Dispose();
|
||||
else
|
||||
_condition?.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "Change element display condition type";
|
||||
|
||||
@@ -41,13 +50,4 @@ public class ChangeConditionType : IProfileEditorCommand, IDisposable
|
||||
_profileElement.DisplayCondition = _oldCondition;
|
||||
_executed = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_executed)
|
||||
_oldCondition?.Dispose();
|
||||
else
|
||||
_condition?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,12 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
/// </summary>
|
||||
public class ChangeKeyframeEasing : IProfileEditorCommand
|
||||
{
|
||||
private readonly ILayerPropertyKeyframe _keyframe;
|
||||
private readonly Easings.Functions _easingFunction;
|
||||
private readonly ILayerPropertyKeyframe _keyframe;
|
||||
private readonly Easings.Functions _originalEasingFunction;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ChangeKeyframeEasing"/> class.
|
||||
/// Creates a new instance of the <see cref="ChangeKeyframeEasing" /> class.
|
||||
/// </summary>
|
||||
public ChangeKeyframeEasing(ILayerPropertyKeyframe keyframe, Easings.Functions easingFunction)
|
||||
{
|
||||
|
||||
@@ -12,9 +12,9 @@ public class ChangeLayerBrush : IProfileEditorCommand, IDisposable
|
||||
private readonly Layer _layer;
|
||||
private readonly LayerBrushDescriptor _layerBrushDescriptor;
|
||||
private readonly BaseLayerBrush? _previousBrush;
|
||||
private bool _executed;
|
||||
|
||||
private BaseLayerBrush? _newBrush;
|
||||
private bool _executed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ChangeLayerBrush" /> class.
|
||||
@@ -26,6 +26,15 @@ public class ChangeLayerBrush : IProfileEditorCommand, IDisposable
|
||||
_previousBrush = _layer.LayerBrush;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_executed)
|
||||
_previousBrush?.Dispose();
|
||||
else
|
||||
_newBrush?.Dispose();
|
||||
}
|
||||
|
||||
#region Implementation of IProfileEditorCommand
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -38,7 +47,7 @@ public class ChangeLayerBrush : IProfileEditorCommand, IDisposable
|
||||
_newBrush ??= _layerBrushDescriptor.CreateInstance(_layer, null);
|
||||
// Change the brush to the new brush
|
||||
_layer.ChangeLayerBrush(_newBrush);
|
||||
|
||||
|
||||
_executed = true;
|
||||
}
|
||||
|
||||
@@ -50,17 +59,4 @@ public class ChangeLayerBrush : IProfileEditorCommand, IDisposable
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_executed)
|
||||
_previousBrush?.Dispose();
|
||||
else
|
||||
_newBrush?.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -9,8 +9,8 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
/// </summary>
|
||||
public class CompositeCommand : IProfileEditorCommand, IDisposable
|
||||
{
|
||||
private bool _ignoreNextExecute;
|
||||
private readonly List<IProfileEditorCommand> _commands;
|
||||
private bool _ignoreNextExecute;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="CompositeCommand" /> class.
|
||||
@@ -45,8 +45,10 @@ public class CompositeCommand : IProfileEditorCommand, IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (IProfileEditorCommand profileEditorCommand in _commands)
|
||||
{
|
||||
if (profileEditorCommand is IDisposable disposable)
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#region Implementation of IProfileEditorCommand
|
||||
|
||||
@@ -11,11 +11,6 @@ public class DuplicateKeyframe : IProfileEditorCommand
|
||||
private readonly ILayerPropertyKeyframe _keyframe;
|
||||
private readonly TimeSpan _position;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duplicated keyframe, only available after the command has been executed.
|
||||
/// </summary>
|
||||
public ILayerPropertyKeyframe? Duplication { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="DeleteKeyframe" /> class.
|
||||
/// </summary>
|
||||
@@ -27,6 +22,11 @@ public class DuplicateKeyframe : IProfileEditorCommand
|
||||
_position = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the duplicated keyframe, only available after the command has been executed.
|
||||
/// </summary>
|
||||
public ILayerPropertyKeyframe? Duplication { get; private set; }
|
||||
|
||||
#region Implementation of IProfileEditorCommand
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -5,16 +5,16 @@ using Artemis.Core.LayerEffects;
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profile editor command that can be used to remove a layer effect from a profile element.
|
||||
/// Represents a profile editor command that can be used to remove a layer effect from a profile element.
|
||||
/// </summary>
|
||||
public class RemoveLayerEffect : IProfileEditorCommand, IDisposable
|
||||
{
|
||||
private readonly RenderProfileElement _renderProfileElement;
|
||||
private readonly BaseLayerEffect _layerEffect;
|
||||
private readonly RenderProfileElement _renderProfileElement;
|
||||
private bool _executed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="RemoveLayerEffect"/> class.
|
||||
/// Creates a new instance of the <see cref="RemoveLayerEffect" /> class.
|
||||
/// </summary>
|
||||
public RemoveLayerEffect(BaseLayerEffect layerEffect)
|
||||
{
|
||||
@@ -22,6 +22,13 @@ public class RemoveLayerEffect : IProfileEditorCommand, IDisposable
|
||||
_layerEffect = layerEffect;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_executed)
|
||||
_layerEffect.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "Remove layer effect";
|
||||
|
||||
@@ -38,11 +45,4 @@ public class RemoveLayerEffect : IProfileEditorCommand, IDisposable
|
||||
_renderProfileElement.AddLayerEffect(_layerEffect);
|
||||
_executed = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_executed)
|
||||
_layerEffect.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,18 @@ using Artemis.Core;
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profile editor command that can be used to remove a profile element.
|
||||
/// Represents a profile editor command that can be used to remove a profile element.
|
||||
/// </summary>
|
||||
public class RemoveProfileElement : IProfileEditorCommand, IDisposable
|
||||
{
|
||||
private readonly int _index;
|
||||
private readonly RenderProfileElement _subject;
|
||||
private readonly ProfileElement _target;
|
||||
private bool _isRemoved;
|
||||
private readonly bool _wasEnabled;
|
||||
private bool _isRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="RemoveProfileElement"/> class.
|
||||
/// Creates a new instance of the <see cref="RemoveProfileElement" /> class.
|
||||
/// </summary>
|
||||
public RemoveProfileElement(RenderProfileElement subject)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.Core.LayerEffects;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profile editor command that can be used to rename a layer effect
|
||||
/// Represents a profile editor command that can be used to rename a layer effect
|
||||
/// </summary>
|
||||
public class RenameLayerEffect : IProfileEditorCommand
|
||||
{
|
||||
@@ -15,7 +13,7 @@ public class RenameLayerEffect : IProfileEditorCommand
|
||||
private readonly bool _wasRenamed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="RenameLayerEffect"/> class.
|
||||
/// Creates a new instance of the <see cref="RenameLayerEffect" /> class.
|
||||
/// </summary>
|
||||
public RenameLayerEffect(BaseLayerEffect layerEffect, string name)
|
||||
{
|
||||
|
||||
@@ -7,9 +7,9 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
/// </summary>
|
||||
public class ResetLayerProperty<T> : IProfileEditorCommand
|
||||
{
|
||||
private readonly bool _keyframesEnabled;
|
||||
private readonly LayerProperty<T> _layerProperty;
|
||||
private readonly T _originalBaseValue;
|
||||
private readonly bool _keyframesEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ResetLayerProperty{T}" /> class.
|
||||
@@ -37,13 +37,13 @@ public class ResetLayerProperty<T> : IProfileEditorCommand
|
||||
if (_keyframesEnabled)
|
||||
_layerProperty.KeyframesEnabled = false;
|
||||
|
||||
_layerProperty.SetCurrentValue(CoreJson.DeserializeObject<T>(json)!, null);
|
||||
_layerProperty.SetCurrentValue(CoreJson.DeserializeObject<T>(json)!);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
_layerProperty.SetCurrentValue(_originalBaseValue, null);
|
||||
_layerProperty.SetCurrentValue(_originalBaseValue);
|
||||
if (_keyframesEnabled)
|
||||
_layerProperty.KeyframesEnabled = true;
|
||||
}
|
||||
|
||||
+3
-3
@@ -5,18 +5,18 @@ using Artemis.Core;
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a profile editor command that can be used to enable or disable keyframes on a layer property.
|
||||
/// Represents a profile editor command that can be used to enable or disable keyframes on a layer property.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class ToggleLayerPropertyKeyframes<T> : IProfileEditorCommand
|
||||
{
|
||||
private readonly bool _enable;
|
||||
private readonly TimeSpan _time;
|
||||
private readonly LayerProperty<T> _layerProperty;
|
||||
private readonly TimeSpan _time;
|
||||
private LayerPropertyKeyframe<T>? _keyframe;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ToggleLayerPropertyKeyframes{T}"/> class.
|
||||
/// Creates a new instance of the <see cref="ToggleLayerPropertyKeyframes{T}" /> class.
|
||||
/// </summary>
|
||||
public ToggleLayerPropertyKeyframes(LayerProperty<T> layerProperty, bool enable, TimeSpan time)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core;
|
||||
|
||||
@@ -11,8 +10,8 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
public class UpdateColorGradient : IProfileEditorCommand
|
||||
{
|
||||
private readonly ColorGradient _colorGradient;
|
||||
private readonly List<ColorGradientStop> _stops;
|
||||
private readonly List<ColorGradientStop> _originalStops;
|
||||
private readonly List<ColorGradientStop> _stops;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="UpdateColorGradient" /> class.
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
public class UpdateEventConditionPath : IProfileEditorCommand, IDisposable
|
||||
{
|
||||
private readonly EventCondition _eventCondition;
|
||||
private readonly DataModelPath? _value;
|
||||
private readonly DataModelPath? _oldValue;
|
||||
private readonly NodeConnectionStore? _store;
|
||||
private readonly DataModelPath? _value;
|
||||
private bool _executed;
|
||||
|
||||
/// <summary>
|
||||
@@ -29,6 +29,15 @@ public class UpdateEventConditionPath : IProfileEditorCommand, IDisposable
|
||||
_store = new NodeConnectionStore(startNode);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_executed)
|
||||
_oldValue?.Dispose();
|
||||
else
|
||||
_value?.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "Update event path";
|
||||
|
||||
@@ -57,17 +66,4 @@ public class UpdateEventConditionPath : IProfileEditorCommand, IDisposable
|
||||
|
||||
_executed = false;
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_executed)
|
||||
_oldValue?.Dispose();
|
||||
else
|
||||
_value?.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -8,8 +8,8 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
public class UpdateEventOverlapMode : IProfileEditorCommand
|
||||
{
|
||||
private readonly EventCondition _eventCondition;
|
||||
private readonly EventOverlapMode _value;
|
||||
private readonly EventOverlapMode _oldValue;
|
||||
private readonly EventOverlapMode _value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="UpdateEventOverlapMode" /> class.
|
||||
|
||||
@@ -8,8 +8,8 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
public class UpdateEventToggleOffMode : IProfileEditorCommand
|
||||
{
|
||||
private readonly EventCondition _eventCondition;
|
||||
private readonly EventToggleOffMode _value;
|
||||
private readonly EventToggleOffMode _oldValue;
|
||||
private readonly EventToggleOffMode _value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="UpdateEventOverlapMode" /> class.
|
||||
|
||||
@@ -9,11 +9,11 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
/// </summary>
|
||||
public class UpdateLayerProperty<T> : IProfileEditorCommand
|
||||
{
|
||||
private readonly bool _hasKeyframe;
|
||||
private readonly LayerProperty<T> _layerProperty;
|
||||
private readonly T _newValue;
|
||||
private readonly T _originalValue;
|
||||
private readonly TimeSpan? _time;
|
||||
private readonly bool _hasKeyframe;
|
||||
private LayerPropertyKeyframe<T>? _newKeyframe;
|
||||
|
||||
/// <summary>
|
||||
@@ -65,7 +65,9 @@ public class UpdateLayerProperty<T> : IProfileEditorCommand
|
||||
DisplayName = $"Add {_layerProperty.PropertyDescription.Name ?? "property"} keyframe";
|
||||
}
|
||||
else
|
||||
{
|
||||
_layerProperty.SetCurrentValue(_newValue, _time);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Loaded 100 of 691 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user