diff --git a/Project-Aurora/Project-Aurora/App.xaml b/Project-Aurora/Project-Aurora/App.xaml
index ffe649af5..17822c10e 100644
--- a/Project-Aurora/Project-Aurora/App.xaml
+++ b/Project-Aurora/Project-Aurora/App.xaml
@@ -1,15 +1,24 @@
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:s="clr-namespace:System;assembly=mscorlib"
+ DispatcherUnhandledException="App_DispatcherUnhandledException">
+
-
+ -->
+
+
+
+
+
+
+
+
+
diff --git a/Project-Aurora/Project-Aurora/Controls/GameStateParameterPicker.xaml b/Project-Aurora/Project-Aurora/Controls/GameStateParameterPicker.xaml
new file mode 100644
index 000000000..7a260238d
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/Controls/GameStateParameterPicker.xaml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Project-Aurora/Project-Aurora/Controls/GameStateParameterPicker.xaml.cs b/Project-Aurora/Project-Aurora/Controls/GameStateParameterPicker.xaml.cs
new file mode 100644
index 000000000..ded1e4a62
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/Controls/GameStateParameterPicker.xaml.cs
@@ -0,0 +1,351 @@
+using Aurora.Profiles;
+using Aurora.Utils;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using Application = Aurora.Profiles.Application;
+
+namespace Aurora.Controls {
+
+ public partial class GameStateParameterPicker : UserControl, INotifyPropertyChanged {
+
+ public event EventHandler SelectedPathChanged;
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private List parameterList;
+
+ public GameStateParameterPicker() {
+ InitializeComponent();
+ }
+
+ #region UI Properties
+ ///
+ /// The current parts that make up the path. E.G. "LocalPCInfo/RAM" -> "LocalPCInfo", "RAM"
+ ///
+ private Stack WorkingPath { get; set; } = new Stack();
+
+ ///
+ /// Lazy-evaluated list of parameters for this application and property type.
+ ///
+ public List ParameterList => parameterList ?? (parameterList = Application?.ParameterLookup?.GetParameters(PropertyType).ToList());
+
+ ///
+ /// Gets a list of items that should be displayed in the parameter list (based on the current "parent" variable).
+ ///
+ public IEnumerable CurrentParameterListItems {
+ get {
+ // If the application or param lookup is null, we don't know the parameters so do nothing
+ if (Application?.ParameterLookup == null) return null;
+
+ // If the given working path is a path to a variable (which it shouldn't be), pop the last item (the variable name) from the path to give just the "directory"
+ if (Application.ParameterLookup.IsValidParameter(WorkingPathStr))
+ WorkingPath.Pop();
+
+ // Generate the string version of this working path (and cache it)
+ var _workingPath = WorkingPathStr;
+ if (_workingPath != "") _workingPath += "/"; // If not at the root directory, add / to the end of the test path. This means it doesn't get confused with things such as `CPU` and `CPUUsage`.
+ return from path in ParameterList // With all properties in the current param lookup that are of a valid type (e.g. numbers)
+ where path.StartsWith(_workingPath) // Pick only the ones that start with the same working path
+ let pathSplit = path.Substring(_workingPath.Length).Split('/') // Get a list of all remaining parts of the path (e.g. if this was A/B/C and current path was A, pathSplit would be 'B', 'C')
+ let isFolder = pathSplit.Length > 1 // If there is more than one part of the path remaining, this must be a directory
+ group isFolder by pathSplit[0] into g // Group by the path name so duplicates are removed
+ orderby !g.First(), g.Key // Order the remaining (distinct) items by folders first, then order by their name
+ select new PathOption(g.Key, g.First()); // Finally, put them in a POCO so we can bind the UI to these properties.
+ }
+ }
+
+ ///
+ /// Returns the string representation of the current working path.
+ ///
+ public string WorkingPathStr => string.Join("/", WorkingPath.Reverse());
+ #endregion
+
+ #region IsOpen Dependency Property
+ ///
+ /// Whether or not the dropdown for this picker is open.
+ ///
+ public bool IsOpen {
+ get => (bool)GetValue(IsOpenProperty);
+ set => SetValue(IsOpenProperty, value);
+ }
+
+ public static readonly DependencyProperty IsOpenProperty =
+ DependencyProperty.Register(nameof(IsOpen), typeof(bool), typeof(GameStateParameterPicker), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
+ #endregion
+
+ #region SelectedPath Dependency Property
+ ///
+ /// The path to a GameStateVariable the user has selected.
+ ///
+ public string SelectedPath {
+ get => (string)GetValue(SelectedPathProperty);
+ set => SetValue(SelectedPathProperty, value);
+ }
+
+ public static readonly DependencyProperty SelectedPathProperty =
+ DependencyProperty.Register(nameof(SelectedPath), typeof(string), typeof(GameStateParameterPicker), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedPathDPChanged));
+
+ private static void SelectedPathDPChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
+ // Do nothing if the value hasn't actually changed.
+ if (e.OldValue == e.NewValue || e.NewValue == null) return;
+
+ var picker = (GameStateParameterPicker)sender;
+
+ // If the path isn't valid, set it to "".
+ if (!picker.ValidatePath((string)e.NewValue)) {
+ picker.SelectedPath = "";
+ // We need to return out of this method as we don't want to change anything since NewValue is now no longer respective of the new new value.
+ return;
+ }
+
+ if (double.TryParse(e.NewValue.ToString(), out double val)) {
+ // If a raw number has been entered, fill in the numeric stepper
+ picker.numericEntry.Value = val;
+ } else {
+ // Else if an actual path has been given, split it up into it's ""directories""
+ // For the path to be valid (and to be passed as a param to this method) it will be a path to a variable, not a "directory". We use this assumption.
+ picker.WorkingPath = new Stack(e.NewValue.ToString().Split('/'));
+ picker.WorkingPath.Pop(); // Remove the last one, since the working path should not include the actual var name
+ picker.NotifyChanged(nameof(WorkingPath), nameof(WorkingPathStr), nameof(ParameterList), nameof(CurrentParameterListItems)); // All these things will be different now, so trigger an update of anything requiring them
+ picker.mainListBox.SelectedValue = e.NewValue.ToString().Split('/').Last(); // The selected item in the list will be the last part of the path
+ }
+
+ // Raise an event informing subscribers
+ picker.SelectedPathChanged?.Invoke(picker, new SelectedPathChangedEventArgs(e.OldValue.ToString(), e.NewValue.ToString()));
+ }
+ #endregion
+
+ #region Application Dependency Property
+ ///
+ /// The current application whose context this picker is for. Determines the available variables.
+ ///
+ public Application Application {
+ get => (Application)GetValue(ApplicationProperty);
+ set => SetValue(ApplicationProperty, value);
+ }
+
+ public static readonly DependencyProperty ApplicationProperty =
+ DependencyProperty.Register(nameof(Application), typeof(Application), typeof(GameStateParameterPicker), new PropertyMetadata(null, ApplicationOrPropertyTypeChange));
+ #endregion
+
+ #region PropertyType Dependency Property
+ ///
+ /// The types of properties that will be shown to the user.
+ ///
+ public PropertyType PropertyType {
+ get => (PropertyType)GetValue(PropertyTypeProperty);
+ set => SetValue(PropertyTypeProperty, value);
+ }
+
+ public static readonly DependencyProperty PropertyTypeProperty =
+ DependencyProperty.Register(nameof(PropertyType), typeof(PropertyType), typeof(GameStateParameterPicker), new PropertyMetadata(PropertyType.None, ApplicationOrPropertyTypeChange));
+
+ public static void ApplicationOrPropertyTypeChange(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
+ var picker = (GameStateParameterPicker)sender;
+ picker.parameterList = null;
+ picker.NotifyChanged(nameof(ParameterList), nameof(CurrentParameterListItems));
+
+ if (!picker.ValidatePath(picker.SelectedPath))
+ picker.SelectedPath = "";
+ }
+ #endregion
+
+ ///
+ /// Determines if the selected path is a valid one (i.e. it is a number or a valid variable in the current application context).
+ /// If the current application context is null (i.e. not yet loaded), the path is assumed to be valid.
+ ///
+ private bool ValidatePath(string path) =>
+ // If application parameter context doesn't exist or there is no set type, assume non loaded and allow the path
+ Application?.ParameterLookup == null || PropertyType == PropertyType.None
+ // An empty path is fine
+ || string.IsNullOrEmpty(path)
+ // If we're in number mode, allow the selected path to be a double
+ || (PropertyType == PropertyType.Number && double.TryParse(path, out var _))
+ // If not in number mode, must be a valid path and have the same type as the expected property type
+ || Application.ParameterLookup.IsValidParameter(path, PropertyType);
+
+ #region Animation
+ /// Animates the list boxes.
+ /// Direction of animation. -1 for previous, 1 for next.
+ private void Animate(int dx) {
+ var auxillaryScrollViewer = auxillaryListbox.FindChildOfType();
+ var mainScrollViewer = mainListBox.FindChildOfType();
+ auxillaryScrollViewer.ScrollToVerticalOffset(mainScrollViewer.VerticalOffset);
+
+ // Move the aux to the centre and move the main to the side of it
+ SetTransformRelativeOffset(mainListBox, dx);
+ SetTransformRelativeOffset(mainListBox, 0);
+
+ // Animate the aux moving away and the main moving in
+ CreateStoryboard(dx, 0, mainListBox).Begin();
+ CreateStoryboard(0, -dx, auxillaryListbox).Begin();
+ }
+
+ /// Creates a storyboard animation that changes the TransformRelativeOffsetProperty property from `fromX` to `toX` for the given target.
+ private Storyboard CreateStoryboard(int from, int to, UIElement target) {
+ var sb = new Storyboard {
+ Children = new TimelineCollection(new[] {
+ new DoubleAnimation(from, to, new Duration(new TimeSpan(0, 0, 0, 0, 300))) {
+ EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut }
+ }
+ })
+ };
+ Storyboard.SetTarget(sb, target);
+ Storyboard.SetTargetProperty(sb, new PropertyPath(TransformRelativeOffsetProperty));
+ return sb;
+ }
+
+ #region TransformRelativeOffset Attached Property
+ public static double GetTransformRelativeOffset(DependencyObject obj) => (double)obj.GetValue(TransformRelativeOffsetProperty);
+ public static void SetTransformRelativeOffset(DependencyObject obj, double value) => obj.SetValue(TransformRelativeOffsetProperty, value);
+
+ public static readonly DependencyProperty TransformRelativeOffsetProperty =
+ DependencyProperty.RegisterAttached("TransformRelativeOffset", typeof(double), typeof(GameStateParameterPicker), new PropertyMetadata(0d));
+ #endregion
+ #endregion
+
+ #region Event Handlers
+ private void BackBtn_Click(object sender, RoutedEventArgs e) {
+ if (WorkingPath.Count > 0) {
+ // Make the aux list box take on the same items as the current one so that when animated (since the aux is moved to the middle first) it looks natural
+ auxillaryListbox.ItemsSource = CurrentParameterListItems;
+
+ Animate(-1);
+ WorkingPath.Pop(); // Remove the last "directory" off the working path
+ NotifyChanged(nameof(CurrentParameterListItems), nameof(WorkingPathStr)); // These properties will have changed so any UI stuff that relies on it should update
+ }
+ }
+
+ private void MainListBox_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
+ /* The reason this is a PreviewMouseLeftButtonDown event rather than using a SelectionChanged event is because I was having issues where when the next items
+ * were loaded it was immediately selecting the first item and then selecting a path the user didn't click. There is no click event available for the ListBox
+ * so that was out of the question. Next I tried double click but it was counter-intuitive as items were being selected by not actually chosen, which could
+ * easily confuse users. It also felt unresponsive as it would be a few hundred milliseconds after the double click that it actually called this method.
+ * Finally preview left down was chosen as, after a bit of tweaking, it seemed to work properly - felt responsive and didn't repeatedly select new items. The
+ * only real downside of this is that the user can't use the up and down arrows to change the selected item but I think that's a small price to pay.
+ * Side note: THIS PICKER HAS TAKEN ME SO DAMN LONG TO MAKE. Probably longer than the actual GSI plugin system itself.... But hey, I'm proud of it. */
+
+ // Element selection code is adapted from http://kevin-berridge.blogspot.com/2008/06/wpf-listboxitem-double-click.html
+ var el = (UIElement)mainListBox.InputHitTest(e.GetPosition(mainListBox));
+ while (el != null && el != mainListBox) {
+ if (el is ListBoxItem item) {
+
+ // Since the user has picked an item on the list, we want to clear the numeric box so it is obvious to the user that the number is having no effect.
+ numericEntry.Value = null;
+
+ // Copy the current list items to the aux list box incase the list box is animated later. This must be done BEFORE the workingpath.push call.
+ auxillaryListbox.ItemsSource = CurrentParameterListItems;
+
+ // Add the clicked item to the working path (even if it is an end variable, not a "directory")
+ WorkingPath.Push(((PathOption)item.DataContext).Path);
+
+ var path = string.Join("/", WorkingPath.Reverse());
+ if (Application?.ParameterLookup?.IsValidParameter(path) ?? false) {
+ // If it turns out the user has selected an end variable, we want to update the DependencyObject for the selected path
+ SelectedPath = path;
+ NotifyChanged(nameof(SelectedPath));
+ } else {
+ // If the user has selected a directory instead (i.e. isn't not a valid parameter) then perform the animation since there will now be new properties to choose from
+ Animate(1);
+ }
+
+ // Regardless of whether it was a variable or a directory, the list and path will have changed
+ NotifyChanged(nameof(CurrentParameterListItems), nameof(WorkingPathStr));
+ }
+ el = (UIElement)VisualTreeHelper.GetParent(el);
+ }
+ }
+
+ private void NumericEntry_ValueChanged(object sender, RoutedPropertyChangedEventArgs
-
+
+
+
+
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_OverrideDynamicValue.xaml.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_OverrideDynamicValue.xaml.cs
index 67660e24e..725e36567 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_OverrideDynamicValue.xaml.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_OverrideDynamicValue.xaml.cs
@@ -14,15 +14,13 @@ namespace Aurora.Settings.Overrides.Logic {
public partial class Control_OverrideDynamicValue : UserControl {
public OverrideDynamicValue Context { get; }
- public Profiles.Application Application { get; }
- public List> Parameters { get; }
+ public List> Parameters { get; }
- public Control_OverrideDynamicValue(OverrideDynamicValue context, Profiles.Application application) {
+ public Control_OverrideDynamicValue(OverrideDynamicValue context) {
InitializeComponent();
Context = context;
- Application = application;
// Get a list that contains the label (string), the type of evaluatable (to restrict the list box), the evaluatable itself and the description.
- Parameters = context.ConstructorParameters.Select(kvp => new Tuple(
+ Parameters = context.ConstructorParameters.Select(kvp => new Tuple(
kvp.Key,
OverrideDynamicValue.typeDynamicDefMap[context.VarType].constructorParameters.First(p => p.name == kvp.Key).type,
kvp.Value,
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_OverrideLookupTable.xaml b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_OverrideLookupTable.xaml
index 61105655f..6a409fda0 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_OverrideLookupTable.xaml
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_OverrideLookupTable.xaml
@@ -3,6 +3,7 @@
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:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:Aurora.Settings.Overrides.Logic"
xmlns:controls="clr-namespace:Aurora.Controls"
mc:Ignorable="d"
@@ -12,6 +13,7 @@
+
@@ -59,7 +61,10 @@
-
+
+
+
+
+
+
+
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_OverrideLookupTable.xaml.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_OverrideLookupTable.xaml.cs
index 35b4d36a8..e6e0bf7a0 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_OverrideLookupTable.xaml.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_OverrideLookupTable.xaml.cs
@@ -20,12 +20,10 @@ namespace Aurora.Settings.Overrides.Logic {
public partial class Control_OverrideLookupTable : UserControl {
public OverrideLookupTable Table { get; }
- public Profiles.Application Application { get; }
- public Control_OverrideLookupTable(OverrideLookupTable context, Profiles.Application application) {
+ public Control_OverrideLookupTable(OverrideLookupTable context) {
InitializeComponent();
Table = context;
- Application = application;
DataContext = this;
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_SublogicHolder.xaml b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_SublogicHolder.xaml
index e123b66a8..8b6387323 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_SublogicHolder.xaml
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_SublogicHolder.xaml
@@ -2,39 +2,35 @@
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:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:Aurora.Settings.Overrides.Logic"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="200">
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_SublogicHolder.xaml.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_SublogicHolder.xaml.cs
index 3e47092db..4d1184663 100755
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_SublogicHolder.xaml.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Control_SublogicHolder.xaml.cs
@@ -4,26 +4,32 @@
using System.Windows.Controls;
namespace Aurora.Settings.Overrides.Logic {
+
///
/// Interaction logic for Control_SubconditionHolder.xaml
///
public partial class Control_SubconditionHolder : UserControl {
- public Control_SubconditionHolder(IHasSubConditons parent, Profiles.Application app, string description="") {
+
+ public Control_SubconditionHolder(IHasSubConditons parent, string description = "") {
InitializeComponent();
- ParentExpr = parent;
- Application = app;
+ DataContext = Context = parent;
Description = description;
- ((FrameworkElement)Content).DataContext = this;
}
+ /// The parent evaluatable of this control. Must be an evaluatable that has sub conditions.
+ public IHasSubConditons Context { get; }
+
+ /// The title/description text of this control.
+ public string Description { get; }
+
private void AddSubconditionButton_Click(object sender, RoutedEventArgs e) {
- ParentExpr.SubConditions.Add(new BooleanConstant());
+ Context.SubConditions.Add(new BooleanConstant());
}
// We cannot do a TwoWay binding on the items of an ObservableCollection if that item may be replaced (it would be fine if only the instance's
// values were being changed), so we have to capture change events and replace them in the list.
private void ConditionPresenter_ConditionChanged(object sender, ExpressionChangeEventArgs e) {
- ParentExpr.SubConditions[ParentExpr.SubConditions.IndexOf((IEvaluatable)e.OldExpression)] = (IEvaluatable)e.NewExpression;
+ Context.SubConditions[Context.SubConditions.IndexOf((IEvaluatable)e.OldExpression)] = (IEvaluatable)e.NewExpression;
}
private void DeleteButton_Click(object sender, RoutedEventArgs e) {
@@ -31,25 +37,7 @@ private void DeleteButton_Click(object sender, RoutedEventArgs e) {
// it is created for each item inside the ItemsControl items source.
// We can then simply call remove on the conditions list to remove it.
var cond = (IEvaluatable)((FrameworkElement)sender).DataContext;
- ParentExpr.SubConditions.Remove(cond);
- }
-
- #region Properties/Dependency Properties
- /// The parent evaluatable of this control. Must be an evaluatable that has sub conditions.
- public IHasSubConditons ParentExpr { get; }
-
- /// The title/description text of this control.
- public string Description { get; }
-
- /// The application context of this control. Is passed to the EvaluatablePresenter children.
- public Profiles.Application Application {
- get => (Profiles.Application)GetValue(ApplicationProperty);
- set => SetValue(ApplicationProperty, value);
+ Context.SubConditions.Remove(cond);
}
-
- /// The property used as a backing store for the application context.
- public static readonly DependencyProperty ApplicationProperty =
- DependencyProperty.Register("Application", typeof(Profiles.Application), typeof(Control_SubconditionHolder), new PropertyMetadata(null));
- #endregion
}
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/OverrideLogicAttribute.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/EvaluatableAttribute.cs
similarity index 78%
rename from Project-Aurora/Project-Aurora/Settings/Overrides/Logic/OverrideLogicAttribute.cs
rename to Project-Aurora/Project-Aurora/Settings/Overrides/Logic/EvaluatableAttribute.cs
index 29ee6d795..0b412f325 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/OverrideLogicAttribute.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/EvaluatableAttribute.cs
@@ -6,15 +6,16 @@
using System.Threading.Tasks;
namespace Aurora.Settings.Overrides.Logic {
+
///
/// Simple attribute that can be added to conditions to add metadata to them and register them as conditions.
/// Unregistered conditions will still work, but they will not be shown in the dropdown list when editing layer visibility conditions.
///
[AttributeUsage(AttributeTargets.Class)]
- public class OverrideLogicAttribute : Attribute {
+ public class EvaluatableAttribute : Attribute {
/// The name of the condition (will appear in the dropdown list).
- public OverrideLogicAttribute(string name, OverrideLogicCategory category = OverrideLogicCategory.Misc) {
+ public EvaluatableAttribute(string name, EvaluatableCategory category = EvaluatableCategory.Misc) {
Name = name;
Category = category;
}
@@ -23,18 +24,18 @@ public OverrideLogicAttribute(string name, OverrideLogicCategory category = Over
public string Name { get; }
/// The category this condition belongs to (items will be grouped by this in the dropdown list).
- public OverrideLogicCategory Category { get; }
+ public EvaluatableCategory Category { get; }
/// Gets the description of the category as a string.
public string CategoryStr => Utils.EnumUtils.GetDescription(Category);
}
- public enum OverrideLogicCategory {
+ public enum EvaluatableCategory {
[Description("Logic")] Logic,
[Description("State Variable")] State,
[Description("Input")] Input,
[Description("Misc.")] Misc,
- [Description("Maths (Advanced)")] Maths,
- [Description("String (Advanced)")] String
+ [Description("Maths")] Maths,
+ [Description("String")] String
}
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/EvaluatableRegistry.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/EvaluatableRegistry.cs
index 98d4caa94..68313e00b 100755
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/EvaluatableRegistry.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/EvaluatableRegistry.cs
@@ -1,3 +1,4 @@
+using Aurora.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -10,31 +11,45 @@ namespace Aurora.Settings.Overrides.Logic {
public static class EvaluatableRegistry {
/// Cached list of all classes that have the OverrideLogic attribute applied to them.
- private static readonly Dictionary allOverrideLogics = Utils.TypeUtils
- .GetTypesWithCustomAttribute()
+ private static readonly IEnumerable allOverrideLogics = Utils.TypeUtils
+ .GetTypesWithCustomAttribute()
.Where(kvp => typeof(IEvaluatable).IsAssignableFrom(kvp.Key))
- .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
-
- /// Cached list of all classes with a OverrideLogic attribute that also are a specific subtype.
- private static readonly Dictionary> specificOverrideLogics = new Dictionary>();
+ .OrderBy(kvp => kvp.Value.Name, StringComparer.OrdinalIgnoreCase)
+ .Select(kvp => new EvaluatableTypeContainer {
+ Evaluatable = kvp.Key,
+ Metadata = kvp.Value,
+ ResultType = kvp.Key.GetGenericInterfaceTypes(typeof(IEvaluatable<>))[0]
+ });
/// Fetches a specific subset of logic operand types (e.g. all booleans).
/// Caches results to that subsequent calls are marginally faster.
- /// The type to fetch (e.g. IEvaluatable).
- public static Dictionary Get() where T : IEvaluatable => Get(typeof(T));
+ /// The type to fetch (e.g. IEvaluatable<bool>>).
+ public static IEnumerable Get() where T : IEvaluatable => Get(typeof(T));
- /// Fetches a specific subset of logic operand types (e.g. all booleans).
- /// Caches results to that subsequent calls are marginally faster.
- /// The type to fetch (e.g. IEvaluatable).
- public static Dictionary Get(Type t) {
- if (!specificOverrideLogics.ContainsKey(t))
- specificOverrideLogics[t] = allOverrideLogics
- .Where(kvp => t.IsAssignableFrom(kvp.Key))
- .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
- return specificOverrideLogics[t];
+ /// Fetches a specific subset of logic operand types (e.g. all booleans).
+ /// The type to fetch (e.g. IEvaluatable<bool>).
+ public static IEnumerable Get(Type t) {
+ // Ensure all numbers (double, float, int, etc) become double
+ if (TypeUtils.IsNumericType(t)) t = typeof(double);
+
+ return allOverrideLogics.Where(kvp => kvp.ResultType == t);
}
/// Fetches all logic operands that have been found with the OverrideLogicAttribute attached.
- public static Dictionary Get() => allOverrideLogics;
+ public static IEnumerable Get() => allOverrideLogics;
+
+
+
+ public class EvaluatableTypeContainer {
+
+ /// The that represents the evaluatable.
+ public Type Evaluatable { get; set; }
+
+ /// The that contains the metadata about this evaluatable.
+ public EvaluatableAttribute Metadata { get; set; }
+
+ /// The that represents the type of value that is returned when this evaluatable is evaluated.
+ public Type ResultType { get; set; }
+ }
}
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_Delay.xaml b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_Delay.xaml
new file mode 100644
index 000000000..172f7574d
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_Delay.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_Delay.xaml.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_Delay.xaml.cs
new file mode 100644
index 000000000..98011c205
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_Delay.xaml.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Windows.Controls;
+
+namespace Aurora.Settings.Overrides.Logic.Generic {
+
+ public partial class Control_Delay : UserControl {
+ public Control_Delay() {
+ InitializeComponent();
+ }
+ }
+
+ public class Control_Delay : Control_Delay {
+ public Control_Delay(DelayGeneric context) : base() {
+ DataContext = context;
+ }
+
+ public Type EvalType => typeof(T);
+ }
+}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_IfElse.xaml b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_IfElse.xaml
index 9a330fa60..337cd3cbd 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_IfElse.xaml
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_IfElse.xaml
@@ -2,7 +2,8 @@
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:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:Aurora.Settings.Overrides.Logic"
xmlns:util="clr-namespace:Aurora.Utils"
mc:Ignorable="d">
@@ -14,38 +15,6 @@
-
-
@@ -56,39 +25,41 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_IfElse.xaml.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_IfElse.xaml.cs
index 6066ce5d9..76ba6ed96 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_IfElse.xaml.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Control_IfElse.xaml.cs
@@ -25,18 +25,17 @@ public partial class Control_Ternary : Control_Ternary {
private Control_Ternary_Context context;
- public Control_Ternary(IfElseGeneric context, Profiles.Application application) : base() {
+ public Control_Ternary(IfElseGeneric context) : base() {
DataContext = this.context = new Control_Ternary_Context {
- Application = application,
ParentCondition = context,
- EvaluatableType = EvaluatableTypeResolver.GetEvaluatableType(typeof(IEvaluatable))
+ EvaluatableType = typeof(T)
};
}
protected override void AddElseIfCase_Click(object sender, RoutedEventArgs e) {
context.ParentCondition.Cases.Insert(
context.ParentCondition.Cases.Count - (HasElseCase ? 2 : 1), // If there is an "Else" case, we need to insert this Else-If before that
- new IfElseGeneric.Branch(new BooleanConstant(), (IEvaluatable)EvaluatableTypeResolver.GetDefault(context.EvaluatableType))
+ new IfElseGeneric.Branch(new BooleanConstant(), EvaluatableDefaults.Get())
);
}
@@ -82,8 +81,7 @@ protected override void DeleteCase_Click(object sender, RoutedEventArgs e) {
///
internal class Control_Ternary_Context {
public IfElseGeneric ParentCondition { get; set; }
- public Profiles.Application Application { get; set; }
- public EvaluatableType EvaluatableType { get; set; }
+ public Type EvaluatableType { get; set; }
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Delay.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Delay.cs
new file mode 100644
index 000000000..da068b16f
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/Delay.cs
@@ -0,0 +1,81 @@
+using Aurora.Profiles;
+using System;
+using System.Collections.Generic;
+using System.Windows.Media;
+
+namespace Aurora.Settings.Overrides.Logic.Generic {
+
+ ///
+ /// Generic evaluatable that delays the source input by a set amount of time.
+ ///
+ public abstract class DelayGeneric : IEvaluatable {
+
+ /// Keeps track of all changes that have happened and which will need to be repeated.
+ private readonly Queue<(DateTime changeTime, T value)> history = new Queue<(DateTime changeTime, T value)>();
+
+ /// The last value returned by the evaluation of the 'Source' evaluatable.
+ private T lastValue;
+
+ /// The currently (delayed) value returned by this evaluatable.
+ private T currentValue;
+
+ /// An evaluatable that will be delayed bu the desired amount.
+ public IEvaluatable Source { get; set; }
+
+ /// The amount of time to delay the evaluatable by (in seconds).
+ public double Delay { get; set; } = 3;
+
+ // Ctors
+ public DelayGeneric() { Source = EvaluatableDefaults.Get(); }
+ public DelayGeneric(IEvaluatable source, double delay) { Source = source; Delay = delay; }
+
+ // Control
+ public Visual GetControl() => new Control_Delay(this);
+
+ // Eval
+ public T Evaluate(IGameState gameState) {
+ // First, evaluate the source evaluatable and check if the returned value is different from the last one we read.
+ var val = Source.Evaluate(gameState);
+ if (!EqualityComparer.Default.Equals(val, lastValue)) {
+ // If different, record the time it changed and (add the delay so that we don't have to keep adding when checking later)
+ history.Enqueue((DateTime.Now.AddSeconds(Delay), val));
+ lastValue = val;
+ }
+
+ // Next, check if the time next item in the queue changed has passed, update the current value
+ // Note that we don't need to check other items since they are kept in order by the queue
+ if (history.Count > 0 && history.Peek().changeTime < DateTime.Now)
+ currentValue = history.Dequeue().value;
+
+ return currentValue;
+ }
+ object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
+
+ // Clone
+ public abstract IEvaluatable Clone();
+ IEvaluatable IEvaluatable.Clone() => Clone();
+ }
+
+
+ // Concrete classes
+ [Evaluatable("Delay", category: EvaluatableCategory.Misc)]
+ public class DelayBoolean : DelayGeneric {
+ public DelayBoolean() : base() { }
+ public DelayBoolean(IEvaluatable source, double delay) : base(source, delay) { }
+ public override IEvaluatable Clone() => new DelayBoolean(Source, Delay);
+ }
+
+ [Evaluatable("Delay", category: EvaluatableCategory.Misc)]
+ public class DelayNumeric : DelayGeneric {
+ public DelayNumeric() : base() { }
+ public DelayNumeric(IEvaluatable source, double delay) : base(source, delay) { }
+ public override IEvaluatable Clone() => new DelayNumeric(Source, Delay);
+ }
+
+ [Evaluatable("Delay", category: EvaluatableCategory.Misc)]
+ public class DelayString : DelayGeneric {
+ public DelayString() : base() { }
+ public DelayString(IEvaluatable source, double delay) : base(source, delay) { }
+ public override IEvaluatable Clone() => new DelayString(Source, Delay);
+ }
+}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/IfElseIf.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/IfElseIf.cs
index 8078fe3ef..91b67efb7 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/IfElseIf.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Generic/IfElseIf.cs
@@ -1,4 +1,5 @@
using Aurora.Profiles;
+using Aurora.Utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -8,17 +9,16 @@
using System.Windows.Media;
-namespace Aurora.Settings.Overrides.Logic
-{
- //[OverrideLogic("Numeric State Variable", category: OverrideLogicCategory.State)]
- public class IfElseGeneric : IEvaluatable {
+namespace Aurora.Settings.Overrides.Logic {
+
+ public abstract class IfElseGeneric : IEvaluatable {
///
/// A list of all branches of the conditional.
///
public ObservableCollection Cases { get; set; } = CreateDefaultCases(
new BooleanConstant(), // Condition
- (IEvaluatable)EvaluatableTypeResolver.GetDefault(EvaluatableTypeResolver.GetEvaluatableType(typeof(IEvaluatable))), // True
- (IEvaluatable)EvaluatableTypeResolver.GetDefault(EvaluatableTypeResolver.GetEvaluatableType(typeof(IEvaluatable))) // False
+ EvaluatableDefaults.Get(), // True
+ EvaluatableDefaults.Get() // False
);
/// Creates a new If-Else evaluatable with default evaluatables.
@@ -28,7 +28,7 @@ public IfElseGeneric() { }
/// Creates a new evaluatable using the given case tree.
public IfElseGeneric(ObservableCollection cases) : this() { Cases = cases; }
- public Visual GetControl(Application application) => new Control_Ternary(this, application);
+ public Visual GetControl() => new Control_Ternary(this);
/// Evaluate conditions and return the appropriate evaluation.
public T Evaluate(IGameState gameState) {
@@ -40,15 +40,7 @@ public T Evaluate(IGameState gameState) {
object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
- /// Update the applications of the children evaluatables.
- public void SetApplication(Application application) {
- foreach (var kvp in Cases) {
- kvp.Condition?.SetApplication(application);
- kvp.Value?.SetApplication(application);
- }
- }
-
- public IEvaluatable Clone() => new IfElseGeneric(new ObservableCollection(Cases));
+ public abstract IEvaluatable Clone();
IEvaluatable IEvaluatable.Clone() => Clone();
private static ObservableCollection CreateDefaultCases(IEvaluatable condition, IEvaluatable caseTrue, IEvaluatable caseFalse) =>
@@ -57,17 +49,19 @@ private static ObservableCollection CreateDefaultCases(IEvaluatable Condition { get; set; }
public IEvaluatable Value { get; set; }
- public Branch() { }
public Branch(IEvaluatable condition, IEvaluatable value) { Condition = condition; Value = value; }
+
+ public object Clone() => new Branch(Condition?.Clone(), Value.Clone());
}
}
- [OverrideLogic("If - Else If - Else", category: OverrideLogicCategory.Logic)]
+ // Concrete classes
+ [Evaluatable("If - Else If - Else", category: EvaluatableCategory.Logic)]
public class IfElseBoolean : IfElseGeneric {
/// Creates a new If-Else evaluatable with default evaluatables.
public IfElseBoolean() : base() { }
@@ -77,10 +71,11 @@ public IfElseBoolean(IEvaluatable condition, IEvaluatable caseTrue,
/// Creates a new evaluatable using the given case tree.
public IfElseBoolean(ObservableCollection cases) : base(cases) { }
+ public override IEvaluatable Clone() => new IfElseBoolean(Cases.Clone());
}
- [OverrideLogic("If - Else If - Else", category: OverrideLogicCategory.Logic)]
+ [Evaluatable("If - Else If - Else", category: EvaluatableCategory.Logic)]
public class IfElseNumeric : IfElseGeneric {
/// Creates a new If-Else evaluatable with default evaluatables.
public IfElseNumeric() : base() { }
@@ -90,10 +85,11 @@ public IfElseNumeric(IEvaluatable condition, IEvaluatable caseTrue
/// Creates a new evaluatable using the given case tree.
public IfElseNumeric(ObservableCollection cases) : base(cases) { }
+ public override IEvaluatable Clone() => new IfElseNumeric(Cases.Clone());
}
- [OverrideLogic("If - Else If - Else", category: OverrideLogicCategory.Logic)]
+ [Evaluatable("If - Else If - Else", category: EvaluatableCategory.Logic)]
public class IfElseString : IfElseGeneric {
/// Creates a new If-Else evaluatable with default evaluatables.
public IfElseString() : base() { }
@@ -103,5 +99,6 @@ public IfElseString(IEvaluatable condition, IEvaluatable caseTrue,
/// Creates a new evaluatable using the given case tree.
public IfElseString(ObservableCollection cases) : base(cases) { }
+ public override IEvaluatable Clone() => new IfElseString(Cases.Clone());
}
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/IEvaluatable.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/IEvaluatable.cs
index 522694353..13ddfaf8f 100755
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/IEvaluatable.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/IEvaluatable.cs
@@ -1,61 +1,22 @@
using Aurora.Profiles;
using System;
using System.Collections.Generic;
-using System.Windows.Media;
using System.Linq;
+using System.Windows;
+using System.Windows.Media;
namespace Aurora.Settings.Overrides.Logic {
- #region Types Enum
- /** Unfortunately, this section has been added to allow the genericness of the EvaluatablePresenter. We required it to be able to handle
- multiple types of IEvaluatable, but allow it to be restricted to one specific type (e.g. IEvaluatable). To do this I, originally
- had it take a "EvalType" property as a System.Type and you would pass the interface type that you wanted (e.g. IEvaluatable),
- which I thought worked well as it was possible to do in XAML by setting `EvalType="{x:Type, local:IEvaluatable}`, but it turns out that
- with XAML you cannot reference _interfaces_ that way, only actually classes. This seems like a design flaw with XAML if you ask me.
- The next option was to then bind the EvalType to a property, however this meant that I was adding extra properties to the DataContexts
- which was cluttering the class and was frankly quite ugly.
- The final solution I've settled on is to make the EvalType a enum instead (EvaluatableType) enum. That way, it can easily be set in the
- XAML editor (since enums are supported) and also does not polute the DataContext of the presenter containers. The downside is, I needed
- some way of converting the enum to interface type so that the code can generate a list of the matching classes. That's what this is: */
-
- /// Enum of all evaluatable types.
- public enum EvaluatableType { All, Boolean, Number, String }
- /// Class that stores a dictionary to convert EvaluatableType enums into the interface type.
- public static class EvaluatableTypeResolver {
- private static Dictionary enumToTypeDictionary = new Dictionary {
- { EvaluatableType.All, typeof(IEvaluatable) },
- { EvaluatableType.Boolean, typeof(IEvaluatable) },
- { EvaluatableType.Number, typeof(IEvaluatable) },
- { EvaluatableType.String, typeof(IEvaluatable) }
- };
-
- private static Dictionary typeToEnumDictionary = enumToTypeDictionary.ToList().ToDictionary(s => s.Value, s=> s.Key);
- private static Dictionary> enumToDefaultDictionary = new Dictionary> {
- { EvaluatableType.All, () => null },
- { EvaluatableType.Boolean, () => new BooleanConstant() },
- { EvaluatableType.Number, () => new NumberConstant() },
- { EvaluatableType.String, () => new StringConstant() }
- };
- public static Type Resolve(EvaluatableType inType) => enumToTypeDictionary.TryGetValue(inType, out Type outType) ? outType : typeof(IEvaluatable);
- public static IEvaluatable GetDefault(EvaluatableType inType) => enumToDefaultDictionary.TryGetValue(inType, out Func outFunc) ? outFunc() : null;
- public static EvaluatableType GetEvaluatableType(Type inType) => typeToEnumDictionary.TryGetValue(inType, out EvaluatableType outVal) ? outVal : throw new Exception($"No EvaluatableType matches Type {inType.ToString()}");
- }
- #endregion
-
///
- /// Interface that defines a logic operand that can be evaluated into a value. Should also have a Visual control that can
- /// be used to edit the operand. The control will be given the current application that can be used to have contextual
- /// prompts (e.g. a dropdown list with the valid game state variable paths) for that application.
+ /// Interface that defines a logic operand that can be evaluated into a value. Should also have a Visual control
+ /// that can be used to edit the operand.
///
public interface IEvaluatable {
/// Should evaluate the operand and return the evaluation result.
object Evaluate(IGameState gameState);
/// Should return a control that is bound to this logic element.
- Visual GetControl(Application application);
-
- /// Indicates the UserControl should be updated with a new application.
- void SetApplication(Application application);
+ Visual GetControl();
/// Creates a copy of this IEvaluatable.
IEvaluatable Clone();
@@ -69,4 +30,45 @@ public interface IEvaluatable : IEvaluatable
/// Creates a copy of this IEvaluatable.
new IEvaluatable Clone();
}
+
+
+ ///
+ /// Class that provides a lookup for the default Evaluatable for a particular type.
+ ///
+ public static class EvaluatableDefaults {
+
+ private static Dictionary defaultsMap = new Dictionary {
+ [typeof(bool)] = typeof(BooleanConstant),
+ [typeof(double)] = typeof(NumberConstant),
+ [typeof(string)] = typeof(StringConstant)
+ };
+
+ public static IEvaluatable Get() => (IEvaluatable)Get(typeof(T));
+
+ public static IEvaluatable Get(Type t) {
+ if (!defaultsMap.TryGetValue(t, out Type @default))
+ throw new ArgumentException($"Type '{t.Name}' does not have a default evaluatable type.");
+ return (IEvaluatable)Activator.CreateInstance(@default);
+ }
+ }
+
+
+ ///
+ /// Helper classes for the Evaluatables.
+ ///
+ public static class EvaluatableHelpers {
+ /// Attempts to get an evaluatable from the suppliied data object. Will return true/false indicating if data is of correct format
+ /// (an where T matches the given type. If the eval type is null, no type check is performed, the returned
+ /// evaluatable may be of any sub-type.
+ internal static bool TryGetData(IDataObject @do, out IEvaluatable evaluatable, out Control_EvaluatablePresenter source, Type evalType) {
+ if (@do.GetData(@do.GetFormats().FirstOrDefault(x => x != "SourcePresenter")) is IEvaluatable data && (evalType == null || Utils.TypeUtils.ImplementsGenericInterface(data.GetType(), typeof(IEvaluatable<>), evalType))) {
+ evaluatable = data;
+ source = @do.GetData("SourcePresenter") as Control_EvaluatablePresenter;
+ return true;
+ }
+ evaluatable = null;
+ source = null;
+ return false;
+ }
+ }
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/IOverrideLogic.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/IOverrideLogic.cs
index 113df418c..718acd57c 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/IOverrideLogic.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/IOverrideLogic.cs
@@ -14,6 +14,6 @@ public interface IOverrideLogic : System.ComponentModel.INotifyPropertyChanged {
///
/// Gets a control for editing this overridge logic system.
///
- System.Windows.Media.Visual GetControl(Profiles.Application application);
+ System.Windows.Media.Visual GetControl();
}
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericMap.xaml b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericMap.xaml
index 7b327a52c..e50e8d14c 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericMap.xaml
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericMap.xaml
@@ -3,43 +3,43 @@
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:sys="clr-namespace:System;assembly=mscorlib"
xmlns:logic="clr-namespace:Aurora.Settings.Overrides.Logic"
+ xmlns:u="clr-namespace:Aurora.Utils"
mc:Ignorable="d"
d:DesignWidth="500">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericMap.xaml.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericMap.xaml.cs
index 5efc1a9ac..6e31b3f25 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericMap.xaml.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericMap.xaml.cs
@@ -5,14 +5,9 @@ namespace Aurora.Settings.Overrides.Logic {
public partial class Control_NumericMap : UserControl {
- public NumberMap Map { get; set; }
- public Application Application { get; set; }
-
- public Control_NumericMap(NumberMap context, Application application) {
+ public Control_NumericMap(NumberMap context) {
InitializeComponent();
- Map = context;
- Application = application;
- DataContext = this;
+ DataContext = context;
}
}
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericUnaryOpHolder.xaml b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericUnaryOpHolder.xaml
index 85aa14527..7dddb259b 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericUnaryOpHolder.xaml
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericUnaryOpHolder.xaml
@@ -3,6 +3,7 @@
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:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:Aurora.Settings.Overrides.Logic"
xmlns:utils="clr-namespace:Aurora.Utils"
mc:Ignorable="d"
@@ -10,14 +11,14 @@
-
-
-
-
-
+
+
+
+
+
-
+
@@ -27,9 +28,12 @@
-
+
+
+
+
-
+
@@ -39,5 +43,5 @@
-
+
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericUnaryOpHolder.xaml.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericUnaryOpHolder.xaml.cs
index a99ac5cbc..33d4cc791 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericUnaryOpHolder.xaml.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Control_NumericUnaryOpHolder.xaml.cs
@@ -15,23 +15,21 @@ namespace Aurora.Settings.Overrides.Logic {
public partial class Control_NumericUnaryOpHolder : UserControl {
/// Base constructor for the unary operation holder.
- public Control_NumericUnaryOpHolder(Profiles.Application application) {
+ public Control_NumericUnaryOpHolder() {
InitializeComponent();
- Application = application;
DataContext = this;
}
/// Creates a new unary operation control using the given Enum type as a source for the possible operators to choose from.
- public Control_NumericUnaryOpHolder(Profiles.Application application, Type enumType) : this(application) {
+ public Control_NumericUnaryOpHolder(Type enumType) : this() {
OperatorList = Utils.EnumUtils.GetEnumItemsSource(enumType).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
/// Creates a new unary opeartion control using the given string as the name of the operator, disallowing the user to choose an option.
- public Control_NumericUnaryOpHolder(Profiles.Application application, string operatorName) : this(application) {
+ public Control_NumericUnaryOpHolder(string operatorName) : this() {
StaticOperator = operatorName;
}
- public Profiles.Application Application { get; set; }
public Dictionary OperatorList { get; set; } = null;
public string StaticOperator { get; set; } = null;
@@ -48,9 +46,5 @@ public object SelectedOperator {
get => GetValue(SelectedOperatorProperty);
set => SetValue(SelectedOperatorProperty, value);
}
-
- public void SetApplication(Profiles.Application application) {
- Application = application;
- }
}
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_GameState.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_GameState.cs
index cb58bfa76..5f9783dbc 100755
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_GameState.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_GameState.cs
@@ -1,4 +1,6 @@
-using Aurora.Profiles;
+using Aurora.Controls;
+using Aurora.Profiles;
+using Aurora.Utils;
using System;
using System.Linq;
using System.Windows.Controls;
@@ -10,7 +12,7 @@ namespace Aurora.Settings.Overrides.Logic {
///
/// Evaluatable that accesses some specified game state variables (of numeric type) and returns it.
///
- [OverrideLogic("Numeric State Variable", category: OverrideLogicCategory.State)]
+ [Evaluatable("Numeric State Variable", category: EvaluatableCategory.State)]
public class NumberGSINumeric : IEvaluatable {
/// Creates a new numeric game state lookup evaluatable that doesn't target anything.
@@ -22,33 +24,14 @@ public NumberGSINumeric() { }
public string VariablePath { get; set; }
// Control assigned to this evaluatable
- [Newtonsoft.Json.JsonIgnore]
- private ComboBox control;
- public Visual GetControl(Application application) {
- if (control == null) {
- control = new ComboBox { Margin = new System.Windows.Thickness(0, 0, 0, 6) };
- control.SetBinding(ComboBox.SelectedItemProperty, new Binding("VariablePath") { Source = this });
- SetApplication(application);
- }
- return control;
- }
+ public Visual GetControl() => new GameStateParameterPicker { PropertyType = PropertyType.Number }
+ .WithBinding(GameStateParameterPicker.ApplicationProperty, new AttachedApplicationBinding())
+ .WithBinding(GameStateParameterPicker.SelectedPathProperty, new Binding("VariablePath") { Source = this });
/// Parses the numbers, compares the result, and returns the result.
- public double Evaluate(IGameState gameState) => Utils.GameStateUtils.TryGetDoubleFromState(gameState, VariablePath);
+ public double Evaluate(IGameState gameState) => GameStateUtils.TryGetDoubleFromState(gameState, VariablePath);
object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
- /// Update the assigned control with the new application.
- public void SetApplication(Application application) {
- if (control != null)
- control.ItemsSource = application?.ParameterLookup?
- .Where(kvp => Utils.TypeUtils.IsNumericType(kvp.Value.Item1))
- .Select(kvp => kvp.Key);
-
- // Check to ensure the variable path is valid
- if (application != null && !double.TryParse(VariablePath, out _) && !string.IsNullOrWhiteSpace(VariablePath) && !application.ParameterLookup.ContainsKey(VariablePath))
- VariablePath = string.Empty;
- }
-
public IEvaluatable Clone() => new NumberGSINumeric { VariablePath = VariablePath };
IEvaluatable IEvaluatable.Clone() => Clone();
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_Maths.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_Maths.cs
index ea5b5f738..a9df885d4 100755
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_Maths.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_Maths.cs
@@ -6,6 +6,7 @@
using System.Windows.Data;
using System.Windows.Media;
using Aurora.Profiles;
+using Aurora.Utils;
using Newtonsoft.Json;
using Xceed.Wpf.Toolkit;
@@ -14,7 +15,7 @@ namespace Aurora.Settings.Overrides.Logic {
///
/// Evaluatable that performs a binary mathematical operation on two operands.
///
- [OverrideLogic("Arithmetic Operation", category: OverrideLogicCategory.Maths)]
+ [Evaluatable("Arithmetic Operation", category: EvaluatableCategory.Maths)]
public class NumberMathsOperation : IEvaluatable {
/// Creates a new maths operation that has no values pre-set.
@@ -37,18 +38,10 @@ public NumberMathsOperation() { }
public IEvaluatable Operand2 { get; set; } = new NumberConstant();
public MathsOperator Operator { get; set; } = MathsOperator.Add;
- // The control allowing the user to edit the evaluatable
- [JsonIgnore]
- private Control_BinaryOperationHolder control;
- public Visual GetControl(Application application) {
- if (control == null) {
- control = new Control_BinaryOperationHolder(application, EvaluatableType.Number, typeof(MathsOperator));
- control.SetBinding(Control_BinaryOperationHolder.Operand1Property, new Binding("Operand1") { Source = this, Mode = BindingMode.TwoWay });
- control.SetBinding(Control_BinaryOperationHolder.Operand2Property, new Binding("Operand2") { Source = this, Mode = BindingMode.TwoWay });
- control.SetBinding(Control_BinaryOperationHolder.SelectedOperatorProperty, new Binding("Operator") { Source = this, Mode = BindingMode.TwoWay });
- }
- return control;
- }
+ public Visual GetControl() => new Control_BinaryOperationHolder(typeof(double), typeof(MathsOperator))
+ .WithBinding(Control_BinaryOperationHolder.Operand1Property, new Binding("Operand1") { Source = this, Mode = BindingMode.TwoWay })
+ .WithBinding(Control_BinaryOperationHolder.Operand2Property, new Binding("Operand2") { Source = this, Mode = BindingMode.TwoWay })
+ .WithBinding(Control_BinaryOperationHolder.SelectedOperatorProperty, new Binding("Operator") { Source = this, Mode = BindingMode.TwoWay });
/// Resolves the two operands and then compares them using the user specified operator
public double Evaluate(IGameState gameState) {
@@ -58,20 +51,13 @@ public double Evaluate(IGameState gameState) {
case MathsOperator.Add: return op1 + op2;
case MathsOperator.Sub: return op1 - op2;
case MathsOperator.Mul: return op1 * op2;
- case MathsOperator.Div: return op2 == 0 ? 0 : op1 / op2; // Return 0 if user tried to divide by zero. Easier than having to deal with Infinity (which C# returns).
- case MathsOperator.Mod: return op2 == 0 ? 0 : op1 % op2;
+ case MathsOperator.Div when op2 != 0: return op1 / op2; // Return 0 if user tried to divide by zero.
+ case MathsOperator.Mod when op2 != 0: return op1 % op2;
default: return 0;
}
}
object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
- /// Updates the user control and the operands with a new application context.
- public void SetApplication(Application application) {
- control?.SetApplication(application);
- Operand1?.SetApplication(application);
- Operand2?.SetApplication(application);
- }
-
/// Creates a copy of this maths operation.
public IEvaluatable Clone() => new NumberMathsOperation { Operand1 = Operand1.Clone(), Operand2 = Operand2.Clone(), Operator = Operator };
IEvaluatable IEvaluatable.Clone() => Clone();
@@ -82,7 +68,7 @@ public void SetApplication(Application application) {
///
/// Returns the absolute value of the given evaluatable.
///
- [OverrideLogic("Absolute", category: OverrideLogicCategory.Maths)]
+ [Evaluatable("Absolute", category: EvaluatableCategory.Maths)]
public class NumberAbsValue : IEvaluatable {
/// Creates a new absolute operation with the default operand.
@@ -94,22 +80,13 @@ public NumberAbsValue() { }
public IEvaluatable Operand { get; set; } = new NumberConstant();
// Get the control allowing the user to set the operand
- [JsonIgnore]
- private Control_NumericUnaryOpHolder control;
- public Visual GetControl(Application application) {
- if (control == null) {
- control = new Control_NumericUnaryOpHolder(application, "Absolute");
- control.SetBinding(Control_NumericUnaryOpHolder.OperandProperty, new Binding("Operand") { Source = this, Mode = BindingMode.TwoWay });
- }
- return control;
- }
+ public Visual GetControl() => new Control_NumericUnaryOpHolder("Absolute")
+ .WithBinding(Control_NumericUnaryOpHolder.OperandProperty, new Binding("Operand") { Source = this, Mode = BindingMode.TwoWay });
/// Evaluate the operand and return the absolute value of it.
public double Evaluate(IGameState gameState) => Math.Abs(Operand.Evaluate(gameState));
object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
- public void SetApplication(Application application) => Operand?.SetApplication(application);
-
public IEvaluatable Clone() => new NumberAbsValue { Operand = Operand.Clone() };
IEvaluatable IEvaluatable.Clone() => Clone();
}
@@ -119,7 +96,7 @@ public Visual GetControl(Application application) {
///
/// Evaluatable that compares two numerical evaluatables and returns a boolean depending on the comparison.
///
- [OverrideLogic("Arithmetic Comparison", category: OverrideLogicCategory.Maths)]
+ [Evaluatable("Arithmetic Comparison", category: EvaluatableCategory.Maths)]
public class BooleanMathsComparison : IEvaluatable {
/// Creates a new maths comparison that has no values pre-set.
@@ -143,17 +120,10 @@ public BooleanMathsComparison() { }
public ComparisonOperator Operator { get; set; } = ComparisonOperator.EQ;
// The control allowing the user to edit the evaluatable
- [JsonIgnore]
- private Control_BinaryOperationHolder control;
- public Visual GetControl(Application application) {
- if (control == null) {
- control = new Control_BinaryOperationHolder(application, EvaluatableType.Number, typeof(ComparisonOperator));
- control.SetBinding(Control_BinaryOperationHolder.Operand1Property, new Binding("Operand1") { Source = this, Mode = BindingMode.TwoWay });
- control.SetBinding(Control_BinaryOperationHolder.Operand2Property, new Binding("Operand2") { Source = this, Mode = BindingMode.TwoWay });
- control.SetBinding(Control_BinaryOperationHolder.SelectedOperatorProperty, new Binding("Operator") { Source = this, Mode = BindingMode.TwoWay });
- }
- return control;
- }
+ public Visual GetControl() => new Control_BinaryOperationHolder(typeof(double), typeof(ComparisonOperator))
+ .WithBinding(Control_BinaryOperationHolder.Operand1Property, new Binding("Operand1") { Source = this, Mode = BindingMode.TwoWay })
+ .WithBinding(Control_BinaryOperationHolder.Operand2Property, new Binding("Operand2") { Source = this, Mode = BindingMode.TwoWay })
+ .WithBinding(Control_BinaryOperationHolder.SelectedOperatorProperty, new Binding("Operator") { Source = this, Mode = BindingMode.TwoWay });
/// Resolves the two operands and then compares them with the user-specified operator.
public bool Evaluate(IGameState gameState) {
@@ -171,13 +141,6 @@ public bool Evaluate(IGameState gameState) {
}
object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
- /// Updates the user control and the operands with a new application context.
- public void SetApplication(Application application) {
- control?.SetApplication(application);
- Operand1?.SetApplication(application);
- Operand2?.SetApplication(application);
- }
-
/// Creates a copy of this mathematical comparison.
public IEvaluatable Clone() => new BooleanMathsComparison { Operand1 = Operand1.Clone(), Operand2 = Operand2.Clone() };
IEvaluatable IEvaluatable.Clone() => Clone();
@@ -188,7 +151,7 @@ public void SetApplication(Application application) {
///
/// Evaluatable that takes a number in a given range and linearly interpolates it onto another range.
///
- [OverrideLogic("Lerp", category: OverrideLogicCategory.Maths)]
+ [Evaluatable("Lerp", category: EvaluatableCategory.Maths)]
public class NumberMap : IEvaluatable {
/// Creates a new numeric map with the default constant parameters.
@@ -214,9 +177,7 @@ public NumberMap() { }
public IEvaluatable ToMax { get; set; } = new NumberConstant(1);
// The control to edit the map parameters
- [JsonIgnore]
- private Control_NumericMap control;
- public Visual GetControl(Application application) => control ?? (control = new Control_NumericMap(this, application));
+ public Visual GetControl() => new Control_NumericMap(this);
/// Evaluate the from range and to range and return the value in the new range.
public double Evaluate(IGameState gameState) {
@@ -231,15 +192,6 @@ public double Evaluate(IGameState gameState) {
}
object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
- /// Updates the applications on all sub evaluatables.
- public void SetApplication(Application application) {
- Value?.SetApplication(application);
- FromMin?.SetApplication(application);
- ToMin?.SetApplication(application);
- FromMax?.SetApplication(application);
- ToMax?.SetApplication(application);
- }
-
public IEvaluatable Clone() => new NumberMap { Value = Value.Clone(), FromMin = FromMin.Clone(), ToMin = ToMin.Clone(), FromMax = FromMax.Clone(), ToMax = ToMax.Clone() };
IEvaluatable IEvaluatable.Clone() => Clone();
}
@@ -249,7 +201,7 @@ public void SetApplication(Application application) {
///
/// Evaluatable that resolves to a numerical constant.
///
- [OverrideLogic("Number Constant", category: OverrideLogicCategory.Maths)]
+ [Evaluatable("Number Constant", category: EvaluatableCategory.Maths)]
public class NumberConstant : IEvaluatable {
/// Creates a new constant with the zero as the constant value.
@@ -261,23 +213,13 @@ public NumberConstant() { }
public double Value { get; set; }
// The control allowing the user to edit the number value
- [JsonIgnore]
- private DoubleUpDown control;
- public Visual GetControl(Application application) {
- if (control == null) {
- control = new DoubleUpDown { Margin = new System.Windows.Thickness(0, 0, 0, 6) };
- control.SetBinding(DoubleUpDown.ValueProperty, new Binding("Value") { Source = this });
- }
- return control;
- }
+ public Visual GetControl() => new DoubleUpDown { Margin = new System.Windows.Thickness(0, 0, 0, 6) }
+ .WithBinding(DoubleUpDown.ValueProperty, new Binding("Value") { Source = this });
/// Simply returns the constant value specified by the user
public double Evaluate(IGameState gameState) => Value;
object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
- /// Does nothing - this evaluatable is application-independant.
- public void SetApplication(Application application) { }
-
/// Creates a copy of this number constant
public IEvaluatable Clone() => new NumberConstant { Value = Value };
IEvaluatable IEvaluatable.Clone() => Clone();
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_PeripheralInput.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_PeripheralInput.cs
index 22bf01941..a1383ea7e 100644
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_PeripheralInput.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_PeripheralInput.cs
@@ -14,7 +14,7 @@ namespace Aurora.Settings.Overrides.Logic.Number {
///
/// An evaluatable that returns the time since the user has pressed a keyboard key or clicked the mouse.
///
- [OverrideLogic("Away Time", category: OverrideLogicCategory.Input)]
+ [Evaluatable("Away Time", category: EvaluatableCategory.Input)]
public class NumberAwayTime : IEvaluatable {
/// Gets or sets the time unit that the time is being measured in.
@@ -30,12 +30,11 @@ public NumberAwayTime(TimeUnit unit) {
#endregion
// Control
- private ComboBox control;
- public Visual GetControl(Application app) => control ?? (control = new ComboBox {
+ public Visual GetControl() => new ComboBox {
DisplayMemberPath = "Key",
SelectedValuePath = "Value",
ItemsSource = EnumUtils.GetEnumItemsSource()
- }.WithBinding(ComboBox.SelectedValueProperty, this, "TimeUnit", BindingMode.TwoWay));
+ }.WithBinding(ComboBox.SelectedValueProperty, this, "TimeUnit", BindingMode.TwoWay);
/// Checks to see if the duration since the last input is greater than the given inactive time.
public double Evaluate(IGameState gameState) {
@@ -50,8 +49,6 @@ public double Evaluate(IGameState gameState) {
}
object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
- public void SetApplication(Application application) { }
-
public IEvaluatable Clone() => new NumberAwayTime { TimeUnit = TimeUnit };
IEvaluatable IEvaluatable.Clone() => Clone();
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_Waves.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_Waves.cs
index 06860c832..debfcca44 100755
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_Waves.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/Number/Number_Waves.cs
@@ -1,4 +1,5 @@
using Aurora.Profiles;
+using Aurora.Utils;
using Newtonsoft.Json;
using System;
using System.Windows.Data;
@@ -8,7 +9,7 @@ namespace Aurora.Settings.Overrides.Logic {
///
/// A special operator that takes the given (x) input (between 0 and 1) and converts it to a waveform (y) between 0 and 1.
///
- [OverrideLogic("Wave Function", category: OverrideLogicCategory.Maths)]
+ [Evaluatable("Wave Function", category: EvaluatableCategory.Maths)]
public class NumberWaveFunction : IEvaluatable {
/// Creates a new wave function evaluatable with the default parameters.
@@ -23,16 +24,9 @@ public NumberWaveFunction() { }
/// The type of wave to generate.
public WaveFunctionType WaveFunc { get; set; } = WaveFunctionType.Sine;
- [JsonIgnore]
- private Control_NumericUnaryOpHolder control;
- public Visual GetControl(Application application) {
- if (control == null) {
- control = new Control_NumericUnaryOpHolder(application, typeof(WaveFunctionType));
- control.SetBinding(Control_NumericUnaryOpHolder.OperandProperty, new Binding("Operand") { Source = this, Mode = BindingMode.TwoWay });
- control.SetBinding(Control_NumericUnaryOpHolder.SelectedOperatorProperty, new Binding("WaveFunc") { Source = this, Mode = BindingMode.TwoWay });
- }
- return control;
- }
+ public Visual GetControl() => new Control_NumericUnaryOpHolder(typeof(WaveFunctionType))
+ .WithBinding(Control_NumericUnaryOpHolder.OperandProperty, new Binding("Operand") { Source = this, Mode = BindingMode.TwoWay })
+ .WithBinding(Control_NumericUnaryOpHolder.SelectedOperatorProperty, new Binding("WaveFunc") { Source = this, Mode = BindingMode.TwoWay });
///
/// Evaluates this wave function generator using the result of the operand and the given wave type.
@@ -48,11 +42,6 @@ public double Evaluate(IGameState gameState) {
}
object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
- public void SetApplication(Application application) {
- control?.SetApplication(application);
- Operand?.SetApplication(application);
- }
-
public IEvaluatable Clone() => new NumberWaveFunction { Operand = Operand.Clone() };
IEvaluatable IEvaluatable.Clone() => Clone();
}
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/OverrideDynamicValue.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/OverrideDynamicValue.cs
index 46561b007..778fbf5ce 100755
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/OverrideDynamicValue.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/OverrideDynamicValue.cs
@@ -30,7 +30,7 @@ public OverrideDynamicValue(Type type) {
// a new instance of the default IEvaluatable for each parameter. E.G. for a parameter specified as EvaluatableType.Boolean, a new true
// constant will be put in the constructor parameters dictionary.
ConstructorParameters = typeDynamicDefMap.ContainsKey(type)
- ? typeDynamicDefMap[type].constructorParameters.ToDictionary(dcpd => dcpd.name, dcpd => EvaluatableTypeResolver.GetDefault(dcpd.type))
+ ? typeDynamicDefMap[type].constructorParameters.ToDictionary(dcpd => dcpd.name, dcpd => EvaluatableDefaults.Get(dcpd.type))
: null;
}
@@ -72,9 +72,9 @@ public object Evaluate(IGameState gameState) => typeDynamicDefMap.ContainsKey(Va
///
/// Creates the control that is used to edit the IEvaluatables used as parameters for this DynamicValue logic
///
- public Visual GetControl(Application application) => typeDynamicDefMap.ContainsKey(VarType)
+ public Visual GetControl() => typeDynamicDefMap.ContainsKey(VarType)
// If this has a valid type (i.e. supported by the dynamic constructor), then create the control and pass in `this` and `application` for context
- ? new Control_OverrideDynamicValue(this, application)
+ ? new Control_OverrideDynamicValue(this)
// If it is an invalid type, then simply show a red warning message
: (Visual)new Label { Content = "This property type is not supported with the dynamic value editor. Sorry :(", Foreground = Brushes.Red, Margin = new System.Windows.Thickness(6) };
#endregion
@@ -87,33 +87,33 @@ public Visual GetControl(Application application) => typeDynamicDefMap.ContainsK
///
internal static readonly Dictionary typeDynamicDefMap = new Dictionary {
// Boolean
- { typeof(bool), new DCD(p => p["Value"], new[]{ new DCPD("Value", EvaluatableType.Boolean) }) },
+ { typeof(bool), new DCD(p => p["Value"], new[]{ new DCPD("Value", typeof(bool)) }) },
// Numeric
- { typeof(int), new DCD(p => Convert.ToInt32(p["Value"]), new[]{ new DCPD("Value", EvaluatableType.Number) }) },
- { typeof(long), new DCD(p => Convert.ToInt64(p["Value"]), new[]{ new DCPD("Value", EvaluatableType.Number) }) },
- { typeof(float), new DCD(p => Convert.ToSingle(p["Value"]), new[]{ new DCPD("Value", EvaluatableType.Number) }) },
- { typeof(double), new DCD(p => p["Value"], new[]{ new DCPD("Value", EvaluatableType.Number) }) },
+ { typeof(int), new DCD(p => Convert.ToInt32(p["Value"]), new[]{ new DCPD("Value", typeof(double)) }) },
+ { typeof(long), new DCD(p => Convert.ToInt64(p["Value"]), new[]{ new DCPD("Value", typeof(double)) }) },
+ { typeof(float), new DCD(p => Convert.ToSingle(p["Value"]), new[]{ new DCPD("Value", typeof(double)) }) },
+ { typeof(double), new DCD(p => p["Value"], new[]{ new DCPD("Value", typeof(double)) }) },
// Special
{ typeof(System.Drawing.Color), new DCD(
p => System.Drawing.Color.FromArgb(ToColorComp(p["Alpha"]), ToColorComp(p["Red"]), ToColorComp(p["Green"]), ToColorComp(p["Blue"])),
new[] {
- new DCPD("Alpha", EvaluatableType.Number, "A value between 0 (transparent) and 1 (opaque) for the transparency of the color."),
- new DCPD("Red", EvaluatableType.Number, "A value between 0 and 1 for the amount of red in the color."),
- new DCPD("Green", EvaluatableType.Number, "A value between 0 and 1 for the amount of green in the color."),
- new DCPD("Blue", EvaluatableType.Number, "A value between 0 and 1 for the amount of blue in the color.")
+ new DCPD("Alpha", typeof(double), "A value between 0 (transparent) and 1 (opaque) for the transparency of the color."),
+ new DCPD("Red", typeof(double), "A value between 0 and 1 for the amount of red in the color."),
+ new DCPD("Green", typeof(double), "A value between 0 and 1 for the amount of green in the color."),
+ new DCPD("Blue", typeof(double), "A value between 0 and 1 for the amount of blue in the color.")
}
) },
{ typeof(KeySequence), new DCD(
p => new KeySequence(new FreeFormObject(Convert.ToSingle(p["X"]), Convert.ToSingle(p["Y"]), Convert.ToSingle(p["Width"]), Convert.ToSingle(p["Height"]), Convert.ToSingle(p["Angle"]))),
new[] {
- new DCPD("X", EvaluatableType.Number),
- new DCPD("Y", EvaluatableType.Number),
- new DCPD("Width", EvaluatableType.Number),
- new DCPD("Height", EvaluatableType.Number),
- new DCPD("Angle", EvaluatableType.Number)
+ new DCPD("X", typeof(double)),
+ new DCPD("Y", typeof(double)),
+ new DCPD("Width", typeof(double)),
+ new DCPD("Height", typeof(double)),
+ new DCPD("Angle", typeof(double))
}
) }
};
@@ -143,11 +143,11 @@ struct DynamicConstructorParamDefinition {
/// Parameter name.
public string name;
/// The type of variable this parameter is.
- public EvaluatableType type;
+ public Type type;
/// A simple description of the parameter for the user.
public string description;
- public DynamicConstructorParamDefinition(string name, EvaluatableType type, string description=null) {
+ public DynamicConstructorParamDefinition(string name, Type type, string description=null) {
this.name = name;
this.type = type;
this.description = description;
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/OverrideLookupTable.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/OverrideLookupTable.cs
index ecdcb6961..66908b3cc 100755
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/OverrideLookupTable.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/OverrideLookupTable.cs
@@ -61,7 +61,7 @@ public object Evaluate(IGameState gameState) {
///
/// Gets the control allowing the user to edit this LookupTable.
///
- public Visual GetControl(Application application) => _control ?? (_control = new Control_OverrideLookupTable(this, application));
+ public Visual GetControl() => _control ?? (_control = new Control_OverrideLookupTable(this));
[JsonIgnore]
private Control_OverrideLookupTable _control;
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/String/String_Constant.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/String/String_Constant.cs
index 0d8d2e78e..7b40b32fb 100755
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/String/String_Constant.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/String/String_Constant.cs
@@ -1,4 +1,5 @@
using Aurora.Profiles;
+using Aurora.Utils;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
@@ -7,30 +8,20 @@ namespace Aurora.Settings.Overrides.Logic {
///
/// Represents a constant string value that will always evaluate to the same value.
///
- [OverrideLogic("String Constant", category: OverrideLogicCategory.String)]
+ [Evaluatable("String Constant", category: EvaluatableCategory.String)]
public class StringConstant : IEvaluatable {
/// The value of the constant.
public string Value { get; set; } = "";
/// A control for setting the string value
- [Newtonsoft.Json.JsonIgnore]
- private TextBox control;
- public Visual GetControl(Application application) {
- if (control == null) {
- control = new TextBox { Margin = new System.Windows.Thickness(0, 0, 0, 6) };
- control.SetBinding(TextBox.TextProperty, new Binding("Value") { Source = this, Mode = BindingMode.TwoWay });
- }
- return control;
- }
+ public Visual GetControl() => new TextBox { MinWidth = 40 }
+ .WithBinding(TextBox.TextProperty, new Binding("Value") { Source = this, Mode = BindingMode.TwoWay });
/// Simply return the constant value.
public string Evaluate(IGameState gameState) => Value;
object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
-
- /// Does nothing. This is an application-independent evaluatable.
- public void SetApplication(Application application) { }
-
+
/// Clones this constant string value.
public IEvaluatable Clone() => new StringConstant { Value = Value };
IEvaluatable IEvaluatable.Clone() => Clone();
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/String/String_GameState.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/String/String_GameState.cs
index 488b4c1ca..5c4c32578 100755
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/String/String_GameState.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/String/String_GameState.cs
@@ -1,4 +1,6 @@
-using Aurora.Profiles;
+using Aurora.Controls;
+using Aurora.Profiles;
+using Aurora.Utils;
using System;
using System.Linq;
using System.Windows.Controls;
@@ -7,23 +9,16 @@
namespace Aurora.Settings.Overrides.Logic {
- [OverrideLogic("String State Variable", category: OverrideLogicCategory.State)]
+ [Evaluatable("String State Variable", category: EvaluatableCategory.State)]
public class StringGSIString : IEvaluatable {
/// Path to the GSI variable
public string VariablePath { get; set; } = "";
/// Control assigned to this logic node.
- [Newtonsoft.Json.JsonIgnore]
- private ComboBox control;
- public Visual GetControl(Application application) {
- if (control == null) {
- control = new ComboBox { Margin = new System.Windows.Thickness(0, 0, 0, 6) };
- control.SetBinding(ComboBox.SelectedItemProperty, new Binding("VariablePath") { Source = this });
- SetApplication(application);
- }
- return control;
- }
+ public Visual GetControl() => new GameStateParameterPicker { PropertyType = PropertyType.String }
+ .WithBinding(GameStateParameterPicker.ApplicationProperty, new AttachedApplicationBinding())
+ .WithBinding(GameStateParameterPicker.SelectedPathProperty, new Binding("VariablePath") { Source = this });
/// Attempts to return the string at the given state variable.
public string Evaluate(IGameState gameState) {
@@ -34,18 +29,6 @@ public string Evaluate(IGameState gameState) {
}
object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
- /// Update the assigned combobox with the new application context.
- public void SetApplication(Application application) {
- if (control != null)
- control.ItemsSource = application?.ParameterLookup?
- .Where(kvp => Type.GetTypeCode(kvp.Value.Item1) == TypeCode.String)
- .Select(kvp => kvp.Key);
-
- // Check to ensure var path is valid
- if (application != null && !string.IsNullOrWhiteSpace(VariablePath) && !application.ParameterLookup.ContainsKey(VariablePath))
- VariablePath = string.Empty;
- }
-
/// Clones this StringGSIString.
public IEvaluatable Clone() => new StringGSIString { VariablePath = VariablePath };
IEvaluatable IEvaluatable.Clone() => Clone();
diff --git a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/String/String_LogicOperators.cs b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/String/String_LogicOperators.cs
index d4e0c365a..00d7f3301 100755
--- a/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/String/String_LogicOperators.cs
+++ b/Project-Aurora/Project-Aurora/Settings/Overrides/Logic/String/String_LogicOperators.cs
@@ -1,4 +1,5 @@
using Aurora.Profiles;
+using Aurora.Utils;
using System;
using System.Windows.Controls;
using System.Windows.Data;
@@ -9,7 +10,7 @@ namespace Aurora.Settings.Overrides.Logic {
///
/// Logic that compares two strings using a selection of operators.
///
- [OverrideLogic("String Comparison", category: OverrideLogicCategory.String)]
+ [Evaluatable("String Comparison", category: EvaluatableCategory.String)]
public class StringComparison : IEvaluatable {
// Operands and operator
@@ -19,17 +20,10 @@ public class StringComparison : IEvaluatable {
public bool CaseInsensitive { get; set; } = false;
// Control allowing the user to edit the comparison
- [Newtonsoft.Json.JsonIgnore]
- private Control_BinaryOperationHolder control;
- public Visual GetControl(Application application) {
- if (control == null) {
- control = new Control_BinaryOperationHolder(application, EvaluatableType.String, typeof(StringComparisonOperator));
- control.SetBinding(Control_BinaryOperationHolder.Operand1Property, new Binding("Operand1") { Source = this, Mode = BindingMode.TwoWay });
- control.SetBinding(Control_BinaryOperationHolder.Operand2Property, new Binding("Operand2") { Source = this, Mode = BindingMode.TwoWay });
- control.SetBinding(Control_BinaryOperationHolder.SelectedOperatorProperty, new Binding("Operator") { Source = this, Mode = BindingMode.TwoWay });
- }
- return control;
- }
+ public Visual GetControl() => new Control_BinaryOperationHolder(typeof(string), typeof(StringComparisonOperator))
+ .WithBinding(Control_BinaryOperationHolder.Operand1Property, new Binding("Operand1") { Source = this, Mode = BindingMode.TwoWay })
+ .WithBinding(Control_BinaryOperationHolder.Operand2Property, new Binding("Operand2") { Source = this, Mode = BindingMode.TwoWay })
+ .WithBinding(Control_BinaryOperationHolder.SelectedOperatorProperty, new Binding("Operator") { Source = this, Mode = BindingMode.TwoWay });
/// Compares the two strings with the given operator
public bool Evaluate(IGameState gameState) {
@@ -57,13 +51,6 @@ public bool Evaluate(IGameState gameState) {
}
object IEvaluatable.Evaluate(IGameState gameState) => Evaluate(gameState);
- /// Updates the application for this IEvaluatable.
- public void SetApplication(Application application) {
- control?.SetApplication(application);
- Operand1?.SetApplication(application);
- Operand2?.SetApplication(application);
- }
-
/// Clones this StringComparison.
public IEvaluatable Clone() => new StringComparison { Operand1 = Operand1.Clone(), Operand2 = Operand2.Clone(), Operator = Operator, CaseInsensitive = CaseInsensitive };
IEvaluatable IEvaluatable.Clone() => Clone();
diff --git a/Project-Aurora/Project-Aurora/Utils/AttachedApplication.cs b/Project-Aurora/Project-Aurora/Utils/AttachedApplication.cs
new file mode 100644
index 000000000..d1445d5ea
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/Utils/AttachedApplication.cs
@@ -0,0 +1,31 @@
+using System.Windows;
+using System.Windows.Data;
+using Application = Aurora.Profiles.Application;
+
+namespace Aurora.Utils {
+
+ ///
+ /// This class exposes an inherited attached property which allows for providing a cascading application context down to subcontrols.
+ ///
+ public static class AttachedApplication {
+
+ /* This attached property allows for providing a cascading application context down to subcontrols.
+ * Also provides a binding that will automatically provide this value to relevant dependency properties. */
+ public static Application GetApplication(DependencyObject obj) => (Application)obj.GetValue(ApplicationProperty);
+ public static void SetApplication(DependencyObject obj, Application value) => obj.SetValue(ApplicationProperty, value);
+
+ // Using a DependencyProperty as the backing store for Application. This enables animation, styling, binding, etc...
+ public static readonly DependencyProperty ApplicationProperty =
+ DependencyProperty.RegisterAttached("Application", typeof(Application), typeof(AttachedApplication), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior | FrameworkPropertyMetadataOptions.AffectsRender));
+ }
+
+ ///
+ /// A binding that automatically sets itself up to be bound to the attached property.
+ ///
+ public class AttachedApplicationBinding : Binding {
+ public AttachedApplicationBinding() {
+ Path = new PropertyPath("(0)", AttachedApplication.ApplicationProperty);
+ RelativeSource = new RelativeSource(RelativeSourceMode.Self);
+ }
+ }
+}
diff --git a/Project-Aurora/Project-Aurora/Utils/CollectionUtils.cs b/Project-Aurora/Project-Aurora/Utils/CollectionUtils.cs
new file mode 100644
index 000000000..caaefe759
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/Utils/CollectionUtils.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+
+namespace Aurora.Utils {
+
+ public static class CollectionUtils {
+
+ ///
+ /// Creates a deep clone of an observable collection by cloning all individual items in the collection also.
+ ///
+ public static ObservableCollection Clone(this ObservableCollection source) where T : ICloneable =>
+ new ObservableCollection(
+ source.Select(c => (T)c.Clone())
+ );
+ }
+
+}
diff --git a/Project-Aurora/Project-Aurora/Utils/DragBehaviour.cs b/Project-Aurora/Project-Aurora/Utils/DragBehaviour.cs
new file mode 100644
index 000000000..40b40fde9
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/Utils/DragBehaviour.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Windows;
+using System.Windows.Input;
+
+namespace Aurora.Utils {
+
+ ///
+ /// Behaviour that can be attached to any to provide an event that fires when the user clicks and drags the element
+ /// with their left mouse button. Abstracts away the code to store initial mouse position and calculate different to current mouse position.
+ ///
+ public static class DragBehaviour {
+
+ #region MouseDownStart Property
+ // Property that is attached to an element and used to determine the location the mouse when down relative to the element to decide whether or not to
+ // initiate a drag. This is private as there is no need for this to be accessed outside of the StartDrag property.
+ private static Point? GetMouseDownStart(DependencyObject obj) => (Point?)obj.GetValue(MouseDownStartProperty);
+ private static void SetMouseDownStart(DependencyObject obj, Point? value) => obj.SetValue(MouseDownStartProperty, value);
+ private static readonly DependencyProperty MouseDownStartProperty =
+ DependencyProperty.RegisterAttached("MouseDownStart", typeof(Point?), typeof(DragBehaviour), new PropertyMetadata(null));
+ #endregion
+
+ #region StartDrag Property
+ /// Sets the event that will be called when the target element is pressed and the mosue moved a minimum distance as set by the system parameters.
+ /// The target element which the event should be attached to.
+ public static StartDragEventHandler GetStartDrag(DependencyObject obj) => (StartDragEventHandler)obj.GetValue(StartDragProperty);
+ /// Sets the event that will be called when the target element is pressed and the mosue moved a minimum distance as set by the system parameters.
+ /// The target element which the event should be attached to. Has no effect on DependencyProperties that are not s.
+ /// The event that will be called when the drag is started.
+ public static void SetStartDrag(DependencyObject obj, StartDragEventHandler value) => obj.SetValue(StartDragProperty, value);
+
+ /// This property represents an event that is fired when the drag is initiated.
+ public static readonly DependencyProperty StartDragProperty =
+ DependencyProperty.RegisterAttached("StartDrag", typeof(StartDragEventHandler), typeof(DragBehaviour), new UIPropertyMetadata(null, StartDragPropertyChanged));
+
+ /// Callback that will update the event handlers on the target when the StartDrag property is changed.
+ private static void StartDragPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) {
+ // Can only add MouseEvents onto UIElements.
+ if (!(sender is UIElement el)) return;
+
+ // If there is not an existing DragEvent, but there is one being set, add the relevant event listeners.
+ if (e.OldValue == null && e.NewValue != null) {
+ el.MouseLeftButtonDown += DependencyTarget_MouseLeftButtonDown;
+ el.MouseMove += DependencyTarget_MouseMove;
+ el.MouseLeftButtonUp += DependencyTarget_MouseLeftButtonUp;
+
+ // If there is an existing DragEvent, but the new one is now null, remove the relevant event listeners.
+ } else if (e.OldValue != null && e.NewValue == null) {
+ el.MouseLeftButtonDown -= DependencyTarget_MouseLeftButtonDown;
+ el.MouseMove -= DependencyTarget_MouseMove;
+ }
+ }
+ #endregion
+
+ #region Events
+ /// LeftButtonDown event for any elements who have the behaviour attached. Will set that object's to the
+ /// current position of the mouse relative to the sending object.
+ private static void DependencyTarget_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
+ var trg = (UIElement)sender;
+ e.Handled = true;
+ trg.CaptureMouse(); // Capture mouse to detect mouse movement off the element
+ SetMouseDownStart(trg, e.GetPosition((IInputElement)sender));
+ }
+
+ /// MouseMove event from any elements who have the behaviour attached. Will check if the mouse went down on this element and then if the user
+ /// has dragged the mouse atleast the distance set by the system parameters. If both these cases are true, fires a single StartDrag event.
+ private static void DependencyTarget_MouseMove(object sender, MouseEventArgs e) {
+ var @do = (UIElement)sender;
+ var p = GetMouseDownStart(@do);
+ var delta = e.GetPosition((IInputElement)sender) - p; // Calculate the distance between the current mouse position and the initial mouse down position
+ // Note that this is a Vector? because one of the operands (GetMouseDownStart) is a Point?, so if the Point is null, this cascades to the Vector.
+
+ // If this element is being dragged (delta will be null if not), then check the delta is atleast as much as the system parameters
+ if (delta != null && (Math.Abs(delta.Value.X) > SystemParameters.MinimumHorizontalDragDistance || Math.Abs(delta.Value.Y) > SystemParameters.MinimumVerticalDragDistance)) {
+ @do.ReleaseMouseCapture();
+ GetStartDrag(@do)?.Invoke(sender, e, p.Value); // Invoke the StartDrag event assigned to the DependecyObject
+ SetMouseDownStart(@do, null); // Set the start mouse point to null to prevent this StartDrag being called again for this drag.
+ }
+ }
+
+ /// LeftButtonUp handler for any elements with the behaviour attached. Will set that object's to null to prevent
+ /// the event from running and triggering a StartDrag event.
+ private static void DependencyTarget_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
+ var trg = (UIElement)sender;
+ trg.ReleaseMouseCapture();
+ SetMouseDownStart(trg, null);
+ }
+ #endregion
+ }
+
+ /// Event handler that is called when the StartDrag event is triggered.
+ /// The object that initiated the event.
+ /// The event of the most recent MouseMove event that caused the StartDrag event to fire.
+ /// The initial mouse location relative to the target when the mouse was originally pressed.
+ public delegate void StartDragEventHandler(object sender, MouseEventArgs e, Point initialPoint);
+}
\ No newline at end of file
diff --git a/Project-Aurora/Project-Aurora/Utils/FrameworkElementUtils.cs b/Project-Aurora/Project-Aurora/Utils/FrameworkElementUtils.cs
index dc1b7f857..9c61999a6 100644
--- a/Project-Aurora/Project-Aurora/Utils/FrameworkElementUtils.cs
+++ b/Project-Aurora/Project-Aurora/Utils/FrameworkElementUtils.cs
@@ -1,11 +1,47 @@
-using System.Windows;
+using System.Collections.Generic;
+using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
+using System.Windows.Media;
namespace Aurora.Utils {
public static class FrameworkElementExtensions {
+ ///
+ /// Performs a breadth-first search from the given element and returns the first found visual child of the target type.
+ /// Returns null if an element of the target type was not found.
+ ///
+ public static T FindChildOfType(this DependencyObject self) where T : DependencyObject {
+ var toSearch = new Queue();
+ toSearch.Enqueue(self);
+ while (toSearch.Count > 0) {
+ var cur = toSearch.Dequeue();
+ for (int i = 0, count = VisualTreeHelper.GetChildrenCount(cur); i < count; i++) {
+ var child = VisualTreeHelper.GetChild(cur, i);
+ if (child is T tChild) return tChild;
+ toSearch.Enqueue(child);
+ }
+ }
+ return null;
+ }
+
+ /// Tests to see if the given 'parent' object is a parent of the given 'child' object (as according to
+ /// ).
+ /// The parent element. Will return true if 'child' is inside this.
+ /// The child element. Will return true if this is inside 'parent'.
+ public static bool IsParentOf(this DependencyObject parent, DependencyObject child) {
+ var cur = child; // Starting at the child,
+ while (cur != null) { // Continuing until we run out of elements
+ if (cur == parent) // If the current item is the parent, return true
+ return true;
+ cur = VisualTreeHelper.GetParent(cur); // Move on to the parent of the current element
+ }
+ return false; // If we ran out of elements, 'parent' is not a parent of 'child'.
+ }
+
+
+ #region Fluent Helper Methods
///
/// Tiny extension for the FrameworkElement that allows to set a binding on an element and return that element (so it can be chained).
/// Used in the TypeControlMap to shorten the code.
@@ -42,5 +78,6 @@ public static T WithChild(this T self, UIElement child, Dock dock) where T :
self.Children.Add(child);
return self;
}
+ #endregion
}
}
diff --git a/Project-Aurora/Project-Aurora/Utils/GameStateUtils.cs b/Project-Aurora/Project-Aurora/Utils/GameStateUtils.cs
index 4142b9ad1..40eb6688a 100644
--- a/Project-Aurora/Project-Aurora/Utils/GameStateUtils.cs
+++ b/Project-Aurora/Project-Aurora/Utils/GameStateUtils.cs
@@ -6,6 +6,7 @@
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
+using GamestateParameterLookup = System.Collections.Generic.Dictionary>;
namespace Aurora.Utils
{
@@ -16,9 +17,9 @@ public static class GameStateUtils
{ typeof(string), true },
};
- public static Dictionary> ReflectGameStateParameters(Type typ)
+ public static GamestateParameterLookup ReflectGameStateParameters(Type typ)
{
- Dictionary> parameters = new Dictionary>();
+ var parameters = new GamestateParameterLookup();
foreach (MemberInfo prop in typ.GetFields().Cast().Concat(typ.GetProperties().Cast()).Concat(typ.GetMethods().Where(m => !m.IsSpecialName).Cast()))
{
@@ -35,6 +36,8 @@ public static Dictionary> ReflectGameStateParameters(T
case MemberTypes.Property:
prop_type = ((PropertyInfo)prop).PropertyType;
break;
+
+ /* Why is this here? There is no way of passing parameters to methods from the game state UI?? */
case MemberTypes.Method:
//if (((MethodInfo)prop).IsSpecialName)
//continue;
@@ -98,10 +101,10 @@ public static Dictionary> ReflectGameStateParameters(T
}
else
{
- Dictionary> sub_params;
+ GamestateParameterLookup sub_params;
if (prop_type == typ)
- sub_params = new Dictionary>(parameters);
+ sub_params = new GamestateParameterLookup(parameters);
else
sub_params = ReflectGameStateParameters(prop_type);
@@ -159,21 +162,14 @@ private static object _RetrieveGameStateParameter(IGameState state, string param
return val;
}
- public static object RetrieveGameStateParameter(IGameState state, string parameter_path, params object[] input_values)
- {
- if (Global.isDebug)
+ public static object RetrieveGameStateParameter(IGameState state, string parameter_path, params object[] input_values) {
+ try {
return _RetrieveGameStateParameter(state, parameter_path, input_values);
- else
- {
- try
- {
- return _RetrieveGameStateParameter(state, parameter_path, input_values);
- }
- catch (Exception exc)
- {
- Global.logger.Error($"Exception: {exc}");
- return null;
- }
+ } catch (Exception exc) {
+ Global.logger.Error($"Exception: {exc}");
+ if (Global.isDebug)
+ throw exc;
+ return null;
}
}
@@ -186,10 +182,8 @@ public static double TryGetDoubleFromState(IGameState state, string path) {
if (!double.TryParse(path, out double value) && !string.IsNullOrWhiteSpace(path)) {
try {
value = Convert.ToDouble(RetrieveGameStateParameter(state, path));
- } catch (Exception exc) {
+ } catch {
value = 0;
- if (Global.isDebug)
- throw exc;
}
}
return value;
@@ -223,11 +217,37 @@ public static Enum TryGetEnumFromState(IGameState state, string path) {
}
- /*public static object RetrieveGameStateParameter(GameState state, string parameter_path, Type type = null)
- {
+ #region ParameterLookup Extensions
+ ///
+ /// Checks if the given parameter is valid for the current parameter lookup.
+ ///
+ public static bool IsValidParameter(this GamestateParameterLookup parameterLookup, string parameter)
+ => parameterLookup.ContainsKey(parameter);
+ ///
+ /// Checks if the given parameter is valid for the current parameter lookup and if the type of parameter matches the given .
+ ///
+ public static bool IsValidParameter(this GamestateParameterLookup parameterLookup, string parameter, PropertyType type)
+ => parameterLookup.TryGetValue(parameter, out var paramDef) && PropertyTypePredicate[type](new KeyValuePair>(parameter, paramDef));
- return null;
- }*/
+ ///
+ /// Gets a list of the names of all variables (inc. path) of a certain type in this parameter map.
+ ///
+ public static IEnumerable GetParameters(this GamestateParameterLookup parameterLookup, PropertyType type)
+ => parameterLookup.Where(PropertyTypePredicate[type]).Select(kvp => kvp.Key);
+
+ static Dictionary>, bool>> PropertyTypePredicate { get; } = new Dictionary>, bool>> {
+ [PropertyType.None] = _ => false,
+ [PropertyType.Number] = kvp => TypeUtils.IsNumericType(kvp.Value.Item1),
+ [PropertyType.Boolean] = kvp => Type.GetTypeCode(kvp.Value.Item1) == TypeCode.Boolean,
+ [PropertyType.String] = kvp => Type.GetTypeCode(kvp.Value.Item1) == TypeCode.String,
+ [PropertyType.Enum] = kvp => kvp.Value.Item1.IsEnum
+ };
+ #endregion
}
+
+ ///
+ /// Available types that can be handled by the gamestate parameter methods.
+ ///
+ public enum PropertyType { None, Number, Boolean, String, Enum }
}
diff --git a/Project-Aurora/Project-Aurora/Utils/GridHelper.cs b/Project-Aurora/Project-Aurora/Utils/GridHelper.cs
new file mode 100644
index 000000000..74027b752
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/Utils/GridHelper.cs
@@ -0,0 +1,49 @@
+using System.ComponentModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Aurora.Utils {
+
+ ///
+ /// A GridHelper utility that provides "Columns" and "Rows" attached properties to the Grid control.
+ /// These properties are shorthand for adding row and column definitions.
+ /// Usage: <Grid u:GridHelper.Columns="Auto,1*,2*,30px">
+ ///
+ public class GridHelper {
+
+ // Converter that XAML uses to convert a string to a GridLength (e.g. "1*", "230px", "Auto", etc)
+ private static TypeConverter gridLengthConverter = TypeDescriptor.GetConverter(typeof(GridLength));
+ private static GridLength ParseGridLength(string s) => (GridLength)gridLengthConverter.ConvertFromString(s);
+
+
+ // Columns
+ public static string GetColumns(DependencyObject obj) => (string)obj.GetValue(ColumnsProperty);
+ public static void SetColumns(DependencyObject obj, string value) => obj.SetValue(ColumnsProperty, value);
+
+ public static readonly DependencyProperty ColumnsProperty =
+ DependencyProperty.RegisterAttached("Columns", typeof(string), typeof(GridHelper), new PropertyMetadata("", ColumnsChanged));
+
+ public static void ColumnsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
+ if (!(obj is Grid grid)) return;
+ grid.ColumnDefinitions.Clear();
+ foreach (var str in ((string)e.NewValue).Split(','))
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = ParseGridLength(str) });
+ }
+
+
+ // Rows
+ public static string GetRows(DependencyObject obj) => (string)obj.GetValue(RowsProperty);
+ public static void SetRows(DependencyObject obj, string value) => obj.SetValue(RowsProperty, value);
+
+ public static readonly DependencyProperty RowsProperty =
+ DependencyProperty.RegisterAttached("Rows", typeof(string), typeof(GridHelper), new PropertyMetadata("", RowsChanged));
+
+ public static void RowsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
+ if (!(obj is Grid grid)) return;
+ grid.RowDefinitions.Clear();
+ foreach (var str in ((string)e.NewValue).Split(','))
+ grid.RowDefinitions.Add(new RowDefinition { Height = ParseGridLength(str) });
+ }
+ }
+}
diff --git a/Project-Aurora/Project-Aurora/Utils/ObservableConcurrentDictionary.cs b/Project-Aurora/Project-Aurora/Utils/ObservableConcurrentDictionary.cs
new file mode 100644
index 000000000..39cce7f32
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/Utils/ObservableConcurrentDictionary.cs
@@ -0,0 +1,172 @@
+//--------------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+// File: ObservableConcurrentDictionary.cs
+//
+//--------------------------------------------------------------------------
+
+
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Threading;
+using System.Diagnostics;
+
+namespace System.Collections.Concurrent {
+ ///
+ /// Provides a thread-safe dictionary for use with data binding.
+ ///
+ /// Specifies the type of the keys in this collection.
+ /// Specifies the type of the values in this collection.
+ [DebuggerDisplay("Count={Count}")]
+ public class ObservableConcurrentDictionary :
+ ICollection>, IDictionary,
+ INotifyCollectionChanged, INotifyPropertyChanged {
+ private readonly SynchronizationContext _context;
+ private readonly ConcurrentDictionary _dictionary;
+
+ ///
+ /// Initializes an instance of the ObservableConcurrentDictionary class.
+ ///
+ public ObservableConcurrentDictionary() {
+ _context = AsyncOperationManager.SynchronizationContext;
+ _dictionary = new ConcurrentDictionary();
+ }
+
+ /// Event raised when the collection changes.
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+ /// Event raised when a property on the collection changes.
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ ///
+ /// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary.
+ ///
+ private void NotifyObserversOfChange() {
+ var collectionHandler = CollectionChanged;
+ var propertyHandler = PropertyChanged;
+ if (collectionHandler != null || propertyHandler != null) {
+ _context.Post(s => {
+ if (collectionHandler != null) {
+ collectionHandler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
+ }
+ if (propertyHandler != null) {
+ propertyHandler(this, new PropertyChangedEventArgs("Count"));
+ propertyHandler(this, new PropertyChangedEventArgs("Keys"));
+ propertyHandler(this, new PropertyChangedEventArgs("Values"));
+ }
+ }, null);
+ }
+ }
+
+ /// Attempts to add an item to the dictionary, notifying observers of any changes.
+ /// The item to be added.
+ /// Whether the add was successful.
+ private bool TryAddWithNotification(KeyValuePair item) {
+ return TryAddWithNotification(item.Key, item.Value);
+ }
+
+ /// Attempts to add an item to the dictionary, notifying observers of any changes.
+ /// The key of the item to be added.
+ /// The value of the item to be added.
+ /// Whether the add was successful.
+ private bool TryAddWithNotification(TKey key, TValue value) {
+ bool result = _dictionary.TryAdd(key, value);
+ if (result) NotifyObserversOfChange();
+ return result;
+ }
+
+ /// Attempts to remove an item from the dictionary, notifying observers of any changes.
+ /// The key of the item to be removed.
+ /// The value of the item removed.
+ /// Whether the removal was successful.
+ private bool TryRemoveWithNotification(TKey key, out TValue value) {
+ bool result = _dictionary.TryRemove(key, out value);
+ if (result) NotifyObserversOfChange();
+ return result;
+ }
+
+ /// Attempts to add or update an item in the dictionary, notifying observers of any changes.
+ /// The key of the item to be updated.
+ /// The new value to set for the item.
+ /// Whether the update was successful.
+ private void UpdateWithNotification(TKey key, TValue value) {
+ _dictionary[key] = value;
+ NotifyObserversOfChange();
+ }
+
+ #region ICollection> Members
+ void ICollection>.Add(KeyValuePair item) {
+ TryAddWithNotification(item);
+ }
+
+ void ICollection>.Clear() {
+ ((ICollection>)_dictionary).Clear();
+ NotifyObserversOfChange();
+ }
+
+ bool ICollection>.Contains(KeyValuePair item) {
+ return ((ICollection>)_dictionary).Contains(item);
+ }
+
+ void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) {
+ ((ICollection>)_dictionary).CopyTo(array, arrayIndex);
+ }
+
+ int ICollection>.Count {
+ get { return ((ICollection>)_dictionary).Count; }
+ }
+
+ bool ICollection>.IsReadOnly {
+ get { return ((ICollection>)_dictionary).IsReadOnly; }
+ }
+
+ bool ICollection>.Remove(KeyValuePair item) {
+ TValue temp;
+ return TryRemoveWithNotification(item.Key, out temp);
+ }
+ #endregion
+
+ #region IEnumerable> Members
+ IEnumerator> IEnumerable>.GetEnumerator() {
+ return ((ICollection>)_dictionary).GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() {
+ return ((ICollection>)_dictionary).GetEnumerator();
+ }
+ #endregion
+
+ #region IDictionary Members
+ public void Add(TKey key, TValue value) {
+ TryAddWithNotification(key, value);
+ }
+
+ public bool ContainsKey(TKey key) {
+ return _dictionary.ContainsKey(key);
+ }
+
+ public ICollection Keys {
+ get { return _dictionary.Keys; }
+ }
+
+ public bool Remove(TKey key) {
+ TValue temp;
+ return TryRemoveWithNotification(key, out temp);
+ }
+
+ public bool TryGetValue(TKey key, out TValue value) {
+ return _dictionary.TryGetValue(key, out value);
+ }
+
+ public ICollection Values {
+ get { return _dictionary.Values; }
+ }
+
+ public TValue this[TKey key] {
+ get { return _dictionary[key]; }
+ set { UpdateWithNotification(key, value); }
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Project-Aurora/Project-Aurora/Utils/TextUtils.cs b/Project-Aurora/Project-Aurora/Utils/TextUtils.cs
new file mode 100644
index 000000000..5fdabbf13
--- /dev/null
+++ b/Project-Aurora/Project-Aurora/Utils/TextUtils.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Text;
+
+namespace Aurora.Utils {
+
+ public static class TextUtils {
+
+ ///
+ /// Converts a string from camel case to space case.
+ ///
+ public static string CamelCaseToSpaceCase(this string camelCase) {
+ // What the shit have I even made? Seems to work well tho.
+ if (camelCase.Length <= 2) return camelCase;
+ var sb = new StringBuilder();
+
+ // Determines if the character at the given position is uppercase
+ // If out of range or a number, keeps the case of the previous letter.
+ bool isUpper(int i) {
+ i = Math.Min(i, camelCase.Length - 1);
+ var c = camelCase[i];
+ return c >= '0' && c <= '9'
+ ? isUpper(i - 1)
+ : c >= 'A' && c <= 'X';
+ }
+
+ // Iterate through all the characters in the string and insert a space at boundaries
+ // between uppercase and lowercase letters.
+ bool prevUp = isUpper(0), curUp = isUpper(1), nextUp;
+ sb.Append(camelCase[0]);
+ for (var i = 1; i < camelCase.Length; i++) {
+ nextUp = isUpper(i + 1);
+ if ((!prevUp && curUp) || (curUp && !nextUp))
+ sb.Append(" ");
+ sb.Append(camelCase[i]);
+ prevUp = curUp;
+ curUp = nextUp;
+ }
+ return sb.ToString();
+ }
+ }
+}
diff --git a/Project-Aurora/Project-Aurora/Utils/TypeUtils.cs b/Project-Aurora/Project-Aurora/Utils/TypeUtils.cs
index 7d95a67a4..531caf319 100644
--- a/Project-Aurora/Project-Aurora/Utils/TypeUtils.cs
+++ b/Project-Aurora/Project-Aurora/Utils/TypeUtils.cs
@@ -51,9 +51,35 @@ public static IEnumerable> GetTypesWithCustomAttribute(
.Where(kvp => kvp.Value != null); // Filter out any where the attribute is null (i.e. doesn't exist)
}
- ///
- /// Checks if the given type implements the given interface type.
- ///
- public static bool IsInterface(Type type, Type @interface) => @interface.IsAssignableFrom(type);
+
+ /// Checks whether the given 'this' type extends the given *generic* interface, regardless of type parameters.
+ /// The type that will be checked to see if ti implements the given interface.
+ /// The type of generic interface to check if 'type' extends. Does not account for type parameters.
+ public static bool ImplementsGenericInterface(this Type type, Type interfaceType) =>
+ type.GetInterfaces().Select(i => i.IsGenericType ? i.GetGenericTypeDefinition() : i).Contains(interfaceType);
+
+ /// Checks whether the given 'this' type extends the given *generic* interface with the given type parameters.
+ /// The type that will be checked to see if it implements the given interface.
+ /// The type of generic interface to check if 'type' extends.
+ /// An array of types to check against the type parameters in the generic interface.
+ /// Note that the length of this array must match the number of type parameters in the target interface.
+ public static bool ImplementsGenericInterface(Type type, Type interfaceType, params Type[] interfaceGenericParameters) =>
+ type.GetInterfaces()
+ .Where(i => i.IsGenericType)
+ .SingleOrDefault(i => i.GetGenericTypeDefinition() == interfaceType)?
+ .GetGenericArguments()
+ .Select((arg, i) => arg == interfaceGenericParameters[i])
+ .All(v => v)
+ ?? false;
+
+ /// Gets the generic argument types of the given interface for the given type.
+ /// The type to check interfaces on.
+ /// The type of interface whose generic parameters to fetch.
+ /// An array containing all the types of generic parameters defined for this type on the given interface, or null if interface not found.
+ public static Type[] GetGenericInterfaceTypes(this Type type, Type interfaceType) =>
+ type.GetInterfaces()
+ .Where(i => i.IsGenericType)
+ .SingleOrDefault(i => i.GetGenericTypeDefinition() == interfaceType)?
+ .GetGenericArguments();
}
}