rendernodeparser: Add support for reusing nodes
authorBenjamin Otte <otte@redhat.com>
Tue, 28 Mar 2023 21:58:50 +0000 (23:58 +0200)
committerBenjamin Otte <otte@redhat.com>
Wed, 29 Mar 2023 01:53:52 +0000 (03:53 +0200)
We extend the syntax for nodes from:
  <node-type> { ... }
to
  <node-type> { ... }
  <node-type> <string> { ... }
  <string>;
where the first is the same as before, the 2nd defines a named node and
the last references a previously defined node.
Or to give an example:
  color "node" {
    bounds: 0 0 10 10;
    color: red;
  }
  transform {
    bounds: 20 0 10 10;
    child: "node";
  }
This will draw the red box twice, once at (0,0) and once at
(20,0).

The intended use for this is both shortening generated node files as
well as allowing to write tests that reuse nodes, in particular when
dealing with caches.

gsk/gskrendernodeparser.c

index c74d16660a4a0e82df57badf784cce31ef8397e3..07825fd56e3c80e6e86d252eb88712c3e20011ff 100644 (file)
@@ -2136,19 +2136,60 @@ parse_node (GtkCssParser *parser,
     { "mask", parse_mask_node },
   };
   GskRenderNode **node_p = out_node;
+  const GtkCssToken *token;
   guint i;
 
+  token = gtk_css_parser_get_token (parser);
+  if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_STRING))
+  if (gtk_css_token_is (token, GTK_CSS_TOKEN_STRING))
+    {
+      GskRenderNode *node;
+      char *node_name;
+
+      node_name = gtk_css_parser_consume_string (parser);
+
+      if (context->named_nodes)
+        node = g_hash_table_lookup (context->named_nodes, node_name);
+      else
+        node = NULL;
+
+      if (node)
+        {
+          *node_p = gsk_render_node_ref (node);
+          g_free (node_name);
+          return TRUE;
+        }
+      else
+        {
+          gtk_css_parser_error_value (parser, "No node named \"%s\"", node_name);
+          g_free (node_name);
+          return FALSE;
+        }
+    }
+
   for (i = 0; i < G_N_ELEMENTS (node_parsers); i++)
     {
       if (gtk_css_parser_try_ident (parser, node_parsers[i].name))
         {
           GskRenderNode *node;
+          GtkCssLocation node_name_start_location, node_name_end_location;
+          char *node_name;
+
+          if (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_STRING))
+            {
+              node_name_start_location = *gtk_css_parser_get_start_location (parser);
+              node_name_end_location = *gtk_css_parser_get_end_location (parser);
+              node_name = gtk_css_parser_consume_string (parser);
+            }
+          else
+            node_name = NULL;
 
           if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
             {
               gtk_css_parser_error_syntax (parser, "Expected '{' after node name");
               return FALSE;
             }
+
           gtk_css_parser_end_block_prelude (parser);
           node = node_parsers[i].func (parser, context);
           if (node)
@@ -2156,9 +2197,31 @@ parse_node (GtkCssParser *parser,
               if (!gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF))
                 gtk_css_parser_error_syntax (parser, "Expected '}' at end of node definition");
               g_clear_pointer (node_p, gsk_render_node_unref);
+
+              if (node_name)
+                {
+                  if (context->named_nodes == NULL)
+                    context->named_nodes = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                                  g_free, (GDestroyNotify) gsk_render_node_unref);
+                  if (g_hash_table_lookup (context->named_nodes, node_name))
+                    {
+                      gtk_css_parser_error (parser,
+                                            GTK_CSS_PARSER_ERROR_FAILED,
+                                            &node_name_start_location,
+                                            &node_name_end_location,
+                                            "A node named \"%s\" already exists.", node_name);
+                    }
+                  else
+                    {
+                      g_hash_table_insert (context->named_nodes, g_strdup (node_name), gsk_render_node_ref (node));
+                    }
+                }
+
               *node_p = node;
             }
 
+          g_free (node_name);
+
           return node != NULL;
         }
     }