Add project files.
This commit is contained in:
53
Examples/Nodify.Playground/App.xaml
Normal file
53
Examples/Nodify.Playground/App.xaml
Normal file
@@ -0,0 +1,53 @@
|
||||
<Application x:Class="Nodify.Playground.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary>
|
||||
<Style x:Key="{x:Static SystemParameters.FocusVisualStyleKey}">
|
||||
<Setter Property="Control.Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Rectangle StrokeThickness="1"
|
||||
StrokeDashArray="2"
|
||||
Margin="-2"
|
||||
RadiusX="3"
|
||||
RadiusY="3"
|
||||
Stroke="DodgerBlue" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/Nodify.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/FocusVisual.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify.Shared;component/Themes/Icons.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify.Shared;component/Themes/Nodify.xaml" />
|
||||
<ResourceDictionary Source="pack://application:,,,/Nodify.Playground;component/Themes/Nodify.xaml" />
|
||||
<ResourceDictionary>
|
||||
<Color x:Key="NodifyEditor.FocusVisualColor">DodgerBlue</Color>
|
||||
|
||||
<Style TargetType="{x:Type nodify:HotKeyControl}"
|
||||
BasedOn="{StaticResource {x:Type nodify:HotKeyControl}}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type nodify:HotKeyControl}">
|
||||
<Border CornerRadius="3"
|
||||
Background="OrangeRed">
|
||||
<TextBlock Text="{Binding Number, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="White" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
17
Examples/Nodify.Playground/App.xaml.cs
Normal file
17
Examples/Nodify.Playground/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.Playground
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
}
|
||||
10
Examples/Nodify.Playground/AssemblyInfo.cs
Normal file
10
Examples/Nodify.Playground/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
41
Examples/Nodify.Playground/BaseSettingViewModel.cs
Normal file
41
Examples/Nodify.Playground/BaseSettingViewModel.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class BaseSettingViewModel<T> : ObservableObject, ISettingViewModel
|
||||
{
|
||||
public string Name { get; }
|
||||
public string? Description { get; }
|
||||
|
||||
private object? _value;
|
||||
|
||||
object? ISettingViewModel.Value
|
||||
{
|
||||
get => _value;
|
||||
set => SetProperty(ref _value, value);
|
||||
}
|
||||
|
||||
public SettingsType Type { get;}
|
||||
|
||||
public T Value
|
||||
{
|
||||
get => (T)((ISettingViewModel)this).Value!;
|
||||
set => ((ISettingViewModel)this).Value = value;
|
||||
}
|
||||
|
||||
public BaseSettingViewModel(string name, string? description = default)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
Type = typeof(T) switch
|
||||
{
|
||||
{ } t when t == typeof(string) => SettingsType.Text,
|
||||
{ } t when t == typeof(bool) => SettingsType.Boolean,
|
||||
{ } t when t == typeof(uint) || t == typeof(double) => SettingsType.Number,
|
||||
{ } t when t == typeof(PointEditor) => SettingsType.Point,
|
||||
{ IsEnum: true } => SettingsType.Option,
|
||||
_ => throw new InvalidOperationException($"Type {typeof(T).Name} does not have a matching {nameof(SettingsType)}.")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class FlowToConnectorPositionConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is ConnectionViewModel connection)
|
||||
{
|
||||
var connector = parameter is "Input" ? connection.Input : connection.Output;
|
||||
|
||||
if (connector.Node is KnotNodeViewModel)
|
||||
{
|
||||
var otherConnector = connection.Input == connector ? connection.Output : connection.Input;
|
||||
|
||||
if (otherConnector.Node is KnotNodeViewModel)
|
||||
{
|
||||
return ToPosition(connector == connection.Input ? ConnectorFlow.Input : ConnectorFlow.Output, connector.Node.Orientation);
|
||||
}
|
||||
|
||||
return ToPosition(otherConnector.Flow == ConnectorFlow.Output ? ConnectorFlow.Input : ConnectorFlow.Output, connector.Node.Orientation);
|
||||
}
|
||||
|
||||
return ToPosition(connector.Flow, connector.Node.Orientation);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private ConnectorPosition ToPosition(ConnectorFlow flow, Orientation orientation)
|
||||
{
|
||||
if (orientation == Orientation.Horizontal)
|
||||
{
|
||||
return flow == ConnectorFlow.Output
|
||||
? ConnectorPosition.Right
|
||||
: ConnectorPosition.Left;
|
||||
}
|
||||
|
||||
return flow == ConnectorFlow.Output
|
||||
? ConnectorPosition.Bottom
|
||||
: ConnectorPosition.Top;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class FlowToDirectionConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is ConnectorFlow flow)
|
||||
{
|
||||
return flow == ConnectorFlow.Output ? ConnectionDirection.Forward : ConnectionDirection.Backward;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is ConnectionDirection dir)
|
||||
{
|
||||
return dir == ConnectionDirection.Forward ? ConnectorFlow.Output : ConnectorFlow.Input;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Examples/Nodify.Playground/Converters/UIntToRectConverter.cs
Normal file
27
Examples/Nodify.Playground/Converters/UIntToRectConverter.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class UIntToRectConverter : MarkupExtension, IValueConverter
|
||||
{
|
||||
public uint Multiplier { get; set; } = 1;
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
uint size = System.Convert.ToUInt32(value) * Multiplier;
|
||||
return new Rect(0d, 0d, size, size);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||
=> this;
|
||||
}
|
||||
}
|
||||
21
Examples/Nodify.Playground/Editor/CommentNodeViewModel.cs
Normal file
21
Examples/Nodify.Playground/Editor/CommentNodeViewModel.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class CommentNodeViewModel : NodeViewModel
|
||||
{
|
||||
private string? _title;
|
||||
public string? Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
private Size _size;
|
||||
public Size Size
|
||||
{
|
||||
get => _size;
|
||||
set => SetProperty(ref _size, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Examples/Nodify.Playground/Editor/ConnectionViewModel.cs
Normal file
51
Examples/Nodify.Playground/Editor/ConnectionViewModel.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class ConnectionViewModel : ObservableObject
|
||||
{
|
||||
private NodifyEditorViewModel _graph = default!;
|
||||
public NodifyEditorViewModel Graph
|
||||
{
|
||||
get => _graph;
|
||||
internal set => SetProperty(ref _graph, value);
|
||||
}
|
||||
|
||||
private ConnectorViewModel _input = default!;
|
||||
public ConnectorViewModel Input
|
||||
{
|
||||
get => _input;
|
||||
set => SetProperty(ref _input, value);
|
||||
}
|
||||
|
||||
private ConnectorViewModel _output = default!;
|
||||
public ConnectorViewModel Output
|
||||
{
|
||||
get => _output;
|
||||
set => SetProperty(ref _output, value);
|
||||
}
|
||||
|
||||
private bool _isSelected;
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetProperty(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public ICommand SplitCommand { get; }
|
||||
public ICommand DisconnectCommand { get; }
|
||||
|
||||
public ConnectionViewModel()
|
||||
{
|
||||
SplitCommand = new DelegateCommand<Point>(Split);
|
||||
DisconnectCommand = new DelegateCommand(Remove);
|
||||
}
|
||||
|
||||
public void Split(Point point)
|
||||
=> Graph.Schema.SplitConnection(this, point);
|
||||
|
||||
public void Remove()
|
||||
=> Graph.Connections.Remove(this);
|
||||
}
|
||||
}
|
||||
109
Examples/Nodify.Playground/Editor/ConnectorViewModel.cs
Normal file
109
Examples/Nodify.Playground/Editor/ConnectorViewModel.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public enum ConnectorFlow
|
||||
{
|
||||
Input,
|
||||
Output
|
||||
}
|
||||
|
||||
public enum ConnectorShape
|
||||
{
|
||||
Circle,
|
||||
Triangle,
|
||||
Square,
|
||||
}
|
||||
|
||||
public class ConnectorViewModel : ObservableObject
|
||||
{
|
||||
private string? _title;
|
||||
public string? Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
private bool _isConnected;
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
|
||||
private Point _anchor;
|
||||
public Point Anchor
|
||||
{
|
||||
get => _anchor;
|
||||
set => SetProperty(ref _anchor, value);
|
||||
}
|
||||
|
||||
private NodeViewModel _node = default!;
|
||||
public NodeViewModel Node
|
||||
{
|
||||
get => _node;
|
||||
internal set
|
||||
{
|
||||
if (SetProperty(ref _node, value))
|
||||
{
|
||||
OnNodeChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ConnectorShape _shape;
|
||||
public ConnectorShape Shape
|
||||
{
|
||||
get => _shape;
|
||||
set => SetProperty(ref _shape, value);
|
||||
}
|
||||
|
||||
public ConnectorFlow Flow { get; private set; }
|
||||
|
||||
public int MaxConnections { get; set; } = 2;
|
||||
|
||||
public NodifyObservableCollection<ConnectionViewModel> Connections { get; } = new NodifyObservableCollection<ConnectionViewModel>();
|
||||
|
||||
public ConnectorViewModel()
|
||||
{
|
||||
Connections.WhenAdded(c =>
|
||||
{
|
||||
c.Input.IsConnected = true;
|
||||
c.Output.IsConnected = true;
|
||||
}).WhenRemoved(c =>
|
||||
{
|
||||
if (c.Input.Connections.Count == 0)
|
||||
{
|
||||
c.Input.IsConnected = false;
|
||||
}
|
||||
|
||||
if (c.Output.Connections.Count == 0)
|
||||
{
|
||||
c.Output.IsConnected = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual void OnNodeChanged()
|
||||
{
|
||||
if (Node is FlowNodeViewModel flow)
|
||||
{
|
||||
Flow = flow.Input.Contains(this) ? ConnectorFlow.Input : ConnectorFlow.Output;
|
||||
}
|
||||
else if (Node is KnotNodeViewModel knot)
|
||||
{
|
||||
Flow = knot.Flow;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsConnectedTo(ConnectorViewModel con)
|
||||
=> Connections.Any(c => c.Input == con || c.Output == con);
|
||||
|
||||
public virtual bool AllowsNewConnections()
|
||||
=> Connections.Count < MaxConnections;
|
||||
|
||||
public void Disconnect()
|
||||
=> Node.Graph.Schema.DisconnectConnector(this);
|
||||
}
|
||||
}
|
||||
34
Examples/Nodify.Playground/Editor/FlowNodeViewModel.cs
Normal file
34
Examples/Nodify.Playground/Editor/FlowNodeViewModel.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class FlowNodeViewModel : NodeViewModel
|
||||
{
|
||||
private string? _title;
|
||||
public string? Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
public NodifyObservableCollection<ConnectorViewModel> Input { get; } = new NodifyObservableCollection<ConnectorViewModel>();
|
||||
public NodifyObservableCollection<ConnectorViewModel> Output { get; } = new NodifyObservableCollection<ConnectorViewModel>();
|
||||
|
||||
public FlowNodeViewModel()
|
||||
{
|
||||
Orientation = Orientation.Horizontal;
|
||||
|
||||
Input.WhenAdded(c => c.Node = this)
|
||||
.WhenRemoved(c => c.Disconnect());
|
||||
|
||||
Output.WhenAdded(c => c.Node = this)
|
||||
.WhenRemoved(c => c.Disconnect());
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
Input.Clear();
|
||||
Output.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
133
Examples/Nodify.Playground/Editor/GraphSchema.cs
Normal file
133
Examples/Nodify.Playground/Editor/GraphSchema.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class GraphSchema
|
||||
{
|
||||
#region Add Connection
|
||||
|
||||
public bool CanAddConnection(ConnectorViewModel source, object target)
|
||||
{
|
||||
if (target is ConnectorViewModel con)
|
||||
{
|
||||
return source != con
|
||||
&& source.Node != con.Node
|
||||
&& source.Node.Graph == con.Node.Graph
|
||||
&& source.Shape == con.Shape
|
||||
&& source.AllowsNewConnections()
|
||||
&& con.AllowsNewConnections()
|
||||
&& (source.Flow != con.Flow || con.Node is KnotNodeViewModel)
|
||||
&& !source.IsConnectedTo(con);
|
||||
}
|
||||
else if (source.AllowsNewConnections() && target is FlowNodeViewModel node)
|
||||
{
|
||||
var allConnectors = source.Flow == ConnectorFlow.Input ? node.Output : node.Input;
|
||||
return allConnectors.Any(c => c.AllowsNewConnections());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryAddConnection(ConnectorViewModel source, object? target)
|
||||
{
|
||||
if (target != null && CanAddConnection(source, target))
|
||||
{
|
||||
if (target is ConnectorViewModel connector)
|
||||
{
|
||||
AddConnection(source, connector);
|
||||
return true;
|
||||
}
|
||||
else if (target is FlowNodeViewModel node)
|
||||
{
|
||||
AddConnection(source, node);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void AddConnection(ConnectorViewModel source, ConnectorViewModel target)
|
||||
{
|
||||
var sourceIsInput = source.Flow == ConnectorFlow.Input;
|
||||
|
||||
source.Node.Graph.Connections.Add(new ConnectionViewModel
|
||||
{
|
||||
Input = sourceIsInput ? source : target,
|
||||
Output = sourceIsInput ? target : source
|
||||
});
|
||||
}
|
||||
|
||||
private void AddConnection(ConnectorViewModel source, FlowNodeViewModel target)
|
||||
{
|
||||
var allConnectors = source.Flow == ConnectorFlow.Input ? target.Output : target.Input;
|
||||
var connector = allConnectors.First(c => c.AllowsNewConnections());
|
||||
|
||||
AddConnection(source, connector);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void DisconnectConnector(ConnectorViewModel connector)
|
||||
{
|
||||
var graph = connector.Node.Graph;
|
||||
var connections = connector.Connections.ToList();
|
||||
connections.ForEach(c => graph.Connections.Remove(c));
|
||||
}
|
||||
|
||||
public void SplitConnection(ConnectionViewModel connection, Point location)
|
||||
{
|
||||
var knot = new KnotNodeViewModel(connection.Output.Node.Orientation)
|
||||
{
|
||||
Location = location,
|
||||
Flow = connection.Output.Flow,
|
||||
Connector = new ConnectorViewModel
|
||||
{
|
||||
MaxConnections = connection.Output.MaxConnections + connection.Input.MaxConnections,
|
||||
Shape = connection.Input.Shape
|
||||
}
|
||||
};
|
||||
connection.Graph.Nodes.Add(knot);
|
||||
|
||||
AddConnection(connection.Output, knot.Connector);
|
||||
AddConnection(knot.Connector, connection.Input);
|
||||
|
||||
connection.Remove();
|
||||
}
|
||||
|
||||
public void AddCommentAroundNodes(IList<NodeViewModel> nodes, string? text = default)
|
||||
{
|
||||
var rect = nodes.GetBoundingBox(50);
|
||||
var comment = new CommentNodeViewModel
|
||||
{
|
||||
Location = rect.Location,
|
||||
Size = rect.Size,
|
||||
Title = text ?? "New comment"
|
||||
};
|
||||
|
||||
nodes[0].Graph.Nodes.Add(comment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rewires all connections from the source connector to the target connector if possible.
|
||||
/// </summary>
|
||||
/// <remarks>The source must be an input connector.</remarks>
|
||||
public void Rewire(ConnectorViewModel source, ConnectorViewModel target)
|
||||
{
|
||||
if (source == target || source.Flow != ConnectorFlow.Input)
|
||||
return;
|
||||
|
||||
var connectionsToRewire = source.Connections.ToList();
|
||||
foreach (var connection in connectionsToRewire)
|
||||
{
|
||||
if (CanAddConnection(connection.Output, target))
|
||||
{
|
||||
source.Node.Graph.Connections.Remove(connection);
|
||||
AddConnection(connection.Output, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Examples/Nodify.Playground/Editor/KnotNodeViewModel.cs
Normal file
31
Examples/Nodify.Playground/Editor/KnotNodeViewModel.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class KnotNodeViewModel : NodeViewModel
|
||||
{
|
||||
public KnotNodeViewModel(Orientation orientation)
|
||||
{
|
||||
Orientation = orientation;
|
||||
}
|
||||
|
||||
public KnotNodeViewModel() : this(Orientation.Horizontal)
|
||||
{
|
||||
}
|
||||
|
||||
private ConnectorViewModel _connector = default!;
|
||||
public ConnectorViewModel Connector
|
||||
{
|
||||
get => _connector;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _connector, value))
|
||||
{
|
||||
_connector.Node = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ConnectorFlow Flow { get; set; }
|
||||
}
|
||||
}
|
||||
32
Examples/Nodify.Playground/Editor/NodeViewModel.cs
Normal file
32
Examples/Nodify.Playground/Editor/NodeViewModel.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public abstract class NodeViewModel : ObservableObject
|
||||
{
|
||||
private NodifyEditorViewModel _graph = default!;
|
||||
public NodifyEditorViewModel Graph
|
||||
{
|
||||
get => _graph;
|
||||
internal set => SetProperty(ref _graph, value);
|
||||
}
|
||||
|
||||
private Point _location;
|
||||
public Point Location
|
||||
{
|
||||
get => _location;
|
||||
set => SetProperty(ref _location, value);
|
||||
}
|
||||
|
||||
public Orientation Orientation { get; protected set; }
|
||||
|
||||
public ICommand DeleteCommand { get; }
|
||||
|
||||
public NodeViewModel()
|
||||
{
|
||||
DeleteCommand = new DelegateCommand(() => Graph.Nodes.Remove(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
735
Examples/Nodify.Playground/Editor/NodifyEditorView.xaml
Normal file
735
Examples/Nodify.Playground/Editor/NodifyEditorView.xaml
Normal file
@@ -0,0 +1,735 @@
|
||||
<UserControl x:Class="Nodify.Playground.NodifyEditorView"
|
||||
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.Playground"
|
||||
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||
mc:Ignorable="d"
|
||||
Background="{DynamicResource NodifyEditor.BackgroundBrush}"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
|
||||
<UserControl.DataContext>
|
||||
<local:NodifyEditorViewModel />
|
||||
</UserControl.DataContext>
|
||||
|
||||
<UserControl.Resources>
|
||||
<shared:RandomBrushConverter x:Key="RandomBrushConverter" />
|
||||
<local:FlowToDirectionConverter x:Key="FlowToDirectionConverter" />
|
||||
<local:FlowToConnectorPositionConverter x:Key="FlowToConnectorPositionConverter" />
|
||||
|
||||
<GeometryDrawing x:Key="SmallGridGeometry"
|
||||
Geometry="M0,0 L0,1 0.03,1 0.03,0.03 1,0.03 1,0 Z"
|
||||
Brush="{DynamicResource GridLinesBrush}" />
|
||||
|
||||
<GeometryDrawing x:Key="LargeGridGeometry"
|
||||
Geometry="M0,0 L0,1 0.015,1 0.015,0.015 1,0.015 1,0 Z"
|
||||
Brush="{DynamicResource GridLinesBrush}" />
|
||||
|
||||
<DrawingBrush x:Key="SmallGridLinesDrawingBrush"
|
||||
TileMode="Tile"
|
||||
ViewportUnits="Absolute"
|
||||
Viewport="{Binding GridSpacing, Source={x:Static local:EditorSettings.Instance}, Converter={local:UIntToRectConverter}}"
|
||||
Transform="{Binding ViewportTransform, ElementName=Editor}"
|
||||
Drawing="{StaticResource SmallGridGeometry}" />
|
||||
|
||||
<DrawingBrush x:Key="LargeGridLinesDrawingBrush"
|
||||
TileMode="Tile"
|
||||
ViewportUnits="Absolute"
|
||||
Opacity="0.5"
|
||||
Viewport="{Binding GridSpacing, Source={x:Static local:EditorSettings.Instance}, Converter={local:UIntToRectConverter Multiplier=10}}"
|
||||
Transform="{Binding ViewportTransform, ElementName=Editor}"
|
||||
Drawing="{StaticResource LargeGridGeometry}" />
|
||||
|
||||
<SolidColorBrush x:Key="SquareConnectorColor"
|
||||
Color="MediumSlateBlue" />
|
||||
<SolidColorBrush x:Key="TriangleConnectorColor"
|
||||
Color="MediumVioletRed" />
|
||||
<SolidColorBrush x:Key="SquareConnectorOutline"
|
||||
Color="MediumSlateBlue"
|
||||
Opacity="0.15" />
|
||||
<SolidColorBrush x:Key="TriangleConnectorOutline"
|
||||
Color="MediumVioletRed"
|
||||
Opacity="0.15" />
|
||||
|
||||
<UIElement x:Key="ConnectionAnimationPlaceholder"
|
||||
Opacity="1" />
|
||||
|
||||
<Storyboard x:Key="HighlightConnection">
|
||||
<DoubleAnimation Storyboard.Target="{StaticResource ConnectionAnimationPlaceholder}"
|
||||
Storyboard.TargetProperty="(UIElement.Opacity)"
|
||||
Duration="0:0:0.3"
|
||||
From="1"
|
||||
To="0.3" />
|
||||
</Storyboard>
|
||||
|
||||
<Style x:Key="ConnectionStyle"
|
||||
TargetType="{x:Type nodify:BaseConnection}"
|
||||
BasedOn="{StaticResource {x:Type nodify:BaseConnection}}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Input.Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Square}">
|
||||
<Setter Property="Stroke"
|
||||
Value="{StaticResource SquareConnectorColor}" />
|
||||
<Setter Property="Fill"
|
||||
Value="{StaticResource SquareConnectorColor}" />
|
||||
<Setter Property="OutlineBrush"
|
||||
Value="{StaticResource SquareConnectorOutline}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Input.Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||
<Setter Property="Stroke"
|
||||
Value="{StaticResource TriangleConnectorColor}" />
|
||||
<Setter Property="Fill"
|
||||
Value="{StaticResource TriangleConnectorColor}" />
|
||||
<Setter Property="OutlineBrush"
|
||||
Value="{StaticResource TriangleConnectorOutline}" />
|
||||
</DataTrigger>
|
||||
<Trigger Property="IsMouseDirectlyOver"
|
||||
Value="True">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard Name="HighlightConnection"
|
||||
Storyboard="{StaticResource HighlightConnection}" />
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<RemoveStoryboard BeginStoryboardName="HighlightConnection" />
|
||||
</Trigger.ExitActions>
|
||||
<Setter Property="Opacity"
|
||||
Value="1" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelectable"
|
||||
Value="True">
|
||||
<Setter Property="Cursor"
|
||||
Value="Hand" />
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseDirectlyOver"
|
||||
Value="False" />
|
||||
<Condition Property="IsSelected"
|
||||
Value="False" />
|
||||
</MultiTrigger.Conditions>
|
||||
<MultiTrigger.Setters>
|
||||
<Setter Property="OutlineBrush"
|
||||
Value="Transparent" />
|
||||
</MultiTrigger.Setters>
|
||||
</MultiTrigger>
|
||||
</Style.Triggers>
|
||||
<Setter Property="Opacity"
|
||||
Value="{Binding Source={StaticResource ConnectionAnimationPlaceholder}, Path=Opacity}" />
|
||||
<Setter Property="Stroke"
|
||||
Value="{DynamicResource Connection.StrokeBrush}" />
|
||||
<Setter Property="Fill"
|
||||
Value="{DynamicResource Connection.StrokeBrush}" />
|
||||
<Setter Property="OutlineBrush">
|
||||
<Setter.Value>
|
||||
<SolidColorBrush Color="{DynamicResource Connection.StrokeColor}"
|
||||
Opacity="0.15" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="ToolTip"
|
||||
Value="Double click to split" />
|
||||
<Setter Property="Source"
|
||||
Value="{Binding Output.Anchor}" />
|
||||
<Setter Property="Target"
|
||||
Value="{Binding Input.Anchor}" />
|
||||
<Setter Property="SplitCommand"
|
||||
Value="{Binding SplitCommand}" />
|
||||
<Setter Property="DisconnectCommand"
|
||||
Value="{Binding DisconnectCommand}" />
|
||||
<Setter Property="SourceOffsetMode"
|
||||
Value="{Binding ConnectionSourceOffsetMode, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="TargetOffsetMode"
|
||||
Value="{Binding ConnectionTargetOffsetMode, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="SourceOffset"
|
||||
Value="{Binding ConnectionSourceOffset.Size, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="TargetOffset"
|
||||
Value="{Binding ConnectionTargetOffset.Size, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="ArrowSize"
|
||||
Value="{Binding ConnectionArrowSize.Size, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="ArrowEnds"
|
||||
Value="{Binding ArrowHeadEnds, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="ArrowShape"
|
||||
Value="{Binding ArrowHeadShape, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="Spacing"
|
||||
Value="{Binding ConnectionSpacing, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="Direction"
|
||||
Value="{Binding Output.Flow, Converter={StaticResource FlowToDirectionConverter}}" />
|
||||
<Setter Property="SourceOrientation"
|
||||
Value="{Binding Output.Node.Orientation}" />
|
||||
<Setter Property="TargetOrientation"
|
||||
Value="{Binding Input.Node.Orientation}" />
|
||||
<Setter Property="DirectionalArrowsCount"
|
||||
Value="{Binding DirectionalArrowsCount, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="DirectionalArrowsOffset"
|
||||
Value="{Binding DirectionalArrowsOffset, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="IsAnimatingDirectionalArrows"
|
||||
Value="{Binding IsAnimatingConnections, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="DirectionalArrowsAnimationDuration"
|
||||
Value="{Binding DirectionalArrowsAnimationDuration, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="Text"
|
||||
Value="{Binding ConnectionText, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="IsSelectable"
|
||||
Value="{Binding SelectableConnections, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="IsSelected"
|
||||
Value="{Binding IsSelected}" />
|
||||
<Setter Property="StrokeThickness"
|
||||
Value="{Binding ConnectionStrokeThickness, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="OutlineThickness"
|
||||
Value="{Binding ConnectionOutlineThickness, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="FocusVisualPadding"
|
||||
Value="{Binding ConnectionFocusVisualPadding, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="CircuitConnectionTemplate">
|
||||
<nodify:CircuitConnection Style="{StaticResource ConnectionStyle}"
|
||||
Angle="{Binding CircuitConnectionAngle, Source={x:Static local:EditorSettings.Instance}}"
|
||||
CornerRadius="{Binding ConnectionCornerRadius, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="StepConnectionTemplate">
|
||||
<nodify:StepConnection Style="{StaticResource ConnectionStyle}"
|
||||
CornerRadius="{Binding ConnectionCornerRadius, Source={x:Static local:EditorSettings.Instance}}"
|
||||
SourcePosition="{Binding ., Converter={StaticResource FlowToConnectorPositionConverter}, ConverterParameter=Output}"
|
||||
TargetPosition="{Binding ., Converter={StaticResource FlowToConnectorPositionConverter}, ConverterParameter=Input}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="LineConnectionTemplate">
|
||||
<nodify:LineConnection Style="{StaticResource ConnectionStyle}"
|
||||
CornerRadius="{Binding ConnectionCornerRadius, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate x:Key="ConnectionTemplate">
|
||||
<nodify:Connection Style="{StaticResource ConnectionStyle}" />
|
||||
</DataTemplate>
|
||||
|
||||
<ControlTemplate x:Key="SquareConnector"
|
||||
TargetType="Control">
|
||||
<Rectangle Width="14"
|
||||
Height="14"
|
||||
StrokeDashCap="Round"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Stroke="{TemplateBinding BorderBrush}"
|
||||
Fill="{TemplateBinding Background}"
|
||||
StrokeThickness="2" />
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="TriangleConnector"
|
||||
TargetType="Control">
|
||||
<Polygon Width="14"
|
||||
Height="14"
|
||||
Points="1,13 13,13 7,1"
|
||||
StrokeDashCap="Round"
|
||||
StrokeLineJoin="Round"
|
||||
StrokeStartLineCap="Round"
|
||||
StrokeEndLineCap="Round"
|
||||
Stroke="{TemplateBinding BorderBrush}"
|
||||
Fill="{TemplateBinding Background}"
|
||||
StrokeThickness="2" />
|
||||
</ControlTemplate>
|
||||
|
||||
<Storyboard x:Key="MarchingAnts">
|
||||
<DoubleAnimation RepeatBehavior="Forever"
|
||||
Storyboard.TargetProperty="StrokeDashOffset"
|
||||
BeginTime="00:00:00"
|
||||
Duration="0:3:0"
|
||||
From="1000"
|
||||
To="0" />
|
||||
</Storyboard>
|
||||
|
||||
<Style x:Key="SelectionRectangleStyle"
|
||||
TargetType="Rectangle"
|
||||
BasedOn="{StaticResource NodifyEditor.SelectionRectangleStyle}">
|
||||
<Setter Property="StrokeDashArray"
|
||||
Value="4 4" />
|
||||
<Setter Property="StrokeThickness"
|
||||
Value="2" />
|
||||
<Style.Triggers>
|
||||
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
|
||||
<BeginStoryboard Storyboard="{StaticResource MarchingAnts}" />
|
||||
</EventTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CuttingLineStyle"
|
||||
TargetType="{x:Type nodify:CuttingLine}"
|
||||
BasedOn="{StaticResource {x:Type nodify:CuttingLine}}">
|
||||
<Setter Property="StrokeDashArray"
|
||||
Value="1 1" />
|
||||
<Setter Property="StrokeThickness"
|
||||
Value="2" />
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<nodify:NodifyEditor x:Name="Editor"
|
||||
ItemsSource="{Binding Nodes}"
|
||||
SelectedItem="{Binding SelectedNode}"
|
||||
SelectedItems="{Binding SelectedNodes}"
|
||||
CanSelectMultipleItems="{Binding CanSelectMultipleNodes, Source={x:Static local:EditorSettings.Instance}}"
|
||||
Connections="{Binding Connections}"
|
||||
SelectedConnection="{Binding SelectedConnection}"
|
||||
SelectedConnections="{Binding SelectedConnections}"
|
||||
CanSelectMultipleConnections="{Binding CanSelectMultipleConnections, Source={x:Static local:EditorSettings.Instance}}"
|
||||
PendingConnection="{Binding PendingConnection}"
|
||||
DisconnectConnectorCommand="{Binding DisconnectConnectorCommand}"
|
||||
ViewportLocation="{Binding Location.Value, Source={x:Static local:EditorSettings.Instance}}"
|
||||
ViewportSize="{Binding ViewportSize, Mode=OneWayToSource}"
|
||||
ViewportZoom="{Binding Zoom, Source={x:Static local:EditorSettings.Instance}}"
|
||||
MinViewportZoom="{Binding MinZoom, Source={x:Static local:EditorSettings.Instance}}"
|
||||
MaxViewportZoom="{Binding MaxZoom, Source={x:Static local:EditorSettings.Instance}}"
|
||||
AutoPanSpeed="{Binding AutoPanningSpeed, Source={x:Static local:EditorSettings.Instance}}"
|
||||
AutoPanEdgeDistance="{Binding AutoPanningEdgeDistance, Source={x:Static local:EditorSettings.Instance}}"
|
||||
GridCellSize="{Binding GridSpacing, Source={x:Static local:EditorSettings.Instance}}"
|
||||
EnableRealtimeSelection="{Binding EnableRealtimeSelection, Source={x:Static local:EditorSettings.Instance}}"
|
||||
DisableAutoPanning="{Binding DisableAutoPanning, Source={x:Static local:EditorSettings.Instance}}"
|
||||
DisablePanning="{Binding DisablePanning, Source={x:Static local:EditorSettings.Instance}}"
|
||||
DisableZooming="{Binding DisableZooming, Source={x:Static local:EditorSettings.Instance}}"
|
||||
DisplayConnectionsOnTop="{Binding DisplayConnectionsOnTop, Source={x:Static local:EditorSettings.Instance}}"
|
||||
BringIntoViewSpeed="{Binding BringIntoViewSpeed, Source={x:Static local:EditorSettings.Instance}}"
|
||||
BringIntoViewMaxDuration="{Binding BringIntoViewMaxDuration, Source={x:Static local:EditorSettings.Instance}}"
|
||||
SelectionRectangleStyle="{StaticResource SelectionRectangleStyle}"
|
||||
CuttingLineStyle="{StaticResource CuttingLineStyle}">
|
||||
<nodify:NodifyEditor.Style>
|
||||
<Style TargetType="{x:Type nodify:NodifyEditor}"
|
||||
BasedOn="{StaticResource {x:Type nodify:NodifyEditor}}">
|
||||
<Setter Property="ConnectionTemplate"
|
||||
Value="{StaticResource ConnectionTemplate}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ShowGridLines, Source={x:Static local:PlaygroundSettings.Instance}}"
|
||||
Value="True">
|
||||
<Setter Property="Background"
|
||||
Value="{StaticResource SmallGridLinesDrawingBrush}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ConnectionStyle, Mode=TwoWay, Source={x:Static local:EditorSettings.Instance}}"
|
||||
Value="Line">
|
||||
<Setter Property="ConnectionTemplate"
|
||||
Value="{StaticResource LineConnectionTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ConnectionStyle, Mode=TwoWay, Source={x:Static local:EditorSettings.Instance}}"
|
||||
Value="Circuit">
|
||||
<Setter Property="ConnectionTemplate"
|
||||
Value="{StaticResource CircuitConnectionTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ConnectionStyle, Mode=TwoWay, Source={x:Static local:EditorSettings.Instance}}"
|
||||
Value="Step">
|
||||
<Setter Property="ConnectionTemplate"
|
||||
Value="{StaticResource StepConnectionTemplate}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</nodify:NodifyEditor.Style>
|
||||
|
||||
<nodify:NodifyEditor.InputBindings>
|
||||
<KeyBinding Key="Delete"
|
||||
Command="{Binding DeleteSelectionCommand}" />
|
||||
<KeyBinding Key="C"
|
||||
Command="{Binding CommentSelectionCommand}" />
|
||||
</nodify:NodifyEditor.InputBindings>
|
||||
|
||||
<nodify:NodifyEditor.Resources>
|
||||
<Style TargetType="{x:Type nodify:PendingConnection}"
|
||||
BasedOn="{StaticResource {x:Type nodify:PendingConnection}}">
|
||||
<Setter Property="CompletedCommand"
|
||||
Value="{Binding Graph.CreateConnectionCommand}" />
|
||||
<Setter Property="Source"
|
||||
Value="{Binding Source, Mode=OneWayToSource}" />
|
||||
<Setter Property="Target"
|
||||
Value="{Binding PreviewTarget, Mode=OneWayToSource}" />
|
||||
<Setter Property="PreviewTarget"
|
||||
Value="{Binding PreviewTarget, Mode=OneWayToSource}" />
|
||||
<Setter Property="Content"
|
||||
Value="{Binding PreviewText}" />
|
||||
<Setter Property="EnablePreview"
|
||||
Value="{Binding EnablePendingConnectionPreview, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="EnableSnapping"
|
||||
Value="{Binding EnablePendingConnectionSnapping, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="AllowOnlyConnectors"
|
||||
Value="{Binding AllowConnectingToConnectorsOnly, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="Direction"
|
||||
Value="{Binding Source.Flow, Converter={StaticResource FlowToDirectionConverter}}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type nodify:PendingConnection}">
|
||||
<Canvas>
|
||||
<nodify:Connection Source="{TemplateBinding SourceAnchor}"
|
||||
Target="{TemplateBinding TargetAnchor}"
|
||||
Direction="{TemplateBinding Direction}"
|
||||
SourceOrientation="{Binding Source.Node.Orientation}"
|
||||
TargetOrientation="{Binding TargetOrientation}"
|
||||
DirectionalArrowsCount="{Binding DirectionalArrowsCount, Source={x:Static local:EditorSettings.Instance}}"
|
||||
StrokeThickness="{TemplateBinding StrokeThickness}"
|
||||
SourceOffset="{Binding ConnectionSourceOffset.Size, Source={x:Static local:EditorSettings.Instance}}"
|
||||
TargetOffset="{Binding ConnectionTargetOffset.Size, Source={x:Static local:EditorSettings.Instance}}"
|
||||
SourceOffsetMode="{Binding ConnectionSourceOffsetMode, Source={x:Static local:EditorSettings.Instance}}"
|
||||
TargetOffsetMode="None"
|
||||
ArrowSize="{Binding ConnectionArrowSize.Size, Source={x:Static local:EditorSettings.Instance}}"
|
||||
ArrowEnds="{Binding ArrowHeadEnds, Source={x:Static local:EditorSettings.Instance}}"
|
||||
ArrowShape="{Binding ArrowHeadShape, Source={x:Static local:EditorSettings.Instance}}"
|
||||
Spacing="{Binding ConnectionSpacing, Source={x:Static local:EditorSettings.Instance}}">
|
||||
<nodify:Connection.Style>
|
||||
<Style TargetType="nodify:Connection"
|
||||
BasedOn="{StaticResource {x:Type nodify:Connection}}">
|
||||
<Setter Property="Stroke"
|
||||
Value="{DynamicResource Connection.StrokeBrush}" />
|
||||
<Setter Property="Fill"
|
||||
Value="{DynamicResource Connection.StrokeBrush}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Source.Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Square}">
|
||||
<Setter Property="Stroke"
|
||||
Value="{StaticResource SquareConnectorColor}" />
|
||||
<Setter Property="Fill"
|
||||
Value="{StaticResource SquareConnectorColor}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Source.Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||
<Setter Property="Stroke"
|
||||
Value="{StaticResource TriangleConnectorColor}" />
|
||||
<Setter Property="Fill"
|
||||
Value="{StaticResource TriangleConnectorColor}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</nodify:Connection.Style>
|
||||
</nodify:Connection>
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
Canvas.Left="{Binding TargetAnchor.X, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Canvas.Top="{Binding TargetAnchor.Y, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Visibility="{Binding PreviewText, Converter={shared:StringToVisibilityConverter}}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
CornerRadius="3"
|
||||
Margin="15">
|
||||
<ContentPresenter />
|
||||
</Border>
|
||||
</Canvas>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type nodify:Connector}"
|
||||
BasedOn="{StaticResource {x:Type nodify:Connector}}">
|
||||
<Setter Property="Anchor"
|
||||
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||
<Setter Property="IsConnected"
|
||||
Value="{Binding IsConnected}" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type nodify:NodeInput}"
|
||||
BasedOn="{StaticResource {x:Type nodify:NodeInput}}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Square}">
|
||||
<Setter Property="ConnectorTemplate"
|
||||
Value="{StaticResource SquareConnector}" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{StaticResource SquareConnectorColor}" />
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Title}"
|
||||
Margin="0 0 5 0" />
|
||||
<TextBox Text="{Binding MaxConnections}"
|
||||
MinWidth="30" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||
<Setter Property="ConnectorTemplate"
|
||||
Value="{StaticResource TriangleConnector}" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{StaticResource TriangleConnectorColor}" />
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Title}"
|
||||
Margin="0 0 5 0"
|
||||
VerticalAlignment="Center" />
|
||||
<CheckBox />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
<Setter Property="HeaderTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<TextBlock Text="{Binding Title}" />
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Header"
|
||||
Value="{Binding}" />
|
||||
<Setter Property="Anchor"
|
||||
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||
<Setter Property="IsConnected"
|
||||
Value="{Binding IsConnected}" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<Style TargetType="{x:Type nodify:NodeOutput}"
|
||||
BasedOn="{StaticResource {x:Type nodify:NodeOutput}}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Square}">
|
||||
<Setter Property="ConnectorTemplate"
|
||||
Value="{StaticResource SquareConnector}" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{StaticResource SquareConnectorColor}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Shape}"
|
||||
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||
<Setter Property="ConnectorTemplate"
|
||||
Value="{StaticResource TriangleConnector}" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{StaticResource TriangleConnectorColor}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
<Setter Property="Header"
|
||||
Value="{Binding Title}" />
|
||||
<Setter Property="Anchor"
|
||||
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||
<Setter Property="IsConnected"
|
||||
Value="{Binding IsConnected}" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:KnotNodeViewModel}">
|
||||
<nodify:KnotNode Content="{Binding Connector}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:CommentNodeViewModel}">
|
||||
<nodify:GroupingNode ActualSize="{Binding Size}"
|
||||
Header="{Binding Title}"
|
||||
MovementMode="{Binding GroupingNodeMovement, Mode=TwoWay, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:FlowNodeViewModel}">
|
||||
<nodify:Node Input="{Binding Input}"
|
||||
Output="{Binding Output}"
|
||||
Header="{Binding Title}" />
|
||||
</DataTemplate>
|
||||
|
||||
<DataTemplate DataType="{x:Type local:VerticalNodeViewModel}">
|
||||
<nodify:Node Header="{Binding Input}"
|
||||
Footer="{Binding Output}"
|
||||
Content="{Binding Title}">
|
||||
<nodify:Node.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}"
|
||||
Margin="5" />
|
||||
</DataTemplate>
|
||||
</nodify:Node.ContentTemplate>
|
||||
<nodify:Node.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl ItemsSource="{Binding}"
|
||||
Focusable="False">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<nodify:NodeInput Orientation="Vertical" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</nodify:Node.HeaderTemplate>
|
||||
<nodify:Node.FooterTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl ItemsSource="{Binding}"
|
||||
Focusable="False">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||
<nodify:NodeOutput Orientation="Vertical" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</nodify:Node.FooterTemplate>
|
||||
</nodify:Node>
|
||||
</DataTemplate>
|
||||
</nodify:NodifyEditor.Resources>
|
||||
|
||||
<nodify:NodifyEditor.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type nodify:ItemContainer}"
|
||||
BasedOn="{StaticResource {x:Type nodify:ItemContainer}}">
|
||||
<Setter Property="BorderThickness"
|
||||
Value="2" />
|
||||
<Setter Property="SelectedBorderThickness"
|
||||
Value="4" />
|
||||
<Setter Property="IsSelectable"
|
||||
Value="{Binding SelectableNodes, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="IsDraggable"
|
||||
Value="{Binding DraggableNodes, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
<Setter Property="CacheMode">
|
||||
<Setter.Value>
|
||||
<BitmapCache RenderAtScale="{Binding MaxZoom, Source={x:Static local:EditorSettings.Instance}}"
|
||||
EnableClearType="True" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Location"
|
||||
Value="{Binding Location}" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsSelected"
|
||||
Value="True">
|
||||
<Setter Property="Panel.ZIndex"
|
||||
Value="1" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</nodify:NodifyEditor.ItemContainerStyle>
|
||||
</nodify:NodifyEditor>
|
||||
|
||||
<Grid Background="{StaticResource LargeGridLinesDrawingBrush}"
|
||||
Visibility="{Binding ShowGridLines, Source={x:Static local:PlaygroundSettings.Instance}, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
Panel.ZIndex="-2" />
|
||||
|
||||
<nodify:Minimap ItemsSource="{Binding ItemsSource, ElementName=Editor}"
|
||||
ViewportSize="{Binding ViewportSize, ElementName=Editor}"
|
||||
ViewportLocation="{Binding ViewportLocation, ElementName=Editor}"
|
||||
Visibility="{Binding ShowMinimap, Source={x:Static local:PlaygroundSettings.Instance}, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
IsReadOnly="{Binding DisableMinimapControls, Source={x:Static local:PlaygroundSettings.Instance}}"
|
||||
ResizeToViewport="{Binding ResizeToViewport, Source={x:Static local:PlaygroundSettings.Instance}}"
|
||||
MaxViewportOffset="{Binding MinimapMaxViewportOffset.Size, Source={x:Static local:PlaygroundSettings.Instance}}"
|
||||
Zoom="Minimap_Zoom"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Width="300"
|
||||
Height="200"
|
||||
Margin="5 40">
|
||||
<nodify:Minimap.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:NodeViewModel}">
|
||||
<Grid />
|
||||
</DataTemplate>
|
||||
</nodify:Minimap.ItemTemplate>
|
||||
<nodify:Minimap.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type nodify:MinimapItem}"
|
||||
BasedOn="{StaticResource {x:Type nodify:MinimapItem}}">
|
||||
<Setter Property="Location"
|
||||
Value="{Binding Location}" />
|
||||
<Setter Property="Width"
|
||||
Value="150" />
|
||||
<Setter Property="Height"
|
||||
Value="130" />
|
||||
</Style>
|
||||
</nodify:Minimap.ItemContainerStyle>
|
||||
</nodify:Minimap>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Margin="5 60"
|
||||
Width="250">
|
||||
<Border CornerRadius="3"
|
||||
Visibility="{Binding SelectedConnection, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
Background="{DynamicResource PanelBackgroundBrush}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderBrush}"
|
||||
Margin="0 0 0 10">
|
||||
<StackPanel Margin="10">
|
||||
<StackPanel.Resources>
|
||||
<Style TargetType="{x:Type TextBlock}"
|
||||
BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||
<Setter Property="Margin"
|
||||
Value="0 0 0 5" />
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
|
||||
<StackPanel Margin="0 0 0 14">
|
||||
<TextBlock Text="Selected connection"
|
||||
Foreground="{DynamicResource Node.ForegroundBrush}"
|
||||
FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock TextWrapping="Wrap"
|
||||
Margin="0 0 0 14"
|
||||
Foreground="{DynamicResource Node.ForegroundBrush}">
|
||||
<Run>From</Run>
|
||||
<Run Text="{Binding SelectedConnection.Output.Node.Title}"
|
||||
Foreground="Red" />
|
||||
<Run> - </Run>
|
||||
<Run Text="{Binding SelectedConnection.Output.Title}"
|
||||
Foreground="Red" />
|
||||
<Run>to</Run>
|
||||
<Run Text="{Binding SelectedConnection.Input.Node.Title}"
|
||||
Foreground="Red" />
|
||||
<Run> - </Run>
|
||||
<Run Text="{Binding SelectedConnection.Input.Title}"
|
||||
Foreground="Red" />
|
||||
</TextBlock>
|
||||
|
||||
<Button Command="{Binding SelectedConnection.DisconnectCommand}"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource HollowButton}"
|
||||
Content="Delete" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border CornerRadius="3"
|
||||
Visibility="{Binding SelectedNode, Converter={shared:BooleanToVisibilityConverter}}"
|
||||
Background="{DynamicResource PanelBackgroundBrush}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderBrush}">
|
||||
<StackPanel Margin="10">
|
||||
<StackPanel.Resources>
|
||||
<Style TargetType="{x:Type TextBlock}"
|
||||
BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||
<Setter Property="Margin"
|
||||
Value="0 0 0 5" />
|
||||
</Style>
|
||||
</StackPanel.Resources>
|
||||
|
||||
<StackPanel Margin="0 0 0 14">
|
||||
<TextBlock Text="Selected node"
|
||||
Foreground="{DynamicResource Node.ForegroundBrush}"
|
||||
FontWeight="Bold" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock TextWrapping="Wrap"
|
||||
Margin="0 0 0 14"
|
||||
Foreground="{DynamicResource Node.ForegroundBrush}">
|
||||
<Run>Title: </Run>
|
||||
<Run Text="{Binding SelectedNode.Title}"
|
||||
Foreground="Red" />
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap"
|
||||
Margin="0 0 0 14"
|
||||
Foreground="{DynamicResource Node.ForegroundBrush}">
|
||||
<Run>Location: </Run>
|
||||
<Run Text="{Binding SelectedNode.Location}"
|
||||
Foreground="Red" />
|
||||
</TextBlock>
|
||||
|
||||
<Button Command="{Binding SelectedNode.DeleteCommand}"
|
||||
HorizontalAlignment="Left"
|
||||
Style="{StaticResource HollowButton}"
|
||||
Content="Delete" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
51
Examples/Nodify.Playground/Editor/NodifyEditorView.xaml.cs
Normal file
51
Examples/Nodify.Playground/Editor/NodifyEditorView.xaml.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Nodify.Events;
|
||||
using Nodify.Interactivity;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public partial class NodifyEditorView : UserControl
|
||||
{
|
||||
public NodifyEditor EditorInstance => Editor;
|
||||
|
||||
public NodifyEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
EditorInstance.ActiveNavigationLayerChanged += DisplayActiveNavigationLayer;
|
||||
}
|
||||
|
||||
static NodifyEditorView()
|
||||
{
|
||||
InputProcessor.Shared<Connector>.ReplaceHandlerFactory<ConnectorState.Connecting>(elem => new CustomConnecting(elem));
|
||||
InputProcessor.Shared<Connector>.RegisterHandlerFactory(elem => new RetargetConnections(elem));
|
||||
}
|
||||
|
||||
private void Minimap_Zoom(object sender, ZoomEventArgs e)
|
||||
{
|
||||
EditorInstance.ZoomAtPosition(e.Zoom, e.Location);
|
||||
}
|
||||
|
||||
private void DisplayActiveNavigationLayer(KeyboardNavigationLayerId layerId)
|
||||
{
|
||||
var editorVm = (NodifyEditorViewModel)EditorInstance.DataContext;
|
||||
|
||||
if (layerId == KeyboardNavigationLayerId.Nodes)
|
||||
{
|
||||
editorVm.KeyboardNavigationLayer = nameof(KeyboardNavigationLayerId.Nodes);
|
||||
}
|
||||
else if (layerId == KeyboardNavigationLayerId.Connections)
|
||||
{
|
||||
editorVm.KeyboardNavigationLayer = nameof(KeyboardNavigationLayerId.Connections);
|
||||
}
|
||||
else if (layerId == KeyboardNavigationLayerId.Decorators)
|
||||
{
|
||||
editorVm.KeyboardNavigationLayer = nameof(KeyboardNavigationLayerId.Decorators);
|
||||
}
|
||||
else
|
||||
{
|
||||
editorVm.KeyboardNavigationLayer = "Custom";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
Examples/Nodify.Playground/Editor/NodifyEditorViewModel.cs
Normal file
133
Examples/Nodify.Playground/Editor/NodifyEditorViewModel.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class NodifyEditorViewModel : ObservableObject
|
||||
{
|
||||
public NodifyEditorViewModel()
|
||||
{
|
||||
Schema = new GraphSchema();
|
||||
|
||||
PendingConnection = new PendingConnectionViewModel
|
||||
{
|
||||
Graph = this
|
||||
};
|
||||
|
||||
DeleteSelectionCommand = new DelegateCommand(DeleteSelection, () => SelectedNodes.Count > 0 || SelectedConnections.Count > 0);
|
||||
CommentSelectionCommand = new RequeryCommand(() => Schema.AddCommentAroundNodes(SelectedNodes, "New comment"), () => SelectedNodes.Count > 0);
|
||||
DisconnectConnectorCommand = new DelegateCommand<ConnectorViewModel>(c => c.Disconnect());
|
||||
CreateConnectionCommand = new DelegateCommand<object>(target => Schema.TryAddConnection(PendingConnection.Source!, target),
|
||||
target => PendingConnection.Source != null && target != null && Schema.CanAddConnection(PendingConnection.Source, target));
|
||||
|
||||
Connections.WhenAdded(c =>
|
||||
{
|
||||
c.Graph = this;
|
||||
c.Input.Connections.Add(c);
|
||||
c.Output.Connections.Add(c);
|
||||
})
|
||||
// Called when the collection is cleared
|
||||
.WhenRemoved(c =>
|
||||
{
|
||||
c.Input.Connections.Remove(c);
|
||||
c.Output.Connections.Remove(c);
|
||||
});
|
||||
|
||||
Nodes.WhenAdded(x => x.Graph = this)
|
||||
// Not called when the collection is cleared
|
||||
.WhenRemoved(x =>
|
||||
{
|
||||
if (x is FlowNodeViewModel flow)
|
||||
{
|
||||
flow.Disconnect();
|
||||
}
|
||||
else if (x is KnotNodeViewModel knot)
|
||||
{
|
||||
knot.Connector.Disconnect();
|
||||
}
|
||||
})
|
||||
.WhenCleared(x => Connections.Clear());
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<NodeViewModel> _nodes = new NodifyObservableCollection<NodeViewModel>();
|
||||
public NodifyObservableCollection<NodeViewModel> Nodes
|
||||
{
|
||||
get => _nodes;
|
||||
set => SetProperty(ref _nodes, value);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<NodeViewModel> _selectedNodes = new NodifyObservableCollection<NodeViewModel>();
|
||||
public NodifyObservableCollection<NodeViewModel> SelectedNodes
|
||||
{
|
||||
get => _selectedNodes;
|
||||
set => SetProperty(ref _selectedNodes, value);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<ConnectionViewModel> _selectedConnections = new NodifyObservableCollection<ConnectionViewModel>();
|
||||
public NodifyObservableCollection<ConnectionViewModel> SelectedConnections
|
||||
{
|
||||
get => _selectedConnections;
|
||||
set => SetProperty(ref _selectedConnections, value);
|
||||
}
|
||||
|
||||
private NodifyObservableCollection<ConnectionViewModel> _connections = new NodifyObservableCollection<ConnectionViewModel>();
|
||||
public NodifyObservableCollection<ConnectionViewModel> Connections
|
||||
{
|
||||
get => _connections;
|
||||
set => SetProperty(ref _connections, value);
|
||||
}
|
||||
|
||||
private Size _viewportSize;
|
||||
public Size ViewportSize
|
||||
{
|
||||
get => _viewportSize;
|
||||
set => SetProperty(ref _viewportSize, value);
|
||||
}
|
||||
|
||||
public PendingConnectionViewModel PendingConnection { get; }
|
||||
|
||||
private ConnectionViewModel? _selectedConnection;
|
||||
public ConnectionViewModel? SelectedConnection
|
||||
{
|
||||
get => _selectedConnection;
|
||||
set => SetProperty(ref _selectedConnection, value);
|
||||
}
|
||||
|
||||
private NodeViewModel? _selectedNode;
|
||||
public NodeViewModel? SelectedNode
|
||||
{
|
||||
get => _selectedNode;
|
||||
set => SetProperty(ref _selectedNode, value);
|
||||
}
|
||||
|
||||
public GraphSchema Schema { get; }
|
||||
|
||||
private string? _keyboardNavigationLayer;
|
||||
public string? KeyboardNavigationLayer
|
||||
{
|
||||
get => _keyboardNavigationLayer;
|
||||
set => SetProperty(ref _keyboardNavigationLayer, value);
|
||||
}
|
||||
|
||||
public ICommand DeleteSelectionCommand { get; }
|
||||
public ICommand DisconnectConnectorCommand { get; }
|
||||
public ICommand CreateConnectionCommand { get; }
|
||||
public ICommand CommentSelectionCommand { get; }
|
||||
|
||||
private void DeleteSelection()
|
||||
{
|
||||
foreach (var connection in SelectedConnections.ToList())
|
||||
{
|
||||
connection.Remove();
|
||||
}
|
||||
|
||||
var selected = SelectedNodes.ToList();
|
||||
|
||||
for (int i = 0; i < selected.Count; i++)
|
||||
{
|
||||
Nodes.Remove(selected[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class PendingConnectionViewModel : ObservableObject
|
||||
{
|
||||
private NodifyEditorViewModel _graph = default!;
|
||||
public NodifyEditorViewModel Graph
|
||||
{
|
||||
get => _graph;
|
||||
internal set => SetProperty(ref _graph, value);
|
||||
}
|
||||
|
||||
private ConnectorViewModel? _source;
|
||||
public ConnectorViewModel? Source
|
||||
{
|
||||
get => _source;
|
||||
set
|
||||
{
|
||||
if(SetProperty(ref _source, value))
|
||||
{
|
||||
SetTargetOrientation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object? _previewTarget;
|
||||
public object? PreviewTarget
|
||||
{
|
||||
get => _previewTarget;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _previewTarget, value))
|
||||
{
|
||||
OnPreviewTargetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string? _previewText;
|
||||
public string? PreviewText
|
||||
{
|
||||
get => _previewText;
|
||||
set => SetProperty(ref _previewText, value);
|
||||
}
|
||||
|
||||
private Orientation _targetOrientation;
|
||||
public Orientation TargetOrientation
|
||||
{
|
||||
get => _targetOrientation;
|
||||
set => SetProperty(ref _targetOrientation, value);
|
||||
}
|
||||
|
||||
protected virtual void OnPreviewTargetChanged()
|
||||
{
|
||||
bool canConnect = PreviewTarget != null && Graph.Schema.CanAddConnection(Source!, PreviewTarget);
|
||||
PreviewText = PreviewTarget switch
|
||||
{
|
||||
ConnectorViewModel con when con == Source => $"Can't connect to self",
|
||||
ConnectorViewModel con => $"{(canConnect ? "Connect" : "Can't connect")} to {con.Title ?? "pin"}",
|
||||
FlowNodeViewModel flow => $"{(canConnect ? "Connect" : "Can't connect")} to {flow.Title ?? "node"}",
|
||||
_ => null
|
||||
};
|
||||
|
||||
SetTargetOrientation();
|
||||
}
|
||||
|
||||
private void SetTargetOrientation()
|
||||
{
|
||||
TargetOrientation = PreviewTarget switch
|
||||
{
|
||||
ConnectorViewModel con when con.Node is FlowNodeViewModel flow => flow.Orientation,
|
||||
FlowNodeViewModel flow => flow.Orientation,
|
||||
NodifyEditorViewModel editor when Source?.Node is FlowNodeViewModel flow => flow.Orientation,
|
||||
_ => Orientation.Horizontal,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
132
Examples/Nodify.Playground/Editor/RetargetConnections.cs
Normal file
132
Examples/Nodify.Playground/Editor/RetargetConnections.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using Nodify.Interactivity;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
/// <summary>
|
||||
/// Connecting state that prevents connecting when <see cref="RetargetConnections"/> is in progress.
|
||||
/// </summary>
|
||||
public class CustomConnecting : ConnectorState.Connecting
|
||||
{
|
||||
protected override bool CanBegin => !RetargetConnections.InProgress;
|
||||
|
||||
public CustomConnecting(Connector connector) : base(connector)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hold CTRL+LeftClick on a connector to start reconnecting it.
|
||||
/// </summary>
|
||||
public class RetargetConnections : DragState<Connector>
|
||||
{
|
||||
public static InputGestureRef Reconnect { get; } = new Interactivity.MouseGesture(MouseAction.LeftClick, ModifierKeys.Control)
|
||||
{
|
||||
IgnoreModifierKeysOnRelease = true
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Used to prevent connecting when <see cref="EditorSettings.EnableStickyConnectors"/> is enabled.
|
||||
/// </summary>
|
||||
public static bool InProgress { get; private set; }
|
||||
|
||||
protected override bool CanBegin => ViewModel.IsConnected && ViewModel.Flow == ConnectorFlow.Input;
|
||||
protected override bool IsToggle => EditorSettings.Instance.EnableStickyConnectors;
|
||||
|
||||
private ConnectorViewModel ViewModel => (ConnectorViewModel)Element.DataContext;
|
||||
private Vector _connectorOffset;
|
||||
private Connector? _targetConnector;
|
||||
|
||||
public RetargetConnections(Connector element) : base(element, Reconnect, EditorGestures.Mappings.Connector.CancelAction)
|
||||
{
|
||||
PositionElement = Element.Editor ?? (IInputElement)Element;
|
||||
}
|
||||
|
||||
protected override void OnBegin(InputEventArgs e)
|
||||
{
|
||||
_connectorOffset = ViewModel.Node.Orientation == Orientation.Horizontal
|
||||
? (Vector)EditorSettings.Instance.ConnectionTargetOffset.Value
|
||||
: new Vector(EditorSettings.Instance.ConnectionTargetOffset.Value.Y, EditorSettings.Instance.ConnectionTargetOffset.Value.X);
|
||||
|
||||
InProgress = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
var position = Element.Editor!.MouseLocation;
|
||||
|
||||
if (EditorSettings.Instance.EnablePendingConnectionHitTesting)
|
||||
{
|
||||
var connector = Element.FindTargetConnector(position);
|
||||
connector?.UpdateAnchor();
|
||||
|
||||
SetTargetConnector(connector);
|
||||
UpdateConnections(connector != null ? connector.Anchor : position + _connectorOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateConnections(position + _connectorOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateConnections(Point position)
|
||||
{
|
||||
foreach (var connection in ViewModel.Connections)
|
||||
{
|
||||
connection.Input.Anchor = position;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnEnd(InputEventArgs e)
|
||||
{
|
||||
var position = Element.Editor!.MouseLocation;
|
||||
var target = Element.FindTargetConnector(position);
|
||||
target?.UpdateAnchor();
|
||||
|
||||
if (target?.DataContext is ConnectorViewModel targetVM && ViewModel != targetVM)
|
||||
{
|
||||
ViewModel.Node.Graph.Schema.Rewire(ViewModel, targetVM);
|
||||
}
|
||||
|
||||
SetTargetConnector(null);
|
||||
|
||||
// Reset the position of connections that were not rewired
|
||||
Element.UpdateAnchor();
|
||||
InProgress = false;
|
||||
}
|
||||
|
||||
protected override void OnCancel(InputEventArgs e)
|
||||
{
|
||||
SetTargetConnector(null);
|
||||
|
||||
// Reset the position of connections that were not rewired
|
||||
Element.UpdateAnchor();
|
||||
InProgress = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the connection target and updates the visual state of the target element.
|
||||
/// </summary>
|
||||
private void SetTargetConnector(Connector? target)
|
||||
{
|
||||
if (target == _targetConnector)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_targetConnector != null)
|
||||
{
|
||||
PendingConnection.SetIsOverElement(_targetConnector, false);
|
||||
}
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
PendingConnection.SetIsOverElement(target, true);
|
||||
}
|
||||
|
||||
_targetConnector = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Examples/Nodify.Playground/Editor/VerticalNodeViewModel.cs
Normal file
12
Examples/Nodify.Playground/Editor/VerticalNodeViewModel.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class VerticalNodeViewModel : FlowNodeViewModel
|
||||
{
|
||||
public VerticalNodeViewModel()
|
||||
{
|
||||
Orientation = Orientation.Vertical;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Examples/Nodify.Playground/EditorInputMode.cs
Normal file
82
Examples/Nodify.Playground/EditorInputMode.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using Nodify.Interactivity;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public enum EditorInputMode
|
||||
{
|
||||
Default,
|
||||
PanOnly,
|
||||
SelectOnly,
|
||||
CutOnly
|
||||
}
|
||||
|
||||
public enum EditorGesturesMappings
|
||||
{
|
||||
Default,
|
||||
Custom
|
||||
}
|
||||
|
||||
public static class EditorInputModeExtensions
|
||||
{
|
||||
public static void Apply(this EditorGestures mappings, EditorInputMode inputMode)
|
||||
{
|
||||
mappings.Apply(PlaygroundSettings.Instance.EditorGesturesMappings.ToGesturesMappings());
|
||||
|
||||
switch (inputMode)
|
||||
{
|
||||
case EditorInputMode.PanOnly:
|
||||
mappings.Editor.Selection.Unbind();
|
||||
mappings.Editor.Cutting.Unbind();
|
||||
mappings.ItemContainer.Selection.Unbind();
|
||||
mappings.ItemContainer.Drag.Unbind();
|
||||
mappings.Connector.Connect.Unbind();
|
||||
break;
|
||||
case EditorInputMode.SelectOnly:
|
||||
mappings.Editor.Pan.Unbind();
|
||||
mappings.Editor.Cutting.Unbind();
|
||||
mappings.ItemContainer.Drag.Unbind();
|
||||
mappings.Connector.Connect.Unbind();
|
||||
break;
|
||||
case EditorInputMode.CutOnly:
|
||||
mappings.Editor.Cutting.Value = new Interactivity.MouseGesture(MouseAction.LeftClick);
|
||||
mappings.Editor.Selection.Unbind();
|
||||
mappings.Editor.Pan.Unbind();
|
||||
mappings.ItemContainer.Selection.Unbind();
|
||||
mappings.ItemContainer.Drag.Unbind();
|
||||
mappings.Connector.Connect.Unbind();
|
||||
break;
|
||||
case EditorInputMode.Default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Apply(this EditorGestures value, EditorGesturesMappings mappings)
|
||||
{
|
||||
var newMappings = mappings.ToGesturesMappings();
|
||||
value.Apply(newMappings);
|
||||
}
|
||||
|
||||
public static EditorGestures ToGesturesMappings(this EditorGesturesMappings mappings)
|
||||
{
|
||||
return mappings switch
|
||||
{
|
||||
EditorGesturesMappings.Custom => new CustomGesturesMappings(),
|
||||
_ => new EditorGestures()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomGesturesMappings : EditorGestures
|
||||
{
|
||||
public CustomGesturesMappings()
|
||||
{
|
||||
Editor.Pan.Value = new AnyGesture(new Interactivity.MouseGesture(MouseAction.LeftClick), new Interactivity.MouseGesture(MouseAction.MiddleClick));
|
||||
Editor.ZoomModifierKey = ModifierKeys.Control;
|
||||
Editor.Selection.Apply(new SelectionGestures(MouseAction.RightClick));
|
||||
// comment to drag with right click - we copy the default gestures of the item container which uses left click for selection
|
||||
ItemContainer.Drag.Value = new AnyGesture(ItemContainer.Selection.Replace.Value, ItemContainer.Selection.Remove.Value, ItemContainer.Selection.Append.Value, ItemContainer.Selection.Invert.Value);
|
||||
ItemContainer.Selection.Apply(Editor.Selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
951
Examples/Nodify.Playground/EditorSettings.cs
Normal file
951
Examples/Nodify.Playground/EditorSettings.cs
Normal file
@@ -0,0 +1,951 @@
|
||||
using Nodify.Interactivity;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public enum ConnectionStyle
|
||||
{
|
||||
Default,
|
||||
Line,
|
||||
Circuit,
|
||||
Step
|
||||
}
|
||||
|
||||
public class EditorSettings : ObservableObject
|
||||
{
|
||||
private readonly IReadOnlyCollection<ISettingViewModel> _settings;
|
||||
public IEnumerable<ISettingViewModel> Settings => PlaygroundSettings.Instance.FilterAndSort(_settings);
|
||||
|
||||
private readonly IReadOnlyCollection<ISettingViewModel> _advancedSettings;
|
||||
public IEnumerable<ISettingViewModel> AdvancedSettings => PlaygroundSettings.Instance.FilterAndSort(_advancedSettings);
|
||||
|
||||
private EditorSettings()
|
||||
{
|
||||
PlaygroundSettings.Instance.PropertyChanged += OnSearchTextChanged;
|
||||
|
||||
_settings = new List<ISettingViewModel>()
|
||||
{
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableRealtimeSelection,
|
||||
val => Instance.EnableRealtimeSelection = val,
|
||||
"Realtime selection: ",
|
||||
"Selects items when finished if disabled."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.SelectableNodes,
|
||||
val => Instance.SelectableNodes = val,
|
||||
"Selectable nodes: ",
|
||||
"Whether nodes can be selected."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.DraggableNodes,
|
||||
val => Instance.DraggableNodes= val,
|
||||
"Draggable nodes: ",
|
||||
"Whether nodes can be dragged."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.CanSelectMultipleNodes,
|
||||
val => Instance.CanSelectMultipleNodes = val,
|
||||
"Can select multiple nodes: "),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnablePendingConnectionSnapping,
|
||||
val => Instance.EnablePendingConnectionSnapping = val,
|
||||
"Pending connection snapping: ",
|
||||
"Whether to snap the pending connection to connectors"),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnablePendingConnectionPreview,
|
||||
val => Instance.EnablePendingConnectionPreview = val,
|
||||
"Pending connection preview: ",
|
||||
"Show information about the pending connection."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowConnectingToConnectorsOnly,
|
||||
val => Instance.AllowConnectingToConnectorsOnly = val,
|
||||
"Disable drop connection on node: ",
|
||||
"Can connect directly to nodes if enabled"),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.DisableAutoPanning,
|
||||
val => Instance.DisableAutoPanning = val,
|
||||
"Disable auto panning: "),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.DisablePanning,
|
||||
val => Instance.DisablePanning = val,
|
||||
"Disable panning: "),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.DisableZooming,
|
||||
val => Instance.DisableZooming = val,
|
||||
"Disable zooming: "),
|
||||
new ProxySettingViewModel<uint>(
|
||||
() => Instance.GridSpacing,
|
||||
val => Instance.GridSpacing = val,
|
||||
"Grid spacing: ",
|
||||
"Snapping value in pixels"),
|
||||
new ProxySettingViewModel<PointEditor>(
|
||||
() => Instance.Location,
|
||||
val => Instance.Location = val,
|
||||
"Location: ",
|
||||
"The viewport's location."),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.Zoom,
|
||||
val => Instance.Zoom = val,
|
||||
"Zoom: ",
|
||||
"The viewport's zoom. Not accurate when trying to zoom outside the MinViewportZoom and MaxViewportZoom because of dependency property coercion not updating the binding with the final result."),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.MinZoom,
|
||||
val => Instance.MinZoom = val,
|
||||
"Min zoom: "),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.MaxZoom,
|
||||
val => Instance.MaxZoom = val,
|
||||
"Max zoom: "),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.AutoPanningSpeed,
|
||||
val => Instance.AutoPanningSpeed = val,
|
||||
"Auto panning speed: ",
|
||||
"Speed value in pixels per tick"),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.AutoPanningEdgeDistance,
|
||||
val => Instance.AutoPanningEdgeDistance = val,
|
||||
"Auto panning edge distance: ",
|
||||
"Distance from edge to trigger auto panning"),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableStickyConnectors,
|
||||
val => Instance.EnableStickyConnectors = val,
|
||||
"Enable sticky connectors: ",
|
||||
"The connection can be completed in two steps (e.g. click to create pending connection, click to connect)"),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.SelectableConnections,
|
||||
val => Instance.SelectableConnections = val,
|
||||
"Selectable connections: ",
|
||||
"Whether connections can be selected."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.CanSelectMultipleConnections,
|
||||
val => Instance.CanSelectMultipleConnections = val,
|
||||
"Can select multiple connections: "),
|
||||
new ProxySettingViewModel<ConnectionStyle>(
|
||||
() => Instance.ConnectionStyle,
|
||||
val => Instance.ConnectionStyle = val,
|
||||
"Connection style: "),
|
||||
new ProxySettingViewModel<string?>(
|
||||
() => Instance.ConnectionText,
|
||||
val => Instance.ConnectionText = val,
|
||||
"Connection text: "),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.CircuitConnectionAngle,
|
||||
val => Instance.CircuitConnectionAngle = val,
|
||||
"Connection angle: ",
|
||||
"Applies to circuit connection style"),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.ConnectionCornerRadius,
|
||||
val => Instance.ConnectionCornerRadius = val,
|
||||
"Connection corner radius: ",
|
||||
"The corner radius between the line segments."),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.ConnectionSpacing,
|
||||
val => Instance.ConnectionSpacing = val,
|
||||
"Connection spacing: ",
|
||||
"The distance between the start point and the where the angle breaks"),
|
||||
new ProxySettingViewModel<PointEditor>(
|
||||
() => Instance.ConnectionArrowSize,
|
||||
val => Instance.ConnectionArrowSize = val,
|
||||
"Connection arrowhead size: ",
|
||||
"The size of the arrowhead."),
|
||||
new ProxySettingViewModel<uint>(
|
||||
() => Instance.DirectionalArrowsCount,
|
||||
val => Instance.DirectionalArrowsCount = val,
|
||||
"Directional arrows count: ",
|
||||
"The number of arrowheads to draw on the line flowing in the direction of the connection."),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.DirectionalArrowsOffset,
|
||||
val => Instance.DirectionalArrowsOffset = val,
|
||||
"Directional arrows offset: ",
|
||||
"Used to animate the directional arrowheads flowing in the direction of the connection (value is between 0 and 1)."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.IsAnimatingConnections,
|
||||
val => Instance.IsAnimatingConnections = val,
|
||||
"Animate directional arrows: ",
|
||||
"Used to animate the directional arrowheads by animating the DirectionalArrowsOffset value"),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.DirectionalArrowsAnimationDuration,
|
||||
val => Instance.DirectionalArrowsAnimationDuration = val,
|
||||
"Arrows animation duration: ",
|
||||
"The duration in seconds of a directional arrowhead flowing from start to end."),
|
||||
new ProxySettingViewModel<ArrowHeadEnds>(
|
||||
() => Instance.ArrowHeadEnds,
|
||||
val => Instance.ArrowHeadEnds = val,
|
||||
"Connection arrowhead end: ",
|
||||
"The location of the arrowhead."),
|
||||
new ProxySettingViewModel<ArrowHeadShape>(
|
||||
() => Instance.ArrowHeadShape,
|
||||
val => Instance.ArrowHeadShape = val,
|
||||
"Connection arrowhead shape: ",
|
||||
"The shape of the arrow head."),
|
||||
new ProxySettingViewModel<ConnectionOffsetMode>(
|
||||
() => Instance.ConnectionSourceOffsetMode,
|
||||
val => Instance.ConnectionSourceOffsetMode = val,
|
||||
"Connection source offset mode: "),
|
||||
new ProxySettingViewModel<ConnectionOffsetMode>(
|
||||
() => Instance.ConnectionTargetOffsetMode,
|
||||
val => Instance.ConnectionTargetOffsetMode = val,
|
||||
"Connection target offset mode: "),
|
||||
new ProxySettingViewModel<PointEditor>(
|
||||
() => Instance.ConnectionSourceOffset,
|
||||
val => Instance.ConnectionSourceOffset = val,
|
||||
"Connection source offset: "),
|
||||
new ProxySettingViewModel<PointEditor>(
|
||||
() => Instance.ConnectionTargetOffset,
|
||||
val => Instance.ConnectionTargetOffset = val,
|
||||
"Connection target offset: "),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.ConnectionStrokeThickness,
|
||||
val => Instance.ConnectionStrokeThickness = val,
|
||||
"Connection stroke thickness: "),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.ConnectionOutlineThickness,
|
||||
val => Instance.ConnectionOutlineThickness = val,
|
||||
"Connection outline thickness: "),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.ConnectionFocusVisualPadding,
|
||||
val => Instance.ConnectionFocusVisualPadding = val,
|
||||
"Connection focus visual padding: "),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.DisplayConnectionsOnTop,
|
||||
val => Instance.DisplayConnectionsOnTop = val,
|
||||
"Display connections on top: "),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.BringIntoViewSpeed,
|
||||
val => Instance.BringIntoViewSpeed = val,
|
||||
"Bring into view speed: ",
|
||||
"Bring location into view animation speed in pixels per second"),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.BringIntoViewMaxDuration,
|
||||
val => Instance.BringIntoViewMaxDuration = val,
|
||||
"Bring into view max duration: ",
|
||||
"Bring location into view max animation duration"),
|
||||
new ProxySettingViewModel<GroupingMovementMode>(
|
||||
() => Instance.GroupingNodeMovement,
|
||||
val => Instance.GroupingNodeMovement = val,
|
||||
"Grouping node movement: ",
|
||||
"Whether the grouping node is sticky or not"),
|
||||
};
|
||||
|
||||
_advancedSettings = new List<ISettingViewModel>()
|
||||
{
|
||||
new ProxySettingViewModel<uint>(
|
||||
() => Instance.MaxHotKeys,
|
||||
val => Instance.MaxHotKeys = val,
|
||||
"Max hot keys: ",
|
||||
"The maximum number of generated hot keys"),
|
||||
new ProxySettingViewModel<HotKeysDisplayMode>(
|
||||
() => Instance.HotKeysDisplayMode,
|
||||
val => Instance.HotKeysDisplayMode = val,
|
||||
"Hot keys display mode: ",
|
||||
"Specifies how hotkeys are displayed for a pending connection."),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.MouseActionSuppressionThreshold,
|
||||
val => Instance.MouseActionSuppressionThreshold = val,
|
||||
"Context menu suppression threshold: ",
|
||||
"Disable context menu after mouse moved this far."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.PreserveSelectionOnRightClick,
|
||||
val => Instance.PreserveSelectionOnRightClick = val,
|
||||
"Preserve selection on right click: ",
|
||||
"Whether right-click on the container should preserve the current selection."),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.AutoPanningTickRate,
|
||||
val => Instance.AutoPanningTickRate = val,
|
||||
"Auto panning tick rate: ",
|
||||
"How often is the new position calculated in milliseconds. Disable and enable auto panning for this to have effect."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableSnappingCorrection,
|
||||
val => Instance.EnableSnappingCorrection = val,
|
||||
"Enable snapping correction: ",
|
||||
"Correct the final position when moving a selection."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableCuttingLinePreview,
|
||||
val => Instance.EnableCuttingLinePreview = val,
|
||||
"Enable cutting line preview: ",
|
||||
"Applies custom connection style on intersection (hurts performance due to hit testing)."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnablePendingConnectionHitTesting,
|
||||
val => Instance.EnablePendingConnectionHitTesting = val,
|
||||
"Enable pending connection hit testing: ",
|
||||
"Disable to improve performance."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableConnectorOptimizations,
|
||||
val => Instance.EnableConnectorOptimizations = val,
|
||||
"Enable connector optimizations: ",
|
||||
"Enables optimizations for connectors based on viewport distance and minimum selected nodes."),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.OptimizeSafeZone,
|
||||
val => Instance.OptimizeSafeZone = val,
|
||||
"Optimize connectors safe zone: ",
|
||||
"The minimum distance from the viewport where connectors will start optimizing"),
|
||||
new ProxySettingViewModel<uint>(
|
||||
() => Instance.OptimizeMinimumSelectedItems,
|
||||
val => Instance.OptimizeMinimumSelectedItems = val,
|
||||
"Optimize connectors minimum selection: ",
|
||||
"The minimum selected items needed to start optimizing when outside the safe zone."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableRenderingOptimizations,
|
||||
val => Instance.EnableRenderingOptimizations = val,
|
||||
"Enable nodes rendering optimization: ",
|
||||
"Enables rendering optimizations for nodes based on zoom out percent and nodes count. (zoom in/out to apply optimization)"),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.OptimizeRenderingZoomOutPercent,
|
||||
val => Instance.OptimizeRenderingZoomOutPercent = val,
|
||||
"Rendering optimization zoom out percent: ",
|
||||
"The zoom out percent that triggers the optimization. (percent of x = 1 - MinViewportZoom)"),
|
||||
new ProxySettingViewModel<uint>(
|
||||
() => Instance.OptimizeRenderingMinimumNodes,
|
||||
val => Instance.OptimizeRenderingMinimumNodes = val,
|
||||
"Rendering optimization minimum nodes: ",
|
||||
"The minimum nodes needed to start optimizing when zoom out percent is met."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableDraggingOptimizations,
|
||||
val => Instance.EnableDraggingOptimizations = val,
|
||||
"Enable nodes dragging optimizations: ",
|
||||
"Simulates dragging visually but only commits the changes at the end."),
|
||||
new ProxySettingViewModel<double>(
|
||||
() => Instance.FitToScreenExtentMargin,
|
||||
val => Instance.FitToScreenExtentMargin = val,
|
||||
"Fit to screen extent margin: ",
|
||||
"Adds some margin to the nodes extent when fit to screen"),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowMinimapPanningCancellation,
|
||||
val => Instance.AllowMinimapPanningCancellation = val,
|
||||
"Allow minimap panning cancellation: ",
|
||||
"Right click or escape to cancel panning."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowCuttingCancellation,
|
||||
val => Instance.AllowCuttingCancellation = val,
|
||||
"Allow cutting cancellation: ",
|
||||
"Right click to cancel cutting."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowPushItemsCancellation,
|
||||
val => Instance.AllowPushItemsCancellation = val,
|
||||
"Allow push nodes cancellation: ",
|
||||
"Right click to cancel pushing nodes."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowPanningCancellation,
|
||||
val => Instance.AllowPanningCancellation= val,
|
||||
"Allow panning cancellation: ",
|
||||
"Press Escape to cancel panning."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowSelectionCancellation,
|
||||
val => Instance.AllowSelectionCancellation = val,
|
||||
"Allow selection cancellation: ",
|
||||
"Press Escape to cancel selecting."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowDraggingCancellation,
|
||||
val => Instance.AllowDraggingCancellation = val,
|
||||
"Allow dragging cancellation: ",
|
||||
"Right click to cancel dragging."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowPendingConnectionCancellation,
|
||||
val => Instance.AllowPendingConnectionCancellation = val,
|
||||
"Allow pending connection cancellation: ",
|
||||
"Right click to cancel pending connection."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableToggledCutting,
|
||||
val => Instance.EnableToggledCutting = val,
|
||||
"Enable toggled cutting mode: ",
|
||||
"The interaction will be completed in two steps using the same gesture to start and end."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableToggledPushingItems,
|
||||
val => Instance.EnableToggledPushingItems = val,
|
||||
"Enable toggled pushing items mode: ",
|
||||
"The interaction will be completed in two steps using the same gesture to start and end."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableToggledPanning,
|
||||
val => Instance.EnableToggledPanning = val,
|
||||
"Enable toggled panning mode: ",
|
||||
"The interaction will be completed in two steps using the same gesture to start and end."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableToggledSelecting,
|
||||
val => Instance.EnableToggledSelecting = val,
|
||||
"Enable toggled selecting mode: ",
|
||||
"The interaction will be completed in two steps using the same gesture to start and end."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableToggledDragging,
|
||||
val => Instance.EnableToggledDragging = val,
|
||||
"Enable toggled dragging mode: ",
|
||||
"The interaction will be completed in two steps using the same gesture to start and end."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.EnableMinimapToggledPanning,
|
||||
val => Instance.EnableMinimapToggledPanning = val,
|
||||
"Enable minimap toggled panning mode: ",
|
||||
"The interaction will be completed in two steps using the same gesture to start and end."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowPanningWhileSelecting,
|
||||
val => Instance.AllowPanningWhileSelecting = val,
|
||||
"Allow panning while selecting: ",
|
||||
"Whether panning is allowed while selecting items in the editor."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowPanningWhileCutting,
|
||||
val => Instance.AllowPanningWhileCutting = val,
|
||||
"Allow panning while cutting: ",
|
||||
"Whether panning is allowed while cutting connections in the editor."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowPanningWhilePushingItems,
|
||||
val => Instance.AllowPanningWhilePushingItems = val,
|
||||
"Allow panning while pushing items: ",
|
||||
"Whether panning is allowed while pushing items items in the editor."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowZoomingWhileSelecting,
|
||||
val => Instance.AllowZoomingWhileSelecting = val,
|
||||
"Allow zooming while selecting: ",
|
||||
"Whether zooming is allowed while selecting items in the editor."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowZoomingWhileCutting,
|
||||
val => Instance.AllowZoomingWhileCutting = val,
|
||||
"Allow zooming while cutting: ",
|
||||
"Whether zooming is allowed while cutting connections in the editor."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowZoomingWhilePushingItems,
|
||||
val => Instance.AllowZoomingWhilePushingItems = val,
|
||||
"Allow zooming while pushing items: ",
|
||||
"Whether zooming is allowed while pushing items connections in the editor."),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AllowZoomingWhilePanning,
|
||||
val => Instance.AllowZoomingWhilePanning = val,
|
||||
"Allow zooming while panning: ",
|
||||
"Whether zooming is allowed while panning connections in the editor."),
|
||||
};
|
||||
|
||||
EnableCuttingLinePreview = true;
|
||||
}
|
||||
|
||||
private void OnSearchTextChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(PlaygroundSettings.SearchText))
|
||||
{
|
||||
OnPropertyChanged(nameof(Settings));
|
||||
OnPropertyChanged(nameof(AdvancedSettings));
|
||||
}
|
||||
}
|
||||
|
||||
public static EditorSettings Instance { get; } = new EditorSettings();
|
||||
|
||||
#region Default settings
|
||||
|
||||
private bool _enablePendingConnectionSnapping = true;
|
||||
public bool EnablePendingConnectionSnapping
|
||||
{
|
||||
get => _enablePendingConnectionSnapping;
|
||||
set => SetProperty(ref _enablePendingConnectionSnapping, value);
|
||||
}
|
||||
|
||||
private bool _enablePendingConnectionPreview = true;
|
||||
public bool EnablePendingConnectionPreview
|
||||
{
|
||||
get => _enablePendingConnectionPreview;
|
||||
set => SetProperty(ref _enablePendingConnectionPreview, value);
|
||||
}
|
||||
|
||||
private bool _allowConnectingToConnectorsOnly;
|
||||
public bool AllowConnectingToConnectorsOnly
|
||||
{
|
||||
get => _allowConnectingToConnectorsOnly;
|
||||
set => SetProperty(ref _allowConnectingToConnectorsOnly, value);
|
||||
}
|
||||
|
||||
private bool _realtimeSelection = true;
|
||||
public bool EnableRealtimeSelection
|
||||
{
|
||||
get => _realtimeSelection;
|
||||
set => SetProperty(ref _realtimeSelection, value);
|
||||
}
|
||||
|
||||
private bool _disableAutoPanning = false;
|
||||
public bool DisableAutoPanning
|
||||
{
|
||||
get => _disableAutoPanning;
|
||||
set => SetProperty(ref _disableAutoPanning, value);
|
||||
}
|
||||
|
||||
private double _autoPanningSpeed = 15d;
|
||||
public double AutoPanningSpeed
|
||||
{
|
||||
get => _autoPanningSpeed;
|
||||
set => SetProperty(ref _autoPanningSpeed, value);
|
||||
}
|
||||
|
||||
private double _autoPanningEdgeDistance = 15d;
|
||||
public double AutoPanningEdgeDistance
|
||||
{
|
||||
get => _autoPanningEdgeDistance;
|
||||
set => SetProperty(ref _autoPanningEdgeDistance, value);
|
||||
}
|
||||
|
||||
private bool _disablePanning = false;
|
||||
public bool DisablePanning
|
||||
{
|
||||
get => _disablePanning;
|
||||
set => SetProperty(ref _disablePanning, value);
|
||||
}
|
||||
|
||||
private bool _disableZooming = false;
|
||||
public bool DisableZooming
|
||||
{
|
||||
get => _disableZooming;
|
||||
set => SetProperty(ref _disableZooming, value);
|
||||
}
|
||||
|
||||
private uint _gridSpacing = 15u;
|
||||
public uint GridSpacing
|
||||
{
|
||||
get => _gridSpacing;
|
||||
set => SetProperty(ref _gridSpacing, value);
|
||||
}
|
||||
|
||||
private double _minZoom = 0.1;
|
||||
public double MinZoom
|
||||
{
|
||||
get => _minZoom;
|
||||
set => SetProperty(ref _minZoom, value);
|
||||
}
|
||||
|
||||
private double _maxZoom = 2;
|
||||
public double MaxZoom
|
||||
{
|
||||
get => _maxZoom;
|
||||
set => SetProperty(ref _maxZoom, value);
|
||||
}
|
||||
|
||||
private double _zoom = 1;
|
||||
public double Zoom
|
||||
{
|
||||
get => _zoom;
|
||||
set => SetProperty(ref _zoom, value);
|
||||
}
|
||||
|
||||
private PointEditor _location = new PointEditor();
|
||||
public PointEditor Location
|
||||
{
|
||||
get => _location;
|
||||
set => SetProperty(ref _location, value);
|
||||
}
|
||||
|
||||
private bool _selectableConnections = true;
|
||||
public bool SelectableConnections
|
||||
{
|
||||
get => _selectableConnections;
|
||||
set => SetProperty(ref _selectableConnections, value);
|
||||
}
|
||||
|
||||
private bool _canSelectMultipleConnections = true;
|
||||
public bool CanSelectMultipleConnections
|
||||
{
|
||||
get => _canSelectMultipleConnections;
|
||||
set => SetProperty(ref _canSelectMultipleConnections, value);
|
||||
}
|
||||
|
||||
private bool _draggableNodes = true;
|
||||
public bool DraggableNodes
|
||||
{
|
||||
get => _draggableNodes;
|
||||
set => SetProperty(ref _draggableNodes, value);
|
||||
}
|
||||
|
||||
private bool _selectableNodes = true;
|
||||
public bool SelectableNodes
|
||||
{
|
||||
get => _selectableNodes;
|
||||
set => SetProperty(ref _selectableNodes, value);
|
||||
}
|
||||
|
||||
private bool _canSelectMultipleNodes = true;
|
||||
public bool CanSelectMultipleNodes
|
||||
{
|
||||
get => _canSelectMultipleNodes;
|
||||
set => SetProperty(ref _canSelectMultipleNodes, value);
|
||||
}
|
||||
|
||||
private ConnectionStyle _connectionStyle;
|
||||
public ConnectionStyle ConnectionStyle
|
||||
{
|
||||
get => _connectionStyle;
|
||||
set => SetProperty(ref _connectionStyle, value);
|
||||
}
|
||||
|
||||
private string? _connectionText;
|
||||
public string? ConnectionText
|
||||
{
|
||||
get => _connectionText;
|
||||
set => SetProperty(ref _connectionText, value);
|
||||
}
|
||||
|
||||
private double _circuitConnectionAngle = 45;
|
||||
public double CircuitConnectionAngle
|
||||
{
|
||||
get => _circuitConnectionAngle;
|
||||
set => SetProperty(ref _circuitConnectionAngle, value);
|
||||
}
|
||||
|
||||
private double _connectionCornerRadius = 10;
|
||||
public double ConnectionCornerRadius
|
||||
{
|
||||
get => _connectionCornerRadius;
|
||||
set => SetProperty(ref _connectionCornerRadius, value);
|
||||
}
|
||||
|
||||
private double _connectionSpacing = 20;
|
||||
public double ConnectionSpacing
|
||||
{
|
||||
get => _connectionSpacing;
|
||||
set => SetProperty(ref _connectionSpacing, value);
|
||||
}
|
||||
|
||||
private ConnectionOffsetMode _srcConnectionOffsetMode = ConnectionOffsetMode.Static;
|
||||
public ConnectionOffsetMode ConnectionSourceOffsetMode
|
||||
{
|
||||
get => _srcConnectionOffsetMode;
|
||||
set => SetProperty(ref _srcConnectionOffsetMode, value);
|
||||
}
|
||||
|
||||
private ConnectionOffsetMode _targetConnectionOffsetMode = ConnectionOffsetMode.Static;
|
||||
public ConnectionOffsetMode ConnectionTargetOffsetMode
|
||||
{
|
||||
get => _targetConnectionOffsetMode;
|
||||
set => SetProperty(ref _targetConnectionOffsetMode, value);
|
||||
}
|
||||
|
||||
private ArrowHeadEnds _arrowHeadEnds = ArrowHeadEnds.End;
|
||||
public ArrowHeadEnds ArrowHeadEnds
|
||||
{
|
||||
get => _arrowHeadEnds;
|
||||
set => SetProperty(ref _arrowHeadEnds, value);
|
||||
}
|
||||
|
||||
private ArrowHeadShape _arrowHeadShape = ArrowHeadShape.Arrowhead;
|
||||
public ArrowHeadShape ArrowHeadShape
|
||||
{
|
||||
get => _arrowHeadShape;
|
||||
set => SetProperty(ref _arrowHeadShape, value);
|
||||
}
|
||||
|
||||
private PointEditor _connectionSourceOffset = new Size(14, 0);
|
||||
public PointEditor ConnectionSourceOffset
|
||||
{
|
||||
get => _connectionSourceOffset;
|
||||
set => SetProperty(ref _connectionSourceOffset, value);
|
||||
}
|
||||
|
||||
private PointEditor _connectionTargetOffset = new Size(14, 0);
|
||||
public PointEditor ConnectionTargetOffset
|
||||
{
|
||||
get => _connectionTargetOffset;
|
||||
set => SetProperty(ref _connectionTargetOffset, value);
|
||||
}
|
||||
|
||||
private double _connectionStrokeThickness = 3;
|
||||
public double ConnectionStrokeThickness
|
||||
{
|
||||
get => _connectionStrokeThickness;
|
||||
set => SetProperty(ref _connectionStrokeThickness, value);
|
||||
}
|
||||
|
||||
private double _connectionOutlineThickness = 5;
|
||||
public double ConnectionOutlineThickness
|
||||
{
|
||||
get => _connectionOutlineThickness;
|
||||
set => SetProperty(ref _connectionOutlineThickness, value);
|
||||
}
|
||||
|
||||
private double _connectionFocusVisualPadding = 1;
|
||||
public double ConnectionFocusVisualPadding
|
||||
{
|
||||
get => _connectionFocusVisualPadding;
|
||||
set => SetProperty(ref _connectionFocusVisualPadding, value);
|
||||
}
|
||||
|
||||
private uint _directionalArrowsCount = 3;
|
||||
public uint DirectionalArrowsCount
|
||||
{
|
||||
get => _directionalArrowsCount;
|
||||
set => SetProperty(ref _directionalArrowsCount, value);
|
||||
}
|
||||
|
||||
private double _directionalArrowsOffset;
|
||||
public double DirectionalArrowsOffset
|
||||
{
|
||||
get => _directionalArrowsOffset;
|
||||
set => SetProperty(ref _directionalArrowsOffset, value);
|
||||
}
|
||||
|
||||
private bool _isAnimatingConnections;
|
||||
public bool IsAnimatingConnections
|
||||
{
|
||||
get => _isAnimatingConnections;
|
||||
set => SetProperty(ref _isAnimatingConnections, value);
|
||||
}
|
||||
|
||||
private double _directionalArrowsAnimationDuration = 2.0;
|
||||
public double DirectionalArrowsAnimationDuration
|
||||
{
|
||||
get => _directionalArrowsAnimationDuration;
|
||||
set => SetProperty(ref _directionalArrowsAnimationDuration, value);
|
||||
}
|
||||
|
||||
private PointEditor _connectionArrowSize = new Size(8, 8);
|
||||
public PointEditor ConnectionArrowSize
|
||||
{
|
||||
get => _connectionArrowSize;
|
||||
set => SetProperty(ref _connectionArrowSize, value);
|
||||
}
|
||||
|
||||
private bool _displayConnectionsOnTop;
|
||||
public bool DisplayConnectionsOnTop
|
||||
{
|
||||
get => _displayConnectionsOnTop;
|
||||
set => SetProperty(ref _displayConnectionsOnTop, value);
|
||||
}
|
||||
|
||||
private double _bringIntoViewSpeed = 1000;
|
||||
public double BringIntoViewSpeed
|
||||
{
|
||||
get => _bringIntoViewSpeed;
|
||||
set => SetProperty(ref _bringIntoViewSpeed, value);
|
||||
}
|
||||
|
||||
private double _bringIntoViewMaxDuration = 1;
|
||||
public double BringIntoViewMaxDuration
|
||||
{
|
||||
get => _bringIntoViewMaxDuration;
|
||||
set => SetProperty(ref _bringIntoViewMaxDuration, value);
|
||||
}
|
||||
|
||||
private GroupingMovementMode _groupingNodeMovement;
|
||||
public GroupingMovementMode GroupingNodeMovement
|
||||
{
|
||||
get => _groupingNodeMovement;
|
||||
set => SetProperty(ref _groupingNodeMovement, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Advanced settings
|
||||
|
||||
public uint MaxHotKeys
|
||||
{
|
||||
get => PendingConnection.MaxHotKeys;
|
||||
set => PendingConnection.MaxHotKeys = value;
|
||||
}
|
||||
|
||||
public HotKeysDisplayMode HotKeysDisplayMode
|
||||
{
|
||||
get => PendingConnection.HotKeysDisplayMode;
|
||||
set => PendingConnection.HotKeysDisplayMode = value;
|
||||
}
|
||||
|
||||
public bool PreserveSelectionOnRightClick
|
||||
{
|
||||
get => ItemContainer.PreserveSelectionOnRightClick;
|
||||
set => ItemContainer.PreserveSelectionOnRightClick = value;
|
||||
}
|
||||
|
||||
public double MouseActionSuppressionThreshold
|
||||
{
|
||||
get => NodifyEditor.MouseActionSuppressionThreshold;
|
||||
set => NodifyEditor.MouseActionSuppressionThreshold = value;
|
||||
}
|
||||
|
||||
public double AutoPanningTickRate
|
||||
{
|
||||
get => NodifyEditor.AutoPanningTickRate;
|
||||
set => NodifyEditor.AutoPanningTickRate = value;
|
||||
}
|
||||
|
||||
public bool AllowMinimapPanningCancellation
|
||||
{
|
||||
get => Minimap.AllowPanningCancellation;
|
||||
set => Minimap.AllowPanningCancellation = value;
|
||||
}
|
||||
|
||||
public bool AllowCuttingCancellation
|
||||
{
|
||||
get => NodifyEditor.AllowCuttingCancellation;
|
||||
set => NodifyEditor.AllowCuttingCancellation = value;
|
||||
}
|
||||
|
||||
public bool AllowPushItemsCancellation
|
||||
{
|
||||
get => NodifyEditor.AllowPushItemsCancellation;
|
||||
set => NodifyEditor.AllowPushItemsCancellation = value;
|
||||
}
|
||||
|
||||
public bool AllowPanningCancellation
|
||||
{
|
||||
get => NodifyEditor.AllowPanningCancellation;
|
||||
set => NodifyEditor.AllowPanningCancellation = value;
|
||||
}
|
||||
|
||||
public bool AllowSelectionCancellation
|
||||
{
|
||||
get => NodifyEditor.AllowSelectionCancellation;
|
||||
set => NodifyEditor.AllowSelectionCancellation = value;
|
||||
}
|
||||
|
||||
public bool AllowDraggingCancellation
|
||||
{
|
||||
get => NodifyEditor.AllowDraggingCancellation;
|
||||
set => NodifyEditor.AllowDraggingCancellation = value;
|
||||
}
|
||||
|
||||
public bool AllowPendingConnectionCancellation
|
||||
{
|
||||
get => Connector.AllowPendingConnectionCancellation;
|
||||
set => Connector.AllowPendingConnectionCancellation = value;
|
||||
}
|
||||
|
||||
public bool EnableSnappingCorrection
|
||||
{
|
||||
get => NodifyEditor.EnableSnappingCorrection;
|
||||
set => NodifyEditor.EnableSnappingCorrection = value;
|
||||
}
|
||||
|
||||
public bool EnableCuttingLinePreview
|
||||
{
|
||||
get => NodifyEditor.EnableCuttingLinePreview;
|
||||
set => NodifyEditor.EnableCuttingLinePreview = value;
|
||||
}
|
||||
|
||||
public bool EnablePendingConnectionHitTesting
|
||||
{
|
||||
get => PendingConnection.EnableHitTesting;
|
||||
set => PendingConnection.EnableHitTesting = value;
|
||||
}
|
||||
|
||||
public bool EnableConnectorOptimizations
|
||||
{
|
||||
get => Connector.EnableOptimizations;
|
||||
set => Connector.EnableOptimizations = value;
|
||||
}
|
||||
|
||||
public double OptimizeSafeZone
|
||||
{
|
||||
get => Connector.OptimizeSafeZone;
|
||||
set => Connector.OptimizeSafeZone = value;
|
||||
}
|
||||
|
||||
public uint OptimizeMinimumSelectedItems
|
||||
{
|
||||
get => Connector.OptimizeMinimumSelectedItems;
|
||||
set => Connector.OptimizeMinimumSelectedItems = value;
|
||||
}
|
||||
|
||||
public bool EnableRenderingOptimizations
|
||||
{
|
||||
get => NodifyEditor.EnableRenderingContainersOptimizations;
|
||||
set => NodifyEditor.EnableRenderingContainersOptimizations = value;
|
||||
}
|
||||
|
||||
public uint OptimizeRenderingMinimumNodes
|
||||
{
|
||||
get => NodifyEditor.OptimizeRenderingMinimumContainers;
|
||||
set => NodifyEditor.OptimizeRenderingMinimumContainers = value;
|
||||
}
|
||||
|
||||
public double OptimizeRenderingZoomOutPercent
|
||||
{
|
||||
get => NodifyEditor.OptimizeRenderingZoomOutPercent;
|
||||
set => NodifyEditor.OptimizeRenderingZoomOutPercent = value;
|
||||
}
|
||||
|
||||
public double FitToScreenExtentMargin
|
||||
{
|
||||
get => NodifyEditor.FitToScreenExtentMargin;
|
||||
set => NodifyEditor.FitToScreenExtentMargin = value;
|
||||
}
|
||||
|
||||
public bool EnableDraggingOptimizations
|
||||
{
|
||||
get => NodifyEditor.EnableDraggingContainersOptimizations;
|
||||
set => NodifyEditor.EnableDraggingContainersOptimizations = value;
|
||||
}
|
||||
|
||||
public bool EnableStickyConnectors
|
||||
{
|
||||
get => ConnectorState.EnableToggledConnectingMode;
|
||||
set => ConnectorState.EnableToggledConnectingMode = value;
|
||||
}
|
||||
|
||||
public bool EnableToggledPanning
|
||||
{
|
||||
get => EditorState.EnableToggledPanningMode;
|
||||
set => EditorState.EnableToggledPanningMode = value;
|
||||
}
|
||||
|
||||
public bool EnableToggledCutting
|
||||
{
|
||||
get => EditorState.EnableToggledCuttingMode;
|
||||
set => EditorState.EnableToggledCuttingMode = value;
|
||||
}
|
||||
|
||||
public bool EnableToggledPushingItems
|
||||
{
|
||||
get => EditorState.EnableToggledPushingItemsMode;
|
||||
set => EditorState.EnableToggledPushingItemsMode = value;
|
||||
}
|
||||
|
||||
public bool EnableToggledSelecting
|
||||
{
|
||||
get => EditorState.EnableToggledSelectingMode;
|
||||
set => EditorState.EnableToggledSelectingMode = value;
|
||||
}
|
||||
|
||||
public bool EnableToggledDragging
|
||||
{
|
||||
get => ContainerState.EnableToggledDraggingMode;
|
||||
set => ContainerState.EnableToggledDraggingMode = value;
|
||||
}
|
||||
|
||||
public bool EnableMinimapToggledPanning
|
||||
{
|
||||
get => MinimapState.EnableToggledPanningMode;
|
||||
set => MinimapState.EnableToggledPanningMode = value;
|
||||
}
|
||||
|
||||
public bool AllowPanningWhileSelecting
|
||||
{
|
||||
get => EditorState.AllowPanningWhileSelecting;
|
||||
set => EditorState.AllowPanningWhileSelecting = value;
|
||||
}
|
||||
|
||||
public bool AllowPanningWhileCutting
|
||||
{
|
||||
get => EditorState.AllowPanningWhileCutting;
|
||||
set => EditorState.AllowPanningWhileCutting = value;
|
||||
}
|
||||
|
||||
public bool AllowPanningWhilePushingItems
|
||||
{
|
||||
get => EditorState.AllowPanningWhilePushingItems;
|
||||
set => EditorState.AllowPanningWhilePushingItems = value;
|
||||
}
|
||||
|
||||
public bool AllowZoomingWhileSelecting
|
||||
{
|
||||
get => EditorState.AllowZoomingWhileSelecting;
|
||||
set => EditorState.AllowZoomingWhileSelecting = value;
|
||||
}
|
||||
|
||||
public bool AllowZoomingWhileCutting
|
||||
{
|
||||
get => EditorState.AllowZoomingWhileCutting;
|
||||
set => EditorState.AllowZoomingWhileCutting = value;
|
||||
}
|
||||
|
||||
public bool AllowZoomingWhilePushingItems
|
||||
{
|
||||
get => EditorState.AllowZoomingWhilePushingItems;
|
||||
set => EditorState.AllowZoomingWhilePushingItems = value;
|
||||
}
|
||||
|
||||
public bool AllowZoomingWhilePanning
|
||||
{
|
||||
get => EditorState.AllowZoomingWhilePanning;
|
||||
set => EditorState.AllowZoomingWhilePanning = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
47
Examples/Nodify.Playground/EditorSettingsView.xaml
Normal file
47
Examples/Nodify.Playground/EditorSettingsView.xaml
Normal file
@@ -0,0 +1,47 @@
|
||||
<UserControl x:Class="Nodify.Playground.EditorSettingsView"
|
||||
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.Playground"
|
||||
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||
d:Foreground="{DynamicResource ForegroundBrush}"
|
||||
d:Background="{DynamicResource PanelBackgroundBrush}"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<StackPanel>
|
||||
<Border BorderThickness="1"
|
||||
Padding="10"
|
||||
HorizontalAlignment="Stretch">
|
||||
<local:SettingsView Items="{Binding Source={x:Static local:EditorSettings.Instance}, Path=Settings}"/>
|
||||
</Border>
|
||||
<Expander
|
||||
Header="Advanced"
|
||||
Padding="0 5 0 0"
|
||||
BorderThickness="0 0 0 1"
|
||||
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>
|
||||
|
||||
<Border BorderThickness="1"
|
||||
Padding="10"
|
||||
HorizontalAlignment="Stretch">
|
||||
<local:SettingsView Items="{Binding Source={x:Static local:EditorSettings.Instance}, Path=AdvancedSettings}"/>
|
||||
</Border>
|
||||
</Expander>
|
||||
</StackPanel>
|
||||
|
||||
</UserControl>
|
||||
26
Examples/Nodify.Playground/EditorSettingsView.xaml.cs
Normal file
26
Examples/Nodify.Playground/EditorSettingsView.xaml.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for EditorSettingsView.xaml
|
||||
/// </summary>
|
||||
public partial class EditorSettingsView : UserControl
|
||||
{
|
||||
public EditorSettingsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public static class NodeViewModelExtensions
|
||||
{
|
||||
public static Rect GetBoundingBox(this IList<NodeViewModel> nodes, double padding = 0, int gridCellSize = 15)
|
||||
{
|
||||
double minX = double.MaxValue;
|
||||
double minY = double.MaxValue;
|
||||
|
||||
double maxX = double.MinValue;
|
||||
double maxY = double.MinValue;
|
||||
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
var node = nodes[i];
|
||||
var width = 200; //node.Width
|
||||
var height = 200; //node.Height
|
||||
|
||||
if (node.Location.X < minX)
|
||||
{
|
||||
minX = node.Location.X;
|
||||
}
|
||||
|
||||
if (node.Location.Y < minY)
|
||||
{
|
||||
minY = node.Location.Y;
|
||||
}
|
||||
|
||||
var sizeX = node.Location.X + width;
|
||||
if (sizeX > maxX)
|
||||
{
|
||||
maxX = sizeX;
|
||||
}
|
||||
|
||||
var sizeY = node.Location.Y + height;
|
||||
if (sizeY > maxY)
|
||||
{
|
||||
maxY = sizeY;
|
||||
}
|
||||
}
|
||||
|
||||
var result = new Rect(minX - padding, minY - padding, maxX - minX + padding * 2, maxY - minY + padding * 2);
|
||||
result.X = (int)result.X / gridCellSize * gridCellSize;
|
||||
result.Y = (int)result.Y / gridCellSize * gridCellSize;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this ICollection<T> col, IEnumerable<T> items)
|
||||
{
|
||||
if (items is IList<T> itemsCol)
|
||||
{
|
||||
for (int i = 0; i < itemsCol.Count; i++)
|
||||
{
|
||||
col.Add(itemsCol[i]);
|
||||
}
|
||||
}
|
||||
else if (items is T[] itemsArr)
|
||||
{
|
||||
for (int i = 0; i < itemsArr.Length; i++)
|
||||
{
|
||||
col.Add(itemsArr[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
col.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
186
Examples/Nodify.Playground/Helpers/RandomNodesGenerator.cs
Normal file
186
Examples/Nodify.Playground/Helpers/RandomNodesGenerator.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public struct NodesGeneratorSettings
|
||||
{
|
||||
private static readonly Random _rand = new Random();
|
||||
|
||||
public NodesGeneratorSettings(uint count)
|
||||
{
|
||||
GridSnap = 15;
|
||||
MinNodesCount = MaxNodesCount = count;
|
||||
MinInputCount = MinOutputCount = 0;
|
||||
MaxInputCount = MaxOutputCount = 7;
|
||||
|
||||
ConnectorNameGenerator = (s, i) => $"{new string('C', (int)i % 5)} {i}";
|
||||
NodeNameGenerator = (s, i) => $"Node {i}";
|
||||
NodeLocationGenerator = (s, i) =>
|
||||
{
|
||||
static double EaseOut(double percent, double increment, double start, double end, double total)
|
||||
=> -end * (increment /= total) * (increment - 2) + start;
|
||||
|
||||
var xDistanceBetweenNodes = _rand.Next(150, 350);
|
||||
var yDistanceBetweenNodes = _rand.Next(200, 350);
|
||||
var randSignX = _rand.Next(0, 100) > 50 ? 1 : -1;
|
||||
var randSignY = _rand.Next(0, 100) > 50 ? 1 : -1;
|
||||
var gridOffsetX = i * xDistanceBetweenNodes;
|
||||
var gridOffsetY = i * yDistanceBetweenNodes;
|
||||
|
||||
var x = gridOffsetX * Math.Sin(xDistanceBetweenNodes * randSignX / (i + 1));
|
||||
var y = gridOffsetY * Math.Sin(yDistanceBetweenNodes * randSignY / (i + 1));
|
||||
var easeX = x * EaseOut(i / count, i, 1, 0.01, count);
|
||||
var easeY = y * EaseOut(i / count, i, 1, 0.01, count);
|
||||
|
||||
x = s.Snap((int)easeX);
|
||||
y = s.Snap((int)easeY);
|
||||
|
||||
return new Point(x, y);
|
||||
};
|
||||
}
|
||||
|
||||
public uint GridSnap;
|
||||
public uint MinNodesCount;
|
||||
public uint MaxNodesCount;
|
||||
public uint MinInputCount;
|
||||
public uint MaxInputCount;
|
||||
public uint MinOutputCount;
|
||||
public uint MaxOutputCount;
|
||||
|
||||
public Func<NodesGeneratorSettings, uint, string?> ConnectorNameGenerator;
|
||||
public Func<NodesGeneratorSettings, uint, string?> NodeNameGenerator;
|
||||
public Func<NodesGeneratorSettings, uint, Point> NodeLocationGenerator;
|
||||
|
||||
public int Snap(int x)
|
||||
=> x / (int)GridSnap * (int)GridSnap;
|
||||
}
|
||||
|
||||
public static class RandomNodesGenerator
|
||||
{
|
||||
private static readonly Random _rand = new Random();
|
||||
|
||||
public static List<T> GenerateNodes<T>(NodesGeneratorSettings settings)
|
||||
where T : FlowNodeViewModel, new()
|
||||
{
|
||||
var nodes = new List<T>();
|
||||
var count = _rand.Next((int)settings.MinNodesCount, (int)settings.MaxNodesCount + 1);
|
||||
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
var node = new T
|
||||
{
|
||||
Title = settings.NodeNameGenerator(settings, i),
|
||||
Location = settings.NodeLocationGenerator(settings, i)
|
||||
};
|
||||
|
||||
nodes.Add(node);
|
||||
node.Input.AddRange(GenerateConnectors(settings, _rand.Next((int)settings.MinInputCount, (int)settings.MaxInputCount + 1)));
|
||||
node.Output.AddRange(GenerateConnectors(settings, _rand.Next((int)settings.MinOutputCount, (int)settings.MaxOutputCount + 1)));
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public static List<ConnectionViewModel> GenerateConnections(IList<NodeViewModel> nodes)
|
||||
{
|
||||
HashSet<NodeViewModel> visited = new HashSet<NodeViewModel>(nodes.Count);
|
||||
List<ConnectionViewModel> connections = new List<ConnectionViewModel>(nodes.Count);
|
||||
|
||||
for (uint i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
var n1 = nodes[_rand.Next(0, nodes.Count)];
|
||||
var n2 = nodes[_rand.Next(0, nodes.Count)];
|
||||
|
||||
if (n1 == n2 && !(visited.Add(n1) && visited.Add(n2)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<ConnectorViewModel> input = n1 is FlowNodeViewModel flow ? flow.Input.ToList() :
|
||||
n1 is KnotNodeViewModel knot ? new List<ConnectorViewModel> { knot.Connector } : new List<ConnectorViewModel>();
|
||||
|
||||
List<ConnectorViewModel> output = n2 is FlowNodeViewModel flow2 ? flow2.Output.ToList() :
|
||||
n2 is KnotNodeViewModel knot2 ? new List<ConnectorViewModel> { knot2.Connector } : new List<ConnectorViewModel>();
|
||||
|
||||
connections.AddRange(ConnectPins(input, output));
|
||||
}
|
||||
|
||||
return connections;
|
||||
}
|
||||
|
||||
public static List<ConnectionViewModel> ConnectPins(IList<ConnectorViewModel> source, IList<ConnectorViewModel> target)
|
||||
{
|
||||
Dictionary<ConnectorViewModel, List<ConnectorViewModel>> connections = new Dictionary<ConnectorViewModel, List<ConnectorViewModel>>();
|
||||
List<ConnectionViewModel> result = new List<ConnectionViewModel>();
|
||||
|
||||
for (int di = 0; di < target.Count; di++)
|
||||
{
|
||||
if (source.Count > 1 && target.Count > 1 && _rand.Next() % 2 == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var outP = target[di];
|
||||
|
||||
if (!connections.TryGetValue(outP, out var outConns))
|
||||
{
|
||||
var newList = new List<ConnectorViewModel>();
|
||||
connections.Add(outP, newList);
|
||||
outConns = newList;
|
||||
}
|
||||
|
||||
var conNum = _rand.Next(0, source.Count + 1);
|
||||
for (uint ci = 0; ci < conNum; ci++)
|
||||
{
|
||||
var inP = source[_rand.Next(0, conNum)];
|
||||
|
||||
if (!connections.TryGetValue(inP, out var inConns))
|
||||
{
|
||||
var newList = new List<ConnectorViewModel>();
|
||||
connections.Add(inP, newList);
|
||||
inConns = newList;
|
||||
}
|
||||
|
||||
if (!connections[inP].Contains(outP) && !connections[outP].Contains(inP))
|
||||
{
|
||||
var isInput = inP.Flow == ConnectorFlow.Input;
|
||||
|
||||
var connection = new ConnectionViewModel
|
||||
{
|
||||
Input = isInput ? inP : outP,
|
||||
Output = isInput ? outP : inP
|
||||
};
|
||||
result.Add(connection);
|
||||
|
||||
inConns.Add(outP);
|
||||
outConns.Add(inP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<ConnectorViewModel> GenerateConnectors(NodesGeneratorSettings settings, int count)
|
||||
{
|
||||
var list = new List<ConnectorViewModel>(count);
|
||||
|
||||
for (uint i = 0; i < count; i++)
|
||||
{
|
||||
int shapeVal = _rand.Next() % 3;
|
||||
var connector = new ConnectorViewModel
|
||||
{
|
||||
Title = settings.ConnectorNameGenerator(settings, i),
|
||||
Shape = PlaygroundSettings.Instance.UseCustomConnectors ? (ConnectorShape)shapeVal : ConnectorShape.Circle
|
||||
};
|
||||
|
||||
list.Add(connector);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Examples/Nodify.Playground/ISettingViewModel.cs
Normal file
24
Examples/Nodify.Playground/ISettingViewModel.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public enum SettingsType
|
||||
{
|
||||
Boolean,
|
||||
Number,
|
||||
Option,
|
||||
Point,
|
||||
Text
|
||||
}
|
||||
|
||||
public interface ISettingViewModel
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Represents the content within the tooltip.
|
||||
/// </summary>
|
||||
string? Description { get; }
|
||||
object? Value { get; set; }
|
||||
|
||||
SettingsType Type { get;}
|
||||
}
|
||||
}
|
||||
275
Examples/Nodify.Playground/MainWindow.xaml
Normal file
275
Examples/Nodify.Playground/MainWindow.xaml
Normal file
@@ -0,0 +1,275 @@
|
||||
<Window x:Class="Nodify.Playground.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.Playground"
|
||||
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow"
|
||||
Height="700"
|
||||
Width="1300">
|
||||
|
||||
<Window.Resources>
|
||||
<shared:DebugConverter x:Key="DebugConverter" />
|
||||
<shared:ToStringConverter x:Key="ToStringConverter" />
|
||||
<shared:StringToVisibilityConverter x:Key="StringToVisibilityConverter" />
|
||||
</Window.Resources>
|
||||
|
||||
<Window.DataContext>
|
||||
<local:PlaygroundViewModel />
|
||||
</Window.DataContext>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<local:NodifyEditorView x:Name="EditorView"
|
||||
DataContext="{Binding GraphViewModel}"
|
||||
Grid.RowSpan="3" />
|
||||
|
||||
<!--ACTIONS-->
|
||||
<Border VerticalAlignment="Top"
|
||||
Background="{DynamicResource PanelBackgroundBrush}"
|
||||
Padding="10">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Command="{Binding GenerateRandomNodesCommand}"
|
||||
Content="GENERATE NODES"
|
||||
ToolTip="Generate nodes using the specified settings."
|
||||
Style="{StaticResource HollowButton}" />
|
||||
<Button Command="{Binding ToggleConnectionsCommand}"
|
||||
Content="{Binding ConnectNodesText}"
|
||||
ToolTip="Will add new connections if Connect Nodes is checked, otherwise it will disconnect nodes."
|
||||
Style="{StaticResource HollowButton}" />
|
||||
<Button Command="{Binding PerformanceTestCommand}"
|
||||
Content="PERFORMANCE TEST"
|
||||
ToolTip="You will encounter rendering performance issues. Try disabling the connections to see the difference."
|
||||
Style="{StaticResource HollowButton}" />
|
||||
<Button Command="{Binding ResetCommand}"
|
||||
Content="RESET PLAYGROUND"
|
||||
ToolTip="Reset the Location, Zoom, Nodes and Connections."
|
||||
Style="{StaticResource HollowButton}" />
|
||||
<Button Click="BringIntoView_Click"
|
||||
Content="BRING INTO VIEW"
|
||||
ToolTip="Bring a random node into view."
|
||||
Style="{StaticResource HollowButton}" />
|
||||
<Button Command="{x:Static nodify:EditorCommands.FitToScreen}"
|
||||
Content="FIT TO SCREEN"
|
||||
ToolTip="Scales the viewport to fit all nodes if that's possible."
|
||||
CommandTarget="{Binding EditorInstance, ElementName=EditorView}"
|
||||
Style="{StaticResource HollowButton}" />
|
||||
<Button Command="{Binding GraphViewModel.CommentSelectionCommand}"
|
||||
Content="COMMENT SELECTION"
|
||||
ToolTip="Creates a comment node containing the selected nodes."
|
||||
Style="{StaticResource HollowButton}" />
|
||||
<Button Click="AnimateConnections_Click"
|
||||
Content="TOGGLE CONNECTIONS ANIMATION"
|
||||
ToolTip="Starts or stops animating the directional arrows on all connections (see DirectionalArrowsCount)"
|
||||
Style="{StaticResource HollowButton}" />
|
||||
</StackPanel>
|
||||
|
||||
<Button Style="{StaticResource IconButton}"
|
||||
Content="{StaticResource ThemeIcon}"
|
||||
Command="{Binding Source={x:Static shared:ThemeManager.SetNextThemeCommand}}"
|
||||
ToolTip="Change theme"
|
||||
Grid.Column="1" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!--SETTINGS-->
|
||||
<Expander Grid.Row="1"
|
||||
HorizontalContentAlignment="Left"
|
||||
VerticalContentAlignment="Center"
|
||||
HorizontalAlignment="Left"
|
||||
Background="{DynamicResource PanelBackgroundBrush}"
|
||||
IsExpanded="True"
|
||||
ExpandDirection="Left"
|
||||
Padding="0 1 4 3">
|
||||
<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>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Margin="0 0 5 10">
|
||||
<TextBlock Margin="0 0 0 3">Search:</TextBlock>
|
||||
<TextBox Text="{Binding Source={x:Static local:PlaygroundSettings.Instance}, Path=SearchText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
|
||||
Padding="4" />
|
||||
</StackPanel>
|
||||
<ScrollViewer Grid.Row="1">
|
||||
<Grid IsSharedSizeScope="True"
|
||||
Width="330">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Expander Header="Playground Settings"
|
||||
Padding="0 5 0 0"
|
||||
BorderThickness="0 0 0 1"
|
||||
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>
|
||||
<Border BorderThickness="1"
|
||||
Padding="10"
|
||||
HorizontalAlignment="Stretch">
|
||||
<local:SettingsView Items="{Binding Source={x:Static local:PlaygroundSettings.Instance}, Path=Settings}" />
|
||||
</Border>
|
||||
</Expander>
|
||||
|
||||
<Expander Header="Editor Settings"
|
||||
Padding="0 5 0 0"
|
||||
BorderThickness="0 0 0 1"
|
||||
IsExpanded="True"
|
||||
BorderBrush="{DynamicResource BackgroundBrush}"
|
||||
Grid.Row="1">
|
||||
<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>
|
||||
|
||||
<local:EditorSettingsView />
|
||||
</Expander>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Expander>
|
||||
|
||||
<!--INFORMATION-->
|
||||
<Border Grid.Row="2"
|
||||
Background="{DynamicResource PanelBackgroundBrush}"
|
||||
VerticalAlignment="Bottom"
|
||||
Padding="10">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.Resources>
|
||||
<Style TargetType="{x:Type TextBlock}"
|
||||
BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||
<Setter Property="Foreground"
|
||||
Value="{DynamicResource ForegroundBrush}" />
|
||||
<Setter Property="Margin"
|
||||
Value="0 0 15 0" />
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock ToolTip="The number of selected items.">
|
||||
<TextBlock.Inlines>
|
||||
<Run Text="Selected nodes: " />
|
||||
<Run Foreground="YellowGreen"
|
||||
Text="{Binding GraphViewModel.SelectedNodes.Count, Mode=OneWay}" />
|
||||
<Run Text="/" />
|
||||
<Run Text="{Binding GraphViewModel.Nodes.Count, Mode=OneWay}" />
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
<TextBlock ToolTip="The number of selected connections.">
|
||||
<TextBlock.Inlines>
|
||||
<Run Text="Selected connections: " />
|
||||
<Run Foreground="YellowGreen"
|
||||
Text="{Binding GraphViewModel.SelectedConnections.Count, Mode=OneWay}" />
|
||||
<Run Text="/" />
|
||||
<Run Text="{Binding GraphViewModel.Connections.Count, Mode=OneWay}" />
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
|
||||
<Border Visibility="{Binding GraphViewModel.KeyboardNavigationLayer, Converter={StaticResource StringToVisibilityConverter}}"
|
||||
ToolTip="Press CTRL+[ or CTRL+] in the editor to change the keyboard navigation layer."
|
||||
Padding="14 0 0 0"
|
||||
Height="16"
|
||||
CornerRadius="3"
|
||||
Background="{DynamicResource PanelBackgroundBrush}"
|
||||
BorderThickness="1"
|
||||
BorderBrush="{DynamicResource BorderBrush}">
|
||||
<TextBlock>
|
||||
<Run Text="Navigating: " />
|
||||
<Run Foreground="{DynamicResource ForegroundBrush}"
|
||||
Text="{Binding GraphViewModel.KeyboardNavigationLayer, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<TextBlock ToolTip="The viewport's location.">
|
||||
<TextBlock.Inlines>
|
||||
<Run Text="Location: " />
|
||||
<Run Foreground="Orange"
|
||||
Text="{Binding Location.Value, Mode=OneWay, Converter={StaticResource ToStringConverter}, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
<TextBlock ToolTip="The viewport's size.">
|
||||
<TextBlock.Inlines>
|
||||
<Run Text="Size: " />
|
||||
<Run Foreground="YellowGreen"
|
||||
Text="{Binding GraphViewModel.ViewportSize, Mode=OneWay, Converter={StaticResource ToStringConverter}}" />
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
<TextBlock ToolTip="The viewport's zoom. Not accurate when trying to zoom outside the MinViewportZoom and MaxViewportZoom because of dependency property coercion not updating the binding with the final result.">
|
||||
<TextBlock.Inlines>
|
||||
<Run Text="Zoom: " />
|
||||
<Run Foreground="DodgerBlue"
|
||||
Text="{Binding Zoom, Mode=OneWay, Converter={StaticResource ToStringConverter}, Source={x:Static local:EditorSettings.Instance}}" />
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
<TextBlock ToolTip="The estimated frame rate. (my be buggy)">
|
||||
<TextBlock.Inlines>
|
||||
<Run Text="FPS: " />
|
||||
<Run Foreground="LawnGreen"
|
||||
Name="FPSText" />
|
||||
</TextBlock.Inlines>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
95
Examples/Nodify.Playground/MainWindow.xaml.cs
Normal file
95
Examples/Nodify.Playground/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public static class CompositionTargetEx
|
||||
{
|
||||
private static TimeSpan _last = TimeSpan.Zero;
|
||||
private static event Action<double>? FrameUpdating;
|
||||
|
||||
public static event Action<double> Rendering
|
||||
{
|
||||
add
|
||||
{
|
||||
if (FrameUpdating == null)
|
||||
{
|
||||
CompositionTarget.Rendering += OnRendering;
|
||||
}
|
||||
FrameUpdating += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
FrameUpdating -= value;
|
||||
if (FrameUpdating == null)
|
||||
{
|
||||
CompositionTarget.Rendering -= OnRendering;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnRendering(object? sender, EventArgs e)
|
||||
{
|
||||
RenderingEventArgs args = (RenderingEventArgs)e;
|
||||
var renderingTime = args.RenderingTime;
|
||||
if (renderingTime == _last)
|
||||
return;
|
||||
|
||||
double fps = 1000 / (renderingTime - _last).TotalMilliseconds;
|
||||
_last = renderingTime;
|
||||
FrameUpdating?.Invoke(fps);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private readonly Random _rand = new Random();
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
CompositionTargetEx.Rendering += OnRendering;
|
||||
|
||||
EventManager.RegisterClassHandler(
|
||||
typeof(UIElement),
|
||||
Keyboard.PreviewGotKeyboardFocusEvent,
|
||||
(KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus);
|
||||
}
|
||||
|
||||
private void OnPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||
{
|
||||
Title = e.NewFocus.ToString();
|
||||
}
|
||||
|
||||
private void OnRendering(double fps)
|
||||
{
|
||||
FPSText.Text = fps.ToString("0");
|
||||
}
|
||||
|
||||
private void BringIntoView_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is PlaygroundViewModel model)
|
||||
{
|
||||
NodifyObservableCollection<NodeViewModel> nodes = model.GraphViewModel.Nodes;
|
||||
int index = _rand.Next(nodes.Count);
|
||||
|
||||
if (nodes.Count > index)
|
||||
{
|
||||
NodeViewModel node = nodes[index];
|
||||
EditorCommands.BringIntoView.Execute(node.Location, EditorView.Editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AnimateConnections_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
EditorSettings.Instance.IsAnimatingConnections = !EditorSettings.Instance.IsAnimatingConnections;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Examples/Nodify.Playground/Nodify.Playground.csproj
Normal file
17
Examples/Nodify.Playground/Nodify.Playground.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>
|
||||
220
Examples/Nodify.Playground/PlaygroundSettings.cs
Normal file
220
Examples/Nodify.Playground/PlaygroundSettings.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
using Nodify.Interactivity;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class PlaygroundSettings : ObservableObject
|
||||
{
|
||||
private readonly IReadOnlyCollection<ISettingViewModel> _settings;
|
||||
public IEnumerable<ISettingViewModel> Settings => FilterAndSort(_settings);
|
||||
|
||||
private string? _searchText;
|
||||
public string? SearchText
|
||||
{
|
||||
get => _searchText;
|
||||
set => SetProperty(ref _searchText, value)
|
||||
.Then(() => OnPropertyChanged(nameof(Settings)));
|
||||
}
|
||||
|
||||
private PlaygroundSettings()
|
||||
{
|
||||
_settings = new List<ISettingViewModel>()
|
||||
{
|
||||
new ProxySettingViewModel<EditorGesturesMappings>(
|
||||
() => Instance.EditorGesturesMappings,
|
||||
val => Instance.EditorGesturesMappings = val,
|
||||
"Editor input mappings"),
|
||||
new ProxySettingViewModel<EditorInputMode>(
|
||||
() => Instance.EditorInputMode,
|
||||
val => Instance.EditorInputMode = val,
|
||||
"Editor input mode"),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.ShowMinimap,
|
||||
val => Instance.ShowMinimap = val,
|
||||
"Show minimap",
|
||||
"Set Enable nodes dragging optimization to false for realtime updates"),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.DisableMinimapControls,
|
||||
val => Instance.DisableMinimapControls = val,
|
||||
"Disable minimap controls",
|
||||
"Whether the minimap can move and zoom the viewport"),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.ResizeToViewport,
|
||||
val => Instance.ResizeToViewport = val,
|
||||
"Minimap resize to viewport",
|
||||
"Whether the minimap should resized to also display the viewport"),
|
||||
new ProxySettingViewModel<PointEditor>(
|
||||
() => Instance.MinimapMaxViewportOffset,
|
||||
val => Instance.MinimapMaxViewportOffset = val,
|
||||
"Minimap max viewport offset",
|
||||
"The max position from the items extent that the viewport can move to"),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.ShowGridLines,
|
||||
val => Instance.ShowGridLines = val,
|
||||
"Show grid lines:"),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.ShouldConnectNodes,
|
||||
val => Instance.ShouldConnectNodes = val,
|
||||
"Connect nodes:"),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.AsyncLoading,
|
||||
val => Instance.AsyncLoading = val,
|
||||
"Async loading:"),
|
||||
new ProxySettingViewModel<bool>(
|
||||
() => Instance.UseCustomConnectors,
|
||||
val => Instance.UseCustomConnectors = val,
|
||||
"Custom connectors:"),
|
||||
new ProxySettingViewModel<uint>(
|
||||
() => Instance.MinNodes,
|
||||
val => Instance.MinNodes = val,
|
||||
"Min nodes:"),
|
||||
new ProxySettingViewModel<uint>(
|
||||
() => Instance.MaxNodes,
|
||||
val => Instance.MaxNodes = val,
|
||||
"Max nodes:"),
|
||||
new ProxySettingViewModel<uint>(
|
||||
() => Instance.MinConnectors,
|
||||
val => Instance.MinConnectors = val,
|
||||
"Min connectors:"),
|
||||
new ProxySettingViewModel<uint>(
|
||||
() => Instance.MaxConnectors,
|
||||
val => Instance.MaxConnectors = val,
|
||||
"Max connectors:"),
|
||||
new ProxySettingViewModel<uint>(
|
||||
() => Instance.PerformanceTestNodes,
|
||||
val => Instance.PerformanceTestNodes = val,
|
||||
"Performance test nodes:"),
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<ISettingViewModel> FilterAndSort(IReadOnlyCollection<ISettingViewModel> settings)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(SearchText))
|
||||
{
|
||||
return settings;
|
||||
}
|
||||
|
||||
string searchText = SearchText!.ToLowerInvariant();
|
||||
|
||||
var matchingValues = settings.Where(s => s.Name.ToLowerInvariant().Contains(searchText) || (s.Description?.ToLowerInvariant()?.Contains(searchText) ?? false));
|
||||
var sortedValues = matchingValues.OrderByDescending(s => s.Name.ToLowerInvariant().Contains(searchText));
|
||||
|
||||
return sortedValues;
|
||||
}
|
||||
|
||||
public static PlaygroundSettings Instance { get; } = new PlaygroundSettings();
|
||||
|
||||
private EditorGesturesMappings _editorGesturesMappings;
|
||||
public EditorGesturesMappings EditorGesturesMappings
|
||||
{
|
||||
get => _editorGesturesMappings;
|
||||
set => SetProperty(ref _editorGesturesMappings, value)
|
||||
.Then(() => EditorGestures.Mappings.Apply(value));
|
||||
}
|
||||
|
||||
private EditorInputMode _editorInputMode;
|
||||
public EditorInputMode EditorInputMode
|
||||
{
|
||||
get => _editorInputMode;
|
||||
set => SetProperty(ref _editorInputMode, value)
|
||||
.Then(() => EditorGestures.Mappings.Apply(value));
|
||||
}
|
||||
|
||||
private bool _showMinimap = true;
|
||||
public bool ShowMinimap
|
||||
{
|
||||
get => _showMinimap;
|
||||
set => SetProperty(ref _showMinimap, value);
|
||||
}
|
||||
|
||||
private bool _disableMinimapControls = false;
|
||||
public bool DisableMinimapControls
|
||||
{
|
||||
get => _disableMinimapControls;
|
||||
set => SetProperty(ref _disableMinimapControls, value);
|
||||
}
|
||||
|
||||
private bool _resizeToViewport = false;
|
||||
public bool ResizeToViewport
|
||||
{
|
||||
get => _resizeToViewport;
|
||||
set => SetProperty(ref _resizeToViewport, value);
|
||||
}
|
||||
|
||||
private PointEditor _minimapViewportOffset = new Size(2000, 2000);
|
||||
public PointEditor MinimapMaxViewportOffset
|
||||
{
|
||||
get => _minimapViewportOffset;
|
||||
set => SetProperty(ref _minimapViewportOffset, value);
|
||||
}
|
||||
|
||||
private bool _shouldConnectNodes = true;
|
||||
public bool ShouldConnectNodes
|
||||
{
|
||||
get => _shouldConnectNodes;
|
||||
set => SetProperty(ref _shouldConnectNodes, value);
|
||||
}
|
||||
|
||||
private bool _asyncLoading = true;
|
||||
public bool AsyncLoading
|
||||
{
|
||||
get => _asyncLoading;
|
||||
set => SetProperty(ref _asyncLoading, value);
|
||||
}
|
||||
|
||||
private uint _minNodes = 10;
|
||||
public uint MinNodes
|
||||
{
|
||||
get => _minNodes;
|
||||
set => SetProperty(ref _minNodes, value)
|
||||
.Then(() => MaxNodes = MaxNodes < MinNodes ? MinNodes : MaxNodes);
|
||||
}
|
||||
|
||||
private uint _maxNodes = 100;
|
||||
public uint MaxNodes
|
||||
{
|
||||
get => _maxNodes;
|
||||
set => SetProperty(ref _maxNodes, value)
|
||||
.Then(() => MaxNodes = MaxNodes < MinNodes ? MinNodes : MaxNodes);
|
||||
}
|
||||
|
||||
private uint _minConnectors = 0;
|
||||
public uint MinConnectors
|
||||
{
|
||||
get => _minConnectors;
|
||||
set => SetProperty(ref _minConnectors, value)
|
||||
.Then(() => MaxConnectors = MaxConnectors < MinConnectors ? MinConnectors : MaxConnectors);
|
||||
}
|
||||
|
||||
private uint _maxConnectors = 4;
|
||||
public uint MaxConnectors
|
||||
{
|
||||
get => _maxConnectors;
|
||||
set => SetProperty(ref _maxConnectors, value)
|
||||
.Then(() => MaxConnectors = MaxConnectors < MinConnectors ? MinConnectors : MaxConnectors);
|
||||
}
|
||||
|
||||
private uint _performanceTestNodes = 1000;
|
||||
public uint PerformanceTestNodes
|
||||
{
|
||||
get => _performanceTestNodes;
|
||||
set => SetProperty(ref _performanceTestNodes, value);
|
||||
}
|
||||
|
||||
private bool _showGridLines = true;
|
||||
public bool ShowGridLines
|
||||
{
|
||||
get => _showGridLines;
|
||||
set => SetProperty(ref _showGridLines, value);
|
||||
}
|
||||
|
||||
private bool _customConnectors = true;
|
||||
public bool UseCustomConnectors
|
||||
{
|
||||
get => _customConnectors;
|
||||
set => SetProperty(ref _customConnectors, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
168
Examples/Nodify.Playground/PlaygroundViewModel.cs
Normal file
168
Examples/Nodify.Playground/PlaygroundViewModel.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class PlaygroundViewModel : ObservableObject
|
||||
{
|
||||
public NodifyEditorViewModel GraphViewModel { get; } = new NodifyEditorViewModel();
|
||||
|
||||
public PlaygroundViewModel()
|
||||
{
|
||||
GenerateRandomNodesCommand = new DelegateCommand(GenerateRandomNodes);
|
||||
PerformanceTestCommand = new DelegateCommand(PerformanceTest);
|
||||
ToggleConnectionsCommand = new DelegateCommand(ToggleConnections);
|
||||
ResetCommand = new DelegateCommand(ResetGraph);
|
||||
|
||||
BindingOperations.EnableCollectionSynchronization(GraphViewModel.Nodes, GraphViewModel.Nodes);
|
||||
BindingOperations.EnableCollectionSynchronization(GraphViewModel.Connections, GraphViewModel.Connections);
|
||||
|
||||
Settings.PropertyChanged += OnSettingsChanged;
|
||||
}
|
||||
|
||||
private void OnSettingsChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(PlaygroundSettings.ShouldConnectNodes))
|
||||
OnPropertyChanged(nameof(ConnectNodesText));
|
||||
}
|
||||
|
||||
public ICommand GenerateRandomNodesCommand { get; }
|
||||
public ICommand PerformanceTestCommand { get; }
|
||||
public ICommand ToggleConnectionsCommand { get; }
|
||||
public ICommand ResetCommand { get; }
|
||||
public PlaygroundSettings Settings => PlaygroundSettings.Instance;
|
||||
|
||||
public string ConnectNodesText => Settings.ShouldConnectNodes ? "CONNECT NODES" : "DISCONNECT NODES";
|
||||
|
||||
private void ResetGraph()
|
||||
{
|
||||
GraphViewModel.Nodes.Clear();
|
||||
EditorSettings.Instance.Location = new System.Windows.Point(0, 0);
|
||||
EditorSettings.Instance.Zoom = 1.0d;
|
||||
}
|
||||
|
||||
private async void GenerateRandomNodes()
|
||||
{
|
||||
uint minNodesByType = Settings.MinNodes / 2;
|
||||
uint maxNodesByType = Settings.MaxNodes / 2;
|
||||
|
||||
var nodes = RandomNodesGenerator.GenerateNodes<FlowNodeViewModel>(new NodesGeneratorSettings(minNodesByType)
|
||||
{
|
||||
MinNodesCount = minNodesByType,
|
||||
MaxNodesCount = maxNodesByType,
|
||||
MinInputCount = Settings.MinConnectors,
|
||||
MaxInputCount = Settings.MaxConnectors,
|
||||
MinOutputCount = Settings.MinConnectors,
|
||||
MaxOutputCount = Settings.MaxConnectors,
|
||||
GridSnap = EditorSettings.Instance.GridSpacing
|
||||
});
|
||||
|
||||
var verticalNodes = RandomNodesGenerator.GenerateNodes<VerticalNodeViewModel>(new NodesGeneratorSettings(minNodesByType)
|
||||
{
|
||||
MinNodesCount = minNodesByType,
|
||||
MaxNodesCount = maxNodesByType,
|
||||
MinInputCount = Settings.MinConnectors,
|
||||
MaxInputCount = Settings.MaxConnectors,
|
||||
MinOutputCount = Settings.MinConnectors,
|
||||
MaxOutputCount = Settings.MaxConnectors,
|
||||
GridSnap = EditorSettings.Instance.GridSpacing
|
||||
});
|
||||
|
||||
GraphViewModel.Nodes.Clear();
|
||||
await CopyToAsync(nodes, GraphViewModel.Nodes);
|
||||
await CopyToAsync(verticalNodes, GraphViewModel.Nodes);
|
||||
|
||||
if (Settings.ShouldConnectNodes)
|
||||
{
|
||||
await ConnectNodes();
|
||||
}
|
||||
}
|
||||
|
||||
private async void ToggleConnections()
|
||||
{
|
||||
if (Settings.ShouldConnectNodes)
|
||||
{
|
||||
await ConnectNodes();
|
||||
}
|
||||
else
|
||||
{
|
||||
GraphViewModel.Connections.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private async void PerformanceTest()
|
||||
{
|
||||
uint count = Settings.PerformanceTestNodes;
|
||||
int distance = 500;
|
||||
int size = (int)count / (int)Math.Sqrt(count);
|
||||
|
||||
var nodes = RandomNodesGenerator.GenerateNodes<FlowNodeViewModel>(new NodesGeneratorSettings(count)
|
||||
{
|
||||
NodeLocationGenerator = (s, i) => new System.Windows.Point(i % size * distance, i / size * distance),
|
||||
MinInputCount = Settings.MinConnectors,
|
||||
MaxInputCount = Settings.MaxConnectors,
|
||||
MinOutputCount = Settings.MinConnectors,
|
||||
MaxOutputCount = Settings.MaxConnectors,
|
||||
GridSnap = EditorSettings.Instance.GridSpacing
|
||||
});
|
||||
|
||||
GraphViewModel.Nodes.Clear();
|
||||
await CopyToAsync(nodes, GraphViewModel.Nodes);
|
||||
|
||||
if (Settings.ShouldConnectNodes)
|
||||
{
|
||||
await ConnectNodes();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConnectNodes()
|
||||
{
|
||||
var schema = new GraphSchema();
|
||||
var connections = RandomNodesGenerator.GenerateConnections(GraphViewModel.Nodes);
|
||||
|
||||
if (Settings.AsyncLoading)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
for (int i = 0; i < connections.Count; i++)
|
||||
{
|
||||
var con = connections[i];
|
||||
schema.TryAddConnection(con.Input, con.Output);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < connections.Count; i++)
|
||||
{
|
||||
var con = connections[i];
|
||||
schema.TryAddConnection(con.Input, con.Output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CopyToAsync(IList source, IList target)
|
||||
{
|
||||
if (Settings.AsyncLoading)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
target.Add(source[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
target.Add(source[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
80
Examples/Nodify.Playground/PointEditor.cs
Normal file
80
Examples/Nodify.Playground/PointEditor.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class PointEditor : ObservableObject
|
||||
{
|
||||
public double X
|
||||
{
|
||||
get => Value.X;
|
||||
set
|
||||
{
|
||||
Value = new Point(value, Value.Y);
|
||||
if (value >= 0)
|
||||
{
|
||||
Size = new Size(value, Size.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double Y
|
||||
{
|
||||
get => Value.Y;
|
||||
set
|
||||
{
|
||||
Value = new Point(Value.X, value);
|
||||
if (value >= 0)
|
||||
{
|
||||
Size = new Size(Size.Width, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Point _value;
|
||||
public Point Value
|
||||
{
|
||||
get => _value;
|
||||
set => SetProperty(ref _value, value)
|
||||
.Then(() =>
|
||||
{
|
||||
OnPropertyChanged(nameof(X));
|
||||
OnPropertyChanged(nameof(Y));
|
||||
});
|
||||
}
|
||||
|
||||
private Size _size;
|
||||
public Size Size
|
||||
{
|
||||
get => _size;
|
||||
set => SetProperty(ref _size, value)
|
||||
.Then(() =>
|
||||
{
|
||||
OnPropertyChanged(nameof(X));
|
||||
OnPropertyChanged(nameof(Y));
|
||||
});
|
||||
}
|
||||
|
||||
public string XLabel { get; set; } = "x";
|
||||
public string YLabel { get; set; } = "y";
|
||||
|
||||
public static implicit operator PointEditor(Point point)
|
||||
{
|
||||
return new PointEditor
|
||||
{
|
||||
X = point.X,
|
||||
Y = point.Y
|
||||
};
|
||||
}
|
||||
|
||||
public static implicit operator PointEditor(Size size)
|
||||
{
|
||||
return new PointEditor
|
||||
{
|
||||
X = size.Width,
|
||||
Y = size.Height,
|
||||
XLabel = "w",
|
||||
YLabel = "h"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Examples/Nodify.Playground/PointEditorView.xaml
Normal file
43
Examples/Nodify.Playground/PointEditorView.xaml
Normal file
@@ -0,0 +1,43 @@
|
||||
<UserControl x:Class="Nodify.Playground.PointEditorView"
|
||||
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.Playground"
|
||||
d:DataContext="{d:DesignInstance Type={x:Type local:PointEditor}, IsDesignTimeCreatable=True}"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
MinWidth="20" />
|
||||
<ColumnDefinition MinWidth="30" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock TextAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<Run Text="{Binding XLabel}" />
|
||||
<Run Text=":" />
|
||||
</TextBlock>
|
||||
<TextBox Text="{Binding X, Mode=TwoWay}"
|
||||
Grid.Column="1" />
|
||||
|
||||
<TextBlock TextAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Grid.Row="1"
|
||||
Margin="0 5 0 0">
|
||||
<Run Text="{Binding YLabel}" />
|
||||
<Run Text=":" />
|
||||
</TextBlock>
|
||||
<TextBox Text="{Binding Y, Mode=TwoWay}"
|
||||
Margin="0 5 0 0"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1" />
|
||||
</Grid>
|
||||
</UserControl>
|
||||
12
Examples/Nodify.Playground/PointEditorView.xaml.cs
Normal file
12
Examples/Nodify.Playground/PointEditorView.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public partial class PointEditorView : UserControl
|
||||
{
|
||||
public PointEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Examples/Nodify.Playground/ProxySettingViewModel.cs
Normal file
23
Examples/Nodify.Playground/ProxySettingViewModel.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public class ProxySettingViewModel<T> : BaseSettingViewModel<T>
|
||||
{
|
||||
private readonly Func<T> _getter;
|
||||
private readonly Action<T> _setter;
|
||||
|
||||
public ProxySettingViewModel(Func<T> getter, Action<T> setter, string name, string? description = default)
|
||||
: base(name, description)
|
||||
{
|
||||
_getter = getter;
|
||||
_setter = setter;
|
||||
}
|
||||
|
||||
public new T Value
|
||||
{
|
||||
get => _getter();
|
||||
set => _setter(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
93
Examples/Nodify.Playground/SettingsView.xaml
Normal file
93
Examples/Nodify.Playground/SettingsView.xaml
Normal file
@@ -0,0 +1,93 @@
|
||||
<UserControl x:Class="Nodify.Playground.SettingsView"
|
||||
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.Playground"
|
||||
xmlns:nodify="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||
d:DataContext="{d:DesignInstance Type=local:PlaygroundViewModel, IsDesignTimeCreatable=True}"
|
||||
d:Foreground="{DynamicResource ForegroundBrush}"
|
||||
d:Background="{DynamicResource PanelBackgroundBrush}"
|
||||
mc:Ignorable="d">
|
||||
<ItemsControl ItemsSource="{Binding Items, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Focusable="False">
|
||||
<ItemsControl.Resources>
|
||||
<DataTemplate x:Key="TextEditorTemplate"
|
||||
DataType="{x:Type local:ISettingViewModel}">
|
||||
<TextBox Text="{Binding Value}"
|
||||
TextWrapping="Wrap"
|
||||
AcceptsReturn="True" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="NumberEditorTemplate"
|
||||
DataType="{x:Type local:ISettingViewModel}">
|
||||
<TextBox Text="{Binding Value}" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="BooleanEditorTemplate"
|
||||
DataType="{x:Type local:ISettingViewModel}">
|
||||
<CheckBox IsChecked="{Binding Value}" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="PointEditorTemplate"
|
||||
DataType="{x:Type local:ISettingViewModel}">
|
||||
<local:PointEditorView DataContext="{Binding Value, Mode=TwoWay}" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="OptionEditorTemplate"
|
||||
DataType="{x:Type local:ISettingViewModel}">
|
||||
<ComboBox DisplayMemberPath="Name"
|
||||
SelectedValuePath="Value"
|
||||
SelectedValue="{Binding Value, Mode=TwoWay}"
|
||||
ItemsSource="{Binding Value, Converter={nodify:EnumValuesConverter}}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.Resources>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:ISettingViewModel}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"
|
||||
SharedSizeGroup="PropertyName" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="{Binding Name}"
|
||||
ToolTip="{Binding Description}"
|
||||
Margin="0 5 5 0"
|
||||
Grid.Column="0" />
|
||||
<ContentControl Content="{Binding}"
|
||||
Margin="5 5 5 0"
|
||||
Grid.Column="1"
|
||||
Focusable="False">
|
||||
<ContentControl.Style>
|
||||
<Style TargetType="{x:Type ContentControl}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Type}"
|
||||
Value="Boolean">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource ResourceKey=BooleanEditorTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Type}"
|
||||
Value="Number">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource ResourceKey=NumberEditorTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Type}"
|
||||
Value="Point">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource ResourceKey=PointEditorTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Type}"
|
||||
Value="Option">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource ResourceKey=OptionEditorTemplate}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Type}"
|
||||
Value="Text">
|
||||
<Setter Property="ContentTemplate"
|
||||
Value="{StaticResource ResourceKey=TextEditorTemplate}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ContentControl.Style>
|
||||
</ContentControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</UserControl>
|
||||
23
Examples/Nodify.Playground/SettingsView.xaml.cs
Normal file
23
Examples/Nodify.Playground/SettingsView.xaml.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Nodify.Playground
|
||||
{
|
||||
public partial class SettingsView : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty ItemsProperty =
|
||||
DependencyProperty.Register(nameof(Items), typeof(IEnumerable<ISettingViewModel>), typeof(SettingsView));
|
||||
|
||||
public IEnumerable<ISettingViewModel> Items
|
||||
{
|
||||
get => (IEnumerable<ISettingViewModel>)GetValue(ItemsProperty);
|
||||
set => SetValue(ItemsProperty, value);
|
||||
}
|
||||
|
||||
public SettingsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Examples/Nodify.Playground/Themes/Brushes.xaml
Normal file
10
Examples/Nodify.Playground/Themes/Brushes.xaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<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}"
|
||||
Opacity="0.8" />
|
||||
|
||||
</ResourceDictionary>
|
||||
10
Examples/Nodify.Playground/Themes/Dark.xaml
Normal file
10
Examples/Nodify.Playground/Themes/Dark.xaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<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">#1A1A1A</Color>
|
||||
|
||||
</ResourceDictionary>
|
||||
10
Examples/Nodify.Playground/Themes/Light.xaml
Normal file
10
Examples/Nodify.Playground/Themes/Light.xaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<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>
|
||||
|
||||
</ResourceDictionary>
|
||||
10
Examples/Nodify.Playground/Themes/Nodify.xaml
Normal file
10
Examples/Nodify.Playground/Themes/Nodify.xaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<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>
|
||||
|
||||
</ResourceDictionary>
|
||||
Reference in New Issue
Block a user