Add project files.

This commit is contained in:
Ankitkumar Satapara
2026-04-17 22:31:58 +05:30
commit 21aaef6776
473 changed files with 50152 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
using System;
using System.Windows;
namespace Nodify.Events
{
/// <summary>
/// Represents the method that will handle resize related routed events.
/// </summary>
/// <param name="sender">The sender of this event.</param>
/// <param name="e">The event data.</param>
public delegate void ResizeEventHandler(object sender, ResizeEventArgs e);
/// <summary>
/// Provides data for resize related routed events.
/// </summary>
public class ResizeEventArgs : RoutedEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="ResizeEventArgs"/> class with the previous and the new <see cref="Size"/>.
/// </summary>
/// <param name="previousSize">The previous size associated with this event.</param>
/// <param name="newSize">The new size associated with this event.</param>
public ResizeEventArgs(Size previousSize, Size newSize)
{
PreviousSize = previousSize;
NewSize = newSize;
}
/// <summary>
/// Gets the previous size of the object.
/// </summary>
public Size PreviousSize { get; }
/// <summary>
/// Gets the new size of the object.
/// </summary>
public Size NewSize { get; }
protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget)
=> ((ResizeEventHandler)genericHandler)(genericTarget, this);
}
}

View File

@@ -0,0 +1,402 @@
using Nodify.Events;
using Nodify.Interactivity;
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
namespace Nodify
{
/// <summary>
/// Specifies the possible movement modes of a <see cref="GroupingNode"/>.
/// </summary>
public enum GroupingMovementMode
{
/// <summary>
/// The <see cref="GroupingNode"/> will move its content when moved.
/// </summary>
Group,
/// <summary>
/// The <see cref="GroupingNode"/> will not move its content when moved.
/// </summary>
Self
}
/// <summary>
/// Defines a panel with a header that groups <see cref="ItemContainer"/>s inside it and can be resized.
/// </summary>
[TemplatePart(Name = ElementResizeThumb, Type = typeof(FrameworkElement))]
[TemplatePart(Name = ElementHeader, Type = typeof(FrameworkElement))]
[TemplatePart(Name = ElementContent, Type = typeof(FrameworkElement))]
public class GroupingNode : HeaderedContentControl
{
protected static readonly object GroupMovementBoxed = GroupingMovementMode.Group;
protected const string ElementResizeThumb = "PART_ResizeThumb";
protected const string ElementHeader = "PART_Header";
protected const string ElementContent = "PART_Content";
#region Routed Events
public static readonly RoutedEvent ResizeStartedEvent = EventManager.RegisterRoutedEvent(nameof(ResizeStarted), RoutingStrategy.Bubble, typeof(ResizeEventHandler), typeof(GroupingNode));
public static readonly RoutedEvent ResizeCompletedEvent = EventManager.RegisterRoutedEvent(nameof(ResizeCompleted), RoutingStrategy.Bubble, typeof(ResizeEventHandler), typeof(GroupingNode));
/// <summary>
/// Occurs when the node finished resizing.
/// </summary>
public event ResizeEventHandler ResizeCompleted
{
add => AddHandler(ResizeCompletedEvent, value);
remove => RemoveHandler(ResizeCompletedEvent, value);
}
/// <summary>
/// Occurs when the node started resizing.
/// </summary>
public event ResizeEventHandler ResizeStarted
{
add => AddHandler(ResizeStartedEvent, value);
remove => RemoveHandler(ResizeStartedEvent, value);
}
#endregion
#region Dependency Properties
public static readonly DependencyProperty HeaderBrushProperty = Node.HeaderBrushProperty.AddOwner(typeof(GroupingNode));
public static readonly DependencyProperty CanResizeProperty = DependencyProperty.Register(nameof(CanResize), typeof(bool), typeof(GroupingNode), new FrameworkPropertyMetadata(BoxValue.True));
public static readonly DependencyProperty ActualSizeProperty = DependencyProperty.Register(nameof(ActualSize), typeof(Size), typeof(GroupingNode), new FrameworkPropertyMetadata(BoxValue.Size, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnActualSizeChanged));
public static readonly DependencyProperty MovementModeProperty = DependencyProperty.Register(nameof(MovementMode), typeof(GroupingMovementMode), typeof(GroupingNode), new FrameworkPropertyMetadata(GroupMovementBoxed));
public static readonly DependencyProperty ResizeCompletedCommandProperty = DependencyProperty.Register(nameof(ResizeCompletedCommand), typeof(ICommand), typeof(GroupingNode));
public static readonly DependencyProperty ResizeStartedCommandProperty = DependencyProperty.Register(nameof(ResizeStartedCommand), typeof(ICommand), typeof(GroupingNode));
private static void OnActualSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var node = (GroupingNode)d;
var newSize = (Size)e.NewValue;
node.Width = newSize.Width;
node.Height = newSize.Height;
}
/// <summary>
/// Gets or sets the brush used for the background of the <see cref="HeaderedContentControl.Header"/> of this <see cref="GroupingNode"/>.
/// </summary>
public Brush HeaderBrush
{
get => (Brush)GetValue(HeaderBrushProperty);
set => SetValue(HeaderBrushProperty, value);
}
/// <summary>
/// Gets or sets a value that indicates whether this <see cref="GroupingNode"/> can be resized.
/// </summary>
public bool CanResize
{
get => (bool)GetValue(CanResizeProperty);
set => SetValue(CanResizeProperty, value);
}
/// <summary>
/// Gets or sets the actual size of this <see cref="GroupingNode"/>.
/// </summary>
public Size ActualSize
{
get => (Size)GetValue(ActualSizeProperty);
set => SetValue(ActualSizeProperty, value);
}
/// <summary>
/// Gets or sets the default movement mode which can be temporarily changed by holding the <see cref="SwitchMovementModeModifierKey"/> while dragging by the header.
/// </summary>
public GroupingMovementMode MovementMode
{
get => (GroupingMovementMode)GetValue(MovementModeProperty);
set => SetValue(MovementModeProperty, value);
}
/// <summary>
/// Invoked when the <see cref="ResizeCompleted"/> event is not handled.
/// Parameter is the <see cref="ItemContainer.ActualSize"/> of the container.
/// </summary>
public ICommand? ResizeCompletedCommand
{
get => (ICommand?)GetValue(ResizeCompletedCommandProperty);
set => SetValue(ResizeCompletedCommandProperty, value);
}
/// <summary>
/// Invoked when the <see cref="ResizeStarted"/> event is not handled.
/// Parameter is the <see cref="ItemContainer.ActualSize"/> of the container.
/// </summary>
public ICommand? ResizeStartedCommand
{
get => (ICommand?)GetValue(ResizeStartedCommandProperty);
set => SetValue(ResizeStartedCommandProperty, value);
}
#endregion
#region Fields
/// <summary>
/// Gets the <see cref="NodifyEditor"/> that owns this <see cref="GroupingNode"/>.
/// </summary>
protected NodifyEditor? Editor { get; private set; }
/// <summary>
/// Gets the <see cref="NodifyEditor"/> that owns this <see cref="Container"/>.
/// </summary>
protected ItemContainer? Container { get; private set; }
/// <summary>
/// Gets the <see cref="FrameworkElement"/> used to resize this <see cref="GroupingNode"/>.
/// </summary>
protected FrameworkElement? ResizeThumb;
/// <summary>
/// Gets the <see cref="HeaderedContentControl.Header"/> control of this <see cref="GroupingNode"/>.
/// </summary>
protected FrameworkElement? HeaderControl;
/// <summary>
/// Gets the <see cref="System.Windows.Controls.ContentControl"/> control of this <see cref="GroupingNode"/>.
/// </summary>
protected FrameworkElement? ContentControl;
private double _minHeight = 30;
private double _minWidth = 30;
#endregion
static GroupingNode()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GroupingNode), new FrameworkPropertyMetadata(typeof(GroupingNode)));
FocusableProperty.OverrideMetadata(typeof(GroupingNode), new FrameworkPropertyMetadata(BoxValue.False));
Panel.ZIndexProperty.OverrideMetadata(typeof(GroupingNode), new FrameworkPropertyMetadata(-1, OnZIndexPropertyChanged));
}
private static void OnZIndexPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var node = (GroupingNode)d;
if (node.Container != null)
{
Panel.SetZIndex(node.Container, (int)e.NewValue);
}
}
/// <summary>
/// Initializes a new instance of the <see cref="GroupingNode"/> class.
/// </summary>
public GroupingNode()
{
AddHandler(Thumb.DragDeltaEvent, new DragDeltaEventHandler(OnResize));
AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(OnResizeCompleted));
AddHandler(Thumb.DragStartedEvent, new DragStartedEventHandler(OnResizeStarted));
Loaded += OnNodeLoaded;
Unloaded += OnNodeUnloaded;
}
private void OnNodeLoaded(object sender, RoutedEventArgs e)
{
if (HeaderControl != null)
{
HeaderControl.MouseDown += OnHeaderMouseDown;
HeaderControl.SizeChanged += OnHeaderSizeChanged;
CalculateDesiredHeaderSize();
}
}
private void OnNodeUnloaded(object sender, RoutedEventArgs e)
{
if (HeaderControl != null)
{
HeaderControl.MouseDown -= OnHeaderMouseDown;
HeaderControl.SizeChanged -= OnHeaderSizeChanged;
}
}
private void OnHeaderMouseDown(object sender, MouseButtonEventArgs e)
{
EditorGestures.ItemContainerGestures gestures = EditorGestures.Mappings.ItemContainer;
if (Container != null && Editor != null && gestures.Drag.Matches(e.Source, e))
{
// Switch the default movement mode if necessary
var prevMovementMode = MovementMode;
if (Keyboard.Modifiers == EditorGestures.Mappings.GroupingNode.SwitchMovementMode)
{
MovementMode = MovementMode == GroupingMovementMode.Group ? GroupingMovementMode.Self : GroupingMovementMode.Group;
}
var groupBounds = new Rect(Container.Location, RenderSize);
// Select the content and move with it
if (gestures.Selection.Append.Matches(e.Source, e))
{
Editor.SelectArea(groupBounds, append: true, fit: true);
}
else if (gestures.Selection.Remove.Matches(e.Source, e))
{
Editor.UnselectArea(groupBounds, fit: true);
}
else if (gestures.Selection.Invert.Matches(e.Source, e))
{
if (Container.IsSelected)
{
Editor.UnselectArea(groupBounds, fit: true);
Container.IsSelected = true;
}
else
{
Editor.SelectArea(groupBounds, append: true, fit: true);
}
}
else if (gestures.Selection.Replace.Matches(e.Source, e) || EditorGestures.Mappings.ItemContainer.Drag.Matches(e.Source, e))
{
Editor.SelectArea(groupBounds, append: Container.IsSelected, fit: true);
}
// Deselect content
if (MovementMode == GroupingMovementMode.Self)
{
Editor.UnselectArea(groupBounds, fit: true);
Container.IsSelected = true;
}
// Switch the default movement mode back
MovementMode = prevMovementMode;
}
}
/// <summary>
/// Toggles the selection of nodes inside this group.
/// If any contained nodes are selected, all will be unselected.
/// If none are selected, all will be selected.
/// </summary>
public void ToggleContentSelection()
{
if (Editor != null && Container != null)
{
var groupBounds = new Rect(Container.Location, RenderSize);
bool hasSelection = Editor.SelectedContainers.Any(x => x != Container && groupBounds.Contains(x.Bounds));
if (hasSelection)
{
Editor.UnselectArea(groupBounds, fit: true);
Container.IsSelected = true;
}
else
{
Editor.SelectArea(groupBounds, append: true, fit: true);
}
}
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ResizeThumb = Template.FindName(ElementResizeThumb, this) as FrameworkElement;
HeaderControl = Template.FindName(ElementHeader, this) as FrameworkElement;
ContentControl = Template.FindName(ElementContent, this) as FrameworkElement;
Container = this.GetParentOfType<ItemContainer>();
Editor = Container?.Editor ?? this.GetParentOfType<NodifyEditor>();
if (Container != null)
{
Panel.SetZIndex(Container, Panel.GetZIndex(this));
}
}
private void OnResize(object sender, DragDeltaEventArgs e)
{
if (CanResize && ReferenceEquals(e.OriginalSource, ResizeThumb))
{
double resultWidth = ActualWidth + e.HorizontalChange;
double resultHeight = ActualHeight + e.VerticalChange;
// Snap to grid
if (Editor != null)
{
uint cellSize = Editor.GridCellSize;
resultWidth = (int)resultWidth / cellSize * cellSize;
resultHeight = (int)resultHeight / cellSize * cellSize;
}
Width = Math.Max(_minWidth, resultWidth);
Height = Math.Max(_minHeight, resultHeight);
e.Handled = true;
}
}
private void OnResizeStarted(object sender, DragStartedEventArgs e)
{
ActualSize = new Size(ActualWidth, ActualHeight);
var args = new ResizeEventArgs(ActualSize, ActualSize)
{
RoutedEvent = ResizeStartedEvent,
Source = this
};
RaiseEvent(args);
// Raise ResizeStartedCommand if ResizeStartedEvent event is not handled
if (!args.Handled && (ResizeStartedCommand?.CanExecute(ActualSize) ?? false))
{
ResizeStartedCommand.Execute(ActualSize);
}
}
private void OnResizeCompleted(object sender, DragCompletedEventArgs e)
{
Size previousSize = ActualSize;
var newSize = new Size(ActualWidth, ActualHeight);
ActualSize = newSize;
var args = new ResizeEventArgs(previousSize, newSize)
{
RoutedEvent = ResizeCompletedEvent,
Source = this
};
RaiseEvent(args);
// Raise ResizeCompletedCommand if ResizeCompletedEvent event is not handled
if (!args.Handled && (ResizeCompletedCommand?.CanExecute(newSize) ?? false))
{
ResizeCompletedCommand.Execute(newSize);
}
}
private void OnHeaderSizeChanged(object sender, SizeChangedEventArgs e)
=> CalculateDesiredHeaderSize();
private void CalculateDesiredHeaderSize()
{
if (HeaderControl != null && ResizeThumb != null)
{
_minHeight = Math.Max(HeaderControl.ActualHeight + ResizeThumb.ActualHeight, MinHeight);
_minWidth = Math.Max(ResizeThumb.ActualWidth, MinWidth);
// If there's content don't resize it
if (ContentControl != null)
{
_minWidth = Math.Max(_minWidth, ContentControl.DesiredSize.Width);
_minHeight = Math.Max(_minHeight, _minHeight + ContentControl.DesiredSize.Height);
}
}
// Allow selecting only by the header
if (Container != null)
{
Container.DesiredSizeForSelection = new Size(ActualWidth, Math.Max(HeaderControl?.ActualHeight ?? _minHeight, MinHeight));
}
}
}
}

