/// <summary> /// Saves the surface to bytes. Performs also modified child surfaces saving before. /// </summary> /// <remarks> /// Assume this method does not throw exceptions but uses return value as a error code. /// </remarks> /// <returns>True if failed, otherwise false.</returns> public bool Save() { // Save all children modified before saving the current surface for (int i = 0; i < Children.Count; i++) { if (Children[i].IsModified && Children[i].Save()) { return(true); } } _meta.Release(); Saving?.Invoke(this); // Save surface meta _meta.AddEntry(10, Utils.StructureToByteArray(ref CachedSurfaceMeta)); // Save all nodes meta VisjectSurface.Meta11 meta11; for (int i = 0; i < Nodes.Count; i++) { var node = Nodes[i]; meta11.Position = node.Location; meta11.Selected = false; // don't save selection to prevent stupid binary diffs on asset node.Meta.Release(); // TODO: reuse byte[] array for all nodes to reduce dynamic memory allocations node.Meta.AddEntry(11, Utils.StructureToByteArray(ref meta11)); } // Save graph try { // Save graph using (var stream = new MemoryStream()) using (var writer = new BinaryWriter(stream)) { // Save graph to bytes SaveGraph(writer); var bytes = stream.ToArray(); // Send data to the container Context.SurfaceData = bytes; Saved?.Invoke(this); // Clear modification flag _isModified = false; } } catch (Exception ex) { // Error Editor.LogWarning("Saving Visject Surface data failed."); Editor.LogWarning(ex); return(true); } return(false); }
/// <summary> /// Searches for the types and fills with data. /// </summary> protected virtual void Search() { // Special case for attributes if (_type.BaseType == typeof(Attribute)) { Utils.GetTypesWithAttributeDefined(_type, _list, _checkFunc, _checkAssembly); } else { Utils.GetDerivedTypes(_type, _list, _checkFunc, _checkAssembly); } }
/// <summary> /// Gets the collection of the Control types that can be spawned in the game (valid ones). /// </summary> /// <returns>The Control types collection (readonly).</returns> public List <Type> GetControlTypes() { if (!_hasValidControlTypes) { _controlTypes.Clear(); _hasValidControlTypes = true; Utils.GetDerivedTypes(typeof(Control), _controlTypes, IsTypeValidControlType, HasAssemblyValidControlTypes); } return(_controlTypes); }
private NodeArchetype GetArchetype(MethodBase method) { // Validate method signature if (!method.IsStatic || !method.IsPublic || method as MethodInfo == null || ((MethodInfo)method).ReturnType != typeof(NodeArchetype) || method.GetParameters().Length != 0 || method.IsGenericMethod) { return(null); } // Invoke method try { var arch = (NodeArchetype)method.Invoke(null, null); // Validate archetype if (arch.Tag != null || string.IsNullOrEmpty(arch.Title)) { Debug.LogWarning(string.Format("Method {0} from {1} returned invalid node archetype. Tag must be null and title must be specified.", method, method.DeclaringType)); return(null); } if (arch.DefaultValues == null || arch.DefaultValues.Length < 2 || string.IsNullOrEmpty(arch.DefaultValues[0] as string) || string.IsNullOrEmpty(arch.DefaultValues[1] as string)) { Debug.LogWarning(string.Format("Method {0} from {1} returned invalid node archetype. Default values are invalid. DefaultValues[0] must specify the C# runtime controller typename. DefaultValues[1] must specify the node group name.", method, method.DeclaringType)); return(null); } // Validate node type var typeName = Surface.Archetypes.Custom.GetNodeTypeName(arch); var type = Utils.GetType(typeName); if (type == null) { Debug.LogWarning(string.Format("Method {0} from {1} returned invalid node archetype. Failed to find node logic defined in type {2}.", method, method.DeclaringType, typeName)); return(null); } // Check if type comes from scripts that can be reloaded at runtime HasTypeFromGameScripts |= Utils.IsTypeFromGameScripts(method.DeclaringType) || Utils.IsTypeFromGameScripts(type); return(arch); } catch (Exception ex) { Debug.LogWarning("Failed to get the custom node archetype."); Debug.LogWarning(ex); } return(null); }
/// <summary> /// Gets all the script types from the all loaded assemblies (including project scripts and scripts from the plugins). /// </summary> /// <returns>The script types collection (readonly).</returns> public List <Type> GetScripts() { if (!_hasValidScripts) { _scripts.Clear(); _hasValidScripts = true; Editor.Log("Searching valid script types"); var start = DateTime.Now; Utils.GetDerivedTypes(typeof(Script), _scripts, IsTypeValidScriptType, HasAssemblyValidScriptTypes); var end = DateTime.Now; Editor.Log(string.Format("Found {0} script types (in {1} ms)", _scripts.Count, (int)(end - start).TotalMilliseconds)); } return(_scripts); }
/// <summary> /// Gets the collection of the Control types that can be spawned in the game (valid ones). /// </summary> /// <returns>The Control types collection (readonly).</returns> public List <Type> GetControlTypes() { if (!_hasValidControlTypes) { _controlTypes.Clear(); _hasValidControlTypes = true; Editor.Log("Searching valid control types"); var start = DateTime.Now; Utils.GetDerivedTypes(typeof(Control), _controlTypes, IsTypeValidControlType, HasAssemblyValidControlTypes); var end = DateTime.Now; Editor.Log(string.Format("Found {0} control types (in {1} ms)", _controlTypes.Count, (int)(end - start).TotalMilliseconds)); } return(_controlTypes); }
/// <summary> /// Called when node gets loaded and should be added to the surface. Creates node elements from the archetype. /// </summary> /// <param name="node">The node.</param> public virtual void OnNodeLoaded(SurfaceNode node) { // Create child elements of the node based on it's archetype int elementsCount = node.Archetype.Elements?.Length ?? 0; for (int i = 0; i < elementsCount; i++) { // ReSharper disable once PossibleNullReferenceException node.AddElement(node.Archetype.Elements[i]); } // Load metadata var meta = node.Meta.GetEntry(11); if (meta.Data != null) { var meta11 = Utils.ByteArrayToStructure <VisjectSurface.Meta11>(meta.Data); node.Location = meta11.Position; //node.IsSelected = meta11.Selected; } }
/// <summary> /// Called when node gets loaded and should be added to the surface. Creates node elements from the archetype. /// </summary> /// <param name="node">The node.</param> public virtual void OnNodeLoaded(SurfaceNode node) { // Create child elements of the node based on it's archetype int elementsCount = node.Archetype.Elements?.Length ?? 0; for (int i = 0; i < elementsCount; i++) { // ReSharper disable once PossibleNullReferenceException var arch = node.Archetype.Elements[i]; ISurfaceNodeElement element = null; switch (arch.Type) { case NodeElementType.Input: element = new InputBox(node, arch); break; case NodeElementType.Output: element = new OutputBox(node, arch); break; case NodeElementType.BoolValue: element = new BoolValue(node, arch); break; case NodeElementType.FloatValue: element = new FloatValue(node, arch); break; case NodeElementType.IntegerValue: element = new IntegerValue(node, arch); break; case NodeElementType.ColorValue: element = new ColorValue(node, arch); break; case NodeElementType.ComboBox: element = new ComboBoxElement(node, arch); break; case NodeElementType.Asset: element = new AssetSelect(node, arch); break; case NodeElementType.Text: element = new TextView(node, arch); break; case NodeElementType.TextBox: element = new TextBoxView(node, arch); break; case NodeElementType.SkeletonNodeSelect: element = new SkeletonNodeSelectElement(node, arch); break; } if (element != null) { // Link element node.AddElement(element); } } // Load metadata var meta = node.Meta.GetEntry(11); if (meta.Data != null) { var meta11 = Utils.ByteArrayToStructure <VisjectSurface.Meta11>(meta.Data); node.Location = meta11.Position; //node.IsSelected = meta11.Selected; } }
private void LoadGraph(BinaryReader stream) { // IMPORTANT! This must match C++ Graph format // Magic Code int tmp = stream.ReadInt32(); if (tmp != 1963542358) { // Error throw new Exception("Invalid Graph format version"); } // Engine Build uint engineBuild = stream.ReadUInt32(); // Load1 { // Time saved (not used anymore to prevent binary diffs after saving unmodified surface) stream.ReadInt64(); byte[] guidBytes = new byte[16]; // Nodes count int nodesCount = stream.ReadInt32(); if (Nodes.Capacity < nodesCount) { Nodes.Capacity = nodesCount; } List <ConnectionHint> tmpHints = _cachedConnections.Value; tmpHints.Clear(); tmpHints.Capacity = Mathf.Max(tmpHints.Capacity, nodesCount * 4); // Parameters count int parametersCount = stream.ReadInt32(); if (Parameters.Capacity < parametersCount) { Parameters.Capacity = parametersCount; } // For each node for (int i = 0; i < nodesCount; i++) { // ID uint id = stream.ReadUInt32(); // Type ushort typeId = stream.ReadUInt16(); ushort groupId = stream.ReadUInt16(); // Create node SurfaceNode node; if (groupId == Archetypes.Custom.GroupID) { node = new Archetypes.Custom.DummyCustomNode(id, this); } else { node = NodeFactory.CreateNode(_surface.NodeArchetypes, id, this, groupId, typeId); } if (node == null) { // Error throw new Exception("Cannot create graph node."); } Nodes.Add(node); } // For each param for (int i = 0; i < parametersCount; i++) { // Create param var param = new SurfaceParameter(); Parameters.Add(param); // Properties param.Type = (ParameterType)stream.ReadByte(); stream.Read(guidBytes, 0, 16); param.ID = new Guid(guidBytes); param.Name = Utils.ReadStr(stream, 97); param.IsPublic = stream.ReadByte() != 0; param.IsStatic = stream.ReadByte() != 0; param.IsUIVisible = stream.ReadByte() != 0; param.IsUIEditable = stream.ReadByte() != 0; // References int refsCount = stream.ReadInt32(); param.ReferencedBy.Capacity = refsCount; for (int j = 0; j < refsCount; j++) { uint refID = stream.ReadUInt32(); var node = FindNode(refID); if (node == null) { // Error Editor.LogWarning($"Invalid node reference id (param: {param.Name}, node ref: {refID})"); } else { param.ReferencedBy.Add(node); } } // Value Utils.ReadCommonValue(stream, ref param.Value); // Meta param.Meta.Load(engineBuild, stream); } // For each node for (int i = 0; i < nodesCount; i++) { var node = Nodes[i]; int valuesCnt = stream.ReadInt32(); int firstValueReadIdx = 0; // Special case for custom nodes if (node is Archetypes.Custom.DummyCustomNode dummyCustom) { // TODO: maybe reuse the same dummy node (static) because is only a placeholder // Values check if (valuesCnt < 2) { throw new Exception("Missing custom nodes data."); } // Node typename check object typeName = null; Utils.ReadCommonValue(stream, ref typeName); firstValueReadIdx = 1; if (string.IsNullOrEmpty(typeName as string)) { throw new Exception("Missing custom node typename."); } // Find custom node archetype that matches this node type (it must be unique) var customNodes = _surface.GetCustomNodes(); if (customNodes?.Archetypes == null) { throw new Exception("Cannot find any custom nodes archetype."); } NodeArchetype arch = null; for (int j = 0; j < customNodes.Archetypes.Length; j++) { if (string.Equals(Archetypes.Custom.GetNodeTypeName(customNodes.Archetypes[j]), (string)typeName, StringComparison.OrdinalIgnoreCase)) { arch = customNodes.Archetypes[j]; break; } } if (arch == null) { throw new Exception("Missing custom node " + typeName); } // Create custom node and replace dummy placeholder node = NodeFactory.CreateNode(dummyCustom.ID, this, customNodes, arch); if (node == null) { throw new Exception("Failed to create custom node " + typeName); } Nodes[i] = node; // Store node typename in values container node.Values[0] = typeName; } // Values int nodeValuesCnt = node.Values?.Length ?? 0; if (valuesCnt == nodeValuesCnt) { for (int j = firstValueReadIdx; j < valuesCnt; j++) { // ReSharper disable once PossibleNullReferenceException Utils.ReadCommonValue(stream, ref node.Values[j]); } } else { Editor.LogWarning(String.Format("Invalid node values. Loaded: {0}, expected: {1}. Type: {2}, {3}", valuesCnt, nodeValuesCnt, node.Archetype.Title, node.Archetype.TypeID)); object dummy = null; for (int j = firstValueReadIdx; j < valuesCnt; j++) { Utils.ReadCommonValue(stream, ref dummy); } } // Boxes ushort boxesCount = stream.ReadUInt16(); for (int j = 0; j < boxesCount; j++) { var id = stream.ReadByte(); uint type = stream.ReadUInt32(); ushort connectionsCnt = stream.ReadUInt16(); ConnectionHint hint; hint.NodeB = node.ID; hint.BoxB = id; for (int k = 0; k < connectionsCnt; k++) { uint targetNodeID = stream.ReadUInt32(); byte targetBoxID = stream.ReadByte(); hint.NodeA = targetNodeID; hint.BoxA = targetBoxID; tmpHints.Add(hint); } } // Meta node.Meta.Load(engineBuild, stream); OnControlLoaded(node); } // Visject Meta _meta.Load(engineBuild, stream); // Setup connections for (int i = 0; i < tmpHints.Count; i++) { var c = tmpHints[i]; var nodeA = FindNode(c.NodeA); var nodeB = FindNode(c.NodeB); if (nodeA == null || nodeB == null) { // Error Editor.LogWarning("Invalid connected node id."); continue; } var boxA = nodeA.GetBox(c.BoxA); var boxB = nodeB.GetBox(c.BoxB); if (boxA != null && boxB != null) { boxA.Connections.Add(boxB); } } // Ending char byte end = stream.ReadByte(); if (end != '\t') { // Error throw new Exception("Invalid data."); } } }
/// <summary> /// Loads the surface from bytes. Clears the surface before and uses context source data as a surface bytes source. /// </summary> /// <remarks> /// Assume this method does not throw exceptions but uses return value as a error code. /// </remarks> /// <returns>True if failed, otherwise false.</returns> public bool Load() { try { // Prepare Clear(); Loading?.Invoke(this); // Load bytes var bytes = Context.SurfaceData; if (bytes == null) { throw new Exception("Failed to load surface data."); } // Load graph (empty bytes data means empty graph for simplicity when using subgraphs) if (bytes.Length > 0) { using (var stream = new MemoryStream(bytes)) using (var reader = new BinaryReader(stream)) { LoadGraph(reader); } } // Load surface meta var meta = _meta.GetEntry(10); if (meta.Data != null) { Utils.ByteArrayToStructure(meta.Data, out CachedSurfaceMeta); } else { // Reset view CachedSurfaceMeta.ViewCenterPosition = Vector2.Zero; CachedSurfaceMeta.Scale = 1.0f; } // Load surface comments var commentsData = _meta.GetEntry(666); if (commentsData.Data != null) { using (var stream = new MemoryStream(commentsData.Data)) using (var reader = new BinaryReader(stream)) { var commentsCount = reader.ReadInt32(); for (int i = 0; i < commentsCount; i++) { var title = Utils.ReadStr(reader, 71); var color = new Color(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); var bounds = new Rectangle(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); var comment = SpawnComment(ref bounds); if (comment == null) { throw new InvalidOperationException("Failed to create comment."); } comment.Title = title; comment.Color = color; OnControlLoaded(comment); } } } // Post load for (int i = 0; i < RootControl.Children.Count; i++) { if (RootControl.Children[i] is SurfaceControl control) { control.OnSurfaceLoaded(); } } RootControl.UnlockChildrenRecursive(); // Update boxes types for nodes that dependant box types based on incoming connections { bool keepUpdating = false; int updateLimit = 100; do { for (int i = 0; i < RootControl.Children.Count; i++) { if (RootControl.Children[i] is SurfaceNode node && !node.HasDependentBoxesSetup) { node.UpdateBoxesTypes(); keepUpdating = true; } } } while (keepUpdating && updateLimit-- > 0); } Loaded?.Invoke(this); // Clear modification flag _isModified = false; } catch (Exception ex) { // Error Editor.LogWarning("Loading Visject Surface data failed."); Editor.LogWarning(ex); return(true); } return(false); }
private void SaveGraph(BinaryWriter stream) { // IMPORTANT! This must match C++ Graph format // Changes: don't write save time and keep engine build constant // Magic Code stream.Write(1963542358); // Engine Build stream.Write(6118); // Time saved stream.Write((long)0); // Nodes count stream.Write(Nodes.Count); // Parameters count stream.Write(Parameters.Count); // For each node for (int i = 0; i < Nodes.Count; i++) { var node = Nodes[i]; // ID stream.Write(node.ID); // Type stream.Write(node.Type); } // For each param for (int i = 0; i < Parameters.Count; i++) { var param = Parameters[i]; // Properties stream.Write((byte)param.Type); stream.Write(param.ID.ToByteArray()); Utils.WriteStr(stream, param.Name, 97); stream.Write((byte)(param.IsPublic ? 1 : 0)); stream.Write((byte)(param.IsStatic ? 1 : 0)); stream.Write((byte)(param.IsUIVisible ? 1 : 0)); stream.Write((byte)(param.IsUIEditable ? 1 : 0)); // References stream.Write(param.ReferencedBy.Count); for (int j = 0; j < param.ReferencedBy.Count; j++) { stream.Write(param.ReferencedBy[j].ID); } // Value Utils.WriteCommonValue(stream, param.Value); // Meta param.Meta.Save(stream); } // For each node var boxes = _cachedBoxes.Value; boxes.Clear(); for (int i = 0; i < Nodes.Count; i++) { var node = Nodes[i]; // Values if (node.Values != null) { stream.Write(node.Values.Length); for (int j = 0; j < node.Values.Length; j++) { Utils.WriteCommonValue(stream, node.Values[j]); } } else { stream.Write(0); } // Boxes node.GetBoxes(boxes); stream.Write((ushort)boxes.Count); for (int j = 0; j < boxes.Count; j++) { var box = boxes[j]; stream.Write((byte)box.ID); stream.Write((uint)box.DefaultType); stream.Write((ushort)box.Connections.Count); for (int k = 0; k < box.Connections.Count; k++) { var targetBox = box.Connections[k]; if (targetBox == null) { throw new Exception("Missing target box."); } stream.Write(targetBox.ParentNode.ID); stream.Write((byte)targetBox.ID); } } // Meta node.Meta.Save(stream); } boxes.Clear(); // Visject Meta _meta.Save(stream); // Ending char stream.Write((byte)'\t'); }
private void LoadGraph(BinaryReader stream) { // IMPORTANT! This must match C++ Graph format // Magic Code int tmp = stream.ReadInt32(); if (tmp != 1963542358) { // Error throw new Exception("Invalid Graph format version"); } // Engine Build uint engineBuild = stream.ReadUInt32(); // Load1 { // Time saved (not used anymore to prevent binary diffs after saving unmodified surface) stream.ReadInt64(); byte[] guidBytes = new byte[16]; // Nodes count int nodesCount = stream.ReadInt32(); if (Nodes.Capacity < nodesCount) { Nodes.Capacity = nodesCount; } List <ConnectionHint> tmpHints = _cachedConnections.Value; tmpHints.Clear(); tmpHints.Capacity = Mathf.Max(tmpHints.Capacity, nodesCount * 4); // Parameters count int parametersCount = stream.ReadInt32(); if (Parameters.Capacity < parametersCount) { Parameters.Capacity = parametersCount; } // For each node for (int i = 0; i < nodesCount; i++) { // ID uint id = stream.ReadUInt32(); // Type ushort typeId = stream.ReadUInt16(); ushort groupId = stream.ReadUInt16(); // Create node var node = NodeFactory.CreateNode(_surface.NodeArchetypes, id, this, groupId, typeId); if (node == null) { // Error throw new Exception("Cannot create graph node."); } Nodes.Add(node); } // For each param for (int i = 0; i < parametersCount; i++) { // Create param var param = new SurfaceParameter(); Parameters.Add(param); // Properties param.Type = (ParameterType)stream.ReadByte(); stream.Read(guidBytes, 0, 16); param.ID = new Guid(guidBytes); param.Name = Utils.ReadStr(stream, 97); param.IsPublic = stream.ReadByte() != 0; param.IsStatic = stream.ReadByte() != 0; param.IsUIVisible = stream.ReadByte() != 0; param.IsUIEditable = stream.ReadByte() != 0; // References int refsCount = stream.ReadInt32(); param.ReferencedBy.Capacity = refsCount; for (int j = 0; j < refsCount; j++) { uint refID = stream.ReadUInt32(); var node = FindNode(refID); if (node == null) { // Error Editor.LogWarning($"Invalid node reference id (param: {param.Name}, node ref: {refID})"); } else { param.ReferencedBy.Add(node); } } // Value Utils.ReadCommonValue(stream, ref param.Value); // Meta param.Meta.Load(engineBuild, stream); } // For each node for (int i = 0; i < nodesCount; i++) { var node = Nodes[i]; // Values int valuesCnt = stream.ReadInt32(); int nodeValuesCnt = node.Values?.Length ?? 0; if (valuesCnt == nodeValuesCnt) { for (int j = 0; j < valuesCnt; j++) { // ReSharper disable once PossibleNullReferenceException Utils.ReadCommonValue(stream, ref node.Values[j]); } } else { Editor.LogWarning(String.Format("Invalid node values. Loaded: {0}, expected: {1}. Type: {2}, {3}", valuesCnt, nodeValuesCnt, node.Archetype.Title, node.Archetype.TypeID)); object dummy = null; for (int j = 0; j < valuesCnt; j++) { Utils.ReadCommonValue(stream, ref dummy); } } // Boxes ushort boxesCount = stream.ReadUInt16(); for (int j = 0; j < boxesCount; j++) { var id = stream.ReadByte(); uint type = stream.ReadUInt32(); ushort connectionsCnt = stream.ReadUInt16(); ConnectionHint hint; hint.NodeB = node; hint.BoxB = id; for (int k = 0; k < connectionsCnt; k++) { uint targetNodeID = stream.ReadUInt32(); byte targetBoxID = stream.ReadByte(); hint.NodeA = FindNode(targetNodeID); if (hint.NodeA == null) { // Error Editor.LogWarning("Invalid connected node id."); } else { hint.BoxA = targetBoxID; tmpHints.Add(hint); } } } // Meta node.Meta.Load(engineBuild, stream); OnControlLoaded(node); } // Visject Meta _meta.Load(engineBuild, stream); // Setup connections for (int i = 0; i < tmpHints.Count; i++) { var c = tmpHints[i]; var boxA = c.NodeA.GetBox(c.BoxA); var boxB = c.NodeB.GetBox(c.BoxB); if (boxA != null && boxB != null) { boxA.Connections.Add(boxB); } } // Ending char byte end = stream.ReadByte(); if (end != '\t') { // Error throw new Exception("Invalid data."); } } }
/// <summary> /// Resizes collection to the specified new size. /// </summary> /// <param name="newSize">The new size.</param> protected void Resize(int newSize) { var dictionary = Values[0] as IDictionary; var oldSize = dictionary?.Count ?? 0; if (oldSize == newSize) { return; } // Allocate new collection var type = Values.Type; var argTypes = type.GetGenericArguments(); var keyType = argTypes[0]; var valueType = argTypes[1]; var newValues = (IDictionary)Activator.CreateInstance(type); // Copy all keys/values int itemsLeft = newSize; if (dictionary != null) { foreach (var e in dictionary.Keys) { if (itemsLeft == 0) { break; } newValues[e] = dictionary[e]; itemsLeft--; } } // Insert new items (find unique keys) int newItemsLeft = newSize - oldSize; while (newItemsLeft-- > 0) { if (keyType.IsPrimitive) { long uniqueKey = 0; bool isUnique; do { isUnique = true; foreach (var e in newValues.Keys) { var asLong = Convert.ToInt64(e); if (asLong == uniqueKey) { uniqueKey++; isUnique = false; break; } } } while (!isUnique); newValues[Convert.ChangeType(uniqueKey, keyType)] = Utils.GetDefaultValue(valueType); } else if (keyType.IsEnum) { var enumValues = Enum.GetValues(keyType); int uniqueKeyIndex = 0; bool isUnique; do { isUnique = true; foreach (var e in newValues.Keys) { if (Equals(e, enumValues.GetValue(uniqueKeyIndex))) { uniqueKeyIndex++; isUnique = false; break; } } } while (!isUnique && uniqueKeyIndex < enumValues.Length); newValues[enumValues.GetValue(uniqueKeyIndex)] = Utils.GetDefaultValue(valueType); } else if (keyType == typeof(string)) { string uniqueKey = "Key"; bool isUnique; do { isUnique = true; foreach (var e in newValues.Keys) { if ((string)e == uniqueKey) { uniqueKey += "*"; isUnique = false; break; } } } while (!isUnique); newValues[uniqueKey] = Utils.GetDefaultValue(valueType); } else { throw new InvalidOperationException(); } } SetValue(newValues); }
/// <inheritdoc /> public override void Initialize(LayoutElementsContainer layout) { _readOnly = false; _canReorderItems = true; _notNullItems = false; // No support for different collections for now if (HasDifferentValues || HasDifferentTypes) { return; } var type = Values.Type; var size = Count; // Try get MemberCollectionAttribute for collection editor meta var attributes = Values.GetAttributes(); Type overrideEditorType = null; if (attributes != null) { var memberCollection = (MemberCollectionAttribute)attributes.FirstOrDefault(x => x is MemberCollectionAttribute); if (memberCollection != null) { // TODO: handle ReadOnly and NotNullItems by filtering child editors SetValue // TODO: handle CanReorderItems _readOnly = memberCollection.ReadOnly; _canReorderItems = memberCollection.CanReorderItems; _notNullItems = memberCollection.NotNullItems; overrideEditorType = Utils.GetType(memberCollection.OverrideEditorTypeName); } } // Size if (_readOnly) { layout.Label("Size", size.ToString()); } else { _size = layout.IntegerValue("Size"); _size.IntValue.MinValue = 0; _size.IntValue.MaxValue = ushort.MaxValue; _size.IntValue.Value = size; _size.IntValue.ValueChanged += OnSizeChanged; } // Elements if (size > 0) { var argTypes = type.GetGenericArguments(); var keyType = argTypes[0]; var valueType = argTypes[1]; var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType <object>(); var keys = keysEnumerable as object[] ?? keysEnumerable.ToArray(); for (int i = 0; i < size; i++) { var item = layout.CustomContainer <UniformGridPanel>(); var itemGrid = item.CustomControl; itemGrid.Height = TextBox.DefaultHeight; // TODO: make slots auto sizable instead of fixed height itemGrid.SlotsHorizontally = 2; itemGrid.SlotsVertically = 1; // Key // TODO: allow edit keys var key = keys.ElementAt(i); item.Label(key.ToString()); // Value var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; item.Object(new DictionaryValueContainer(valueType, key, Values), overrideEditor); } } _elementsCount = size; }
/// <summary> /// Resizes collection to the specified new size. /// </summary> /// <param name="newSize">The new size.</param> protected void Resize(int newSize) { var dictionary = Values[0] as IDictionary; var oldSize = dictionary?.Count ?? 0; if (oldSize != newSize) { // Allocate new collection var type = Values.Type; var argTypes = type.GetGenericArguments(); var keyType = argTypes[0]; var valueType = argTypes[1]; var newValues = (IDictionary)Activator.CreateInstance(type); // Copy all keys/values int itemsLeft = newSize; if (dictionary != null) { foreach (var e in dictionary.Keys) { if (itemsLeft == 0) { break; } newValues[e] = dictionary[e]; itemsLeft--; } } // Insert new items (find unique keys) int newItesmLeft = newSize - oldSize; while (newItesmLeft-- > 0) { if (keyType == typeof(int)) { int uniqueKey = 0; bool isUnique; do { isUnique = true; foreach (var e in newValues.Keys) { if ((int)e == uniqueKey) { uniqueKey++; isUnique = false; break; } } } while (!isUnique); newValues[uniqueKey] = Utils.GetDefaultValue(valueType); } else if (keyType == typeof(string)) { string uniqueKey = "Key"; bool isUnique; do { isUnique = true; foreach (var e in newValues.Keys) { if ((string)e == uniqueKey) { uniqueKey += "*"; isUnique = false; break; } } } while (!isUnique); newValues[uniqueKey] = Utils.GetDefaultValue(valueType); } else { throw new InvalidOperationException(); } } SetValue(newValues); } }
/// <inheritdoc /> public override void Initialize(LayoutElementsContainer layout) { _readOnly = false; _canReorderItems = true; _notNullItems = false; // No support for different collections for now if (HasDifferentValues || HasDifferentTypes) { return; } var size = Count; // Try get MemberCollectionAttribute for collection editor meta var attributes = Values.GetAttributes(); Type overrideEditorType = null; if (attributes != null) { var memberCollection = (MemberCollectionAttribute)attributes.FirstOrDefault(x => x is MemberCollectionAttribute); if (memberCollection != null) { // TODO: handle NotNullItems by filtering child editors SetValue _readOnly = memberCollection.ReadOnly; _canReorderItems = memberCollection.CanReorderItems; _notNullItems = memberCollection.NotNullItems; overrideEditorType = Utils.GetType(memberCollection.OverrideEditorTypeName); } } // Size if (_readOnly) { layout.Label("Size", size.ToString()); } else { _size = layout.IntegerValue("Size"); _size.IntValue.MinValue = 0; _size.IntValue.MaxValue = ushort.MaxValue; _size.IntValue.Value = size; _size.IntValue.ValueChanged += OnSizeChanged; } // Elements if (size > 0) { var elementType = ElementType; if (_canReorderItems) { for (int i = 0; i < size; i++) { var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; layout.Object(new CollectionItemLabel(this, i), new ListValueContainer(elementType, i, Values), overrideEditor); } } else { for (int i = 0; i < size; i++) { var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; layout.Object("Element " + i, new ListValueContainer(elementType, i, Values), overrideEditor); } } } _elementsCount = size; // Add/Remove buttons if (!_readOnly) { var area = layout.Space(20); var addButton = new Button(area.ContainerControl.Width - (16 + 16 + 2 + 2), 2, 16, 16) { Text = "+", TooltipText = "Add new item", AnchorPreset = AnchorPresets.TopRight, Parent = area.ContainerControl }; addButton.Clicked += () => { if (IsSetBlocked) { return; } Resize(Count + 1); }; var removeButton = new Button(addButton.Right + 2, addButton.Y, 16, 16) { Text = "-", TooltipText = "Remove last item", AnchorPreset = AnchorPresets.TopRight, Parent = area.ContainerControl, Enabled = size > 0 }; removeButton.Clicked += () => { if (IsSetBlocked) { return; } Resize(Count - 1); }; } }
/// <inheritdoc /> public override void Initialize(LayoutElementsContainer layout) { _readOnly = false; _notNullItems = false; // No support for different collections for now if (HasDifferentValues || HasDifferentTypes) { return; } var type = Values.Type; var size = Count; var argTypes = type.GetGenericArguments(); var keyType = argTypes[0]; var valueType = argTypes[1]; _canEditKeys = keyType == typeof(string) || keyType.IsPrimitive || keyType.IsEnum; // Try get CollectionAttribute for collection editor meta var attributes = Values.GetAttributes(); Type overrideEditorType = null; if (attributes != null) { var collection = (CollectionAttribute)attributes.FirstOrDefault(x => x is CollectionAttribute); if (collection != null) { // TODO: handle ReadOnly and NotNullItems by filtering child editors SetValue _readOnly = collection.ReadOnly; _notNullItems = collection.NotNullItems; overrideEditorType = Utils.GetType(collection.OverrideEditorTypeName); } } // Size if (_readOnly || !_canEditKeys) { layout.Label("Size", size.ToString()); } else { _size = layout.IntegerValue("Size"); _size.IntValue.MinValue = 0; _size.IntValue.MaxValue = ushort.MaxValue; _size.IntValue.Value = size; _size.IntValue.ValueChanged += OnSizeChanged; } // Elements if (size > 0) { var keysEnumerable = ((IDictionary)Values[0]).Keys.OfType <object>(); var keys = keysEnumerable as object[] ?? keysEnumerable.ToArray(); for (int i = 0; i < size; i++) { var key = keys.ElementAt(i); var overrideEditor = overrideEditorType != null ? (CustomEditor)Activator.CreateInstance(overrideEditorType) : null; layout.Object(new DictionaryItemLabel(this, key), new DictionaryValueContainer(valueType, key, Values), overrideEditor); } } _elementsCount = size; // Add/Remove buttons if (!_readOnly && _canEditKeys) { var area = layout.Space(20); var addButton = new Button(area.ContainerControl.Width - (16 + 16 + 2 + 2), 2, 16, 16) { Text = "+", TooltipText = "Add new item", AnchorPreset = AnchorPresets.TopRight, Parent = area.ContainerControl }; addButton.Clicked += () => { if (IsSetBlocked) { return; } Resize(Count + 1); }; var removeButton = new Button(addButton.Right + 2, addButton.Y, 16, 16) { Text = "-", TooltipText = "Remove last item", AnchorPreset = AnchorPresets.TopRight, Parent = area.ContainerControl, Enabled = size > 0 }; removeButton.Clicked += () => { if (IsSetBlocked) { return; } Resize(Count - 1); }; } }