Skip to content

Commit

Permalink
- implemented custom foldout states for groups/foldouts and independe…
Browse files Browse the repository at this point in the history
…ntly from nodeView and inspector. This way a graph will always keep track over every expanded state for each node.

- implemented empty method in graphController to extend for new EdgeDrop action
  • Loading branch information
Doppelkeks committed Mar 10, 2023
1 parent 785c191 commit 69dff13
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 26 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- fixed a visibility related null ref in GraphModelEditor
- removed faulty isExpanded property from foldouts (they are just claused on default now)
- fixed HasSelectedEdges check that worked with the wrong field
- fixed long foldout label that prevented node dragging
- fixed long foldout label that prevented node dragging

## [0.2.0] - 2023-03-10
### Added
- implemented custom foldout states for groups/foldouts and indepedently from nodeView and inspector. This way a graph will always keep track over every expanded state for each node.
- implemented empty method in graphController to extend for new EdgeDrop action
2 changes: 1 addition & 1 deletion Documentation~
11 changes: 9 additions & 2 deletions Editor/Controllers/GraphController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public Vector2 GetViewScale() {
public void ForEachNode(Action<BaseNode> callback) {
graphView.ForEachNodeDo(callback);
}

public GraphController(VisualElement uxmlRoot, VisualElement root) {
graphView = new GraphView(uxmlRoot, root, OnGraphAction);
graphView.OnViewTransformChanged -= OnViewportChanged;
Expand All @@ -54,7 +55,8 @@ public GraphController(VisualElement uxmlRoot, VisualElement root) {
{ Actions.EdgeCreate, OnEdgeCreate },
{ Actions.EdgeDelete, OnEdgeDelete },
{ Actions.SelectionChanged, OnSelected },
{ Actions.SelectionCleared, OnDeselected }
{ Actions.SelectionCleared, OnDeselected },
{ Actions.EdgeDrop, OnEdgeDrop },
};

if (searchWindow == null) {
Expand All @@ -64,10 +66,14 @@ public GraphController(VisualElement uxmlRoot, VisualElement root) {
}
}

private void OnEdgeDrop(object obj) {
//SearchWindow.Open(searchWindow, graphView);
}

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

/// <summary>
Expand Down Expand Up @@ -472,6 +478,7 @@ void ForEachNodeProperty(List<NodeModel> nodes, SerializedProperty nodesProperty
// go over every node...
for (int i = 0; i < nodes.Count; i++) {
NodeModel node = nodes[i];

// initialize the node...
node.Initialize();

Expand Down
43 changes: 25 additions & 18 deletions Editor/Views/NodeView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ public class NodeView : BaseNode {
private List<EditableLabelElement> editableLabels = new List<EditableLabelElement>();
public Color nodeColor;
private bool hasInspectorProperty = false;

public NodeController controller;
public PortView inputPort = null;
public List<PortView> outputPorts = new List<PortView>();
public List<PortListView> portLists = new List<PortListView>();
public List<Foldout> foldouts = new List<Foldout>();

public NodeView(NodeController controller, Color nodeColor) {
this.controller = controller;
Expand Down Expand Up @@ -77,6 +78,8 @@ public void InitializeView() {
(controller.nodeItem.nodeData as IUtilityNode).Initialize(controller);
}

controller.nodeItem.CleanupFoldoutStates();

BindUI(controller.GetSerializedObject());
}

Expand Down Expand Up @@ -159,28 +162,32 @@ PropertyField Create(VisualElement groupParent, Editability edtability) {
}
}


private VisualElement[] CreateGroupUI(GroupInfo groupInfo, VisualElement[] parents, SerializedProperty property) {
VisualElement[] newGroups = new VisualElement[parents.Length];

void AddAtIndex(int index, bool empty) {
void AddAtIndex(int index, bool empty, string prefix) {
// add label/ foldout etc.
if (!empty) {
SerializedProperty prop = property.Copy();
Foldout newGroup = new Foldout();
newGroup.pickingMode = PickingMode.Ignore;
newGroup.AddToClassList(nameof(GroupInfo));
newGroup.text = groupInfo.groupName;
newGroup.name = groupInfo.relativePropertyPath;
newGroup.value = false /*prop.isExpanded*/;
/*
newGroup.name = prefix + groupInfo.relativePropertyPath;

int propertyPathHash = newGroup.name.GetHashCode();
NodeModel.FoldoutState foldOutState = controller.nodeItem.GetOrCreateFoldout(propertyPathHash);
foldOutState.used= true;

newGroup.value = foldOutState.isExpanded;

newGroup.RegisterValueChangedCallback((evt) => {
prop.isExpanded = evt.newValue;
prop.serializedObject.ApplyModifiedProperties();
foldOutState.isExpanded = evt.newValue;
controller.GetSerializedObject().ApplyModifiedPropertiesWithoutUndo();
});
newGroup.bindingPath = prop.propertyPath;
*/
newGroup.name = "unity-foldout-" + prop.propertyPath;

newGroups[index] = newGroup;
foldouts.Add(newGroup);
} else {
newGroups[index] = null;
}
Expand All @@ -189,17 +196,17 @@ void AddAtIndex(int index, bool empty) {
}
}

bool addEmpty = true;
bool noNodeView = true;
if (groupInfo.graphDisplay.displayType.HasFlag(DisplayType.NodeView)) {
addEmpty = false;
noNodeView = false;
}
AddAtIndex(0, addEmpty);
addEmpty = true;
AddAtIndex(0, noNodeView, nameof(DisplayType.NodeView));

bool noInspector = true;
if (groupInfo.graphDisplay.displayType.HasFlag(DisplayType.Inspector)) {
addEmpty = false;
noInspector = false;
}
AddAtIndex(1, addEmpty);
AddAtIndex(1, noInspector, nameof(DisplayType.Inspector));

return newGroups;
}
Expand Down
4 changes: 4 additions & 0 deletions Models/GraphModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ namespace NewGraph {
/// <summary>
/// Our graph data model split into are minimalistic runtime part and editor specific extensions.
/// See everything under #if UNITY_EDITOR for all editor specific parts of this class.
/// The compilation tags allow us to strip away any unwated data for a runtime scenario.
/// Source: https://docs.unity3d.com/2022.2/Documentation/Manual/script-Serialization.html
/// Discussion: https://forum.unity.com/threads/serialize-fields-only-in-editor.433422/
/// </summary>
[CreateAssetMenu(fileName =nameof(GraphModel), menuName = nameof(GraphModel), order = 1)]
public class GraphModel : ScriptableObject {
Expand Down Expand Up @@ -108,6 +111,7 @@ public NodeModel AddNode(NodeModel nodeItem) {
public void RemoveNode(NodeModel node) {
if (!node.isUtilityNode) {
nodes.Remove(node);

} else {
utilityNodes.Remove(node);
}
Expand Down
93 changes: 90 additions & 3 deletions Models/NodeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@

namespace NewGraph {
/// <summary>
/// Our data model for Nodes split into ar minimalistic runtime part and editor specific extensions.
/// Our data model for Nodes split into a minimalistic runtime part and editor specific extensions.
/// See #if UNITY_EDITOR sections for all editor specific parts of this class.
/// The compilation tags allow us to strip away any unwated data for a runtime scenario.
/// Source: https://docs.unity3d.com/2022.2/Documentation/Manual/script-Serialization.html
/// Discussion: https://forum.unity.com/threads/serialize-fields-only-in-editor.433422/
/// </summary>
[Serializable]
public class NodeModel {
Expand All @@ -21,14 +24,61 @@ public class NodeModel {

#if UNITY_EDITOR
// FROM HERE BE DRAGONS...

/// <summary>
/// node position
/// </summary>
[SerializeField]
private float nodeX, nodeY;

/// <summary>
/// Node name
/// </summary>
[SerializeField]
private string name;
public const string nameIdentifier = nameof(name);

/// <summary>
/// Is this node a utility node?
/// </summary>
public bool isUtilityNode = false;

/// <summary>
/// standard .isExpanded behavior does not work for us, especially if there are two views.
/// So we need to manage the expanded states of our "groups"/foldouts ourselves.
/// </summary>
[Serializable]
public class FoldoutState {
public int relativePropertyPathHash;
[NonSerialized]
public bool used = false;
public bool isExpanded = true;
}

/// <summary>
/// Serialied list of all foldout states
/// </summary>
[SerializeField]
private List<FoldoutState> foldouts = new List<FoldoutState>();

/// <summary>
/// Dictionary is needed as we might have many foldouts based on how deep the class structure goes
/// </summary>
private Dictionary<int, FoldoutState> foldoutsLookup = null;
private Dictionary<int, FoldoutState> FoldoutsLookup {
get {
if (foldoutsLookup == null) {
foldoutsLookup = new Dictionary<int, FoldoutState>();
foreach (FoldoutState foldoutState in foldouts) {
foldoutsLookup.Add(foldoutState.relativePropertyPathHash, foldoutState);
}
}
return foldoutsLookup;
}
}

[NonSerialized]
private bool dataIsSet = false;
public bool isUtilityNode = false;
public const string nameIdentifier = nameof(name);
[NonSerialized]
private SerializedProperty serializedProperty;
[NonSerialized]
Expand All @@ -51,6 +101,43 @@ public NodeModel(INode nodeData) {
Initialize();
}

/// <summary>
/// Get a foldout state based on a hash or create it if not present
/// </summary>
/// <param name="pathHash"></param>
/// <param name="defaultState"></param>
/// <returns></returns>
public FoldoutState GetOrCreateFoldout(int pathHash, bool defaultState=true) {
if (!FoldoutsLookup.ContainsKey(pathHash)) {
serializedProperty.serializedObject.Update();

FoldoutState foldoutState = new FoldoutState() { relativePropertyPathHash = pathHash, isExpanded = defaultState };
FoldoutsLookup.Add(pathHash, foldoutState);
foldouts.Add(foldoutState);

EditorUtility.SetDirty(serializedProperty.serializedObject.targetObject);
serializedProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo();
}
return FoldoutsLookup[pathHash];
}

/// <summary>
/// Remove unused states so we dont pollute this if the class structure changes.
/// </summary>
public void CleanupFoldoutStates() {
serializedProperty.serializedObject.Update();

for (int i=foldouts.Count-1; i>=0; i--) {
FoldoutState state = foldouts[i];
if (!state.used || state.relativePropertyPathHash == default) {
foldouts.RemoveAt(i);
}
}

EditorUtility.SetDirty(serializedProperty.serializedObject.targetObject);
serializedProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo();
}

public Vector2 GetPosition() {
return new Vector2(nodeX, nodeY);
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "com.gentlymad.newgraph",
"version": "0.1.9",
"version": "0.2.0",
"displayName": "NewGraph",
"description": "Our general node graph solution. This is build upon the idea to visualize complex data structures as graph networks without having to modify already established data classes, except adding [Node], [Port] and [SerializeReference] attributes to call classes that should show in the Graph View.",
"unity": "2022.2",
Expand Down

0 comments on commit 69dff13

Please sign in to comment.