17
Nodify/Nodes/KnotNode.cs Normal file
View File

@@ -0,0 +1,17 @@
using System.Windows;
using System.Windows.Controls;
namespace Nodify
{
/// <summary>
/// Represents a control that owns a <see cref="Connector"/>.
/// </summary>
public class KnotNode : ContentControl
{
static KnotNode()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(KnotNode), new FrameworkPropertyMetadata(typeof(KnotNode)));
FocusableProperty.OverrideMetadata(typeof(KnotNode), new FrameworkPropertyMetadata(BoxValue.False));
}
}
}

257
Nodify/Nodes/Node.cs Normal file
View File

@@ -0,0 +1,257 @@
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Nodify
{
/// <summary>
/// Represents a control that has a list of <see cref="Input"/> <see cref="Connector"/>s and a list of <see cref="Output"/> <see cref="Connector"/>s.
/// </summary>
[TemplatePart(Name = ElementInputItemsControl, Type = typeof(ItemsControl))]
[TemplatePart(Name = ElementOutputItemsControl, Type = typeof(ItemsControl))]
[StyleTypedProperty(Property = nameof(ContentContainerStyle), StyleTargetType = typeof(Border))]
[StyleTypedProperty(Property = nameof(HeaderContainerStyle), StyleTargetType = typeof(Border))]
[StyleTypedProperty(Property = nameof(FooterContainerStyle), StyleTargetType = typeof(Border))]
public class Node : HeaderedContentControl
{
protected const string ElementInputItemsControl = "PART_Input";
protected const string ElementOutputItemsControl = "PART_Output";
#region Dependency Properties
public static readonly DependencyProperty ContentBrushProperty = DependencyProperty.Register(nameof(ContentBrush), typeof(Brush), typeof(Node));
public static readonly DependencyProperty HeaderBrushProperty = DependencyProperty.Register(nameof(HeaderBrush), typeof(Brush), typeof(Node));
public static readonly DependencyProperty FooterBrushProperty = DependencyProperty.Register(nameof(FooterBrush), typeof(Brush), typeof(Node));
public static readonly DependencyProperty FooterProperty = DependencyProperty.Register(nameof(Footer), typeof(object), typeof(Node), new FrameworkPropertyMetadata(OnFooterChanged));
public static readonly DependencyProperty FooterTemplateProperty = DependencyProperty.Register(nameof(FooterTemplate), typeof(DataTemplate), typeof(Node));
public static readonly DependencyProperty InputConnectorTemplateProperty = DependencyProperty.Register(nameof(InputConnectorTemplate), typeof(DataTemplate), typeof(Node));
protected static readonly DependencyPropertyKey HasFooterPropertyKey = DependencyProperty.RegisterReadOnly(nameof(HasFooter), typeof(bool), typeof(Node), new FrameworkPropertyMetadata(BoxValue.False));
public static readonly DependencyProperty HasFooterProperty = HasFooterPropertyKey.DependencyProperty;
public static readonly DependencyProperty OutputConnectorTemplateProperty = DependencyProperty.Register(nameof(OutputConnectorTemplate), typeof(DataTemplate), typeof(Node));
public static readonly DependencyProperty InputProperty = DependencyProperty.Register(nameof(Input), typeof(IEnumerable), typeof(Node));
public static readonly DependencyProperty OutputProperty = DependencyProperty.Register(nameof(Output), typeof(IEnumerable), typeof(Node));
public static readonly DependencyProperty ContentContainerStyleProperty = DependencyProperty.Register(nameof(ContentContainerStyle), typeof(Style), typeof(Node));
public static readonly DependencyProperty HeaderContainerStyleProperty = DependencyProperty.Register(nameof(HeaderContainerStyle), typeof(Style), typeof(Node));
public static readonly DependencyProperty FooterContainerStyleProperty = DependencyProperty.Register(nameof(FooterContainerStyle), typeof(Style), typeof(Node));
/// <summary>
/// Gets or sets the brush used for the background of the <see cref="ContentControl.Content"/> of this <see cref="Node"/>.
/// </summary>
public Brush ContentBrush
{
get => (Brush)GetValue(ContentBrushProperty);
set => SetValue(ContentBrushProperty, value);
}
/// <summary>
/// Gets or sets the brush used for the background of the <see cref="HeaderedContentControl.Header"/> of this <see cref="Node"/>.
/// </summary>
public Brush HeaderBrush
{
get => (Brush)GetValue(HeaderBrushProperty);
set => SetValue(HeaderBrushProperty, value);
}
/// <summary>
/// Gets or sets the brush used for the background of the <see cref="Node.Footer"/> of this <see cref="Node"/>.
/// </summary>
public Brush FooterBrush
{
get => (Brush)GetValue(FooterBrushProperty);
set => SetValue(FooterBrushProperty, value);
}
/// <summary>
/// Gets or sets the data for the footer of this control.
/// </summary>
public object Footer
{
get => GetValue(FooterProperty);
set => SetValue(FooterProperty, value);
}
/// <summary>
/// Gets or sets the template used to display the content of the control's footer.
/// </summary>
public DataTemplate FooterTemplate
{
get => (DataTemplate)GetValue(FooterTemplateProperty);
set => SetValue(FooterTemplateProperty, value);
}
/// <summary>
/// Gets or sets the template used to display the content of the control's <see cref="Input"/> connectors.
/// </summary>
public DataTemplate InputConnectorTemplate
{
get => (DataTemplate)GetValue(InputConnectorTemplateProperty);
set => SetValue(InputConnectorTemplateProperty, value);
}
/// <summary>
/// Gets or sets the template used to display the content of the control's <see cref="Output"/> connectors.
/// </summary>
public DataTemplate OutputConnectorTemplate
{
get => (DataTemplate)GetValue(OutputConnectorTemplateProperty);
set => SetValue(OutputConnectorTemplateProperty, value);
}
/// <summary>
/// Gets or sets the data for the input <see cref="Connector"/>s of this control.
/// </summary>
public IEnumerable Input
{
get => (IEnumerable)GetValue(InputProperty);
set => SetValue(InputProperty, value);
}
/// <summary>
/// Gets or sets the data for the output <see cref="Connector"/>s of this control.
/// </summary>
public IEnumerable Output
{
get => (IEnumerable)GetValue(OutputProperty);
set => SetValue(OutputProperty, value);
}
/// <summary>
/// Gets or sets the style for the content container.
/// </summary>
public Style ContentContainerStyle
{
get => (Style)GetValue(ContentContainerStyleProperty);
set => SetValue(ContentContainerStyleProperty, value);
}
/// <summary>
/// Gets or sets the style for the header container.
/// </summary>
public Style HeaderContainerStyle
{
get => (Style)GetValue(HeaderContainerStyleProperty);
set => SetValue(HeaderContainerStyleProperty, value);
}
/// <summary>
/// Gets or sets the style for the footer container.
/// </summary>
public Style FooterContainerStyle
{
get => (Style)GetValue(FooterContainerStyleProperty);
set => SetValue(FooterContainerStyleProperty, value);
}
/// <summary>
/// Gets a value that indicates whether the <see cref="Footer"/> is <see langword="null" />.
/// </summary>
public bool HasFooter => (bool)GetValue(HasFooterProperty);
private static void OnFooterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Node node = (Node)d;
node.SetValue(HasFooterPropertyKey, e.NewValue != null ? BoxValue.True : BoxValue.False);
}
#endregion
/// <inheritdoc cref="ItemsControl.GroupStyle"/>
public ObservableCollection<GroupStyle> InputGroupStyle { get; } = new ObservableCollection<GroupStyle>();
/// <inheritdoc cref="ItemsControl.GroupStyle"/>
public ObservableCollection<GroupStyle> OutputGroupStyle { get; } = new ObservableCollection<GroupStyle>();
protected ItemsControl? InputItemsControl { get; private set; }
protected ItemsControl? OutputItemsControl { get; private set; }
static Node()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Node), new FrameworkPropertyMetadata(typeof(Node)));
FocusableProperty.OverrideMetadata(typeof(Node), new FrameworkPropertyMetadata(BoxValue.False));
}
public Node()
{
InputGroupStyle.CollectionChanged += OnInputGroupStyleCollectionChanged;
OutputGroupStyle.CollectionChanged += OnOutputGroupStyleCollectionChanged;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
InputItemsControl = GetTemplateChild(ElementInputItemsControl) as ItemsControl;
OutputItemsControl = GetTemplateChild(ElementOutputItemsControl) as ItemsControl;
if (InputItemsControl != null)
{
foreach (var style in InputGroupStyle)
{
InputItemsControl.GroupStyle.Add(style);
}
}
if (OutputItemsControl != null)
{
foreach (var style in OutputGroupStyle)
{
OutputItemsControl.GroupStyle.Add(style);
}
}
}
private void OnInputGroupStyleCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (InputItemsControl != null)
{
SynchronizeCollection(InputItemsControl.GroupStyle, e);
}
}
private void OnOutputGroupStyleCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (OutputItemsControl != null)
{
SynchronizeCollection(OutputItemsControl.GroupStyle, e);
}
}
private static void SynchronizeCollection(ObservableCollection<GroupStyle> collection, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewItems != null)
{
for (int i = 0; i < e.NewItems.Count; i++)
{
var item = (GroupStyle)e.NewItems[i]!;
collection.Add(item);
}
}
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldItems != null)
{
for (int i = 0; i < e.OldItems.Count; i++)
{
var item = (GroupStyle)e.OldItems[i]!;
collection.Remove(item);
}
}
break;
case NotifyCollectionChangedAction.Replace:
collection[e.NewStartingIndex] = (GroupStyle)e.NewItems![0]!;
break;
case NotifyCollectionChangedAction.Move:
collection.Move(e.OldStartingIndex, e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Reset:
collection.Clear();
break;
}
}
}
}

