Example #1
0
        /// <summary>
        /// Takes a list of nodes and generates unique names for them. Returns a list of node + name pairs.
        /// The names are chosen to be descriptive and usable in code generation.
        /// </summary>
        /// <returns>A lot of node + name pairs usable in code generation.</returns>
        public static IEnumerable <(TNode, string)> GenerateNodeNames(IEnumerable <TNode> nodes)
        {
            var nodesByName = new Dictionary <NodeName, List <TNode> >();

            foreach (var node in nodes)
            {
                // Generate descriptive name for each node. The name is generated based on its type
                // and properties to give as much information about the node as possible, so that
                // a specific node can be identified in the composition.
                var nodeName = node.Type switch
                {
                    Graph.NodeType.CompositionObject => NameCompositionObject(node, (CompositionObject)node.Object),
                    Graph.NodeType.CompositionPath => NodeName.FromNonTypeName("Path"),
                    Graph.NodeType.CanvasGeometry => NodeName.FromNonTypeName("Geometry"),
                    Graph.NodeType.LoadedImageSurface => NameLoadedImageSurface(node, (LoadedImageSurface)node.Object),
                    _ => throw Unreachable,
                };

                if (!nodesByName.TryGetValue(nodeName, out var nodeList))
                {
                    nodeList = new List <TNode>();
                    nodesByName.Add(nodeName, nodeList);
                }

                nodeList.Add(node);
            }

            // Set the names on each node.
            foreach (var entry in nodesByName)
            {
                var nodeName = entry.Key;
                var nodeList = entry.Value;

                // Append a counter suffix.
                // NOTE: For C# there is no need for a suffix if there is only one node with this name,
                //       however this can break C++ which cannot distinguish between a method name and
                //       a type name. For example, if a single CompositionPath node produced a method
                //       called CompositionPath() and then a call was made to "new CompositionPath(...)"
                //       the C++ compiler will complain that CompositionPath is not a type.
                //       So to ensure we don't hit that case, append a counter suffix, unless the name
                //       is known to not be a type name.
                if (nodeList.Count == 1 && nodeName.IsNotATypeName)
                {
                    // The name is unique and is not a type name, so no need for a suffix.
                    yield return(nodeList[0], nodeName.Name);
                }
                else
                {
                    // Use only as many digits as necessary to express the largest count.
                    var digitsRequired = (int)Math.Ceiling(Math.Log10(nodeList.Count));
                    var counterFormat  = new string('0', digitsRequired);

                    for (var i = 0; i < nodeList.Count; i++)
                    {
                        yield return(nodeList[i], $"{nodeName.Name}_{i.ToString(counterFormat)}");
                    }
                }
            }
        }
