Skip to content

Commit

Permalink
- added new edge drop window based on searchwindow instead of Generic…
Browse files Browse the repository at this point in the history
…Menu

- added CustomContextMenu attribute to customize the default context menu [#3](#3)
- added CustomEdgeDropMenu attribute to customize the default edge drop menu
  • Loading branch information
Doppelkeks committed Mar 15, 2023
1 parent 3152b33 commit cc13d6f
Show file tree
Hide file tree
Showing 15 changed files with 305 additions and 113 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed Unbind call when clearing a graph
- PortListView entries will now update their name, if the referenced node name is changed

## [0.2.7] - 2023-03-15
### Added
- added new edge drop window based on searchwindow instead of GenericMenu
- added CustomContextMenu attribute to customize the default context menu [#3](https://github.com/Gentlymad-Studios/NewGraph/issues/3)
- added CustomEdgeDropMenu attribute to customize the default edge drop menu
9 changes: 9 additions & 0 deletions Editor/Attributes/CustomContextMenuAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace NewGraph {
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class CustomContextMenuAttribute : Attribute {

public CustomContextMenuAttribute() { }
}
}
11 changes: 11 additions & 0 deletions Editor/Attributes/CustomContextMenuAttribute.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Editor/Attributes/CustomEdgeDropMenuAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace NewGraph {
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class CustomEdgeDropMenuAttribute : Attribute {

public CustomEdgeDropMenuAttribute() { }
}
}
11 changes: 11 additions & 0 deletions Editor/Attributes/CustomEdgeDropMenuAttribute.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 25 additions & 84 deletions Editor/Controllers/GraphController.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
using GraphViewBase;
using OdinSerializer.Utilities;
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using static NewGraph.GraphSettingsSingleton;

namespace NewGraph {
public class GraphController {

public GraphModel graphData;
private GraphView graphView;
public GraphView graphView;
private InspectorController<GraphModel> inspector;
private GraphSearchWindow searchWindow;
private ContextMenu contextMenu;
private EdgeDropMenu edgeDropMenu;

private Dictionary<Actions, Action<object>> internalActions;
private CopyPasteHandler copyPasteHandler = new CopyPasteHandler();
public CopyPasteHandler copyPasteHandler = new CopyPasteHandler();

private bool isLoading = false;
private Dictionary<object, NodeView> dataToViewLookup = new Dictionary<object, NodeView>();
private Dictionary<Type, string> nodeTypeToCreationLabel = new Dictionary<Type, string>();

private GenericMenu dropdownMenu;
private Type dropdownMenuCurrentType;

public Vector2 GetViewScale() {
return graphView.GetCurrentScale();
}
}

public void ForEachNode(Action<BaseNode> callback) {
graphView.ForEachNodeDo(callback);
Expand Down Expand Up @@ -64,15 +59,19 @@ public GraphController(VisualElement uxmlRoot, VisualElement root) {
{ Actions.Rename, OnRename }
};

if (searchWindow == null) {
searchWindow = ScriptableObject.CreateInstance<GraphSearchWindow>();
searchWindow.Initialize(graphView.shortcutHandler);
BuildSearchableMenu();

if (contextMenu == null) {
contextMenu = ContextMenu.CreateContextMenu(this);
contextMenu.BuildContextMenu();
}

if (edgeDropMenu == null) {
edgeDropMenu = EdgeDropMenu.CreateEdgeDropMenu(this);
}

}

private void OnRename(object obj) {
public void OnRename(object obj) {
if (graphView.GetSelectedNodeCount() == 1){
NodeView node = graphView.GetFirstSelectedNode() as NodeView;
foreach (EditableLabelElement label in node.editableLabels){
Expand All @@ -88,27 +87,15 @@ private void OnEdgeDrop(object edge) {

PortView port = baseEdge.Output != null ? baseEdge.Output as PortView : null;
if (port != null) {
if (port.type != dropdownMenuCurrentType) {
dropdownMenu = null;
dropdownMenu = new GenericMenu();
foreach (Type type in port.connectableTypes) {
if (nodeTypeToCreationLabel.ContainsKey(type)) {
void CreateNodeAndConnect() {
NodeView nodeView = CreateNewNode(type, false);
ConnectPorts(port, nodeView.inputPort);
//Reload();
}
dropdownMenu.AddItem(new GUIContent(nodeTypeToCreationLabel[type]), false, CreateNodeAndConnect);
}
}
dropdownMenu.ShowAsContext();
}
edgeDropMenu.port = port;
edgeDropMenu.BuildContextMenu();
SearchWindow.Open(edgeDropMenu, graphView);
}
}

private void OpenContextMenu(MouseDownEvent evt) {
if (evt.button == 1) {
SearchWindow.Open(searchWindow, graphView);
SearchWindow.Open(contextMenu, graphView);
}
}

Expand All @@ -123,7 +110,7 @@ private void OnHomeClicked() {
/// <summary>
/// Frame the graph based on the active selection.
/// </summary>
private void FrameGraph(object _=null) {
public void FrameGraph(object _=null) {
graphView.FrameSelected();
}

Expand Down Expand Up @@ -184,7 +171,7 @@ private void OnDeselected(object data = null) {
/// Called if a copy operation should be started...
/// </summary>
/// <param name="data">currently unused, check selected lists to get the actual selected objects...</param>
private void OnCopy(object data = null) {
public void OnCopy(object data = null) {
List<NodeView> nodesToCapture = new List<NodeView>();

graphView.ForEachSelectedNodeDo((node) => {
Expand All @@ -201,7 +188,7 @@ private void OnCopy(object data = null) {
/// Called if a paste operation should be started...
/// </summary>
/// <param name="data">currently unused, check selected lists to get the actual selected objects...</param>
private void OnPaste(object data = null) {
public void OnPaste(object data = null) {
copyPasteHandler.Resolve(graphData, (nodes) => {
Undo.RecordObject(graphData, "Paste Action");
// position node clones relative to the current mouse position & add them to the current graph
Expand Down Expand Up @@ -240,7 +227,7 @@ private void PositionNodesRelative(Vector2 viewPosition, List<NodeModel> nodes)
/// Called if a delete operation should be started...
/// </summary>
/// <param name="data"></param>
private void OnDelete(object data = null) {
public void OnDelete(object data = null) {
bool isDirty = false;

// go over every selected edge...
Expand Down Expand Up @@ -300,7 +287,7 @@ private void OnDelete(object data = null) {
/// Called if a cut operation should be started...
/// </summary>
/// <param name="data">currently unused, check selected lists to get the actual selected objects...</param>
private void OnCut(object data = null) {
public void OnCut(object data = null) {
OnCopy();
OnDelete();
}
Expand All @@ -309,7 +296,7 @@ private void OnCut(object data = null) {
/// Called if a duplication operation should be started...
/// </summary>
/// <param name="data">currently unused, check selected lists to get the actual selected objects...</param>
private void OnDuplicate(object data = null) {
public void OnDuplicate(object data = null) {
OnCopy();
OnPaste();
}
Expand Down Expand Up @@ -364,57 +351,11 @@ private void OnGraphAction(Actions actionType, object data = null) {
}
}

private void BuildSearchableMenu() {
Func<bool> defaultEnabledCheck = () => graphView.GetSelectedNodeCount() > 0;
Func<bool> nodeEnabledCheck = () => graphData != null;

searchWindow.StartAddingMenuEntries(Settings.searchWindowRootHeader);
// get all types across all assemblies that implement our INode interface
TypeCache.TypeCollection nodeTypes = TypeCache.GetTypesWithAttribute<NodeAttribute>();
foreach (Type nodeType in nodeTypes) {
// make sure the class tagged with the attribute actually is of type INode
if (nodeType.ImplementsOrInherits(typeof(INode))) {
// check if we have a utility node...
bool isUtilityNode = nodeType.ImplementsOrInherits(typeof(IUtilityNode));
NodeAttribute nodeAttribute = NodeModel.GetNodeAttribute(nodeType);

// retrieve subcategories
string categoryPath = nodeAttribute.categories;
string endSlash = "/";
categoryPath.Replace(@"\", "/");
if (string.IsNullOrWhiteSpace(categoryPath)) {
categoryPath = endSlash;
} else if (!categoryPath.EndsWith(endSlash)) {
categoryPath += endSlash;
}
if (!categoryPath.StartsWith(endSlash)) {
categoryPath = endSlash + categoryPath;
}

// add to the list of createable nodes
string createNodeLabel = $"{categoryPath}{nodeAttribute.GetName(nodeType)}";
createNodeLabel = (!isUtilityNode ? Settings.createNodeLabel : Settings.createUtilityNodeLabel) + createNodeLabel;
nodeTypeToCreationLabel.Add(nodeType, createNodeLabel);

searchWindow.AddNodeEntry(createNodeLabel, (obj) => CreateNewNode(nodeType, isUtilityNode));
}
}
searchWindow.ResolveNodeEntries(nodeEnabledCheck);
searchWindow.AddSeparator(Settings.searchWindowCommandHeader);
searchWindow.AddShortcutEntry(Actions.Frame, SearchTreeEntry.AlwaysEnabled, FrameGraph);
searchWindow.AddShortcutEntry(Actions.Rename, () => graphView.GetSelectedNodeCount() == 1, OnRename);
searchWindow.AddShortcutEntry(Actions.Cut, defaultEnabledCheck, OnCut);
searchWindow.AddShortcutEntry(Actions.Copy, defaultEnabledCheck, OnCopy);
searchWindow.AddShortcutEntry(Actions.Paste, () => copyPasteHandler.HasNodes(), OnPaste);
searchWindow.AddShortcutEntry(Actions.Duplicate, defaultEnabledCheck, OnDuplicate);
searchWindow.AddShortcutEntry(Actions.Delete, () => graphView.HasSelectedEdges() || defaultEnabledCheck(), OnDelete);
}

/// <summary>
/// Create a new node for the graph simply based on a valid type.
/// </summary>
/// <param name="nodeType">The node type.</param>
private NodeView CreateNewNode(Type nodeType, bool isUtilityNode = false) {
public NodeView CreateNewNode(Type nodeType, bool isUtilityNode = false) {
// get the current view position of the mouse, so we can display the new node at the tip of the mouse...
Vector2 viewPosition = graphView.GetMouseViewPosition();

Expand Down
106 changes: 106 additions & 0 deletions Editor/Views/ContextMenu.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using GraphViewBase;
using OdinSerializer.Utilities;
using System;
using System.Collections.Generic;
using UnityEditor;
using static NewGraph.GraphSettingsSingleton;

namespace NewGraph {
public class ContextMenu : GraphSearchWindowProvider {
protected static Dictionary<Type, string> nodeTypeToCreationLabel = new Dictionary<Type, string>();
protected GraphController graphController;

public void Initialize(GraphController graphController) {
this.graphController = graphController;
Initialize(this.graphController.graphView.shortcutHandler);
}

public static ContextMenu CreateContextMenu(GraphController graphController) {
TypeCache.TypeCollection types = TypeCache.GetTypesWithAttribute<CustomContextMenuAttribute>();
ContextMenu menu;

foreach (Type type in types) {
if (type.ImplementsOrInherits(typeof(ContextMenu))) {
menu = CreateInstance(type) as ContextMenu;
menu.Initialize(graphController);
return menu;
}
}

menu = CreateInstance<ContextMenu>();
menu.Initialize(graphController);
return menu;
}

public virtual bool DefaultEnableCheck() {
return graphController.graphView.GetSelectedNodeCount() > 0;
}

public virtual bool DefaultNodeEnabledCheck() {
return graphController.graphData != null;
}

protected virtual string GetHeader() {
return Settings.searchWindowRootHeader;
}

public virtual void BuildContextMenu() {
StartAddingMenuEntries(GetHeader());
AddNodeEntries();
ResolveNodeEntries(DefaultNodeEnabledCheck);
AddCommands();
}

protected virtual void AddNodeEntries() {
AddNodeEntriesDefault();
}

protected void AddNodeEntriesDefault() {
// get all types across all assemblies that implement our INode interface
TypeCache.TypeCollection nodeTypes = TypeCache.GetTypesWithAttribute<NodeAttribute>();
foreach (Type nodeType in nodeTypes) {
// make sure the class tagged with the attribute actually is of type INode
if (nodeType.ImplementsOrInherits(typeof(INode))) {
// check if we have a utility node...
bool isUtilityNode = nodeType.ImplementsOrInherits(typeof(IUtilityNode));
NodeAttribute nodeAttribute = NodeModel.GetNodeAttribute(nodeType);

// retrieve subcategories
string categoryPath = nodeAttribute.categories;
string endSlash = "/";
categoryPath.Replace(@"\", "/");
if (string.IsNullOrWhiteSpace(categoryPath)) {
categoryPath = endSlash;
} else if (!categoryPath.EndsWith(endSlash)) {
categoryPath += endSlash;
}
if (!categoryPath.StartsWith(endSlash)) {
categoryPath = endSlash + categoryPath;
}

// add to the list of createable nodes
string createNodeLabel = $"{categoryPath}{nodeAttribute.GetName(nodeType)}";
nodeTypeToCreationLabel.Add(nodeType, createNodeLabel.Substring(1));
createNodeLabel = (!isUtilityNode ? Settings.createNodeLabel : Settings.createUtilityNodeLabel) + createNodeLabel;
AddNodeEntry(createNodeLabel, (obj) => graphController.CreateNewNode(nodeType, isUtilityNode));
}
}
}

protected virtual void AddCommands() {
AddDefaultCommands();
}

protected void AddDefaultCommands() {
AddSeparator(Settings.searchWindowCommandHeader);
AddShortcutEntry(Actions.Frame, SearchTreeEntry.AlwaysEnabled, graphController.FrameGraph);
AddShortcutEntry(Actions.Rename, () => graphController.graphView.GetSelectedNodeCount() == 1, graphController.OnRename);
AddShortcutEntry(Actions.Cut, DefaultEnableCheck, graphController.OnCut);
AddShortcutEntry(Actions.Copy, DefaultEnableCheck, graphController.OnCopy);
AddShortcutEntry(Actions.Paste, () => graphController.copyPasteHandler.HasNodes(), graphController.OnPaste);
AddShortcutEntry(Actions.Duplicate, DefaultEnableCheck, graphController.OnDuplicate);
AddShortcutEntry(Actions.Delete, () => graphController.graphView.HasSelectedEdges() || DefaultEnableCheck(), graphController.OnDelete);
}
}

}
11 changes: 11 additions & 0 deletions Editor/Views/ContextMenu.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit cc13d6f

Please sign in to comment.