59
Nodify/Nodes/NodeInput.cs Normal file
View File

@@ -0,0 +1,59 @@
using System.Windows;
using System.Windows.Controls;
namespace Nodify
{
/// <summary>
/// Represents the default control for the <see cref="Node.InputConnectorTemplate"/>.
/// </summary>
public class NodeInput : Connector
{
#region Dependency Properties
public static readonly DependencyProperty HeaderProperty = HeaderedContentControl.HeaderProperty.AddOwner(typeof(NodeInput));
public static readonly DependencyProperty HeaderTemplateProperty = HeaderedContentControl.HeaderTemplateProperty.AddOwner(typeof(NodeInput));
public static readonly DependencyProperty ConnectorTemplateProperty = DependencyProperty.Register(nameof(ConnectorTemplate), typeof(ControlTemplate), typeof(NodeInput));
public static readonly DependencyProperty OrientationProperty = StackPanel.OrientationProperty.AddOwner(typeof(NodeInput), new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// Gets of sets the data used for the control's header.
/// </summary>
public object Header
{
get => GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
/// <summary>
/// Gets or sets the template used to display the content of the control's header.
/// </summary>
public DataTemplate HeaderTemplate
{
get => (DataTemplate)GetValue(HeaderTemplateProperty);
set => SetValue(HeaderTemplateProperty, value);
}
/// <summary>
/// Gets or sets the template used to display the connecting point of this <see cref="Connector"/>.
/// </summary>
public ControlTemplate ConnectorTemplate
{
get => (ControlTemplate)GetValue(ConnectorTemplateProperty);
set => SetValue(ConnectorTemplateProperty, value);
}
/// <inheritdoc cref="StackPanel.Orientation" />
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
#endregion
static NodeInput()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NodeInput), new FrameworkPropertyMetadata(typeof(NodeInput)));
}
}
}