Example #2
0
        static NodeName NameCompositionObject(TNode node, CompositionObject obj)
        {
            var name = NameOf(obj);

            if (name != null)
            {
                // The object has a name, so use it.
                return(NodeName.FromNonTypeName(name));
            }

            return(obj.Type switch
            {
                // For some animations, we can include a description of the start and end values
                // to make the names more descriptive.
                CompositionObjectType.ColorKeyFrameAnimation
                => NodeName.FromNameAndDescription("ColorAnimation", DescribeAnimationRange((ColorKeyFrameAnimation)obj)),
                CompositionObjectType.ScalarKeyFrameAnimation
                => NodeName.FromNameAndDescription($"{TryGetAnimatedPropertyName(node)}ScalarAnimation", DescribeAnimationRange((ScalarKeyFrameAnimation)obj)),

                // Do not include descriptions of the animation range for vectors - the names
                // end up being very long, complicated, and confusing to the reader.
                CompositionObjectType.Vector2KeyFrameAnimation => NodeName.FromNonTypeName($"{TryGetAnimatedPropertyName(node)}Vector2Animation"),
                CompositionObjectType.Vector3KeyFrameAnimation => NodeName.FromNonTypeName($"{TryGetAnimatedPropertyName(node)}Vector3Animation"),
                CompositionObjectType.Vector4KeyFrameAnimation => NodeName.FromNonTypeName($"{TryGetAnimatedPropertyName(node)}Vector4Animation"),

                // Boolean animations don't have interesting range descriptions, but their property name
                // is helpful to know (it is typically "IsVisible").
                CompositionObjectType.BooleanKeyFrameAnimation => NodeName.FromNonTypeName($"{TryGetAnimatedPropertyName(node)}BooleanAnimation"),

                // Geometries include their size as part of the description.
                CompositionObjectType.CompositionRectangleGeometry
                => NodeName.FromNameAndDescription("Rectangle", Vector2AsId(((CompositionRectangleGeometry)obj).Size)),
                CompositionObjectType.CompositionRoundedRectangleGeometry
                => NodeName.FromNameAndDescription("RoundedRectangle", Vector2AsId(((CompositionRoundedRectangleGeometry)obj).Size)),
                CompositionObjectType.CompositionEllipseGeometry
                => NodeName.FromNameAndDescription("Ellipse", Vector2AsId(((CompositionEllipseGeometry)obj).Radius)),

                CompositionObjectType.ExpressionAnimation => NameExpressionAnimation((ExpressionAnimation)obj),
                CompositionObjectType.CompositionColorBrush => NameCompositionColorBrush((CompositionColorBrush)obj),
                CompositionObjectType.CompositionColorGradientStop => NameCompositionColorGradientStop((CompositionColorGradientStop)obj),
                CompositionObjectType.StepEasingFunction => NameStepEasingFunction((StepEasingFunction)obj),

                _ => NameCompositionObjectType(obj.Type),
            });
Example #3
0
        public static IEnumerable <(TNode, string)> GenerateNodeNames(IEnumerable <TNode> nodes)
        {
            var nodesByName = new Dictionary <NodeName, List <TNode> >();

            foreach (var node in nodes)
            {
                // Generate descriptive name for each node. The name is generated based on its type
                // and properties to give as much information about the node as possible, so that
                // a specific node can be identified in the composition.
                var nodeName = node.Type switch
                {
                    Graph.NodeType.CompositionObject => NameCompositionObject(node, (CompositionObject)node.Object),
                    Graph.NodeType.CompositionPath => NodeName.FromNonTypeName("Path"),
                    Graph.NodeType.CanvasGeometry => NodeName.FromNonTypeName("Geometry"),
                    Graph.NodeType.LoadedImageSurface => NameLoadedImageSurface(node, (LoadedImageSurface)node.Object),
                    _ => throw Unreachable,
                };

                if (!nodesByName.TryGetValue(nodeName, out var nodeList))
                {
                    nodeList = new List <TNode>();
                    nodesByName.Add(nodeName, nodeList);
                }

                nodeList.Add(node);
            }

            // Set the names on each node.
            var uniqueNames = new HashSet <string>();

            // First deal with names that we know are unique.
            foreach (var(nodeName, nodeList) in nodesByName)
            {
                // NOTE: For C# there is no need for a suffix if there is only one node with this name,
                //       however this can break C++ which cannot distinguish between a method name and
                //       a type name. For example, if a single CompositionPath node produced a method
                //       called CompositionPath() and then a call was made to "new CompositionPath(...)"
                //       the C++ compiler will complain that CompositionPath is not a type.
                //       So to ensure we don't hit that case, append a counter suffix, unless the name
                //       is known to not be a type name.
                if (nodeList.Count == 1 && nodeName.IsNotATypeName)
                {
                    // The name is unique and is not a type name, so no need for a suffix.
                    var name = nodeName.Name;
                    uniqueNames.Add(name);
                    yield return(nodeList[0], name);
                }
            }

            // Now deal with the names that are not unique by appending a counter suffix.
            foreach (var(nodeName, nodeList) in nodesByName)
            {
                if (nodeList.Count > 1 || !nodeName.IsNotATypeName)
                {
                    // Use only as many digits as necessary to express the largest count.
                    var digitsRequired = (int)Math.Ceiling(Math.Log10(nodeList.Count));
                    var counterFormat  = new string('0', digitsRequired);

                    var suffixOffset = 0;
                    for (var i = 0; i < nodeList.Count; i++)
                    {
                        // Create a unique name by appending a suffix.
                        // If the name already exists then increment the suffix until a unique
                        // name is found. This is necessary to deal with collisions with the
                        // names that were known to be unique but that have names that look
                        // like they have counter suffixes, for example Rectangle_15 could
                        // be a 15x15 rectangle, or it could be the 15th rectangle with an
                        // animated size.
                        string name;
                        while (true)
                        {
                            var counter = i + suffixOffset;
                            name = $"{nodeName.Name}_{counter.ToString(counterFormat)}";
                            if (uniqueNames.Add(name))
                            {
                                // The name was unique.
                                break;
                            }

                            // Try the next suffix value.
                            suffixOffset++;
                        }

                        yield return(nodeList[i], name);
                    }
                }
            }
        }