Add project files.
This commit is contained in:
15
Examples/Nodify.StateMachine/App.xaml
Normal file
15
Examples/Nodify.StateMachine/App.xaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<Application x:Class="Nodify.StateMachine.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/Light.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify.Shared;component/Themes/Icons.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify.Shared;component/Themes/Light.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify.StateMachine;component/Themes/Light.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
17
Examples/Nodify.StateMachine/App.xaml.cs
Normal file
17
Examples/Nodify.StateMachine/App.xaml.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
// Condition or Action reference
|
||||
public class BlackboardItemReferenceViewModel
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public Type? Type { get; set; }
|
||||
}
|
||||
}
|
||||
51
Examples/Nodify.StateMachine/BlackboardItemViewModel.cs
Normal file
51
Examples/Nodify.StateMachine/BlackboardItemViewModel.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class BlackboardItemViewModel : ObservableObject
|
||||
{
|
||||
private string? _name;
|
||||
public string? Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetProperty(ref _name, value);
|
||||
}
|
||||
|
||||
private Type? _type;
|
||||
public Type? Type
|
||||
{
|
||||
get => _type;
|
||||
set => SetProperty(ref _type, value);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<BlackboardKeyViewModel> _input = new NodifyObservableCollection<BlackboardKeyViewModel>();
|
||||
public NodifyObservableCollection<BlackboardKeyViewModel> Input
|
||||
{
|
||||
get => _input;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
value = new NodifyObservableCollection<BlackboardKeyViewModel>();
|
||||
}
|
||||
|
||||
SetProperty(ref _input!, value);
|
||||
}
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<BlackboardKeyViewModel> _output = new NodifyObservableCollection<BlackboardKeyViewModel>();
|
||||
public NodifyObservableCollection<BlackboardKeyViewModel> Output
|
||||
{
|
||||
get => _output;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
value = new NodifyObservableCollection<BlackboardKeyViewModel>();
|
||||
}
|
||||
|
||||
SetProperty(ref _output!, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
152
Examples/Nodify.StateMachine/BlackboardKeyEditorView.xaml
Normal file
152
Examples/Nodify.StateMachine/BlackboardKeyEditorView.xaml
Normal file
@@ -0,0 +1,152 @@
|
||||
<UserControl x:Class="Nodify.StateMachine.BlackboardKeyEditorView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Nodify.StateMachine"
|
||||
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance Type={x:Type local:BlackboardKeyEditorViewModel}, IsDesignTimeCreatable=True}"
|
||||
d:Background="{DynamicResource PanelBackgroundBrush}"
|
||||
d:DesignWidth="400">
|
||||
<UserControl.Resources>
|
||||
<DataTemplate x:Key="BooleanTemplate"
|
||||
DataType="{x:Type local:BlackboardKeyEditorViewModel}">
|
||||
<CheckBox IsChecked="{Binding Target.Value}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="IntegerTemplate"
|
||||
DataType="{x:Type local:BlackboardKeyEditorViewModel}">
|
||||
<TextBox Text="{Binding Target.Value, UpdateSourceTrigger=LostFocus}" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="DoubleTemplate"
|
||||
DataType="{x:Type local:BlackboardKeyEditorViewModel}">
|
||||
<TextBox Text="{Binding Target.Value, UpdateSourceTrigger=LostFocus}" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="StringTemplate"
|
||||
DataType="{x:Type local:BlackboardKeyEditorViewModel}">
|
||||
<TextBox Text="{Binding Target.Value, UpdateSourceTrigger=LostFocus}" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="ObjectTemplate"
|
||||
DataType="{x:Type local:BlackboardKeyEditorViewModel}">
|
||||
<TextBox Text="{Binding Target.Value, UpdateSourceTrigger=LostFocus}"
|
||||
IsEnabled="False" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="KeyTemplate"
|
||||
DataType="{x:Type local:BlackboardKeyEditorViewModel}">
|
||||
<ComboBox SelectedItem="{Binding Target.Value}"
|
||||
DisplayMemberPath="Name">
|
||||
<ComboBox.ItemsSource>
|
||||
<MultiBinding Converter="{local:FilterBlackboardKeysConverter}">
|
||||
<Binding Path="AvailableKeys" />
|
||||
<Binding Path="Target.Type" />
|
||||
<!--USED TO NOTIFY OF COLLECTION CHANGED-->
|
||||
<Binding Path="AvailableKeys.Count" />
|
||||
</MultiBinding>
|
||||
</ComboBox.ItemsSource>
|
||||
</ComboBox>
|
||||
</DataTemplate>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="KeyName" />
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="KeyType" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<shared:EditableTextBlock Text="{Binding Target.Name}"
|
||||
d:Text="My blackboard key"
|
||||
IsEditing="{Binding IsEditing}"
|
||||
Foreground="{DynamicResource ForegroundBrush}"
|
||||
VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center"
|
||||
Margin="1 1 5 1" />
|
||||
|
||||
<ComboBox ItemsSource="{Binding Target.Type, Converter={shared:EnumValuesConverter}}"
|
||||
IsEnabled="{Binding CanChangeKeyType}"
|
||||
SelectedValue="{Binding Target.Type}"
|
||||
SelectedValuePath="Value"
|
||||
DisplayMemberPath="Name"
|
||||
Grid.Column="1"
|
||||
Margin="0 0 5 0" />
|
||||
|
||||
<Grid Grid.Column="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MaxWidth="150" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentControl Content="{Binding}">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="{x:Type ContentControl}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Target.Type}"
|
||||
Value="Boolean">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource BooleanTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Target.Type}"
|
||||
Value="Integer">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource IntegerTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Target.Type}"
|
||||
Value="Double">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource DoubleTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Target.Type}"
|
||||
Value="String">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource StringTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Target.Type}"
|
||||
Value="Object">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource ObjectTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Target.Type}"
|
||||
Value="Key">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource KeyTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Target.ValueIsKey}"
|
||||
Value="True">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource KeyTemplate}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
|
||||
<CheckBox Visibility="{Binding CanChangeInputType, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
IsChecked="{Binding Target.ValueIsKey}"
|
||||
ToolTip="Toggle input type"
|
||||
Grid.Column="1">
|
||||
<CheckBox.Style>
|
||||
<Style TargetType="{x:Type CheckBox}"
|
||||
BasedOn="{StaticResource IconCheckBox}">
|
||||
<Setter Property="Content"
|
||||
Value="{StaticResource DiamondIcon}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsChecked"
|
||||
Value="True">
|
||||
<Setter Property="Content"
|
||||
Value="{StaticResource DiamondFillIcon}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</CheckBox.Style>
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
12
Examples/Nodify.StateMachine/BlackboardKeyEditorView.xaml.cs
Normal file
12
Examples/Nodify.StateMachine/BlackboardKeyEditorView.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public partial class BlackboardKeyEditorView : UserControl
|
||||
{
|
||||
public BlackboardKeyEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Examples/Nodify.StateMachine/BlackboardKeyEditorViewModel.cs
Normal file
42
Examples/Nodify.StateMachine/BlackboardKeyEditorViewModel.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class BlackboardKeyEditorViewModel : ObservableObject
|
||||
{
|
||||
private ICollection<BlackboardKeyViewModel>? _availableKeys;
|
||||
public ICollection<BlackboardKeyViewModel>? AvailableKeys
|
||||
{
|
||||
get => _availableKeys;
|
||||
set => SetProperty(ref _availableKeys, value);
|
||||
}
|
||||
|
||||
private BlackboardKeyViewModel? _target;
|
||||
public BlackboardKeyViewModel? Target
|
||||
{
|
||||
get => _target;
|
||||
set => SetProperty(ref _target, value);
|
||||
}
|
||||
|
||||
private bool _canChangeInputType;
|
||||
public bool CanChangeInputType
|
||||
{
|
||||
get => _canChangeInputType;
|
||||
set => SetProperty(ref _canChangeInputType, value);
|
||||
}
|
||||
|
||||
private bool _canChangeKeyType = true;
|
||||
public bool CanChangeKeyType
|
||||
{
|
||||
get => _canChangeKeyType;
|
||||
set => SetProperty(ref _canChangeKeyType, value);
|
||||
}
|
||||
|
||||
private bool _isEditing;
|
||||
public bool IsEditing
|
||||
{
|
||||
get => _isEditing;
|
||||
set => SetProperty(ref _isEditing, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
107
Examples/Nodify.StateMachine/BlackboardKeyViewModel.cs
Normal file
107
Examples/Nodify.StateMachine/BlackboardKeyViewModel.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class BlackboardKeyViewModel : ObservableObject
|
||||
{
|
||||
// Cache the key and the input value so we can restore them when swapping input types
|
||||
private readonly Dictionary<bool, object?> _values = new Dictionary<bool, object?>();
|
||||
|
||||
public string? PropertyName { get; set; }
|
||||
|
||||
private string _name = "New key";
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
SetProperty(ref _name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BlackboardKeyType _type;
|
||||
public BlackboardKeyType Type
|
||||
{
|
||||
get => _type;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _type, value))
|
||||
{
|
||||
Value = GetDefaultValue(_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object? _value = BoxValue.False;
|
||||
public object? Value
|
||||
{
|
||||
get => _value;
|
||||
set => SetProperty(ref _value, GetRealValue(value)).Then(() => _values[ValueIsKey] = Value);
|
||||
}
|
||||
|
||||
private bool _valueIsKey;
|
||||
public bool ValueIsKey
|
||||
{
|
||||
get => _valueIsKey;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _valueIsKey, value) && _values.TryGetValue(_valueIsKey, out var existingValue))
|
||||
{
|
||||
Value = existingValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _canChangeType = true;
|
||||
public bool CanChangeType
|
||||
{
|
||||
get => _canChangeType;
|
||||
set => SetProperty(ref _canChangeType, value);
|
||||
}
|
||||
|
||||
private object? GetRealValue(object? value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case BlackboardKeyType.Boolean:
|
||||
bool.TryParse(str, out var b);
|
||||
value = b;
|
||||
break;
|
||||
|
||||
case BlackboardKeyType.Integer:
|
||||
int.TryParse(str, out var i);
|
||||
value = i;
|
||||
break;
|
||||
|
||||
case BlackboardKeyType.Double:
|
||||
double.TryParse(str, out var d);
|
||||
value = d;
|
||||
break;
|
||||
|
||||
case BlackboardKeyType.String:
|
||||
case BlackboardKeyType.Object:
|
||||
value = str;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static object? GetDefaultValue(BlackboardKeyType type)
|
||||
=> type switch
|
||||
{
|
||||
BlackboardKeyType.Boolean => BoxValue.False,
|
||||
BlackboardKeyType.Integer => BoxValue.Int0,
|
||||
BlackboardKeyType.Double => BoxValue.Double0,
|
||||
BlackboardKeyType.String => null,
|
||||
BlackboardKeyType.Object => null,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
47
Examples/Nodify.StateMachine/BlackboardViewModel.cs
Normal file
47
Examples/Nodify.StateMachine/BlackboardViewModel.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class BlackboardViewModel : ObservableObject
|
||||
{
|
||||
private NodifyObservableCollection<BlackboardKeyViewModel> _keys = new NodifyObservableCollection<BlackboardKeyViewModel>();
|
||||
public NodifyObservableCollection<BlackboardKeyViewModel> Keys
|
||||
{
|
||||
get => _keys;
|
||||
set => SetProperty(ref _keys, value);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<BlackboardItemReferenceViewModel> _actions = new NodifyObservableCollection<BlackboardItemReferenceViewModel>();
|
||||
public NodifyObservableCollection<BlackboardItemReferenceViewModel> Actions
|
||||
{
|
||||
get => _actions;
|
||||
set => SetProperty(ref _actions, value);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<BlackboardItemReferenceViewModel> _conditions = new NodifyObservableCollection<BlackboardItemReferenceViewModel>();
|
||||
public NodifyObservableCollection<BlackboardItemReferenceViewModel> Conditions
|
||||
{
|
||||
get => _conditions;
|
||||
set => SetProperty(ref _conditions, value);
|
||||
}
|
||||
|
||||
public INodifyCommand AddKeyCommand { get; }
|
||||
public INodifyCommand RemoveKeyCommand { get; }
|
||||
|
||||
public BlackboardViewModel()
|
||||
{
|
||||
AddKeyCommand = new DelegateCommand(() => Keys.Add(new BlackboardKeyViewModel
|
||||
{
|
||||
Name = "New Key "
|
||||
}));
|
||||
|
||||
RemoveKeyCommand = new DelegateCommand<BlackboardKeyViewModel>(key => Keys.Remove(key));
|
||||
|
||||
Keys.WhenAdded(key =>
|
||||
{
|
||||
var existingKeyNames = Keys.Where(k => k != key).Select(k => k.Name).ToList();
|
||||
key.Name = existingKeyNames.GetUnique(key.Name);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class BlackboardKeyEditorConverter : MarkupExtension, IMultiValueConverter
|
||||
{
|
||||
public bool CanChangeInputType { get; set; }
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length >= 2 && values[0] is ICollection<BlackboardKeyViewModel> availableKeys && values[1] is BlackboardKeyViewModel target)
|
||||
{
|
||||
return new BlackboardKeyEditorViewModel
|
||||
{
|
||||
AvailableKeys = availableKeys,
|
||||
Target = target,
|
||||
IsEditing = values.Length >= 3 && values[2] is bool b && b,
|
||||
CanChangeInputType = CanChangeInputType && (target.Type != BlackboardKeyType.Object || target.CanChangeType),
|
||||
CanChangeKeyType = target.CanChangeType
|
||||
};
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider) => this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class ConnectorOffsetConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
double offset = System.Convert.ToDouble(parameter);
|
||||
if (value is Size s)
|
||||
{
|
||||
return new Size((s.Width + offset) / 2, (s.Height + offset) / 2);
|
||||
}
|
||||
|
||||
return new Size(offset / 2, offset / 2);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
double offset = System.Convert.ToDouble(parameter);
|
||||
if (value is Size s)
|
||||
{
|
||||
return new Size((s.Width + offset) / 2, (s.Height + offset) / 2);
|
||||
}
|
||||
|
||||
return new Size(offset / 2, offset / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class FilterBlackboardKeysConverter : MarkupExtension, IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length >= 2 && values[0] is IEnumerable<BlackboardKeyViewModel> keys && values[1] is BlackboardKeyType filter)
|
||||
{
|
||||
return keys.Where(k => k.Type == filter || filter == BlackboardKeyType.Object);
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider) => this;
|
||||
}
|
||||
}
|
||||
140
Examples/Nodify.StateMachine/Helpers/BlackboardDescriptor.cs
Normal file
140
Examples/Nodify.StateMachine/Helpers/BlackboardDescriptor.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public static class BlackboardDescriptor
|
||||
{
|
||||
private class KeyDescription
|
||||
{
|
||||
public KeyDescription(string displayName, string propertyName, BlackboardKeyType type, bool canChangeType)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
PropertyName = propertyName;
|
||||
Type = type;
|
||||
CanChangeType = canChangeType;
|
||||
}
|
||||
|
||||
public string DisplayName { get; }
|
||||
public string PropertyName { get; }
|
||||
public BlackboardKeyType Type { get; }
|
||||
public bool CanChangeType { get; }
|
||||
}
|
||||
|
||||
private class ItemDescription
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public List<KeyDescription> Input { get; } = new List<KeyDescription>();
|
||||
public List<KeyDescription> Output { get; } = new List<KeyDescription>();
|
||||
}
|
||||
|
||||
public static BlackboardItemViewModel? GetItem(BlackboardItemReferenceViewModel? actionRef)
|
||||
{
|
||||
if (actionRef?.Type != null)
|
||||
{
|
||||
var description = GetDescription(actionRef.Type);
|
||||
|
||||
var input = description.Input.Select(d => new BlackboardKeyViewModel
|
||||
{
|
||||
Name = d.DisplayName,
|
||||
Type = d.Type,
|
||||
PropertyName = d.PropertyName,
|
||||
CanChangeType = d.CanChangeType,
|
||||
ValueIsKey = true
|
||||
});
|
||||
|
||||
var output = description.Output.Select(d => new BlackboardKeyViewModel
|
||||
{
|
||||
Name = d.DisplayName,
|
||||
Type = d.Type,
|
||||
PropertyName = d.PropertyName,
|
||||
CanChangeType = d.CanChangeType,
|
||||
ValueIsKey = true
|
||||
});
|
||||
|
||||
return new BlackboardItemViewModel
|
||||
{
|
||||
Name = actionRef.Name,
|
||||
Type = actionRef.Type,
|
||||
Input = new NodifyObservableCollection<BlackboardKeyViewModel>(input),
|
||||
Output = new NodifyObservableCollection<BlackboardKeyViewModel>(output),
|
||||
};
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public static BlackboardItemReferenceViewModel GetReference(Type type)
|
||||
{
|
||||
var desc = GetDescription(type);
|
||||
|
||||
return new BlackboardItemReferenceViewModel
|
||||
{
|
||||
Name = desc.Name,
|
||||
Type = type
|
||||
};
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Type, ItemDescription> _descriptions = new Dictionary<Type, ItemDescription>();
|
||||
private static ItemDescription GetDescription(Type type)
|
||||
{
|
||||
if (!_descriptions.TryGetValue(type, out var description))
|
||||
{
|
||||
var actionAttr = type.GetCustomAttribute<BlackboardItemAttribute>();
|
||||
|
||||
var desc = new ItemDescription
|
||||
{
|
||||
Name = actionAttr?.DisplayName ?? type.Name
|
||||
};
|
||||
|
||||
var props = type.GetProperties();
|
||||
for (int i = 0; i < props.Length; i++)
|
||||
{
|
||||
var prop = props[i];
|
||||
var keyAttr = prop.GetCustomAttribute<BlackboardPropertyAttribute>();
|
||||
|
||||
if (keyAttr != null)
|
||||
{
|
||||
var key = new KeyDescription(keyAttr.Name ?? prop.Name, prop.Name, keyAttr.Type, keyAttr.CanChangeType);
|
||||
|
||||
if (keyAttr.Usage == BlackboardKeyUsage.Input)
|
||||
{
|
||||
desc.Input.Add(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
desc.Output.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_descriptions.Add(type, desc);
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
return description;
|
||||
}
|
||||
|
||||
public static List<BlackboardItemReferenceViewModel> GetAvailableItems<T>()
|
||||
{
|
||||
var result = new List<BlackboardItemReferenceViewModel>();
|
||||
var ourType = typeof(T);
|
||||
|
||||
var types = ourType.Assembly.GetTypes();
|
||||
|
||||
for (int i = 0; i < types.Length; i++)
|
||||
{
|
||||
var type = types[i];
|
||||
if (type.IsClass && !type.IsAbstract && ourType.IsAssignableFrom(type) && type.GetCustomAttribute<BlackboardItemAttribute>() != null)
|
||||
{
|
||||
result.Add(GetReference(type));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
744
Examples/Nodify.StateMachine/MainWindow.xaml
Normal file
744
Examples/Nodify.StateMachine/MainWindow.xaml
Normal file
@@ -0,0 +1,744 @@
|
||||
<Window x:Class="Nodify.StateMachine.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Nodify.StateMachine"
|
||||
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||
ResizeMode="CanResizeWithGrip"
|
||||
mc:Ignorable="d"
|
||||
Background="{DynamicResource NodifyEditor.BackgroundBrush}"
|
||||
Foreground="{DynamicResource ForegroundBrush}"
|
||||
Title="State Machine Editor"
|
||||
Height="500"
|
||||
Width="930">
|
||||
<Window.DataContext>
|
||||
<local:StateMachineViewModel />
|
||||
</Window.DataContext>
|
||||
|
||||
<Window.Resources>
|
||||
<shared:BindingProxy x:Key="EditorProxy"
|
||||
DataContext="{Binding}" />
|
||||
<shared:BindingProxy x:Key="BlackboardProxy"
|
||||
DataContext="{Binding Blackboard}" />
|
||||
<local:ConnectorOffsetConverter x:Key="ConnectorOffsetConverter" />
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ScrollViewer CanContentScroll="True"
|
||||
PreviewKeyDown="ScrollViewer_PreviewKeyDown"
|
||||
Grid.Column="1">
|
||||
<nodify:NodifyEditor x:Name="Editor"
|
||||
ItemsSource="{Binding States}"
|
||||
SelectedItem="{Binding SelectedState}"
|
||||
SelectedItems="{Binding SelectedStates}"
|
||||
Connections="{Binding Transitions}"
|
||||
PendingConnection="{Binding PendingTransition}"
|
||||
DisconnectConnectorCommand="{Binding DisconnectStateCommand}"
|
||||
ConnectionCompletedCommand="{Binding CreateTransitionCommand}"
|
||||
RemoveConnectionCommand="{Binding DeleteTransitionCommand}">
|
||||
<nodify:NodifyEditor.PendingConnectionTemplate>
|
||||
<DataTemplate DataType="{x:Type local:TransitionViewModel}">
|
||||
<nodify:PendingConnection Source="{Binding Source, Mode=OneWayToSource}"
|
||||
Target="{Binding Target, Mode=OneWayToSource}"
|
||||
StrokeDashArray=""
|
||||
EnablePreview="True">
|
||||
<nodify:PendingConnection.Template>
|
||||
<ControlTemplate TargetType="{x:Type nodify:PendingConnection}">
|
||||
<nodify:LineConnection Source="{TemplateBinding SourceAnchor}"
|
||||
Target="{TemplateBinding TargetAnchor}"
|
||||
StrokeThickness="{TemplateBinding StrokeThickness}"
|
||||
StrokeDashArray="{TemplateBinding StrokeDashArray}"
|
||||
SourceOffset="{Binding Source.Size, Converter={StaticResource ConnectorOffsetConverter}, ConverterParameter=5}"
|
||||
Spacing="0"
|
||||
SourceOffsetMode="Edge"
|
||||
TargetOffsetMode="None" />
|
||||
</ControlTemplate>
|
||||
</nodify:PendingConnection.Template>
|
||||
</nodify:PendingConnection>
|
||||
</DataTemplate>
|
||||
</nodify:NodifyEditor.PendingConnectionTemplate>
|
||||
|
||||
<nodify:NodifyEditor.ConnectionTemplate>
|
||||
<DataTemplate DataType="{x:Type local:TransitionViewModel}">
|
||||
<nodify:LineConnection Source="{Binding Source.Anchor}"
|
||||
Target="{Binding Target.Anchor}"
|
||||
SourceOffset="{Binding Source.Size, Converter={StaticResource ConnectorOffsetConverter}, ConverterParameter=5}"
|
||||
TargetOffset="{Binding Target.Size, Converter={StaticResource ConnectorOffsetConverter}, ConverterParameter=5}"
|
||||
Spacing="0"
|
||||
SourceOffsetMode="Edge"
|
||||
TargetOffsetMode="Edge"
|
||||
OutlineThickness="5"
|
||||
Tag="{Binding}">
|
||||
<nodify:LineConnection.Style>
|
||||
<Style TargetType="{x:Type nodify:LineConnection}"
|
||||
BasedOn="{StaticResource {x:Type nodify:LineConnection}}">
|
||||
<Setter Property="OutlineBrush"
|
||||
Value="Transparent" />
|
||||
<Setter Property="StrokeThickness"
|
||||
Value="3" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsActive}"
|
||||
Value="True">
|
||||
<Setter Property="Stroke"
|
||||
Value="{DynamicResource ActiveStateBrush}" />
|
||||
<Setter Property="StrokeThickness"
|
||||
Value="6" />
|
||||
</DataTrigger>
|
||||
<Trigger Property="IsMouseOver"
|
||||
Value="True">
|
||||
<Setter Property="OutlineBrush">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="{StaticResource LineConnection.StrokeColor}"
|
||||
Opacity="0.15" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</nodify:LineConnection.Style>
|
||||
<nodify:LineConnection.ContextMenu>
|
||||
<ContextMenu DataContext="{Binding DataContext, Source={StaticResource EditorProxy}}">
|
||||
<MenuItem Header="_Delete"
|
||||
Icon="{StaticResource DeleteIcon}"
|
||||
Command="{Binding DeleteTransitionCommand}"
|
||||
CommandParameter="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
|
||||
</ContextMenu>
|
||||
</nodify:LineConnection.ContextMenu>
|
||||
</nodify:LineConnection>
|
||||
</DataTemplate>
|
||||
</nodify:NodifyEditor.ConnectionTemplate>
|
||||
|
||||
<nodify:NodifyEditor.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:StateViewModel}">
|
||||
<!--If IsConnected is false, Anchor won't be updated-->
|
||||
<nodify:StateNode Content="{Binding}"
|
||||
IsConnected="True"
|
||||
Anchor="{Binding Anchor, Mode=OneWayToSource}">
|
||||
<nodify:StateNode.ContentTemplate>
|
||||
<DataTemplate DataType="{x:Type local:StateViewModel}">
|
||||
<shared:EditableTextBlock Text="{Binding Name}"
|
||||
IsEditing="{Binding IsRenaming}"
|
||||
IsEditable="{Binding IsEditable}"
|
||||
MaxLength="30" />
|
||||
</DataTemplate>
|
||||
</nodify:StateNode.ContentTemplate>
|
||||
<nodify:StateNode.Style>
|
||||
<Style TargetType="{x:Type nodify:StateNode}"
|
||||
BasedOn="{StaticResource {x:Type nodify:StateNode}}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsEditable}"
|
||||
Value="False">
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{DynamicResource ReadOnlyStateBrush}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsActive}"
|
||||
Value="True">
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{DynamicResource ActiveStateBrush}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</nodify:StateNode.Style>
|
||||
</nodify:StateNode>
|
||||
</DataTemplate>
|
||||
</nodify:NodifyEditor.ItemTemplate>
|
||||
|
||||
<nodify:NodifyEditor.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type nodify:ItemContainer}"
|
||||
BasedOn="{StaticResource {x:Type nodify:ItemContainer}}">
|
||||
<Setter Property="BorderBrush"
|
||||
Value="Transparent" />
|
||||
<Setter Property="Location"
|
||||
Value="{Binding Location}" />
|
||||
<Setter Property="ActualSize"
|
||||
Value="{Binding Size, Mode=OneWayToSource}" />
|
||||
<Setter Property="ContextMenu">
|
||||
<Setter.Value>
|
||||
<ContextMenu DataContext="{Binding DataContext, Source={StaticResource EditorProxy}}">
|
||||
<MenuItem Header="_Delete"
|
||||
Icon="{StaticResource DeleteIcon}"
|
||||
Command="{Binding DeleteSelectionCommand}" />
|
||||
<MenuItem Header="Di_sconnect"
|
||||
Icon="{StaticResource DisconnectIcon}"
|
||||
Command="{Binding DisconnectSelectionCommand}" />
|
||||
<MenuItem Header="_Rename"
|
||||
Icon="{StaticResource RenameIcon}"
|
||||
Command="{Binding RenameStateCommand}" />
|
||||
<MenuItem Header="_Lock"
|
||||
Icon="{StaticResource LockIcon}"
|
||||
Command="{x:Static nodify:EditorCommands.LockSelection}" />
|
||||
<MenuItem Header="_Unlock"
|
||||
Icon="{StaticResource UnlockIcon}"
|
||||
Command="{x:Static nodify:EditorCommands.UnlockSelection}" />
|
||||
<MenuItem Header="_Alignment"
|
||||
Icon="{StaticResource AlignTopIcon}">
|
||||
<MenuItem Header="_Top"
|
||||
Icon="{StaticResource AlignTopIcon}"
|
||||
Command="{x:Static nodify:EditorCommands.Align}"
|
||||
CommandParameter="Top" />
|
||||
<MenuItem Header="_Left"
|
||||
Icon="{StaticResource AlignLeftIcon}"
|
||||
Command="{x:Static nodify:EditorCommands.Align}"
|
||||
CommandParameter="Left" />
|
||||
<MenuItem Header="_Bottom"
|
||||
Icon="{StaticResource AlignBottomIcon}"
|
||||
Command="{x:Static nodify:EditorCommands.Align}"
|
||||
CommandParameter="Bottom" />
|
||||
<MenuItem Header="_Right"
|
||||
Icon="{StaticResource AlignRightIcon}"
|
||||
Command="{x:Static nodify:EditorCommands.Align}"
|
||||
CommandParameter="Right" />
|
||||
<MenuItem Header="_Middle"
|
||||
Icon="{StaticResource AlignMiddleIcon}"
|
||||
Command="{x:Static nodify:EditorCommands.Align}"
|
||||
CommandParameter="Middle" />
|
||||
<MenuItem Header="_Center"
|
||||
Icon="{StaticResource AlignCenterIcon}"
|
||||
Command="{x:Static nodify:EditorCommands.Align}"
|
||||
CommandParameter="Center" />
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</nodify:NodifyEditor.ItemContainerStyle>
|
||||
|
||||
<nodify:NodifyEditor.ContextMenu>
|
||||
<ContextMenu DataContext="{Binding DataContext, Source={StaticResource EditorProxy}}">
|
||||
<MenuItem Header="_Add State"
|
||||
Icon="{StaticResource AddStateIcon}"
|
||||
InputGestureText="Shift+A"
|
||||
Command="{Binding AddStateCommand}"
|
||||
CommandParameter="{Binding PlacementTarget.MouseLocation, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
|
||||
<MenuItem Header="_Delete"
|
||||
Icon="{StaticResource DeleteIcon}"
|
||||
InputGestureText="Delete"
|
||||
Command="{Binding DeleteSelectionCommand}" />
|
||||
<Separator Background="{DynamicResource BorderBrush}" />
|
||||
<MenuItem Header="_Select All"
|
||||
Icon="{StaticResource SelectAllIcon}"
|
||||
InputGestureText="Ctrl+A"
|
||||
Command="{x:Static nodify:EditorCommands.SelectAll}" />
|
||||
</ContextMenu>
|
||||
</nodify:NodifyEditor.ContextMenu>
|
||||
|
||||
<nodify:NodifyEditor.InputBindings>
|
||||
<KeyBinding Key="Delete"
|
||||
Command="{Binding DeleteSelectionCommand}" />
|
||||
<KeyBinding Key="A"
|
||||
Modifiers="Shift"
|
||||
Command="{Binding AddStateCommand}"
|
||||
CommandParameter="{Binding MouseLocation, RelativeSource={RelativeSource AncestorType={x:Type nodify:NodifyEditor}}}" />
|
||||
</nodify:NodifyEditor.InputBindings>
|
||||
</nodify:NodifyEditor>
|
||||
</ScrollViewer>
|
||||
|
||||
<!--TOOLBAR-->
|
||||
<Border CornerRadius="2"
|
||||
Background="{DynamicResource PanelBackgroundBrush}"
|
||||
BorderThickness="0 0 0 1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Margin="10 0"
|
||||
Grid.Column="1">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Command="{Binding PauseCommand}">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}"
|
||||
BasedOn="{StaticResource IconButton}">
|
||||
<Setter Property="Content"
|
||||
Value="{StaticResource PauseIcon}" />
|
||||
<Setter Property="ToolTip"
|
||||
Value="Pause" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Runner.State}"
|
||||
Value="Stopped">
|
||||
<Setter Property="Visibility"
|
||||
Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Runner.State}"
|
||||
Value="Paused">
|
||||
<Setter Property="Content"
|
||||
Value="{StaticResource UnpauseIcon}" />
|
||||
<Setter Property="ToolTip"
|
||||
Value="Continue" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<Button Command="{Binding RunCommand}"
|
||||
Style="{StaticResource IconButton}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ContentPresenter DataContext="{Binding}"
|
||||
Margin="0 0 4 0">
|
||||
<ContentPresenter.Style>
|
||||
<Style TargetType="{x:Type ContentPresenter}">
|
||||
<Setter Property="Content"
|
||||
Value="{StaticResource StopIcon}" />
|
||||
<Setter Property="ToolTip"
|
||||
Value="Stop" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Runner.State}"
|
||||
Value="Stopped">
|
||||
<Setter Property="Content"
|
||||
Value="{StaticResource RunIcon}" />
|
||||
<Setter Property="ToolTip"
|
||||
Value="Run" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentPresenter.Style>
|
||||
</ContentPresenter>
|
||||
<TextBlock Text="{Binding Name}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Separator Height="Auto"
|
||||
BorderThickness="0 0 1 0" />
|
||||
|
||||
<Button Content="{StaticResource ZoomInIcon}"
|
||||
Command="{x:Static nodify:EditorCommands.ZoomIn}"
|
||||
CommandTarget="{Binding ElementName=Editor}"
|
||||
ToolTip="Zoom In"
|
||||
Style="{StaticResource IconButton}" />
|
||||
|
||||
<Button Content="{StaticResource ZoomOutIcon}"
|
||||
Command="{x:Static nodify:EditorCommands.ZoomOut}"
|
||||
CommandTarget="{Binding ElementName=Editor}"
|
||||
ToolTip="Zoom Out"
|
||||
Style="{StaticResource IconButton}" />
|
||||
|
||||
<Button Style="{StaticResource IconButton}"
|
||||
Content="{StaticResource ThemeIcon}"
|
||||
Command="{Binding Source={x:Static shared:ThemeManager.SetNextThemeCommand}}"
|
||||
ToolTip="Change theme" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!--Settings-->
|
||||
<Expander HorizontalContentAlignment="Left"
|
||||
VerticalContentAlignment="Center"
|
||||
HorizontalAlignment="Left"
|
||||
Background="{DynamicResource PanelBackgroundBrush}"
|
||||
Padding="0 1 4 3"
|
||||
IsExpanded="True"
|
||||
ExpandDirection="Left">
|
||||
<Expander.Style>
|
||||
<Style TargetType="{x:Type Expander}"
|
||||
BasedOn="{StaticResource {x:Type Expander}}">
|
||||
<Setter Property="Tag"
|
||||
Value="{StaticResource ExpandRightIcon}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsExpanded"
|
||||
Value="True">
|
||||
<Setter Property="Tag"
|
||||
Value="{StaticResource ExpandLeftIcon}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Expander.Style>
|
||||
|
||||
<Border BorderBrush="{DynamicResource BackgroundBrush}"
|
||||
BorderThickness="1"
|
||||
Width="300"
|
||||
Padding="10"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="2*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!--TRANSITIONS-->
|
||||
<ScrollViewer Visibility="{Binding SelectedState, Converter={shared:BooleanToVisibilityConverter Negate=True}}"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Grid.Row="1">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="Transitions"
|
||||
Foreground="{DynamicResource ForegroundBrush}"
|
||||
FontWeight="Bold"
|
||||
FontSize="16" />
|
||||
|
||||
<Separator Height="2"
|
||||
Width="Auto"
|
||||
Margin="0 2 0 5"
|
||||
Grid.Row="1" />
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Transitions}"
|
||||
Grid.Row="2"
|
||||
Focusable="False">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:TransitionViewModel}">
|
||||
<Expander BorderThickness="0 0 0 1"
|
||||
Padding="0 5 0 0"
|
||||
IsExpanded="True"
|
||||
BorderBrush="{DynamicResource BackgroundBrush}">
|
||||
<Expander.Style>
|
||||
<Style TargetType="{x:Type Expander}"
|
||||
BasedOn="{StaticResource {x:Type Expander}}">
|
||||
<Setter Property="Tag"
|
||||
Value="{StaticResource ExpandRightIcon}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsExpanded"
|
||||
Value="True">
|
||||
<Setter Property="Tag"
|
||||
Value="{StaticResource ExpandDownIcon}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Expander.Style>
|
||||
<Expander.Header>
|
||||
<TextBlock>
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground"
|
||||
Value="{DynamicResource ForegroundBrush}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsActive}"
|
||||
Value="True">
|
||||
<Setter Property="Foreground"
|
||||
Value="{DynamicResource ActiveStateBrush}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
<Run Text="{Binding Source.Name, Mode=OneWay}" />
|
||||
<Run Text="🠚" />
|
||||
<Run Text="{Binding Target.Name, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</Expander.Header>
|
||||
|
||||
<Border HorizontalAlignment="Stretch">
|
||||
<Grid IsSharedSizeScope="True">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="ConditionName" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!--CONDITION-->
|
||||
<TextBlock Text="Condition"
|
||||
Margin="0 0 10 0"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<ComboBox ItemsSource="{Binding DataContext.Blackboard.Conditions, Source={StaticResource EditorProxy}}"
|
||||
SelectedItem="{Binding ConditionReference}"
|
||||
DisplayMemberPath="Name"
|
||||
Grid.Column="1" />
|
||||
|
||||
<!--INPUT-->
|
||||
<ItemsControl ItemsSource="{Binding Condition.Input}"
|
||||
Padding="0 5 0 0"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:BlackboardKeyViewModel}">
|
||||
<local:BlackboardKeyEditorView Margin="0 0 0 2">
|
||||
<local:BlackboardKeyEditorView.DataContext>
|
||||
<MultiBinding Converter="{local:BlackboardKeyEditorConverter CanChangeInputType=True}">
|
||||
<Binding Source="{StaticResource BlackboardProxy}"
|
||||
Path="DataContext.Keys" />
|
||||
<Binding BindsDirectlyToSource="True" />
|
||||
</MultiBinding>
|
||||
</local:BlackboardKeyEditorView.DataContext>
|
||||
</local:BlackboardKeyEditorView>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<Grid Grid.Row="2"
|
||||
Grid.ColumnSpan="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="From: "
|
||||
VerticalAlignment="Center" />
|
||||
<Button Style="{StaticResource IconButton}"
|
||||
Command="{x:Static nodify:EditorCommands.BringIntoView}"
|
||||
CommandParameter="{Binding Source.Location}"
|
||||
CommandTarget="{Binding ElementName=Editor}"
|
||||
Foreground="DodgerBlue">
|
||||
<TextBlock Text="{Binding Source.Name}"
|
||||
TextDecorations="Underline" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Column="1">
|
||||
<TextBlock Text="To: "
|
||||
VerticalAlignment="Center" />
|
||||
<Button Style="{StaticResource IconButton}"
|
||||
Command="{x:Static nodify:EditorCommands.BringIntoView}"
|
||||
CommandParameter="{Binding Target.Location}"
|
||||
CommandTarget="{Binding ElementName=Editor}"
|
||||
Foreground="DodgerBlue">
|
||||
<TextBlock Text="{Binding Target.Name}"
|
||||
TextDecorations="Underline" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Expander>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<!--STATES-->
|
||||
<Grid Visibility="{Binding SelectedState, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!--STATE NAME-->
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<shared:EditableTextBlock Text="{Binding SelectedState.Name}"
|
||||
IsEditing="{Binding IsChecked, ElementName=EditStateName}"
|
||||
IsEditable="{Binding SelectedState.IsEditable}"
|
||||
Foreground="{DynamicResource ForegroundBrush}"
|
||||
FontWeight="Bold"
|
||||
FontSize="16"
|
||||
MaxLength="20" />
|
||||
<CheckBox x:Name="EditStateName"
|
||||
Visibility="{Binding SelectedState.IsEditable, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
Content="{StaticResource EditIcon}"
|
||||
Style="{StaticResource IconCheckBox}"
|
||||
Grid.Column="1" />
|
||||
</Grid>
|
||||
|
||||
<Separator Height="2"
|
||||
Width="Auto"
|
||||
Margin="0 2 0 10"
|
||||
Grid.Row="1" />
|
||||
|
||||
<ScrollViewer Visibility="{Binding SelectedState.Action, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
Grid.Row="2">
|
||||
<Grid IsSharedSizeScope="True">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!--ACTION-->
|
||||
<TextBlock Text="Action"
|
||||
Margin="0 0 10 0"
|
||||
VerticalAlignment="Center" />
|
||||
<ComboBox ItemsSource="{Binding Blackboard.Actions}"
|
||||
SelectedItem="{Binding SelectedState.ActionReference}"
|
||||
IsEnabled="{Binding SelectedState.IsEditable}"
|
||||
DisplayMemberPath="Name"
|
||||
Grid.Column="1" />
|
||||
|
||||
<!--INPUT-->
|
||||
<Expander Margin="0 5 0 0"
|
||||
Padding="0 5 0 0"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="2"
|
||||
BorderThickness="0 0 0 1"
|
||||
Header="Input"
|
||||
FontWeight="Bold"
|
||||
IsExpanded="True"
|
||||
BorderBrush="{DynamicResource BackgroundBrush}"
|
||||
Visibility="{Binding SelectedState.Action.Input.Count, Converter={shared:BooleanToVisibilityConverter}}">
|
||||
<Expander.Style>
|
||||
<Style TargetType="{x:Type Expander}"
|
||||
BasedOn="{StaticResource {x:Type Expander}}">
|
||||
<Setter Property="Tag"
|
||||
Value="{StaticResource ExpandRightIcon}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsExpanded"
|
||||
Value="True">
|
||||
<Setter Property="Tag"
|
||||
Value="{StaticResource ExpandDownIcon}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Expander.Style>
|
||||
<ItemsControl ItemsSource="{Binding SelectedState.Action.Input}"
|
||||
FontWeight="Normal">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:BlackboardKeyViewModel}">
|
||||
<local:BlackboardKeyEditorView Margin="0 0 0 2">
|
||||
<local:BlackboardKeyEditorView.DataContext>
|
||||
<MultiBinding Converter="{local:BlackboardKeyEditorConverter CanChangeInputType=True}">
|
||||
<Binding Source="{StaticResource BlackboardProxy}"
|
||||
Path="DataContext.Keys" />
|
||||
<Binding BindsDirectlyToSource="True" />
|
||||
</MultiBinding>
|
||||
</local:BlackboardKeyEditorView.DataContext>
|
||||
</local:BlackboardKeyEditorView>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Expander>
|
||||
|
||||
<!--OUTPUT-->
|
||||
<Expander Margin="0 5 0 0"
|
||||
Padding="0 5 0 0"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="2"
|
||||
Header="Output"
|
||||
FontWeight="Bold"
|
||||
BorderThickness="0 0 0 1"
|
||||
IsExpanded="True"
|
||||
BorderBrush="{DynamicResource BackgroundBrush}"
|
||||
Visibility="{Binding SelectedState.Action.Output.Count, Converter={shared:BooleanToVisibilityConverter}}">
|
||||
<Expander.Style>
|
||||
<Style TargetType="{x:Type Expander}"
|
||||
BasedOn="{StaticResource {x:Type Expander}}">
|
||||
<Setter Property="Tag"
|
||||
Value="{StaticResource ExpandRightIcon}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsExpanded"
|
||||
Value="True">
|
||||
<Setter Property="Tag"
|
||||
Value="{StaticResource ExpandDownIcon}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Expander.Style>
|
||||
<ItemsControl ItemsSource="{Binding SelectedState.Action.Output}"
|
||||
FontWeight="Normal">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:BlackboardKeyViewModel}">
|
||||
<local:BlackboardKeyEditorView Margin="0 0 0 2">
|
||||
<local:BlackboardKeyEditorView.DataContext>
|
||||
<MultiBinding Converter="{local:BlackboardKeyEditorConverter CanChangeInputType=False}">
|
||||
<Binding Source="{StaticResource BlackboardProxy}"
|
||||
Path="DataContext.Keys" />
|
||||
<Binding BindsDirectlyToSource="True" />
|
||||
</MultiBinding>
|
||||
</local:BlackboardKeyEditorView.DataContext>
|
||||
</local:BlackboardKeyEditorView>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Expander>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
<!--BLACKBOARD-->
|
||||
<Grid IsSharedSizeScope="True">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<shared:EditableTextBlock Text="{Binding Name}"
|
||||
IsEditing="{Binding IsChecked, ElementName=EditName}"
|
||||
Foreground="{DynamicResource ForegroundBrush}"
|
||||
FontWeight="Bold"
|
||||
FontSize="16"
|
||||
MaxLength="20" />
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Column="1">
|
||||
<CheckBox x:Name="EditName"
|
||||
Content="{StaticResource EditIcon}"
|
||||
ToolTip="Edit Name"
|
||||
Style="{StaticResource IconCheckBox}" />
|
||||
<Button Content="{StaticResource AddKeyIcon}"
|
||||
Command="{Binding Blackboard.AddKeyCommand}"
|
||||
ToolTip="Add New Key"
|
||||
Style="{StaticResource IconButton}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Separator Height="2"
|
||||
Width="Auto"
|
||||
Margin="0 2 0 10"
|
||||
Grid.Row="1" />
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
Grid.Row="2">
|
||||
<ItemsControl ItemsSource="{Binding Blackboard.Keys}"
|
||||
Focusable="False">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:BlackboardKeyViewModel}">
|
||||
<Grid Margin="0 0 0 2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition SharedSizeGroup="Actions" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<local:BlackboardKeyEditorView>
|
||||
<local:BlackboardKeyEditorView.DataContext>
|
||||
<MultiBinding Converter="{local:BlackboardKeyEditorConverter CanChangeInputType=False}">
|
||||
<Binding Source="{StaticResource BlackboardProxy}"
|
||||
Path="DataContext.Keys" />
|
||||
<Binding BindsDirectlyToSource="True" />
|
||||
<Binding ElementName="EditKeyName"
|
||||
Path="IsChecked" />
|
||||
</MultiBinding>
|
||||
</local:BlackboardKeyEditorView.DataContext>
|
||||
</local:BlackboardKeyEditorView>
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Column="3">
|
||||
<CheckBox x:Name="EditKeyName"
|
||||
Content="{StaticResource EditIcon}"
|
||||
ToolTip="Edit Name"
|
||||
Style="{StaticResource IconCheckBox}" />
|
||||
<Button Content="{StaticResource RemoveKeyIcon}"
|
||||
Command="{Binding DataContext.Blackboard.RemoveKeyCommand, Source={StaticResource EditorProxy}}"
|
||||
CommandParameter="{Binding}"
|
||||
Style="{StaticResource IconButton}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Expander>
|
||||
</Grid>
|
||||
</Window>
|
||||
51
Examples/Nodify.StateMachine/MainWindow.xaml.cs
Normal file
51
Examples/Nodify.StateMachine/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using Nodify.Interactivity;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
ConnectorState.EnableToggledConnectingMode = true;
|
||||
NodifyEditor.EnableCuttingLinePreview = true;
|
||||
|
||||
EditorGestures.Mappings.Connection.Disconnect.Unbind();
|
||||
EditorGestures.Mappings.Editor.ZoomModifierKey = ModifierKeys.Control;
|
||||
EditorGestures.Mappings.Editor.PanWithMouseWheel = true;
|
||||
|
||||
EventManager.RegisterClassHandler(
|
||||
typeof(UIElement),
|
||||
Keyboard.PreviewGotKeyboardFocusEvent,
|
||||
(KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus);
|
||||
}
|
||||
|
||||
private void OnPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||
{
|
||||
Title = e.NewFocus.ToString();
|
||||
}
|
||||
|
||||
private void ScrollViewer_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (Keyboard.Modifiers != ModifierKeys.Shift)
|
||||
return;
|
||||
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
|
||||
if (e.Key == Key.PageUp)
|
||||
{
|
||||
scrollViewer.PageLeft();
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.PageDown)
|
||||
{
|
||||
scrollViewer.PageRight();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Examples/Nodify.StateMachine/Nodify.StateMachine.csproj
Normal file
17
Examples/Nodify.StateMachine/Nodify.StateMachine.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFrameworks>net9-windows;net8-windows;net6-windows;net5-windows;netcoreapp3.1;net48;net472</TargetFrameworks>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(TargetFramework)'=='net472' OR '$(TargetFramework)'=='net48'">
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Nodify\Nodify.csproj" />
|
||||
<ProjectReference Include="..\Nodify.Shared\Nodify.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
25
Examples/Nodify.StateMachine/Runner/Actions/CopyKeyAction.cs
Normal file
25
Examples/Nodify.StateMachine/Runner/Actions/CopyKeyAction.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
[BlackboardItem("Copy Key")]
|
||||
public class CopyKeyAction : IBlackboardAction
|
||||
{
|
||||
[BlackboardProperty("Source", BlackboardKeyType.Object)]
|
||||
public BlackboardProperty Source { get; set; }
|
||||
|
||||
[BlackboardProperty("Target", BlackboardKeyType.Object)]
|
||||
public BlackboardProperty Target { get; set; }
|
||||
|
||||
public Task Execute(Blackboard blackboard)
|
||||
{
|
||||
if (Source != Target && Source.IsKey && Target.IsKey)
|
||||
{
|
||||
var value = blackboard[Source];
|
||||
blackboard[Target] = value;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
[BlackboardItem("Set Value")]
|
||||
public class SetKeyValueAction : IBlackboardAction
|
||||
{
|
||||
[BlackboardProperty(BlackboardKeyType.Object)]
|
||||
public BlackboardProperty Key { get; set; }
|
||||
|
||||
[BlackboardProperty(BlackboardKeyType.Object, CanChangeType = true)]
|
||||
public BlackboardProperty Value { get; set; }
|
||||
|
||||
public Task Execute(Blackboard blackboard)
|
||||
{
|
||||
var value = blackboard.GetValue<int>(Value);
|
||||
blackboard[Key] = value;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
[BlackboardItem("Set State Delay")]
|
||||
public class SetStateDelayAction : IBlackboardAction
|
||||
{
|
||||
[BlackboardProperty("Delay", BlackboardKeyType.Integer)]
|
||||
public BlackboardProperty Delay { get; set; }
|
||||
|
||||
[BlackboardProperty("Success", BlackboardKeyType.Boolean, Usage = BlackboardKeyUsage.Output)]
|
||||
public BlackboardProperty Success { get; set; }
|
||||
|
||||
public Task Execute(Blackboard blackboard)
|
||||
{
|
||||
var delay = blackboard.GetValue<int>(Delay);
|
||||
|
||||
if (delay.HasValue)
|
||||
{
|
||||
blackboard[DebugBlackboardDecorator.StateDelayKey] = delay;
|
||||
}
|
||||
|
||||
blackboard[Success] = delay.HasValue;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Examples/Nodify.StateMachine/Runner/Blackboard/Blackboard.cs
Normal file
88
Examples/Nodify.StateMachine/Runner/Blackboard/Blackboard.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class Blackboard
|
||||
{
|
||||
private readonly Dictionary<BlackboardKey, object?> _objects = new Dictionary<BlackboardKey, object?>();
|
||||
|
||||
public virtual IReadOnlyCollection<BlackboardKey> Keys
|
||||
=> _objects.Keys;
|
||||
|
||||
public virtual T? GetValue<T>(BlackboardKey key)
|
||||
where T : struct
|
||||
{
|
||||
if (_objects.TryGetValue(key, out var value) && value is T result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public virtual T? GetObject<T>(BlackboardKey key)
|
||||
where T : class
|
||||
{
|
||||
if (_objects.TryGetValue(key, out var value))
|
||||
{
|
||||
return value as T;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public virtual object? GetObject(BlackboardKey key)
|
||||
{
|
||||
if (_objects.TryGetValue(key, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public virtual void Set(BlackboardKey key, object? value)
|
||||
=> _objects[key] = value;
|
||||
|
||||
public virtual bool HasKey(BlackboardKey key)
|
||||
=> _objects.ContainsKey(key);
|
||||
|
||||
public virtual void Remove(BlackboardKey key)
|
||||
=> _objects.Remove(key);
|
||||
|
||||
public virtual void Clear()
|
||||
=> _objects.Clear();
|
||||
|
||||
public void CopyTo(Blackboard newBlackboard)
|
||||
{
|
||||
foreach (var kvp in _objects)
|
||||
{
|
||||
newBlackboard.Set(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public object? this[BlackboardKey key]
|
||||
{
|
||||
get => GetObject(key);
|
||||
set => Set(key, value);
|
||||
}
|
||||
|
||||
public T? GetValue<T>(BlackboardProperty value) where T : struct
|
||||
=> value.IsValue ? value.GetValue<T>() : GetValue<T>(value.Key);
|
||||
|
||||
public T? GetObject<T>(BlackboardProperty value) where T : class
|
||||
=> value.IsValue ? value.GetObject<T>() : GetObject<T>(value.Key);
|
||||
|
||||
public object? GetObject(BlackboardProperty value)
|
||||
=> value.IsValue ? value.Value : GetObject(value.Key);
|
||||
}
|
||||
|
||||
public static class BlackboardExtensions
|
||||
{
|
||||
public static bool IsValid(this BlackboardKey key)
|
||||
=> key != BlackboardKey.Invalid;
|
||||
|
||||
public static bool IsValid(this BlackboardProperty action)
|
||||
=> action != BlackboardProperty.Invalid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public enum BooleanOperator
|
||||
{
|
||||
And,
|
||||
Or
|
||||
}
|
||||
|
||||
public class BlackboardConditionSet : IBlackboardCondition
|
||||
{
|
||||
public BlackboardConditionSet(IEnumerable<IBlackboardCondition> conditions, BooleanOperator op)
|
||||
{
|
||||
Conditions = new List<IBlackboardCondition>(conditions);
|
||||
Operator = op;
|
||||
}
|
||||
|
||||
public IReadOnlyList<IBlackboardCondition> Conditions { get; }
|
||||
public BooleanOperator Operator { get; set; }
|
||||
|
||||
public async Task<bool> Evaluate(Blackboard blackboard)
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
if (Operator == BooleanOperator.And)
|
||||
{
|
||||
for (int i = 0; i < Conditions.Count; i++)
|
||||
{
|
||||
result &= await Conditions[i].Evaluate(blackboard);
|
||||
}
|
||||
}
|
||||
else if (Operator == BooleanOperator.Or)
|
||||
{
|
||||
for (int i = 0; i < Conditions.Count; i++)
|
||||
{
|
||||
result |= await Conditions[i].Evaluate(blackboard);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public sealed class BlackboardItemAttribute : Attribute
|
||||
{
|
||||
public BlackboardItemAttribute(string displayName)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
}
|
||||
|
||||
public string DisplayName { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public enum BlackboardKeyType
|
||||
{
|
||||
Boolean,
|
||||
Integer,
|
||||
Double,
|
||||
String,
|
||||
Object
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{Name}: {Type}")]
|
||||
public readonly struct BlackboardKey : IEquatable<BlackboardKey>
|
||||
{
|
||||
public static BlackboardKey Invalid { get; } = new BlackboardKey();
|
||||
|
||||
public BlackboardKey(string name, BlackboardKeyType type)
|
||||
{
|
||||
Name = name ?? throw new ArgumentException(nameof(name));
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public BlackboardKey(string name) : this(name, BlackboardKeyType.Object)
|
||||
{
|
||||
}
|
||||
|
||||
public readonly string Name;
|
||||
public readonly BlackboardKeyType Type;
|
||||
|
||||
public static implicit operator BlackboardKey(string name)
|
||||
=> new BlackboardKey(name);
|
||||
|
||||
public static implicit operator string(BlackboardKey key)
|
||||
=> key.Name;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is BlackboardKey bk && bk.Equals(this);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Name?.GetHashCode() ?? -1;
|
||||
|
||||
public bool Equals(BlackboardKey other)
|
||||
=> other.Name == Name;
|
||||
|
||||
public static bool operator ==(BlackboardKey left, BlackboardKey right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(BlackboardKey left, BlackboardKey right)
|
||||
=> !(left == right);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
[DebuggerDisplay("{IsKey ? Key : Value}")]
|
||||
public struct BlackboardProperty : IEquatable<BlackboardProperty>
|
||||
{
|
||||
public static BlackboardProperty Invalid { get; } = new BlackboardProperty();
|
||||
|
||||
public BlackboardProperty(BlackboardKey key)
|
||||
{
|
||||
Key = key;
|
||||
Value = default;
|
||||
}
|
||||
|
||||
public BlackboardProperty(object? value)
|
||||
{
|
||||
Key = BlackboardKey.Invalid;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public BlackboardKey Key { get; }
|
||||
public object? Value { get; }
|
||||
|
||||
public bool IsKey => Key.IsValid();
|
||||
public bool IsValue => !IsKey;
|
||||
|
||||
public static implicit operator BlackboardKey(BlackboardProperty action)
|
||||
=> action.Key;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is BlackboardProperty action && action.Equals(this);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> IsKey ? Key.GetHashCode() : Value?.GetHashCode() ?? -1;
|
||||
|
||||
public bool Equals(BlackboardProperty other)
|
||||
=> IsKey == other.IsKey && IsValue == other.IsValue && Key == other.Key && Value == other.Value;
|
||||
|
||||
public static bool operator ==(BlackboardProperty left, BlackboardProperty right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(BlackboardProperty left, BlackboardProperty right)
|
||||
=> !(left == right);
|
||||
|
||||
public T? GetValue<T>() where T : struct
|
||||
=> Value is T result ? result : default;
|
||||
|
||||
public T? GetObject<T>() where T : class
|
||||
=> Value as T;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public enum BlackboardKeyUsage
|
||||
{
|
||||
Input,
|
||||
Output
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Properties decorated with this attribute must always be of type <see cref="BlackboardProperty"/>.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class BlackboardPropertyAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Properties decorated with this attribute must always be of type <see cref="BlackboardProperty"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The display name of the key.</param>
|
||||
/// <param name="type">The data type of the value that the key refers to.</param>
|
||||
public BlackboardPropertyAttribute(string? name, BlackboardKeyType type = BlackboardKeyType.Object)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Properties decorated with this attribute must always be of type <see cref="BlackboardProperty"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The data type of the value that the key refers to.</param>
|
||||
public BlackboardPropertyAttribute(BlackboardKeyType type = BlackboardKeyType.Object) : this(null, type)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string? Name { get; }
|
||||
public BlackboardKeyType Type { get; }
|
||||
public BlackboardKeyUsage Usage { get; set; }
|
||||
public bool CanChangeType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public interface IBlackboardAction
|
||||
{
|
||||
Task Execute(Blackboard blackboard);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public interface IBlackboardCondition
|
||||
{
|
||||
Task<bool> Evaluate(Blackboard blackboard);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
[BlackboardItem("Are Equal")]
|
||||
public class AreEqualCondition : IBlackboardCondition
|
||||
{
|
||||
[BlackboardProperty(BlackboardKeyType.Object, CanChangeType = true)]
|
||||
public BlackboardProperty Left { get; set; }
|
||||
|
||||
[BlackboardProperty(BlackboardKeyType.Object, CanChangeType = true)]
|
||||
public BlackboardProperty Right { get; set; }
|
||||
|
||||
public Task<bool> Evaluate(Blackboard blackboard)
|
||||
{
|
||||
var left = blackboard.GetObject(Left);
|
||||
var right = blackboard.GetObject(Right);
|
||||
|
||||
// TODO: Equality
|
||||
return Task.FromResult(Equals(left, right));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
[BlackboardItem("Has Key")]
|
||||
public class HasKeyCondition : IBlackboardCondition
|
||||
{
|
||||
[BlackboardProperty("Key Name", BlackboardKeyType.String)]
|
||||
public BlackboardProperty Key { get; set; }
|
||||
|
||||
public Task<bool> Evaluate(Blackboard blackboard)
|
||||
{
|
||||
var keyName = blackboard.GetObject<string>(Key);
|
||||
|
||||
if (keyName != null)
|
||||
{
|
||||
return Task.FromResult(blackboard.HasKey(keyName));
|
||||
}
|
||||
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
[BlackboardItem("Has Value")]
|
||||
public class HasValueCondition : IBlackboardCondition
|
||||
{
|
||||
[BlackboardProperty(BlackboardKeyType.Object)]
|
||||
public BlackboardKey Key { get; set; }
|
||||
|
||||
public Task<bool> Evaluate(Blackboard blackboard)
|
||||
=> Task.FromResult(blackboard.GetObject(Key) != null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class DebugBlackboardDecorator : Blackboard
|
||||
{
|
||||
public static BlackboardKey StateDelayKey { get; } = "__state.delay";
|
||||
public static BlackboardKey TransitionDelayKey { get; } = "__transition.delay";
|
||||
|
||||
private Blackboard? _blackboard;
|
||||
|
||||
public event Action<BlackboardKey, object?>? ValueChanged;
|
||||
|
||||
public DebugBlackboardDecorator(Blackboard? blackboard = default)
|
||||
=> Attach(blackboard);
|
||||
|
||||
public override IReadOnlyCollection<BlackboardKey> Keys => _blackboard?.Keys ?? Array.Empty<BlackboardKey>();
|
||||
|
||||
public override void Remove(BlackboardKey key)
|
||||
=> _blackboard?.Remove(key);
|
||||
|
||||
public override void Clear()
|
||||
=> _blackboard?.Clear();
|
||||
|
||||
public override T? GetObject<T>(BlackboardKey key) where T : class
|
||||
=> _blackboard?.GetObject<T>(key);
|
||||
|
||||
public override T? GetValue<T>(BlackboardKey key)
|
||||
=> _blackboard?.GetValue<T>(key);
|
||||
|
||||
public override void Set(BlackboardKey key, object? value)
|
||||
{
|
||||
_blackboard?.Set(key, value);
|
||||
ValueChanged?.Invoke(key, value);
|
||||
}
|
||||
|
||||
public override bool HasKey(BlackboardKey key)
|
||||
=> _blackboard?.HasKey(key) ?? false;
|
||||
|
||||
public override object? GetObject(BlackboardKey key)
|
||||
=> _blackboard?.GetObject(key);
|
||||
|
||||
public virtual void Attach(Blackboard? blackboard)
|
||||
{
|
||||
_blackboard = blackboard;
|
||||
|
||||
Set(StateDelayKey, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class DebugStateDecorator : State
|
||||
{
|
||||
private readonly State _state;
|
||||
|
||||
public DebugStateDecorator(State state) : base(state.Id, state.Transitions)
|
||||
{
|
||||
_state = state;
|
||||
}
|
||||
|
||||
public override async Task Activate(Blackboard blackboard)
|
||||
{
|
||||
int? delay = blackboard.GetValue<int>(DebugBlackboardDecorator.StateDelayKey);
|
||||
|
||||
await Task.Delay(Math.Max(10, delay ?? 10));
|
||||
|
||||
await _state.Activate(blackboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class DebugTransitionDecorator : Transition
|
||||
{
|
||||
private readonly Transition _transition;
|
||||
|
||||
public DebugTransitionDecorator(Transition transition) : base(transition.From, transition.To)
|
||||
{
|
||||
_transition = transition;
|
||||
}
|
||||
|
||||
public override async Task<bool> CanActivate(Blackboard blackboard)
|
||||
{
|
||||
int? delay = blackboard.GetValue<int>(DebugBlackboardDecorator.TransitionDelayKey);
|
||||
|
||||
if (delay > 0)
|
||||
{
|
||||
await Task.Delay(delay.Value);
|
||||
}
|
||||
|
||||
return await _transition.CanActivate(blackboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Examples/Nodify.StateMachine/Runner/State.cs
Normal file
24
Examples/Nodify.StateMachine/Runner/State.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class State
|
||||
{
|
||||
public Guid Id { get; }
|
||||
public IBlackboardAction? Action { get; }
|
||||
|
||||
public State(Guid id, IEnumerable<Transition> transitions, IBlackboardAction? action = default)
|
||||
{
|
||||
Id = id;
|
||||
Action = action;
|
||||
Transitions = new List<Transition>(transitions);
|
||||
}
|
||||
|
||||
public IReadOnlyList<Transition> Transitions { get; }
|
||||
|
||||
public virtual Task Activate(Blackboard blackboard)
|
||||
=> Action?.Execute(blackboard) ?? Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
111
Examples/Nodify.StateMachine/Runner/StateMachine.cs
Normal file
111
Examples/Nodify.StateMachine/Runner/StateMachine.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public enum MachineState
|
||||
{
|
||||
Stopped,
|
||||
Running,
|
||||
Paused,
|
||||
}
|
||||
|
||||
public delegate void StateTransitionEventHandler(Guid from, Guid to);
|
||||
public delegate void StateChangedEventHandler(MachineState newStatus);
|
||||
|
||||
public class StateMachine
|
||||
{
|
||||
private readonly Dictionary<Guid, State> _states;
|
||||
|
||||
public State Root { get; }
|
||||
public MachineState? State { get; private set; }
|
||||
public Blackboard Blackboard { get; } = new Blackboard();
|
||||
|
||||
// param = aborted
|
||||
public event StateChangedEventHandler? StateChanged;
|
||||
public event StateTransitionEventHandler? StateTransition;
|
||||
|
||||
public StateMachine(Guid root, IEnumerable<State> states, Blackboard? blackboard = default)
|
||||
{
|
||||
_states = states.ToDictionary(x => x.Id, x => x);
|
||||
|
||||
if (!_states.ContainsKey(root))
|
||||
{
|
||||
throw new ArgumentException(nameof(root));
|
||||
}
|
||||
|
||||
Root = _states[root];
|
||||
|
||||
if (blackboard != null)
|
||||
{
|
||||
Blackboard = blackboard;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
if (ChangeState(MachineState.Running))
|
||||
{
|
||||
// Skip root state
|
||||
State? previous = Root;
|
||||
State? current = await GetNext(Root);
|
||||
|
||||
while (State != MachineState.Stopped && current != null)
|
||||
{
|
||||
if (State == MachineState.Paused)
|
||||
{
|
||||
await Task.Delay(10);
|
||||
}
|
||||
else
|
||||
{
|
||||
StateTransition?.Invoke(previous.Id, current.Id);
|
||||
previous = current;
|
||||
|
||||
await current.Activate(Blackboard);
|
||||
current = await GetNext(current);
|
||||
}
|
||||
}
|
||||
|
||||
ChangeState(MachineState.Stopped);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<State?> GetNext(State current)
|
||||
{
|
||||
var transitions = current.Transitions;
|
||||
for (int i = 0; i < transitions.Count; i++)
|
||||
{
|
||||
var transition = transitions[i];
|
||||
if (_states.TryGetValue(transition.To, out var result) && await transition.CanActivate(Blackboard))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
=> ChangeState(MachineState.Stopped);
|
||||
|
||||
public void Pause()
|
||||
=> ChangeState(MachineState.Paused);
|
||||
|
||||
public void Unpause()
|
||||
=> ChangeState(MachineState.Running);
|
||||
|
||||
private bool ChangeState(MachineState newState)
|
||||
{
|
||||
if (newState == MachineState.Running || (State != null && State != newState))
|
||||
{
|
||||
State = newState;
|
||||
StateChanged?.Invoke(newState);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Examples/Nodify.StateMachine/Runner/Transition.cs
Normal file
22
Examples/Nodify.StateMachine/Runner/Transition.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class Transition
|
||||
{
|
||||
public Transition(Guid from, Guid to, IBlackboardCondition? condition = default)
|
||||
{
|
||||
From = from;
|
||||
To = to;
|
||||
Condition = condition;
|
||||
}
|
||||
|
||||
public Guid From { get; }
|
||||
public Guid To { get; }
|
||||
public IBlackboardCondition? Condition { get; }
|
||||
|
||||
public virtual Task<bool> CanActivate(Blackboard blackboard)
|
||||
=> Condition?.Evaluate(blackboard) ?? Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
230
Examples/Nodify.StateMachine/StateMachineRunnerViewModel.cs
Normal file
230
Examples/Nodify.StateMachine/StateMachineRunnerViewModel.cs
Normal file
@@ -0,0 +1,230 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class StateMachineRunnerViewModel : ObservableObject
|
||||
{
|
||||
private StateMachine? _stateMachine;
|
||||
private StateViewModel? _activeState;
|
||||
private TransitionViewModel? _activeTransition;
|
||||
private readonly DebugBlackboardDecorator _debugger = new DebugBlackboardDecorator();
|
||||
private readonly Blackboard _original = new Blackboard();
|
||||
|
||||
protected StateMachineViewModel StateMachineViewModel { get; }
|
||||
|
||||
private MachineState _state;
|
||||
public MachineState State
|
||||
{
|
||||
get => _state;
|
||||
protected set => SetProperty(ref _state, value);
|
||||
}
|
||||
|
||||
private int _nodesVisited;
|
||||
public int NodesVisited
|
||||
{
|
||||
get => _nodesVisited;
|
||||
protected set => SetProperty(ref _nodesVisited, value);
|
||||
}
|
||||
|
||||
public StateMachineRunnerViewModel(StateMachineViewModel stateMachineViewModel)
|
||||
{
|
||||
StateMachineViewModel = stateMachineViewModel;
|
||||
_debugger.ValueChanged += OnBlackboardKeyValueChanged;
|
||||
}
|
||||
|
||||
private void OnBlackboardKeyValueChanged(BlackboardKey key, object? newValue)
|
||||
{
|
||||
if (_stateMachine != null && _stateMachine.State != MachineState.Stopped)
|
||||
{
|
||||
var existing = StateMachineViewModel.Blackboard.Keys.FirstOrDefault(k => k.Name == key.Name && k.Type == key.Type);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Value = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region State Machine Actions
|
||||
|
||||
public async void Start()
|
||||
{
|
||||
NodesVisited = 0;
|
||||
|
||||
_stateMachine = new StateMachine(StateMachineViewModel.States[0].Id, CreateStates(StateMachineViewModel.States), CreateBlackboard(StateMachineViewModel.Blackboard));
|
||||
|
||||
_stateMachine.StateTransition += HandleStateTransition;
|
||||
_stateMachine.StateChanged += HandleStateChange;
|
||||
|
||||
await _stateMachine.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_stateMachine?.Stop();
|
||||
_stateMachine = null;
|
||||
}
|
||||
|
||||
private void HandleStateTransition(Guid from, Guid to)
|
||||
{
|
||||
NodesVisited++;
|
||||
|
||||
SetActiveStateAndTransition(false);
|
||||
|
||||
_activeTransition = StateMachineViewModel.Transitions.FirstOrDefault(t => t.Source.Id == from);
|
||||
_activeState = StateMachineViewModel.States.FirstOrDefault(st => st.Id == to);
|
||||
|
||||
SetActiveStateAndTransition(true);
|
||||
}
|
||||
|
||||
private void SetActiveStateAndTransition(bool value)
|
||||
{
|
||||
if (_activeState != null)
|
||||
{
|
||||
_activeState.IsActive = value;
|
||||
}
|
||||
|
||||
if (_activeTransition != null)
|
||||
{
|
||||
_activeTransition.IsActive = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleStateChange(MachineState newState)
|
||||
{
|
||||
if (newState == MachineState.Stopped)
|
||||
{
|
||||
SetActiveStateAndTransition(false);
|
||||
ResetBlackboardToOriginal();
|
||||
}
|
||||
|
||||
State = newState;
|
||||
}
|
||||
|
||||
private void ResetBlackboardToOriginal()
|
||||
{
|
||||
var keys = StateMachineViewModel.Blackboard.Keys;
|
||||
for (int i = 0; i < keys.Count; i++)
|
||||
{
|
||||
var key = keys[i];
|
||||
key.Value = _original.GetObject(key.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public void TogglePause()
|
||||
{
|
||||
if (State == MachineState.Paused)
|
||||
{
|
||||
_stateMachine?.Unpause();
|
||||
}
|
||||
else if (State != MachineState.Stopped)
|
||||
{
|
||||
_stateMachine?.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialize State Machine
|
||||
|
||||
private IEnumerable<State> CreateStates(IEnumerable<StateViewModel> states)
|
||||
=> states.Select(s => new DebugStateDecorator(new State(s.Id, CreateTransitions(s), CreateAction(s.Action))));
|
||||
|
||||
private IEnumerable<Transition> CreateTransitions(StateViewModel state)
|
||||
{
|
||||
var transitions = StateMachineViewModel.Transitions.Where(t => t.Source == state).ToList();
|
||||
var result = new List<Transition>(transitions.Count);
|
||||
|
||||
for (int i = 0; i < transitions.Count; i++)
|
||||
{
|
||||
var transition = transitions[i];
|
||||
var tr = new Transition(transition.Source.Id, transition.Target.Id, CreateCondition(transition.Condition));
|
||||
result.Add(new DebugTransitionDecorator(tr));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private IBlackboardCondition? CreateCondition(BlackboardItemViewModel? condition)
|
||||
{
|
||||
if (condition?.Type != null && typeof(IBlackboardCondition).IsAssignableFrom(condition.Type))
|
||||
{
|
||||
// TODO: DI Container
|
||||
var result = (IBlackboardCondition?)Activator.CreateInstance(condition.Type);
|
||||
|
||||
InitializeKeys(condition.Input, result, condition.Type);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private IBlackboardAction? CreateAction(BlackboardItemViewModel? action)
|
||||
{
|
||||
if (action?.Type != null && typeof(IBlackboardAction).IsAssignableFrom(action.Type))
|
||||
{
|
||||
// TODO: DI Container
|
||||
var result = (IBlackboardAction?)Activator.CreateInstance(action.Type);
|
||||
|
||||
InitializeKeys(action.Input, result, action.Type);
|
||||
InitializeKeys(action.Output, result, action.Type);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
private void InitializeKeys(NodifyObservableCollection<BlackboardKeyViewModel> keys, object? instance, Type type)
|
||||
{
|
||||
for (int i = 0; i < keys.Count; i++)
|
||||
{
|
||||
var vm = keys[i];
|
||||
var key = CreateActionValue(vm);
|
||||
|
||||
// TODO: Property cache
|
||||
if (vm.PropertyName != null)
|
||||
{
|
||||
var prop = type.GetProperty(vm.PropertyName);
|
||||
|
||||
if (prop?.CanWrite ?? false)
|
||||
{
|
||||
prop.SetValue(instance, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Blackboard CreateBlackboard(BlackboardViewModel blackboard)
|
||||
{
|
||||
Blackboard result = new Blackboard();
|
||||
for (int i = 0; i < blackboard.Keys.Count; i++)
|
||||
{
|
||||
var key = blackboard.Keys[i];
|
||||
if (!string.IsNullOrWhiteSpace(key.Name))
|
||||
{
|
||||
result.Set(new BlackboardKey(key.Name, key.Type), key.Value);
|
||||
}
|
||||
}
|
||||
|
||||
result.CopyTo(_original);
|
||||
|
||||
_debugger.Attach(result);
|
||||
return _debugger;
|
||||
}
|
||||
|
||||
private BlackboardProperty CreateActionValue(BlackboardKeyViewModel key)
|
||||
{
|
||||
if (key.Value is BlackboardKeyViewModel bkv)
|
||||
{
|
||||
return new BlackboardProperty(new BlackboardKey(bkv.Name, bkv.Type));
|
||||
}
|
||||
|
||||
return new BlackboardProperty(key.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
247
Examples/Nodify.StateMachine/StateMachineViewModel.cs
Normal file
247
Examples/Nodify.StateMachine/StateMachineViewModel.cs
Normal file
@@ -0,0 +1,247 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class StateMachineViewModel : ObservableObject
|
||||
{
|
||||
public StateMachineViewModel()
|
||||
{
|
||||
PendingTransition = new TransitionViewModel();
|
||||
Runner = new StateMachineRunnerViewModel(this);
|
||||
|
||||
Blackboard = new BlackboardViewModel()
|
||||
{
|
||||
Actions = new NodifyObservableCollection<BlackboardItemReferenceViewModel>(BlackboardDescriptor.GetAvailableItems<IBlackboardAction>()),
|
||||
Conditions = new NodifyObservableCollection<BlackboardItemReferenceViewModel>(BlackboardDescriptor.GetAvailableItems<IBlackboardCondition>())
|
||||
};
|
||||
|
||||
Transitions.WhenAdded(c =>
|
||||
{
|
||||
c.Source.Transitions.Add(c.Target);
|
||||
c.Target.Transitions.Add(c.Source);
|
||||
})
|
||||
.WhenRemoved(c =>
|
||||
{
|
||||
c.Source.Transitions.Remove(c.Target);
|
||||
c.Target.Transitions.Remove(c.Source);
|
||||
})
|
||||
.WhenCleared(c => c.ForEach(i =>
|
||||
{
|
||||
i.Source.Transitions.Clear();
|
||||
i.Target.Transitions.Clear();
|
||||
}));
|
||||
|
||||
States.WhenAdded(x => x.Graph = this)
|
||||
.WhenRemoved(x => DisconnectState(x))
|
||||
.WhenCleared(x =>
|
||||
{
|
||||
Transitions.Clear();
|
||||
OnCreateDefaultNodes();
|
||||
});
|
||||
|
||||
OnCreateDefaultKeys();
|
||||
OnCreateDefaultNodes();
|
||||
|
||||
RenameStateCommand = new RequeryCommand(() => SelectedStates[0].IsRenaming = true, () => SelectedStates.Count == 1 && SelectedStates[0].IsEditable);
|
||||
DisconnectStateCommand = new RequeryCommand<StateViewModel>(x => DisconnectState(x), x => !IsRunning && x.Transitions.Count > 0);
|
||||
DisconnectSelectionCommand = new RequeryCommand(() => SelectedStates.ForEach(x => DisconnectState(x)), () => !IsRunning && SelectedStates.Count > 0 && Transitions.Count > 0);
|
||||
DeleteSelectionCommand = new RequeryCommand(() => SelectedStates.ToList().ForEach(x => x.IsEditable.Then(() => States.Remove(x))), () => !IsRunning && (SelectedStates.Count > 1 || (SelectedStates.Count == 1 && SelectedStates[0].IsEditable)));
|
||||
|
||||
AddStateCommand = new RequeryCommand<Point>(p => States.Add(new StateViewModel
|
||||
{
|
||||
Name = "New State",
|
||||
IsRenaming = true,
|
||||
Location = p,
|
||||
ActionReference = Blackboard.Actions.Count > 0 ? Blackboard.Actions[0] : null
|
||||
}), p => !IsRunning);
|
||||
|
||||
CreateTransitionCommand = new DelegateCommand<(object Source, object? Target)>(s => Transitions.Add(new TransitionViewModel
|
||||
{
|
||||
Source = (StateViewModel)s.Source,
|
||||
Target = (StateViewModel)s.Target!
|
||||
}), s => !IsRunning && s.Source is StateViewModel source && s.Target is StateViewModel target && target != s.Source && target != States[0] && !source.Transitions.Contains(s.Target));
|
||||
|
||||
DeleteTransitionCommand = new RequeryCommand<TransitionViewModel>(t => Transitions.Remove(t), t => !IsRunning);
|
||||
|
||||
RunCommand = new RequeryCommand(() => IsRunning.Then(Runner.Stop).Else(Runner.Start), () => Transitions.Count > 0);
|
||||
PauseCommand = new RequeryCommand(Runner.TogglePause, () => IsRunning);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<StateViewModel> _states = new NodifyObservableCollection<StateViewModel>();
|
||||
public NodifyObservableCollection<StateViewModel> States
|
||||
{
|
||||
get => _states;
|
||||
set => SetProperty(ref _states, value);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<StateViewModel> _selectedStates = new NodifyObservableCollection<StateViewModel>();
|
||||
public NodifyObservableCollection<StateViewModel> SelectedStates
|
||||
{
|
||||
get => _selectedStates;
|
||||
set => SetProperty(ref _selectedStates, value);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<TransitionViewModel> _connections = new NodifyObservableCollection<TransitionViewModel>();
|
||||
public NodifyObservableCollection<TransitionViewModel> Transitions
|
||||
{
|
||||
get => _connections;
|
||||
set => SetProperty(ref _connections, value);
|
||||
}
|
||||
|
||||
private StateViewModel? _selectedState;
|
||||
public StateViewModel? SelectedState
|
||||
{
|
||||
get => _selectedState;
|
||||
set => SetProperty(ref _selectedState, value);
|
||||
}
|
||||
|
||||
private string? _name = "State Machine";
|
||||
public string? Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetProperty(ref _name, value);
|
||||
}
|
||||
|
||||
public bool IsRunning => Runner.State != MachineState.Stopped;
|
||||
public bool IsPaused => Runner.State == MachineState.Paused;
|
||||
|
||||
public TransitionViewModel PendingTransition { get; }
|
||||
public StateMachineRunnerViewModel Runner { get; }
|
||||
public BlackboardViewModel Blackboard { get; }
|
||||
|
||||
public INodifyCommand DeleteTransitionCommand { get; }
|
||||
public INodifyCommand DeleteSelectionCommand { get; }
|
||||
public INodifyCommand DisconnectStateCommand { get; }
|
||||
public INodifyCommand DisconnectSelectionCommand { get; }
|
||||
public INodifyCommand RenameStateCommand { get; }
|
||||
public INodifyCommand AddStateCommand { get; }
|
||||
public INodifyCommand CreateTransitionCommand { get; }
|
||||
public INodifyCommand RunCommand { get; }
|
||||
public INodifyCommand PauseCommand { get; }
|
||||
|
||||
public void DisconnectState(StateViewModel state)
|
||||
{
|
||||
var transitions = Transitions.Where(t => t.Source == state || t.Target == state).ToList();
|
||||
transitions.ForEach(t => Transitions.Remove(t));
|
||||
}
|
||||
|
||||
protected virtual void OnCreateDefaultNodes()
|
||||
{
|
||||
States.Insert(0, new StateViewModel
|
||||
{
|
||||
Name = "Enter",
|
||||
Location = new Point(100, 100),
|
||||
IsEditable = false
|
||||
});
|
||||
|
||||
var currentDelayKey = Blackboard.Keys.First(k => k.Name == "Current Delay");
|
||||
var originalDelayKey = Blackboard.Keys.First(k => k.Name == "Original Delay");
|
||||
var welcomeKey = Blackboard.Keys.First(k => k.Name == "Welcome");
|
||||
|
||||
States.Add(new StateViewModel
|
||||
{
|
||||
Name = "Set delay value",
|
||||
Location = new Point(300, 100),
|
||||
ActionReference = Blackboard.Actions.FirstOrDefault(a => a.Type == typeof(SetKeyValueAction))
|
||||
});
|
||||
|
||||
States[1].Action!.Input[0].Value = currentDelayKey;
|
||||
States[1].Action!.Input[1].ValueIsKey = false;
|
||||
States[1].Action!.Input[1].Type = BlackboardKeyType.Integer;
|
||||
States[1].Action!.Input[1].Value = 100;
|
||||
|
||||
States.Add(new StateViewModel
|
||||
{
|
||||
Name = "Set new delay",
|
||||
Location = new Point(380, 250),
|
||||
ActionReference = Blackboard.Actions.FirstOrDefault(a => a.Type == typeof(SetStateDelayAction))
|
||||
});
|
||||
|
||||
States[2].Action!.Input[0].Value = currentDelayKey;
|
||||
|
||||
States.Add(new StateViewModel
|
||||
{
|
||||
Name = "Reset delay",
|
||||
Location = new Point(300, 350),
|
||||
ActionReference = Blackboard.Actions.FirstOrDefault(a => a.Type == typeof(CopyKeyAction))
|
||||
});
|
||||
|
||||
States[3].Action!.Input[0].Value = originalDelayKey;
|
||||
States[3].Action!.Input[1].Value = currentDelayKey;
|
||||
|
||||
States.Add(new StateViewModel
|
||||
{
|
||||
Name = "Set original delay",
|
||||
Location = new Point(200, 250),
|
||||
ActionReference = Blackboard.Actions.FirstOrDefault(a => a.Type == typeof(SetStateDelayAction))
|
||||
});
|
||||
|
||||
States[4].Action!.Input[0].Value = originalDelayKey;
|
||||
|
||||
Transitions.Add(new TransitionViewModel
|
||||
{
|
||||
Source = States[0],
|
||||
Target = States[1],
|
||||
ConditionReference = Blackboard.Conditions.FirstOrDefault(c => c.Type == typeof(HasKeyCondition))
|
||||
});
|
||||
|
||||
Transitions[0].Condition!.Input[0].Value = welcomeKey;
|
||||
|
||||
Transitions.Add(new TransitionViewModel
|
||||
{
|
||||
Source = States[1],
|
||||
Target = States[2],
|
||||
ConditionReference = Blackboard.Conditions.FirstOrDefault(c => c.Type == typeof(AreEqualCondition))
|
||||
});
|
||||
|
||||
Transitions[1].Condition!.Input[0].Value = welcomeKey;
|
||||
Transitions[1].Condition!.Input[1].ValueIsKey = false;
|
||||
Transitions[1].Condition!.Input[1].Type = BlackboardKeyType.String;
|
||||
Transitions[1].Condition!.Input[1].Value = currentDelayKey.Name;
|
||||
|
||||
Transitions.Add(new TransitionViewModel
|
||||
{
|
||||
Source = States[2],
|
||||
Target = States[3]
|
||||
});
|
||||
|
||||
Transitions.Add(new TransitionViewModel
|
||||
{
|
||||
Source = States[3],
|
||||
Target = States[4]
|
||||
});
|
||||
|
||||
Transitions.Add(new TransitionViewModel
|
||||
{
|
||||
Source = States[4],
|
||||
Target = States[1]
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void OnCreateDefaultKeys()
|
||||
{
|
||||
Blackboard.Keys.Add(new BlackboardKeyViewModel
|
||||
{
|
||||
Name = "Current Delay",
|
||||
Type = BlackboardKeyType.Integer,
|
||||
Value = 1000
|
||||
});
|
||||
|
||||
Blackboard.Keys.Add(new BlackboardKeyViewModel
|
||||
{
|
||||
Name = "Original Delay",
|
||||
Type = BlackboardKeyType.Integer,
|
||||
Value = 1000
|
||||
});
|
||||
|
||||
Blackboard.Keys.Add(new BlackboardKeyViewModel
|
||||
{
|
||||
Name = "Welcome",
|
||||
Type = BlackboardKeyType.String,
|
||||
Value = "Current Delay"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
84
Examples/Nodify.StateMachine/StateViewModel.cs
Normal file
84
Examples/Nodify.StateMachine/StateViewModel.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class StateViewModel : ObservableObject
|
||||
{
|
||||
public Guid Id { get; }
|
||||
|
||||
public StateViewModel(Guid id)
|
||||
=> Id = id;
|
||||
|
||||
public StateViewModel() : this(Guid.NewGuid()) { }
|
||||
|
||||
// TODO: Can remove when auto layout is added
|
||||
private Point _location;
|
||||
public Point Location
|
||||
{
|
||||
get => _location;
|
||||
set => SetProperty(ref _location, value);
|
||||
}
|
||||
|
||||
private Point _anchor;
|
||||
public Point Anchor
|
||||
{
|
||||
get => _anchor;
|
||||
set => SetProperty(ref _anchor, value);
|
||||
}
|
||||
|
||||
private Size _size;
|
||||
public Size Size
|
||||
{
|
||||
get => _size;
|
||||
set => SetProperty(ref _size, value);
|
||||
}
|
||||
|
||||
private string? _name;
|
||||
public string? Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetProperty(ref _name, value);
|
||||
}
|
||||
|
||||
private bool _isRenaming;
|
||||
public bool IsRenaming
|
||||
{
|
||||
get => _isRenaming;
|
||||
set => SetProperty(ref _isRenaming, value);
|
||||
}
|
||||
|
||||
private bool _isActive;
|
||||
public bool IsActive
|
||||
{
|
||||
get => _isActive;
|
||||
set => SetProperty(ref _isActive, value);
|
||||
}
|
||||
|
||||
private BlackboardItemReferenceViewModel? _actionReference;
|
||||
public BlackboardItemReferenceViewModel? ActionReference
|
||||
{
|
||||
get => _actionReference;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _actionReference, value))
|
||||
{
|
||||
SetAction(_actionReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BlackboardItemViewModel? Action { get; private set; }
|
||||
|
||||
public bool IsEditable { get; set; } = true;
|
||||
public StateMachineViewModel Graph { get; internal set; } = default!;
|
||||
public NodifyObservableCollection<StateViewModel> Transitions { get; } = new NodifyObservableCollection<StateViewModel>();
|
||||
|
||||
private void SetAction(BlackboardItemReferenceViewModel? actionRef)
|
||||
{
|
||||
Action = BlackboardDescriptor.GetItem(actionRef);
|
||||
|
||||
OnPropertyChanged(nameof(Action));
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Examples/Nodify.StateMachine/Themes/Brushes.xaml
Normal file
17
Examples/Nodify.StateMachine/Themes/Brushes.xaml
Normal file
@@ -0,0 +1,17 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:o="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options">
|
||||
|
||||
<SolidColorBrush x:Key="PanelBackgroundBrush"
|
||||
o:Freeze="True"
|
||||
Color="{DynamicResource PanelBackgroundColor}" />
|
||||
|
||||
<SolidColorBrush x:Key="ActiveStateBrush"
|
||||
o:Freeze="True"
|
||||
Color="{DynamicResource ActiveStateColor}" />
|
||||
|
||||
<SolidColorBrush x:Key="ReadOnlyStateBrush"
|
||||
o:Freeze="True"
|
||||
Color="{DynamicResource ReadOnlyStateColor}" />
|
||||
|
||||
</ResourceDictionary>
|
||||
13
Examples/Nodify.StateMachine/Themes/Dark.xaml
Normal file
13
Examples/Nodify.StateMachine/Themes/Dark.xaml
Normal file
@@ -0,0 +1,13 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Brushes.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Color x:Key="PanelBackgroundColor">#2D2D30</Color>
|
||||
|
||||
<Color x:Key="ActiveStateColor">#8DD28A</Color>
|
||||
<Color x:Key="ReadOnlyStateColor">#E6AF86</Color>
|
||||
|
||||
</ResourceDictionary>
|
||||
13
Examples/Nodify.StateMachine/Themes/Light.xaml
Normal file
13
Examples/Nodify.StateMachine/Themes/Light.xaml
Normal file
@@ -0,0 +1,13 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Brushes.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Color x:Key="PanelBackgroundColor">#E9EEFE</Color>
|
||||
|
||||
<Color x:Key="ActiveStateColor">#8DD28A</Color>
|
||||
<Color x:Key="ReadOnlyStateColor">#E6AF86</Color>
|
||||
|
||||
</ResourceDictionary>
|
||||
13
Examples/Nodify.StateMachine/Themes/Nodify.xaml
Normal file
13
Examples/Nodify.StateMachine/Themes/Nodify.xaml
Normal file
@@ -0,0 +1,13 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Brushes.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Color x:Key="PanelBackgroundColor">#2A1B47</Color>
|
||||
|
||||
<Color x:Key="ActiveStateColor">#A6D99C</Color>
|
||||
<Color x:Key="ReadOnlyStateColor">#FD5618</Color>
|
||||
|
||||
</ResourceDictionary>
|
||||
48
Examples/Nodify.StateMachine/TransitionViewModel.cs
Normal file
48
Examples/Nodify.StateMachine/TransitionViewModel.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
namespace Nodify.StateMachine
|
||||
{
|
||||
public class TransitionViewModel : ObservableObject
|
||||
{
|
||||
private StateViewModel _source = default!;
|
||||
public StateViewModel Source
|
||||
{
|
||||
get => _source;
|
||||
set => SetProperty(ref _source, value);
|
||||
}
|
||||
|
||||
private StateViewModel _target = default!;
|
||||
public StateViewModel Target
|
||||
{
|
||||
get => _target;
|
||||
set => SetProperty(ref _target, value);
|
||||
}
|
||||
|
||||
private BlackboardItemReferenceViewModel? _conditionReference;
|
||||
public BlackboardItemReferenceViewModel? ConditionReference
|
||||
{
|
||||
get => _conditionReference;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _conditionReference, value))
|
||||
{
|
||||
SetCondition(_conditionReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BlackboardItemViewModel? Condition { get; private set; }
|
||||
|
||||
private bool _isActive;
|
||||
public bool IsActive
|
||||
{
|
||||
get => _isActive;
|
||||
set => SetProperty(ref _isActive, value);
|
||||
}
|
||||
|
||||
private void SetCondition(BlackboardItemReferenceViewModel? conditionRef)
|
||||
{
|
||||
Condition = BlackboardDescriptor.GetItem(conditionRef);
|
||||
|
||||
OnPropertyChanged(nameof(Condition));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user