View File

@@ -0,0 +1,59 @@
using System.Windows;
using System.Windows.Controls;
namespace Nodify
{
/// <summary>
/// Represents the default control for the <see cref="Node.OutputConnectorTemplate"/>.
/// </summary>
public class NodeOutput : Connector
{
#region Dependency Properties
public static readonly DependencyProperty HeaderProperty = HeaderedContentControl.HeaderProperty.AddOwner(typeof(NodeOutput));
public static readonly DependencyProperty HeaderTemplateProperty = HeaderedContentControl.HeaderTemplateProperty.AddOwner(typeof(NodeOutput));
public static readonly DependencyProperty ConnectorTemplateProperty = NodeInput.ConnectorTemplateProperty.AddOwner(typeof(NodeOutput));
public static readonly DependencyProperty OrientationProperty = NodeInput.OrientationProperty.AddOwner(typeof(NodeOutput), new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// Gets of sets the data used for the control's header.
/// </summary>
public object Header
{
get => GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
/// <summary>
/// Gets or sets the template used to display the content of the control's header.
/// </summary>
public DataTemplate HeaderTemplate
{
get => (DataTemplate)GetValue(HeaderTemplateProperty);
set => SetValue(HeaderTemplateProperty, value);
}
/// <summary>
/// Gets or sets the template used to display the connecting point of this <see cref="Connector"/>.
/// </summary>
public ControlTemplate ConnectorTemplate
{
get => (ControlTemplate)GetValue(ConnectorTemplateProperty);
set => SetValue(ConnectorTemplateProperty, value);
}
/// <inheritdoc cref="StackPanel.Orientation" />
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
#endregion
static NodeOutput()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NodeOutput), new FrameworkPropertyMetadata(typeof(NodeOutput)));
}
}
}

