1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Core - Fixed an error when enabling a module without a datamodel

Shared UI - XML comments
This commit is contained in:
Robert 2020-11-18 19:28:05 +01:00
parent fb3466e102
commit 833a61ecab
38 changed files with 622 additions and 210 deletions

View File

@ -3,7 +3,7 @@
namespace Artemis.Core
{
/// <summary>
/// Represents errors that occur withing the Artemis Core
/// Represents errors that occur within the Artemis Core
/// </summary>
public class ArtemisCoreException : Exception
{

View File

@ -11,7 +11,7 @@ namespace Artemis.Core.Services
public DataModelService(IPluginManagementService pluginManagementService)
{
// Add data models of already loaded plugins
foreach (Module module in pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled && p.InternalDataModel != null))
foreach (Module module in pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled))
AddModuleDataModel(module);
foreach (BaseDataModelExpansion dataModelExpansion in pluginManagementService.GetFeaturesOfType<BaseDataModelExpansion>().Where(p => p.IsEnabled))
AddDataModelExpansionDataModel(dataModelExpansion);
@ -61,7 +61,8 @@ namespace Artemis.Core.Services
private void AddModuleDataModel(Module module)
{
if (module.InternalDataModel == null)
throw new ArtemisCoreException("Cannot add module data model that is not enabled");
return;
if (module.InternalDataModel.DataModelDescription == null)
throw new ArtemisPluginFeatureException(module, "Module overrides GetDataModelDescription but returned null");

View File

@ -8,6 +8,8 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=events/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=exceptions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=extensions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=ninject/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=ninject_005Cfactories/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=propertyinput/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Cdatamodelvisualization/@EntryIndexedValue">True</s:Boolean>

View File

@ -4,10 +4,14 @@ using Microsoft.Xaml.Behaviors;
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents a behavior that puts the cursor at the end of a text box when it receives focus
/// </summary>
public class PutCursorAtEndTextBox : Behavior<UIElement>
{
private TextBox _textBox;
private TextBox? _textBox;
/// <inheritdoc />
protected override void OnAttached()
{
base.OnAttached();
@ -18,6 +22,7 @@ namespace Artemis.UI.Shared
_textBox.GotFocus += TextBoxGotFocus;
}
/// <inheritdoc />
protected override void OnDetaching()
{
if (_textBox == null) return;
@ -28,6 +33,7 @@ namespace Artemis.UI.Shared
private void TextBoxGotFocus(object sender, RoutedEventArgs routedEventArgs)
{
if (_textBox == null) return;
_textBox.CaretIndex = _textBox.Text.Length;
}
}

View File

@ -3,10 +3,21 @@ using Ninject;
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents the main entry point for the shared UI library
/// <para>The Artemis UI calls this so there's no need to deal with this in a plugin</para>
/// </summary>
public static class Bootstrapper
{
/// <summary>
/// Gets a boolean indicating whether or not the shared UI library has been initialized
/// </summary>
public static bool Initialized { get; private set; }
/// <summary>
/// Initializes the shared UI library
/// </summary>
/// <param name="kernel"></param>
public static void Initialize(IKernel kernel)
{
if (Initialized)

View File

@ -1,9 +1,8 @@
<UserControl x:Class="Artemis.UI.Shared.Controls.ArtemisIcon"
<UserControl x:Class="Artemis.UI.Shared.ArtemisIcon"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Artemis.UI.Shared.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"

View File

@ -3,24 +3,37 @@ using System.Windows;
using System.Windows.Controls;
using MaterialDesignThemes.Wpf;
namespace Artemis.UI.Shared.Controls
namespace Artemis.UI.Shared
{
/// <summary>
/// Interaction logic for ArtemisIcon.xaml
/// </summary>
public partial class ArtemisIcon : UserControl
{
/// <summary>
/// Gets or sets the currently displayed icon as either a <see cref="PackIconKind" /> or an <see cref="Uri" /> pointing
/// to an SVG
/// </summary>
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(object), typeof(ArtemisIcon),
new FrameworkPropertyMetadata(IconPropertyChangedCallback));
/// <summary>
/// Gets or sets the <see cref="PackIconKind" />
/// </summary>
public static readonly DependencyProperty PackIconProperty = DependencyProperty.Register(nameof(PackIcon), typeof(PackIconKind?), typeof(ArtemisIcon),
new FrameworkPropertyMetadata(IconPropertyChangedCallback));
/// <summary>
/// Gets or sets the <see cref="Uri" /> pointing to the SVG
/// </summary>
public static readonly DependencyProperty SvgSourceProperty = DependencyProperty.Register(nameof(SvgSource), typeof(Uri), typeof(ArtemisIcon),
new FrameworkPropertyMetadata(IconPropertyChangedCallback));
private bool _inCallback;
/// <summary>
/// Creates a new instance of the <see cref="ArtemisIcon"/> class
/// </summary>
public ArtemisIcon()
{
InitializeComponent();
@ -30,7 +43,7 @@ namespace Artemis.UI.Shared.Controls
/// Gets or sets the currently displayed icon as either a <see cref="PackIconKind" /> or an <see cref="Uri" /> pointing
/// to an SVG
/// </summary>
public object Icon
public object? Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
@ -73,9 +86,9 @@ namespace Artemis.UI.Shared.Controls
}
else if (artemisIcon.Icon is string iconString)
{
if (Uri.TryCreate(iconString, UriKind.Absolute, out Uri uriResult))
if (Uri.TryCreate(iconString, UriKind.Absolute, out Uri? uriResult))
artemisIcon.Icon = uriResult;
else if (Enum.TryParse(typeof(PackIconKind), iconString, true, out object result))
else if (Enum.TryParse(typeof(PackIconKind), iconString, true, out object? result))
artemisIcon.Icon = result;
}
}

View File

@ -2,7 +2,6 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Artemis.UI.Shared.Services;
@ -12,22 +11,34 @@ namespace Artemis.UI.Shared
/// <summary>
/// Interaction logic for ColorPicker.xaml
/// </summary>
public partial class ColorPicker : UserControl, INotifyPropertyChanged
public partial class ColorPicker : INotifyPropertyChanged
{
private static IColorPickerService _colorPickerService;
private static IColorPickerService? _colorPickerService;
/// <summary>
/// Gets or sets the color
/// </summary>
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register(nameof(Color), typeof(Color), typeof(ColorPicker),
new FrameworkPropertyMetadata(default(Color), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ColorPropertyChangedCallback));
/// <summary>
/// Gets or sets a boolean indicating that the popup containing the color picker is open
/// </summary>
public static readonly DependencyProperty PopupOpenProperty = DependencyProperty.Register(nameof(PopupOpen), typeof(bool), typeof(ColorPicker),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PopupOpenPropertyChangedCallback));
/// <summary>
/// Gets or sets a boolean indicating whether the popup should stay open when clicked outside of it
/// </summary>
public static readonly DependencyProperty StaysOpenProperty = DependencyProperty.Register(nameof(StaysOpen), typeof(bool), typeof(ColorPicker),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, StaysOpenPropertyChangedCallback));
internal static readonly DependencyProperty ColorOpacityProperty = DependencyProperty.Register(nameof(ColorOpacity), typeof(byte), typeof(ColorPicker),
new FrameworkPropertyMetadata((byte) 255, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ColorOpacityPropertyChangedCallback));
/// <summary>
/// Occurs when the selected color has changed
/// </summary>
public static readonly RoutedEvent ColorChangedEvent =
EventManager.RegisterRoutedEvent(
nameof(Color),
@ -35,6 +46,9 @@ namespace Artemis.UI.Shared
typeof(RoutedPropertyChangedEventHandler<Color>),
typeof(ColorPicker));
/// <summary>
/// Occurs when the popup opens or closes
/// </summary>
public static readonly RoutedEvent PopupOpenChangedEvent =
EventManager.RegisterRoutedEvent(
nameof(PopupOpen),
@ -44,6 +58,9 @@ namespace Artemis.UI.Shared
private bool _inCallback;
/// <summary>
/// Creates a new instance of the <see cref="ColorPicker" /> class
/// </summary>
public ColorPicker()
{
InitializeComponent();
@ -51,6 +68,33 @@ namespace Artemis.UI.Shared
Unloaded += OnUnloaded;
}
/// <summary>
/// Gets or sets the color
/// </summary>
public Color Color
{
get => (Color) GetValue(ColorProperty);
set => SetValue(ColorProperty, value);
}
/// <summary>
/// Gets or sets a boolean indicating that the popup containing the color picker is open
/// </summary>
public bool PopupOpen
{
get => (bool) GetValue(PopupOpenProperty);
set => SetValue(PopupOpenProperty, value);
}
/// <summary>
/// Gets or sets a boolean indicating whether the popup should stay open when clicked outside of it
/// </summary>
public bool StaysOpen
{
get => (bool) GetValue(StaysOpenProperty);
set => SetValue(StaysOpenProperty, value);
}
/// <summary>
/// Used by the gradient picker to load saved gradients, do not touch or it'll just throw an exception
/// </summary>
@ -64,33 +108,17 @@ namespace Artemis.UI.Shared
}
}
public Color Color
{
get => (Color) GetValue(ColorProperty);
set => SetValue(ColorProperty, value);
}
public bool PopupOpen
{
get => (bool) GetValue(PopupOpenProperty);
set => SetValue(PopupOpenProperty, value);
}
public bool StaysOpen
{
get => (bool) GetValue(StaysOpenProperty);
set => SetValue(StaysOpenProperty, value);
}
internal byte ColorOpacity
{
get => (byte) GetValue(ColorOpacityProperty);
set => SetValue(ColorOpacityProperty, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
/// <summary>
/// Invokes the <see cref="PropertyChanged" /> event
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
@ -105,7 +133,7 @@ namespace Artemis.UI.Shared
colorPicker.SetCurrentValue(ColorOpacityProperty, ((Color) e.NewValue).A);
colorPicker.OnPropertyChanged(nameof(Color));
_colorPickerService.UpdateColorDisplay(colorPicker.Color);
_colorPickerService?.UpdateColorDisplay(colorPicker.Color);
colorPicker._inCallback = false;
}
@ -145,7 +173,7 @@ namespace Artemis.UI.Shared
color = Color.FromArgb(opacity, color.R, color.G, color.B);
colorPicker.SetCurrentValue(ColorProperty, color);
colorPicker.OnPropertyChanged(nameof(ColorOpacity));
_colorPickerService.UpdateColorDisplay(colorPicker.Color);
_colorPickerService?.UpdateColorDisplay(colorPicker.Color);
colorPicker._inCallback = false;
}
@ -163,6 +191,7 @@ namespace Artemis.UI.Shared
private void Slider_OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (_colorPickerService == null) return;
OnDragStarted();
if (_colorPickerService.PreviewSetting.Value)
@ -171,42 +200,63 @@ namespace Artemis.UI.Shared
private void Slider_OnMouseUp(object sender, MouseButtonEventArgs e)
{
if (_colorPickerService == null) return;
OnDragEnded();
_colorPickerService.StopColorDisplay();
}
private void PreviewCheckBoxClick(object sender, RoutedEventArgs e)
{
_colorPickerService.PreviewSetting.Value = PreviewCheckBox.IsChecked.Value;
if (_colorPickerService == null) return;
_colorPickerService.PreviewSetting.Value = PreviewCheckBox.IsChecked ?? false;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (_colorPickerService == null) return;
PreviewCheckBox.IsChecked = _colorPickerService.PreviewSetting.Value;
_colorPickerService.PreviewSetting.SettingChanged += PreviewSettingOnSettingChanged;
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
if (_colorPickerService == null) return;
_colorPickerService.PreviewSetting.SettingChanged -= PreviewSettingOnSettingChanged;
}
private void PreviewSettingOnSettingChanged(object sender, EventArgs e)
private void PreviewSettingOnSettingChanged(object? sender, EventArgs e)
{
if (_colorPickerService == null) return;
PreviewCheckBox.IsChecked = _colorPickerService.PreviewSetting.Value;
}
/// <inheritdoc />
public event PropertyChangedEventHandler? PropertyChanged;
#region Events
public event EventHandler DragStarted;
public event EventHandler DragEnded;
/// <summary>
/// Occurs when dragging the color picker has started
/// </summary>
public event EventHandler? DragStarted;
/// <summary>
/// Occurs when dragging the color picker has ended
/// </summary>
public event EventHandler? DragEnded;
/// <summary>
/// Invokes the <see cref="DragStarted" /> event
/// </summary>
protected virtual void OnDragStarted()
{
DragStarted?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Invokes the <see cref="DragEnded" /> event
/// </summary>
protected virtual void OnDragEnded()
{
DragEnded?.Invoke(this, EventArgs.Empty);

View File

@ -30,9 +30,8 @@ namespace Artemis.UI.Shared
public ArtemisLed Led { get; }
public Rect LedRect { get; set; }
public BitmapImage LedImage { get; set; }
public Geometry DisplayGeometry { get; private set; }
public BitmapImage? LedImage { get; set; }
public Geometry? DisplayGeometry { get; private set; }
public void RenderColor(DrawingContext drawingContext, bool isDimmed)
{

View File

@ -4,7 +4,6 @@ using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Artemis.UI.Shared
@ -12,15 +11,32 @@ namespace Artemis.UI.Shared
/// <summary>
/// Interaction logic for DraggableFloat.xaml
/// </summary>
public partial class DraggableFloat : UserControl, INotifyPropertyChanged
public partial class DraggableFloat : INotifyPropertyChanged
{
/// <summary>
/// Gets or sets the current value
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(float), typeof(DraggableFloat),
new FrameworkPropertyMetadata(default(float), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, FloatPropertyChangedCallback));
/// <summary>
/// Gets or sets the step size when dragging
/// </summary>
public static readonly DependencyProperty StepSizeProperty = DependencyProperty.Register(nameof(StepSize), typeof(float), typeof(DraggableFloat));
/// <summary>
/// Gets or sets the minimum value
/// </summary>
public static readonly DependencyProperty MinProperty = DependencyProperty.Register(nameof(Min), typeof(float?), typeof(DraggableFloat));
/// <summary>
/// Gets or sets the maximum value
/// </summary>
public static readonly DependencyProperty MaxProperty = DependencyProperty.Register(nameof(Max), typeof(float?), typeof(DraggableFloat));
/// <summary>
/// Occurs when the value has changed
/// </summary>
public static readonly RoutedEvent ValueChangedEvent =
EventManager.RegisterRoutedEvent(
nameof(Value),
@ -28,42 +44,61 @@ namespace Artemis.UI.Shared
typeof(RoutedPropertyChangedEventHandler<float>),
typeof(DraggableFloat));
private readonly Regex _inputRegex = new Regex("^[.][-|0-9]+$|^-?[0-9]*[.]{0,1}[0-9]*$");
private bool _calledDragStarted;
private bool _inCallback;
private readonly Regex _inputRegex = new Regex("^[.][-|0-9]+$|^-?[0-9]*[.]{0,1}[0-9]*$");
private Point _mouseDragStartPoint;
private float _startValue;
/// <summary>
/// Creates a new instance of the <see cref="DraggableFloat" /> class
/// </summary>
public DraggableFloat()
{
InitializeComponent();
}
/// <summary>
/// Gets or sets the current value
/// </summary>
public float Value
{
get => (float) GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
/// <summary>
/// Gets or sets the current value as a string
/// </summary>
public string InputValue
{
get => Value.ToString("N3", CultureInfo.InvariantCulture);
set => UpdateValue(value);
}
/// <summary>
/// Gets or sets the step size when dragging
/// </summary>
public float StepSize
{
get => (float) GetValue(StepSizeProperty);
set => SetValue(StepSizeProperty, value);
}
/// <summary>
/// Gets or sets the minimum value
/// </summary>
public float? Min
{
get => (float?) GetValue(MinProperty);
set => SetValue(MinProperty, value);
}
/// <summary>
/// Gets or sets the maximum value
/// </summary>
public float? Max
{
get => (float?) GetValue(MaxProperty);
@ -135,7 +170,9 @@ namespace Artemis.UI.Shared
Point position = e.GetPosition((IInputElement) sender);
if (position == _mouseDragStartPoint)
{
DisplayInput();
}
else
{
OnDragEnded();
@ -186,10 +223,12 @@ namespace Artemis.UI.Shared
private void InputKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
DisplayDragHandle();
}
else if (e.Key == Key.Escape)
{
DraggableFloatInputTextBox.Text = _startValue.ToString();
DraggableFloatInputTextBox.Text = _startValue.ToString(CultureInfo.InvariantCulture);
DisplayDragHandle();
}
}
@ -208,32 +247,51 @@ namespace Artemis.UI.Shared
{
if (e.DataObject.GetDataPresent(typeof(string)))
{
string text = (string) e.DataObject.GetData(typeof(string));
if (!_inputRegex.IsMatch(text))
if (e.DataObject.GetData(typeof(string)) is string text && !_inputRegex.IsMatch(text))
e.CancelCommand();
}
else
{
e.CancelCommand();
}
}
#endregion
#region Events
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler DragStarted;
public event EventHandler DragEnded;
/// <inheritdoc />
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
/// <summary>
/// Occurs when dragging has started
/// </summary>
public event EventHandler? DragStarted;
/// <summary>
/// Occurs when dragging has ended
/// </summary>
public event EventHandler? DragEnded;
/// <summary>
/// Invokes the <see cref="PropertyChanged" /> event
/// </summary>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Invokes the <see cref="DragStarted" /> event
/// </summary>
protected virtual void OnDragStarted()
{
DragStarted?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Invokes the <see cref="DragEnded" /> event
/// </summary>
protected virtual void OnDragEnded()
{
DragEnded?.Invoke(this, EventArgs.Empty);

View File

@ -2,7 +2,6 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Artemis.Core;
using Artemis.UI.Shared.Properties;
@ -14,16 +13,37 @@ namespace Artemis.UI.Shared
/// <summary>
/// Interaction logic for GradientPicker.xaml
/// </summary>
public partial class GradientPicker : UserControl, INotifyPropertyChanged
public partial class GradientPicker : INotifyPropertyChanged
{
private static IColorPickerService _colorPickerService;
private static IColorPickerService? _colorPickerService;
private bool _inCallback;
/// <summary>
/// Creates a new instance of the <see cref="GradientPicker" /> class
/// </summary>
public GradientPicker()
{
InitializeComponent();
}
/// <summary>
/// Gets or sets the color gradient
/// </summary>
public ColorGradient ColorGradient
{
get => (ColorGradient) GetValue(ColorGradientProperty);
set => SetValue(ColorGradientProperty, value);
}
/// <summary>
/// Gets or sets the dialog host in which to show the gradient dialog
/// </summary>
public string DialogHost
{
get => (string) GetValue(DialogHostProperty);
set => SetValue(DialogHostProperty, value);
}
/// <summary>
/// Used by the gradient picker to load saved gradients, do not touch or it'll just throw an exception
/// </summary>
@ -38,39 +58,35 @@ namespace Artemis.UI.Shared
}
/// <summary>
/// Gets or sets the currently selected color gradient
/// Occurs when the dialog has opened
/// </summary>
public ColorGradient ColorGradient
{
get => (ColorGradient) GetValue(ColorGradientProperty);
set => SetValue(ColorGradientProperty, value);
}
public event EventHandler? DialogOpened;
/// <summary>
/// Gets or sets the currently selected color gradient
/// Occurs when the dialog has closed
/// </summary>
public string DialogHost
{
get => (string) GetValue(DialogHostProperty);
set => SetValue(DialogHostProperty, value);
}
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler DialogOpened;
public event EventHandler DialogClosed;
public event EventHandler? DialogClosed;
/// <summary>
/// Invokes the <see cref="PropertyChanged" /> event
/// </summary>
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Invokes the <see cref="DialogOpened" /> event
/// </summary>
protected virtual void OnDialogOpened()
{
DialogOpened?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Invokes the <see cref="DialogClosed" /> event
/// </summary>
protected virtual void OnDialogClosed()
{
DialogClosed?.Invoke(this, EventArgs.Empty);
@ -89,6 +105,9 @@ namespace Artemis.UI.Shared
private void UIElement_OnMouseUp(object sender, MouseButtonEventArgs e)
{
if (_colorPickerService == null)
return;
Execute.OnUIThread(async () =>
{
OnDialogOpened();
@ -97,14 +116,26 @@ namespace Artemis.UI.Shared
});
}
/// <inheritdoc />
public event PropertyChangedEventHandler? PropertyChanged;
#region Static WPF fields
/// <summary>
/// Gets or sets the color gradient
/// </summary>
public static readonly DependencyProperty ColorGradientProperty = DependencyProperty.Register(nameof(ColorGradient), typeof(ColorGradient), typeof(GradientPicker),
new FrameworkPropertyMetadata(default(ColorGradient), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ColorGradientPropertyChangedCallback));
/// <summary>
/// Gets or sets the dialog host in which to show the gradient dialog
/// </summary>
public static readonly DependencyProperty DialogHostProperty = DependencyProperty.Register(nameof(DialogHost), typeof(string), typeof(GradientPicker),
new FrameworkPropertyMetadata(default(string)));
/// <summary>
/// Occurs when the color gradient has changed
/// </summary>
public static readonly RoutedEvent ColorGradientChangedEvent =
EventManager.RegisterRoutedEvent(nameof(ColorGradient), RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<ColorGradient>), typeof(GradientPicker));

View File

@ -3,24 +3,30 @@ using System.Windows.Controls.Primitives;
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents a toggle button that can be locked using a property
/// </summary>
public class LockableToggleButton : ToggleButton
{
protected override void OnToggle()
{
if (!IsLocked)
{
base.OnToggle();
}
}
public bool IsLocked
{
get { return (bool)GetValue(IsLockedProperty); }
set { SetValue(IsLockedProperty, value); }
}
// Using a DependencyProperty as the backing store for LockToggle. This enables animation, styling, binding, etc...
/// <summary>
/// Gets or sets a boolean indicating whether the toggle button is locked
/// </summary>
public static readonly DependencyProperty IsLockedProperty =
DependencyProperty.Register("IsLocked", typeof(bool), typeof(LockableToggleButton), new UIPropertyMetadata(false));
/// <summary>
/// Gets or sets a boolean indicating whether the toggle button is locked
/// </summary>
public bool IsLocked
{
get => (bool) GetValue(IsLockedProperty);
set => SetValue(IsLockedProperty, value);
}
/// <inheritdoc />
protected override void OnToggle()
{
if (!IsLocked) base.OnToggle();
}
}
}
}

View File

@ -2,6 +2,9 @@
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents errors that occur within the Artemis Shared UI library
/// </summary>
public class ArtemisSharedUIException : Exception
{
internal ArtemisSharedUIException()

View File

@ -4,8 +4,18 @@ using Artemis.UI.Shared.Services;
namespace Artemis.UI.Shared
{
/// <summary>
/// Provides extensions for special data model wrappers used by events and list conditions
/// </summary>
public static class DataModelWrapperExtensions
{
/// <summary>
/// Creates a view model for a <see cref="EventPredicateWrapperDataModel" />
/// </summary>
/// <param name="wrapper">The wrapper to create the view model for</param>
/// <param name="dataModelUIService">The data model UI service to be used by the view model</param>
/// <param name="configuration">The update configuration to be used by the view model</param>
/// <returns>The created view model</returns>
public static DataModelPropertiesViewModel CreateViewModel(this EventPredicateWrapperDataModel wrapper, IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration)
{
DataModelPropertiesViewModel viewModel = new DataModelPropertiesViewModel(wrapper, null, new DataModelPath(wrapper));
@ -16,6 +26,13 @@ namespace Artemis.UI.Shared
return viewModel;
}
/// <summary>
/// Creates a view model for a <see cref="ListPredicateWrapperDataModel" />
/// </summary>
/// <param name="wrapper">The wrapper to create the view model for</param>
/// <param name="dataModelUIService">The data model UI service to be used by the view model</param>
/// <param name="configuration">The update configuration to be used by the view model</param>
/// <returns>The created view model</returns>
public static DataModelPropertiesViewModel CreateViewModel(this ListPredicateWrapperDataModel wrapper, IDataModelUIService dataModelUIService, DataModelUpdateConfiguration configuration)
{
DataModelPropertiesViewModel viewModel = new DataModelPropertiesViewModel(wrapper, null, new DataModelPath(wrapper));

View File

@ -3,15 +3,33 @@ using Artemis.Core.DataModelExpansions;
using Artemis.Core.Modules;
using Artemis.UI.Shared.Input;
namespace Artemis.UI.Shared.Ninject.Factories
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents a factory for view models provided by the Artemis Shared UI library
/// </summary>
public interface ISharedVmFactory
{
}
/// <summary>
/// A factory that allows the creation of data model view models
/// </summary>
public interface IDataModelVmFactory : ISharedVmFactory
{
/// <summary>
/// Creates a new instance of the <see cref="DataModelDynamicViewModel" /> class
/// </summary>
/// <param name="module">The module to associate the dynamic view model with</param>
/// <returns>A new instance of the <see cref="DataModelDynamicViewModel" /> class</returns>
DataModelDynamicViewModel DataModelDynamicViewModel(Module module);
/// <summary>
/// Creates a new instance of the <see cref="DataModelStaticViewModel" /> class
/// </summary>
/// <param name="targetType">The type of property that is expected in this input</param>
/// <param name="targetDescription">The description of the property that this input is for</param>
/// <returns>A new instance of the <see cref="DataModelStaticViewModel" /> class</returns>
DataModelStaticViewModel DataModelStaticViewModel(Type targetType, DataModelPropertyAttribute targetDescription);
}
}

View File

@ -1,11 +1,10 @@
using System;
using Artemis.UI.Shared.Ninject.Factories;
using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using Ninject.Extensions.Conventions;
using Ninject.Modules;
namespace Artemis.UI.Shared.Ninject
namespace Artemis.UI.Shared
{
/// <summary>
/// The main <see cref="NinjectModule" /> of the Artemis Shared UI toolkit that binds all services

View File

@ -4,6 +4,9 @@ using Artemis.UI.Shared.Services;
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents a property input registration, registered through <see cref="IProfileEditorService.RegisterPropertyInput"/>
/// </summary>
public class PropertyInputRegistration
{
private readonly IProfileEditorService _profileEditorService;
@ -19,8 +22,19 @@ namespace Artemis.UI.Shared
Plugin.Disabled += InstanceOnDisabled;
}
/// <summary>
/// Gets the plugin that registered the property input
/// </summary>
public Plugin Plugin { get; }
/// <summary>
/// Gets the type supported by the property input
/// </summary>
public Type SupportedType { get; }
/// <summary>
/// Gets the view model type of the property input
/// </summary>
public Type ViewModelType { get; }
internal void Unsubscribe()
@ -29,7 +43,7 @@ namespace Artemis.UI.Shared
Plugin.Disabled -= InstanceOnDisabled;
}
private void InstanceOnDisabled(object sender, EventArgs e)
private void InstanceOnDisabled(object? sender, EventArgs e)
{
// Profile editor service will call Unsubscribe
_profileEditorService.RemovePropertyInput(this);

View File

@ -5,11 +5,20 @@ using Stylet;
namespace Artemis.UI.Shared
{
/// <summary>
/// Represents the base class for a property input view model that is used to edit layer properties
/// </summary>
/// <typeparam name="T">The type of property this input view model supports</typeparam>
public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
{
private bool _inputDragging;
private T _inputValue;
/// <summary>
/// Creates a new instance of the <see cref="PropertyInputViewModel" /> class
/// </summary>
/// <param name="layerProperty">The layer property this view model will edit</param>
/// <param name="profileEditorService">The profile editor service</param>
protected PropertyInputViewModel(LayerProperty<T> layerProperty, IProfileEditorService profileEditorService)
{
LayerProperty = layerProperty;
@ -21,6 +30,12 @@ namespace Artemis.UI.Shared
UpdateInputValue();
}
/// <summary>
/// Creates a new instance of the <see cref="PropertyInputViewModel" /> class
/// </summary>
/// <param name="layerProperty">The layer property this view model will edit</param>
/// <param name="profileEditorService">The profile editor service</param>
/// <param name="validator">The validator used to validate the input</param>
protected PropertyInputViewModel(LayerProperty<T> layerProperty, IProfileEditorService profileEditorService, IModelValidator validator) : base(validator)
{
LayerProperty = layerProperty;
@ -32,17 +47,32 @@ namespace Artemis.UI.Shared
UpdateInputValue();
}
/// <summary>
/// Gets the layer property this view model is editing
/// </summary>
public LayerProperty<T> LayerProperty { get; }
/// <summary>
/// Gets the profile editor service
/// </summary>
public IProfileEditorService ProfileEditorService { get; }
internal override object InternalGuard { get; } = null;
/// <summary>
/// Gets or sets a boolean indicating whether the input is currently being dragged
/// <para>
/// Only applicable when using something like a <see cref="DraggableFloat" />, see
/// <see cref="InputDragStarted" /> and <see cref="InputDragEnded" />
/// </para>
/// </summary>
public bool InputDragging
{
get => _inputDragging;
private set => SetAndNotify(ref _inputDragging, value);
}
/// <summary>
/// Gets or sets the input value
/// </summary>
public T InputValue
{
get => _inputValue;
@ -53,29 +83,50 @@ namespace Artemis.UI.Shared
}
}
public override void Dispose()
internal override object InternalGuard { get; } = new object();
#region IDisposable
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
LayerProperty.Updated -= LayerPropertyOnUpdated;
LayerProperty.CurrentValueSet -= LayerPropertyOnUpdated;
LayerProperty.DataBindingEnabled -= LayerPropertyOnDataBindingChange;
LayerProperty.DataBindingDisabled -= LayerPropertyOnDataBindingChange;
Dispose(true);
GC.SuppressFinalize(this);
if (disposing)
{
LayerProperty.Updated -= LayerPropertyOnUpdated;
LayerProperty.CurrentValueSet -= LayerPropertyOnUpdated;
LayerProperty.DataBindingEnabled -= LayerPropertyOnDataBindingChange;
LayerProperty.DataBindingDisabled -= LayerPropertyOnDataBindingChange;
}
base.Dispose(disposing);
}
#endregion
/// <summary>
/// Called when the input value has been applied to the layer property
/// </summary>
protected virtual void OnInputValueApplied()
{
}
/// <summary>
/// Called when the input value has changed
/// </summary>
protected virtual void OnInputValueChanged()
{
}
/// <summary>
/// Called when data bindings have been enabled or disabled on the layer property
/// </summary>
protected virtual void OnDataBindingsChanged()
{
}
/// <summary>
/// Applies the input value to the layer property
/// </summary>
protected void ApplyInputValue()
{
// Force the validator to run
@ -89,9 +140,13 @@ namespace Artemis.UI.Shared
OnInputValueApplied();
}
private void LayerPropertyOnUpdated(object sender, EventArgs e)
private void SetCurrentValue(T value, bool saveChanges)
{
UpdateInputValue();
LayerProperty.SetCurrentValue(value, ProfileEditorService.CurrentTime);
if (saveChanges)
ProfileEditorService.UpdateSelectedProfileElement();
else
ProfileEditorService.UpdateProfilePreview();
}
private void UpdateInputValue()
@ -114,27 +169,35 @@ namespace Artemis.UI.Shared
#region Event handlers
/// <summary>
/// Called by the view input drag has started
/// <para>
/// To use, add the following to DraggableFloat in your xaml: <c>DragStarted="{s:Action InputDragStarted}"</c>
/// </para>
/// </summary>
public void InputDragStarted(object sender, EventArgs e)
{
InputDragging = true;
}
/// <summary>
/// Called by the view when input drag has ended
/// <para>
/// To use, add the following to DraggableFloat in your xaml: <c>DragEnded="{s:Action InputDragEnded}"</c>
/// </para>
/// </summary>
public void InputDragEnded(object sender, EventArgs e)
{
InputDragging = false;
ProfileEditorService.UpdateSelectedProfileElement();
}
private void SetCurrentValue(T value, bool saveChanges)
private void LayerPropertyOnUpdated(object? sender, EventArgs e)
{
LayerProperty.SetCurrentValue(value, ProfileEditorService.CurrentTime);
if (saveChanges)
ProfileEditorService.UpdateSelectedProfileElement();
else
ProfileEditorService.UpdateProfilePreview();
UpdateInputValue();
}
private void LayerPropertyOnDataBindingChange(object sender, LayerPropertyEventArgs<T> e)
private void LayerPropertyOnDataBindingChange(object? sender, LayerPropertyEventArgs<T> e)
{
OnDataBindingsChanged();
}
@ -147,10 +210,16 @@ namespace Artemis.UI.Shared
/// </summary>
public abstract class PropertyInputViewModel : ValidatingModelBase, IDisposable
{
/// <summary>
/// For internal use only, implement <see cref="PropertyInputViewModel{T}" /> instead.
/// </summary>
protected PropertyInputViewModel()
{
}
/// <summary>
/// For internal use only, implement <see cref="PropertyInputViewModel{T}" /> instead.
/// </summary>
protected PropertyInputViewModel(IModelValidator validator) : base(validator)
{
}
@ -161,13 +230,29 @@ namespace Artemis.UI.Shared
// ReSharper disable once UnusedMember.Global
internal abstract object InternalGuard { get; }
public abstract void Dispose();
#region IDisposable
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View File

@ -1,12 +0,0 @@
using System;
namespace Artemis.UI.Shared.Services
{
internal class DataBindingUIService : IDataBindingUIService
{
public object GetDataBindingViewModel(Type propertyType)
{
return null;
}
}
}

View File

@ -7,7 +7,6 @@ using Artemis.Core.Modules;
using Artemis.Core.Services;
using Artemis.UI.Shared.DefaultTypes.DataModel.Display;
using Artemis.UI.Shared.Input;
using Artemis.UI.Shared.Ninject.Factories;
using Ninject;
using Ninject.Parameters;

View File

@ -3,38 +3,55 @@ using Stylet;
namespace Artemis.UI.Shared.Services
{
/// <summary>
/// Represents the base class for a dialog view model
/// </summary>
public abstract class DialogViewModelBase : ValidatingModelBase
{
private DialogViewModelHost _dialogViewModelHost;
private DialogSession _session;
private DialogViewModelHost? _dialogViewModelHost;
private DialogSession? _session;
/// <summary>
/// Creates a new instance of the <see cref="DialogViewModelBase" /> class with a model validator
/// </summary>
/// <param name="validator">A validator to apply to the model</param>
protected DialogViewModelBase(IModelValidator validator) : base(validator)
{
}
/// <summary>
/// Creates a new instance of the <see cref="DialogViewModelBase" />
/// </summary>
protected DialogViewModelBase()
{
}
public DialogViewModelHost DialogViewModelHost
{
get => _dialogViewModelHost;
set => SetAndNotify(ref _dialogViewModelHost, value);
}
public DialogSession Session
/// <summary>
/// Gets the dialog session that created this dialog
/// <para>Not available until after the dialog has been opened</para>
/// </summary>
public DialogSession? Session
{
get => _session;
private set => SetAndNotify(ref _session, value);
}
public void OnDialogOpened(object sender, DialogOpenedEventArgs e)
internal DialogViewModelHost? DialogViewModelHost
{
Session = e.Session;
get => _dialogViewModelHost;
set => SetAndNotify(ref _dialogViewModelHost, value);
}
/// <summary>
/// Called when the dialog has closed
/// </summary>
public virtual void OnDialogClosed(object sender, DialogClosingEventArgs e)
{
}
internal void OnDialogOpened(object sender, DialogOpenedEventArgs e)
{
Session = e.Session;
}
}
}

View File

@ -4,7 +4,7 @@ using Stylet;
namespace Artemis.UI.Shared.Services
{
public class DialogViewModelHost : PropertyChangedBase
internal class DialogViewModelHost : PropertyChangedBase
{
private readonly IViewManager _viewManager;

View File

@ -1,6 +1,8 @@
namespace Artemis.UI.Shared.Services
{
// ReSharper disable once InconsistentNaming
/// <summary>
/// Represents a service provided by the Artemis Shared UI library
/// </summary>
public interface IArtemisSharedUIService
{
}

View File

@ -1,9 +0,0 @@
using System;
namespace Artemis.UI.Shared.Services
{
public interface IDataBindingUIService : IArtemisSharedUIService
{
object GetDataBindingViewModel(Type propertyType);
}
}

View File

@ -79,6 +79,7 @@ namespace Artemis.UI.Shared.Services
/// Shows a dialog displaying the provided message and exception. Does not handle, log or throw the exception.
/// </summary>
/// <param name="message">The message to display in the dialog title</param>
/// <param name="exception">The exception to display</param>
/// <returns>A task resolving when the dialog is closed</returns>
void ShowExceptionDialog(string message, Exception exception);
}

View File

@ -3,27 +3,93 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using Artemis.Core;
using Artemis.Core.Modules;
using Ninject;
namespace Artemis.UI.Shared.Services
{
/// <summary>
/// Provides access the the profile editor back-end logic
/// </summary>
public interface IProfileEditorService : IArtemisSharedUIService
{
/// <summary>
/// Gets the currently selected profile
/// </summary>
Profile SelectedProfile { get; }
/// <summary>
/// Gets the currently selected profile element
/// </summary>
RenderProfileElement SelectedProfileElement { get; }
/// <summary>
/// Gets the currently selected data binding property
/// </summary>
ILayerProperty SelectedDataBinding { get; }
/// <summary>
/// Gets or sets the current time
/// </summary>
TimeSpan CurrentTime { get; set; }
/// <summary>
/// Gets or sets the pixels per second (zoom level)
/// </summary>
int PixelsPerSecond { get; set; }
/// <summary>
/// Gets a read-only collection of all registered property editors
/// </summary>
ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors { get; }
IKernel Kernel { get; }
/// <summary>
/// Changes the selected profile
/// </summary>
/// <param name="profile">The profile to select</param>
void ChangeSelectedProfile(Profile profile);
/// <summary>
/// Updates the selected profile and saves it to persistent storage
/// </summary>
void UpdateSelectedProfile();
/// <summary>
/// Changes the selected profile element
/// </summary>
/// <param name="profileElement">The profile element to select</param>
void ChangeSelectedProfileElement(RenderProfileElement profileElement);
/// <summary>
/// Updates the selected profile element and saves the profile it is contained in to persistent storage
/// </summary>
void UpdateSelectedProfileElement();
/// <summary>
/// Changes the selected data binding property
/// </summary>
/// <param name="layerProperty">The data binding property to select</param>
void ChangeSelectedDataBinding(ILayerProperty layerProperty);
/// <summary>
/// Updates the profile preview, forcing UI-elements to re-render
/// </summary>
void UpdateProfilePreview();
/// <summary>
/// Restores the profile to the last <see cref="UpdateSelectedProfile" /> call
/// </summary>
/// <returns><see langword="true" /> if undo was successful, otherwise <see langword="false" /></returns>
bool UndoUpdateProfile();
/// <summary>
/// Restores the profile to the last <see cref="UndoUpdateProfile" /> call
/// </summary>
/// <returns><see langword="true" /> if redo was successful, otherwise <see langword="false" /></returns>
bool RedoUpdateProfile();
/// <summary>
/// Gets the current module the profile editor is initialized for
/// </summary>
/// <returns>The current module the profile editor is initialized for</returns>
ProfileModule GetCurrentModule();
/// <summary>
@ -85,6 +151,10 @@ namespace Artemis.UI.Shared.Services
/// <returns></returns>
PropertyInputRegistration RegisterPropertyInput(Type viewModelType, Plugin plugin);
/// <summary>
/// Removes the property input view model
/// </summary>
/// <param name="registration"></param>
void RemovePropertyInput(PropertyInputRegistration registration);
/// <summary>
@ -97,10 +167,11 @@ namespace Artemis.UI.Shared.Services
/// <param name="snapToCurrentTime">Enable snapping to the current time of the editor</param>
/// <param name="snapTimes">An optional extra list of times to snap to</param>
/// <returns></returns>
TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List<TimeSpan> snapTimes = null);
TimeSpan SnapToTimeline(TimeSpan time, TimeSpan tolerance, bool snapToSegments, bool snapToCurrentTime, List<TimeSpan>? snapTimes = null);
/// <summary>
/// If a matching registration is found, creates a new <see cref="PropertyInputViewModel{T}"/> supporting <typeparamref name="T"/>
/// If a matching registration is found, creates a new <see cref="PropertyInputViewModel{T}" /> supporting
/// <typeparamref name="T" />
/// </summary>
PropertyInputViewModel<T> CreatePropertyInputViewModel<T>(LayerProperty<T> layerProperty);
}

View File

@ -22,6 +22,7 @@ namespace Artemis.UI.Shared.Services
private readonly object _selectedProfileLock = new object();
private TimeSpan _currentTime;
private int _pixelsPerSecond;
private IKernel _kernel;
public ProfileEditorService(IProfileService profileService, IKernel kernel, ILogger logger, ICoreService coreService)
{
@ -29,8 +30,8 @@ namespace Artemis.UI.Shared.Services
_logger = logger;
_coreService = coreService;
_registeredPropertyEditors = new List<PropertyInputRegistration>();
_kernel = kernel;
Kernel = kernel;
PixelsPerSecond = 100;
}
@ -40,7 +41,6 @@ namespace Artemis.UI.Shared.Services
Execute.PostToUIThread(OnProfilePreviewUpdated);
}
public IKernel Kernel { get; }
public ReadOnlyCollection<PropertyInputRegistration> RegisteredPropertyEditors => _registeredPropertyEditors.AsReadOnly();
public Profile SelectedProfile { get; private set; }
public RenderProfileElement SelectedProfileElement { get; private set; }
@ -207,7 +207,7 @@ namespace Artemis.UI.Shared.Services
return existing;
}
Kernel.Bind(viewModelType).ToSelf();
_kernel.Bind(viewModelType).ToSelf();
PropertyInputRegistration registration = new PropertyInputRegistration(this, plugin, supportedType, viewModelType);
_registeredPropertyEditors.Add(registration);
return registration;
@ -223,7 +223,7 @@ namespace Artemis.UI.Shared.Services
registration.Unsubscribe();
_registeredPropertyEditors.Remove(registration);
Kernel.Unbind(registration.ViewModelType);
_kernel.Unbind(registration.ViewModelType);
}
}
}
@ -282,7 +282,7 @@ namespace Artemis.UI.Shared.Services
return null;
ConstructorArgument parameter = new ConstructorArgument("layerProperty", layerProperty);
IKernel kernel = registration != null ? registration.Plugin.Kernel : Kernel;
IKernel kernel = registration != null ? registration.Plugin.Kernel : _kernel;
return (PropertyInputViewModel<T>) kernel.Get(viewModelType, parameter);
}

View File

@ -3,24 +3,47 @@ using System.Windows.Input;
namespace Artemis.UI.Shared
{
/// <summary>
/// Provides a command that simply calls a delegate when invoked
/// </summary>
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
private readonly Predicate<object?>? _canExecute;
private readonly Action<object?> _execute;
public DelegateCommand(Action<object> execute) : this(execute, null)
/// <summary>
/// Creates a new instance of the <see cref="DelegateCommand" /> class
/// </summary>
/// <param name="execute">The delegate to execute</param>
public DelegateCommand(Action<object?> execute) : this(execute, null)
{
}
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
/// <summary>
/// Creates a new instance of the <see cref="DelegateCommand" /> class with a predicate indicating whether the command
/// can be executed
/// </summary>
/// <param name="execute">The delegate to execute</param>
/// <param name="canExecute">The predicate that determines whether the command can execute</param>
public DelegateCommand(Action<object?> execute, Predicate<object?>? canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
/// <summary>
/// Invokes the <see cref="CanExecuteChanged" /> event
/// </summary>
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public bool CanExecute(object parameter)
/// <inheritdoc />
public event EventHandler? CanExecuteChanged;
/// <inheritdoc />
public bool CanExecute(object? parameter)
{
if (_canExecute == null)
return true;
@ -28,14 +51,10 @@ namespace Artemis.UI.Shared
return _canExecute(parameter);
}
public void Execute(object parameter)
/// <inheritdoc />
public void Execute(object? parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}

View File

@ -20,7 +20,7 @@ namespace Artemis.UI.Shared
if (!t.IsEnum)
throw new ArgumentException($"{nameof(t)} must be an enum type");
return Enum.GetValues(t).Cast<Enum>().Select(e => new ValueDescription {Value = e, Description = e.Humanize()}).ToList();
return Enum.GetValues(t).Cast<Enum>().Select(e => new ValueDescription(e, e.Humanize())).ToList();
}
/// <summary>
@ -34,9 +34,30 @@ namespace Artemis.UI.Shared
}
}
/// <summary>
/// Represents a value and a description for an enum value
/// </summary>
public class ValueDescription
{
public object Value { get; set; }
public string Description { get; set; }
/// <summary>
/// Creates a new instance of the <see cref="ValueDescription"/> class
/// </summary>
/// <param name="value">The enum value</param>
/// <param name="description">The description of the value</param>
public ValueDescription(object value, string description)
{
Value = value ?? throw new ArgumentNullException(nameof(value));
Description = description ?? throw new ArgumentNullException(nameof(description));
}
/// <summary>
/// The enum value
/// </summary>
public object Value { get; }
/// <summary>
/// The description of the value
/// </summary>
public string Description { get; }
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.IO;
using Artemis.Core;
using Artemis.UI.Shared.Controls;
using MaterialDesignThemes.Wpf;
namespace Artemis.UI.Shared

View File

@ -1,6 +1,5 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media.Animation;
// Code from http://www.wpfmentor.com/2009/01/how-to-debug-triggers-using-trigger.html
@ -22,7 +21,7 @@ using System.Windows.Media.Animation;
namespace Artemis.UI.Shared
{
#if DEBUG
#if DEBUG
/// <summary>
/// Contains attached properties to activate Trigger Tracing on the specified Triggers.
@ -65,37 +64,31 @@ namespace Artemis.UI.Shared
/// </summary>
private class TriggerTraceListener : TraceListener
{
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
public override void TraceEvent(TraceEventCache? eventCache, string source, TraceEventType eventType, int id, string format, params object?[]? args)
{
base.TraceEvent(eventCache, source, eventType, id, format, args);
if (format.StartsWith("Storyboard has begun;"))
{
TriggerTraceStoryboard storyboard = args[1] as TriggerTraceStoryboard;
if (storyboard != null)
if (format.StartsWith("Storyboard has begun;") && args != null)
if (args[1] is TriggerTraceStoryboard storyboard)
{
// add a breakpoint here to see when your trigger has been
// entered or exited
// the element being acted upon
object targetElement = args[5];
// the namescope of the element being acted upon
INameScope namescope = (INameScope) args[7];
object? targetElement = args[5];
TriggerBase triggerBase = storyboard.TriggerBase;
string triggerName = GetTriggerName(storyboard.TriggerBase);
Debug.WriteLine("Element: {0}, {1}: {2}: {3}", targetElement, triggerBase.GetType().Name, triggerName, storyboard.StoryboardType);
}
}
}
public override void Write(string message)
public override void Write(string? message)
{
}
public override void WriteLine(string message)
public override void WriteLine(string? message)
{
}
}
@ -118,12 +111,16 @@ namespace Artemis.UI.Shared
/// to identify the trigger in the debug output.
/// </summary>
/// <param name="trigger">The trigger.</param>
/// <param name="value">The value.</param>
/// <returns></returns>
public static void SetTriggerName(TriggerBase trigger, string value)
{
trigger.SetValue(TriggerNameProperty, value);
}
/// <summary>
/// Gets or sets the trigger name property
/// </summary>
public static readonly DependencyProperty TriggerNameProperty =
DependencyProperty.RegisterAttached(
"TriggerName",
@ -155,6 +152,9 @@ namespace Artemis.UI.Shared
trigger.SetValue(TraceEnabledProperty, value);
}
/// <summary>
/// Gets or sets whether the trace is enabled
/// </summary>
public static readonly DependencyProperty TraceEnabledProperty =
DependencyProperty.RegisterAttached(
"TraceEnabled",
@ -164,9 +164,7 @@ namespace Artemis.UI.Shared
private static void OnTraceEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TriggerBase triggerBase = d as TriggerBase;
if (triggerBase == null)
if (!(d is TriggerBase triggerBase))
return;
if (!(e.NewValue is bool))
@ -187,22 +185,16 @@ namespace Artemis.UI.Shared
// remove the dummy storyboards
foreach (TriggerActionCollection actionCollection in new[] {triggerBase.EnterActions, triggerBase.ExitActions})
{
foreach (TriggerAction triggerAction in actionCollection)
foreach (TriggerAction triggerAction in actionCollection)
if (triggerAction is BeginStoryboard bsb && bsb.Storyboard is TriggerTraceStoryboard)
{
BeginStoryboard bsb = triggerAction as BeginStoryboard;
if (bsb != null && bsb.Storyboard != null && bsb.Storyboard is TriggerTraceStoryboard)
{
actionCollection.Remove(bsb);
break;
}
actionCollection.Remove(bsb);
break;
}
}
}
}
#endregion
}
#endif
#endif
}

View File

@ -15,7 +15,7 @@ using Artemis.Core.Services;
using Artemis.UI.Ninject;
using Artemis.UI.Screens;
using Artemis.UI.Screens.Splash;
using Artemis.UI.Shared.Ninject;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Stylet;
using Ninject;

View File

@ -1,8 +1,8 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:MaterialDesignExtensions.Model;assembly=MaterialDesignExtensions"
xmlns:shared="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions">
xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared">
<DataTemplate DataType="{x:Type model:FirstLevelNavigationItem}">
<Grid Height="48">

View File

@ -6,7 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:home="clr-namespace:Artemis.UI.Screens.Home"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
d:DesignHeight="574.026"
d:DesignWidth="1029.87"
@ -37,7 +37,7 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<controls:ArtemisIcon SvgSource="/Resources/Images/Logo/bow.svg" Width="100" Height="100"/>
<shared:ArtemisIcon SvgSource="/Resources/Images/Logo/bow.svg" Width="100" Height="100"/>
<StackPanel Grid.Column="1" Margin="24 0 0 0" VerticalAlignment="Center">
<TextBlock Style="{StaticResource MaterialDesignHeadline4TextBlock}" TextWrapping="Wrap">Welcome to Artemis, RGB on steroids.</TextBlock>
<Button Style="{StaticResource MaterialDesignFlatButton}"

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Windows.Media;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Extensions;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Windows.Media;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Extensions;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;

View File

@ -8,7 +8,6 @@
xmlns:wpf="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:modules="clr-namespace:Artemis.Core.Modules;assembly=Artemis.Core"
xmlns:dataModel="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}">
<UserControl.Resources>

View File

@ -6,7 +6,6 @@
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:devices="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
d:DataContext="{d:DesignInstance devices:PluginSettingsViewModel}"
@ -34,7 +33,7 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<controls:ArtemisIcon Icon="{Binding Icon}"
<shared:ArtemisIcon Icon="{Binding Icon}"
Width="48"
Height="48"
Margin="0 5 0 0"