100
Nodify/Nodes/StateNode.cs Normal file
View File

@@ -0,0 +1,100 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace Nodify
{
/// <summary>
/// Represents a control that acts as a <see cref="Connector"/>.
/// </summary>
[TemplatePart(Name = ElementContent, Type = typeof(UIElement))]
public class StateNode : Connector
{
protected const string ElementContent = "PART_Content";
#region Dependency Properties
public static readonly DependencyProperty HighlightBrushProperty = ItemContainer.HighlightBrushProperty.AddOwner(typeof(StateNode));
public static readonly DependencyProperty ContentProperty = ContentPresenter.ContentProperty.AddOwner(typeof(StateNode));
public static readonly DependencyProperty ContentTemplateProperty = ContentPresenter.ContentTemplateProperty.AddOwner(typeof(StateNode));
public static readonly DependencyProperty CornerRadiusProperty = Border.CornerRadiusProperty.AddOwner(typeof(StateNode));
/// <summary>
/// Gets or sets the brush used when the <see cref="PendingConnection.IsOverElementProperty"/> attached property is true for this <see cref="StateNode"/>.
/// </summary>
public Brush HighlightBrush
{
get => (Brush)GetValue(HighlightBrushProperty);
set => SetValue(HighlightBrushProperty, value);
}
/// <summary>
/// Gets or sets the data for the control's content.
/// </summary>
public object Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
/// <summary>
/// Gets or sets the template used to display the content of the control's header.
/// </summary>
public DataTemplate ContentTemplate
{
get => (DataTemplate)GetValue(ContentTemplateProperty);
set => SetValue(ContentTemplateProperty, value);
}
/// <summary>
/// Gets or sets a value that represents the degree to which the corners of the <see cref="StateNode"/> are rounded.
/// </summary>
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
#endregion
/// <summary>
/// Gets the <see cref="ContentControl"/> control of this <see cref="StateNode"/>.
/// </summary>
protected UIElement? ContentControl { get; private set; }
static StateNode()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(StateNode), new FrameworkPropertyMetadata(typeof(StateNode)));
FocusableProperty.OverrideMetadata(typeof(StateNode), new FrameworkPropertyMetadata(BoxValue.False));
}
/// <inheritdoc />
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ContentControl = Template.FindName(ElementContent, this) as UIElement;
}
/// <inheritdoc />
protected override void OnMouseDown(MouseButtonEventArgs e)
{
// Do not raise PendingConnection events if clicked on content
if (e.OriginalSource is Visual visual && (!ContentControl?.IsAncestorOf(visual) ?? true))
{
base.OnMouseDown(e);
}
}
/// <inheritdoc />
protected override void OnMouseUp(MouseButtonEventArgs e)
{
// Do not raise PendingConnection events if clicked on content
if (e.OriginalSource is Visual visual && (!ContentControl?.IsAncestorOf(visual) ?? true))
{
base.OnMouseUp(e);
}
}
}
}