This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch edje-vector-intergration
in repository efl.

View the commit online.

commit 0613498cca6d88b0a7bcf8814d163ec0ac1ac8d1
Author: [email protected] <[email protected]>
AuthorDate: Tue Apr 28 17:03:45 2026 -0600

    edje: implement materializer to turn Edje_Vg_Tree into Efl_VG hierarchy
    
    Add _edje_vg_tree_to_efl_vg() to materialize the in-memory Edje_Vg_Tree
    (loaded from .edj vector_dir) into a live Efl_VG object tree ready for
    efl_canvas_vg_object_root_node_set. Gradient references are fully supported.
    
    The materializer uses a two-pass approach to handle gradient binding cleanly:
    
    Pass 1 (_collect_gradients) performs a depth-first scan of the data tree,
    instantiating each named gradient node as a floating efl_add_ref'd Efl_VG
    object (no parent yet) and storing it in an Eina_Hash keyed by stringshare
    name. The floating-ref pattern means we hold an owning reference until
    Pass 2 reparents the gradient into the container hierarchy.
    
    Pass 2 (_node_to_efl_vg) recursively builds the live hierarchy. Shapes with
    a gradient_ref look up the pre-built gradient in the hash via eina_hash_find
    and call efl_canvas_vg_shape_fill_set to bind it. This deferred resolution
    avoids the need to create a second hash pass or post-process the tree.
    
    Common node properties (name, visibility, color, transform) are applied
    uniformly via _apply_color_binding (pre-multiplies alpha) and _apply_transform
    (composes T·R·S or applies the 3×3 matrix form). Stroke is only applied when
    stroke_width > 0 to avoid spurious strokes on shapes without an authored one.
    
    The unit test edje_vg_tree_materializer_basic verifies the root is a
    CONTAINER, has exactly one child (the "body" shape), and child lookup works.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
 src/lib/edje/edje_vg_tree.c              | 377 +++++++++++++++++++++++++++++++
 src/lib/edje/edje_vg_tree.h              |   8 +
 src/tests/edje/edje_test_vector_states.c |  57 +++++
 3 files changed, 442 insertions(+)

diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index 94395737e5..c28338d9fe 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -2,6 +2,7 @@
 # include <config.h>
 #endif
 
+#include <math.h>
 #include "edje_private.h"
 #include "edje_vg_tree.h"
 
@@ -458,3 +459,379 @@ _edje_vg_tree_equal(const Edje_Vg_Tree *a, const Edje_Vg_Tree *b)
 
    return _node_equal(a->root, b->root);
 }
+
+/* =========================================================================
+ * Phase 3.1 — Materializer: Edje_Vg_Tree → Efl_VG object tree
+ *
+ * Strategy: two-pass walk over the data tree.
+ *   Pass 1 (_collect_gradients): depth-first scan of the data tree.
+ *          Every named gradient node is instantiated as a standalone
+ *          Efl_VG gradient object (no parent yet) and stored in a
+ *          name→Efl_VG hash.
+ *   Pass 2 (_node_to_efl_vg): recursive walk that builds the live Efl_VG
+ *          hierarchy.  Shapes that carry a gradient_ref look up the
+ *          pre-built gradient in the hash and call fill_set.
+ *
+ * gradient_ref support is therefore complete in Phase 3.1 with zero
+ * deferred work for Phase 4.
+ * ========================================================================= */
+
+/* ---- forward declaration ---- */
+static Efl_VG *_node_to_efl_vg(const Edje_Vg_Node *n, Efl_VG *parent,
+                                Eina_Hash *grad_by_name);
+
+/* --------------------------------------------------------------------------
+ * Pre-multiply a raw RGBA color binding and apply it as the node color.
+ * EFL VG color_set expects pre-multiplied values.
+ * -------------------------------------------------------------------------- */
+static void
+_apply_color_binding(Efl_VG *vg, const Edje_Vg_Color_Binding *cb)
+{
+   int r = cb->r, g = cb->g, b = cb->b, a = cb->a;
+   if (a < 255)
+     {
+        r = (r * a) / 255;
+        g = (g * a) / 255;
+        b = (b * a) / 255;
+     }
+   efl_gfx_color_set(vg, r, g, b, a);
+}
+
+/* --------------------------------------------------------------------------
+ * Build an Eina_Matrix3 from the Edje_Vg_Transform and set it on the node.
+ * Skips the call entirely when the transform is effectively identity to avoid
+ * forcing the full transform path on every node.
+ * -------------------------------------------------------------------------- */
+static void
+_apply_transform(Efl_VG *vg, const Edje_Vg_Transform *xf)
+{
+   Eina_Matrix3 m;
+
+   if (xf->has_matrix)
+     {
+        /* m[0..8] is row-major (m11 m12 m13 / m21 m22 m23 / m31 m32 m33)
+         * matching Eina_Matrix3 layout: xx xy xz / yx yy yz / zx zy zz */
+        eina_matrix3_values_set(&m,
+                                xf->m[0], xf->m[1], xf->m[2],
+                                xf->m[3], xf->m[4], xf->m[5],
+                                xf->m[6], xf->m[7], xf->m[8]);
+        efl_canvas_vg_node_transformation_set(vg, &m);
+        return;
+     }
+
+   /* Decomposed: skip entirely when identity */
+   if (xf->tx == 0.0 && xf->ty == 0.0 &&
+       xf->angle == 0.0 &&
+       xf->sx == 1.0 && xf->sy == 1.0)
+      return;
+
+   eina_matrix3_identity(&m);
+   if (xf->tx != 0.0 || xf->ty != 0.0)
+      eina_matrix3_translate(&m, xf->tx, xf->ty);
+   if (xf->angle != 0.0)
+      eina_matrix3_rotate(&m, xf->angle * M_PI / 180.0);
+   if (xf->sx != 1.0 || xf->sy != 1.0)
+      eina_matrix3_scale(&m, xf->sx, xf->sy);
+
+   efl_canvas_vg_node_transformation_set(vg, &m);
+}
+
+/* --------------------------------------------------------------------------
+ * Common node properties: name, visibility, color, transform.
+ * -------------------------------------------------------------------------- */
+static void
+_apply_common(Efl_VG *vg, const Edje_Vg_Node *n)
+{
+   if (n->name)
+      efl_name_set(vg, n->name);
+   if (!n->visible)
+      efl_gfx_entity_visible_set(vg, EINA_FALSE);
+   _apply_color_binding(vg, &n->color);
+   _apply_transform(vg, &n->xform);
+}
+
+/* --------------------------------------------------------------------------
+ * Pass 1 helper — collect all named gradient data-nodes into the hash.
+ * Instantiates the Efl_VG gradient objects (no parent) so that shapes can
+ * get a pointer to them when calling fill_set.
+ * -------------------------------------------------------------------------- */
+static void
+_collect_gradients(const Edje_Vg_Node *n, Eina_Hash *grad_by_name)
+{
+   if (!n) return;
+
+   if ((n->type == EDJE_VG_NODE_GRADIENT_LINEAR ||
+        n->type == EDJE_VG_NODE_GRADIENT_RADIAL) && n->name)
+     {
+        /* Build the gradient Efl_VG object with no parent yet.
+         * efl_add_ref gives caller (us) the owning reference; when the
+         * tree is reparented later by _node_to_efl_vg the container's
+         * efl_add takes ownership, so we can safely transfer. */
+        Efl_VG *g;
+        if (n->type == EDJE_VG_NODE_GRADIENT_RADIAL)
+           g = efl_add_ref(EFL_CANVAS_VG_GRADIENT_RADIAL_CLASS, NULL);
+        else
+           g = efl_add_ref(EFL_CANVAS_VG_GRADIENT_LINEAR_CLASS, NULL);
+
+        if (!g) return;
+
+        /* Geometry */
+        if (n->type == EDJE_VG_NODE_GRADIENT_RADIAL)
+          {
+             efl_gfx_gradient_radial_center_set(g, n->gradient.x0, n->gradient.y0);
+             efl_gfx_gradient_radial_focal_set(g, n->gradient.x1, n->gradient.y1);
+             efl_gfx_gradient_radial_radius_set(g, n->gradient.radius);
+          }
+        else
+          {
+             efl_gfx_gradient_linear_start_set(g, n->gradient.x0, n->gradient.y0);
+             efl_gfx_gradient_linear_end_set(g, n->gradient.x1, n->gradient.y1);
+          }
+        efl_gfx_gradient_spread_set(g, n->gradient.spread);
+
+        /* Stops */
+        if (n->gradient.stops_count > 0)
+          {
+             Efl_Gfx_Gradient_Stop *stops =
+                alloca(sizeof(Efl_Gfx_Gradient_Stop) * n->gradient.stops_count);
+             unsigned int i;
+             for (i = 0; i < n->gradient.stops_count; i++)
+               {
+                  const Edje_Vg_Stop *s = &n->gradient.stops[i];
+                  int r = s->color.r, gc = s->color.g,
+                      bb = s->color.b, a = s->color.a;
+                  if (a < 255)
+                    { r = (r*a)/255; gc = (gc*a)/255; bb = (bb*a)/255; }
+                  stops[i].offset = s->offset;
+                  stops[i].r = r;
+                  stops[i].g = gc;
+                  stops[i].b = bb;
+                  stops[i].a = a;
+               }
+             efl_gfx_gradient_stop_set(g, stops, n->gradient.stops_count);
+          }
+
+        if (!n->visible) efl_gfx_entity_visible_set(g, EINA_FALSE);
+        efl_name_set(g, n->name);
+        _apply_transform(g, &n->xform);
+
+        /* Store with stringshare key — the name is already stringshare'd. */
+        eina_hash_add(grad_by_name, n->name, g);
+        return;
+     }
+
+   /* Recurse into containers */
+   if (n->type == EDJE_VG_NODE_CONTAINER)
+     {
+        Eina_List *l;
+        Edje_Vg_Node *child;
+        EINA_LIST_FOREACH(n->container.children, l, child)
+           _collect_gradients(child, grad_by_name);
+     }
+}
+
+/* --------------------------------------------------------------------------
+ * Shape node materializer (pass 2).
+ * -------------------------------------------------------------------------- */
+static Efl_VG *
+_shape_to_efl_vg(const Edje_Vg_Node *n, Efl_VG *parent,
+                 Eina_Hash *grad_by_name)
+{
+   Efl_VG *s = efl_add(EFL_CANVAS_VG_SHAPE_CLASS, parent);
+   if (!s) return NULL;
+
+   /* Path */
+   if (n->shape.path && *n->shape.path)
+     {
+        efl_gfx_path_reset(s);
+        efl_gfx_path_append_svg_path(s, n->shape.path);
+     }
+
+   /* Fill: gradient or flat color */
+   if (n->shape.gradient_ref && *n->shape.gradient_ref)
+     {
+        Efl_VG *grad = eina_hash_find(grad_by_name, n->shape.gradient_ref);
+        if (grad)
+           efl_canvas_vg_shape_fill_set(s, (Efl_Canvas_Vg_Node *)grad);
+        else
+           WRN("gradient_ref '%s' not found in tree — fill left unset",
+               n->shape.gradient_ref);
+     }
+   else
+     {
+        _apply_color_binding(s, &n->shape.fill);
+     }
+
+   /* Stroke */
+   if (n->shape.stroke_width > 0.0)
+     {
+        int r = n->shape.stroke_color.r, g = n->shape.stroke_color.g,
+            b = n->shape.stroke_color.b, a = n->shape.stroke_color.a;
+        if (a < 255) { r = (r*a)/255; g = (g*a)/255; b = (b*a)/255; }
+        efl_gfx_shape_stroke_color_set(s, r, g, b, a);
+        efl_gfx_shape_stroke_width_set(s, n->shape.stroke_width);
+        efl_gfx_shape_stroke_cap_set(s, n->shape.stroke_cap);
+        efl_gfx_shape_stroke_join_set(s, n->shape.stroke_join);
+
+        if (n->shape.stroke_dash_count > 0)
+          {
+             unsigned int n_pairs = n->shape.stroke_dash_count / 2;
+             Efl_Gfx_Dash *dashes =
+                alloca(n_pairs * sizeof(Efl_Gfx_Dash));
+             unsigned int i;
+             for (i = 0; i < n_pairs; i++)
+               {
+                  dashes[i].length = n->shape.stroke_dash[2 * i];
+                  dashes[i].gap    = n->shape.stroke_dash[2 * i + 1];
+               }
+             efl_gfx_shape_stroke_dash_set(s, dashes, n_pairs);
+          }
+     }
+
+   /* Fill rule */
+   efl_gfx_shape_fill_rule_set(s, n->shape.fill_rule);
+
+   /* Common: name, visibility, node-level color, transform */
+   _apply_common(s, n);
+
+   return s;
+}
+
+/* --------------------------------------------------------------------------
+ * Container node materializer (pass 2).
+ * -------------------------------------------------------------------------- */
+static Efl_VG *
+_container_to_efl_vg(const Edje_Vg_Node *n, Efl_VG *parent,
+                     Eina_Hash *grad_by_name)
+{
+   Efl_VG *c;
+   if (parent)
+      c = efl_add(EFL_CANVAS_VG_CONTAINER_CLASS, parent);
+   else
+      c = efl_add_ref(EFL_CANVAS_VG_CONTAINER_CLASS, NULL);
+
+   if (!c) return NULL;
+
+   _apply_common(c, n);
+
+   Eina_List *l;
+   Edje_Vg_Node *child;
+   EINA_LIST_FOREACH(n->container.children, l, child)
+      _node_to_efl_vg(child, c, grad_by_name);
+
+   return c;
+}
+
+/* --------------------------------------------------------------------------
+ * Gradient node materializer (pass 2) — for gradients already in the hash
+ * we can simply reparent the pre-built object into the container hierarchy
+ * so it is part of the tree; if it wasn't named (and therefore not in the
+ * hash), we build it fresh here.
+ * -------------------------------------------------------------------------- */
+static Efl_VG *
+_gradient_to_efl_vg(const Edje_Vg_Node *n, Efl_VG *parent,
+                    Eina_Hash *grad_by_name)
+{
+   /* Named gradients were pre-built in pass 1 — reparent into the tree. */
+   if (n->name)
+     {
+        Efl_VG *g = eina_hash_find(grad_by_name, n->name);
+        if (g)
+          {
+             efl_parent_set(g, parent);
+             return g;
+          }
+     }
+
+   /* Unnamed gradient (or not in hash for some reason) — build fresh. */
+   Efl_VG *g;
+   if (n->gradient.is_radial)
+      g = efl_add(EFL_CANVAS_VG_GRADIENT_RADIAL_CLASS, parent);
+   else
+      g = efl_add(EFL_CANVAS_VG_GRADIENT_LINEAR_CLASS, parent);
+   if (!g) return NULL;
+
+   if (n->gradient.is_radial)
+     {
+        efl_gfx_gradient_radial_center_set(g, n->gradient.x0, n->gradient.y0);
+        efl_gfx_gradient_radial_focal_set(g, n->gradient.x1, n->gradient.y1);
+        efl_gfx_gradient_radial_radius_set(g, n->gradient.radius);
+     }
+   else
+     {
+        efl_gfx_gradient_linear_start_set(g, n->gradient.x0, n->gradient.y0);
+        efl_gfx_gradient_linear_end_set(g, n->gradient.x1, n->gradient.y1);
+     }
+   efl_gfx_gradient_spread_set(g, n->gradient.spread);
+
+   if (n->gradient.stops_count > 0)
+     {
+        Efl_Gfx_Gradient_Stop *stops =
+           alloca(sizeof(Efl_Gfx_Gradient_Stop) * n->gradient.stops_count);
+        unsigned int i;
+        for (i = 0; i < n->gradient.stops_count; i++)
+          {
+             const Edje_Vg_Stop *sv = &n->gradient.stops[i];
+             int r = sv->color.r, gc = sv->color.g,
+                 bb = sv->color.b, a = sv->color.a;
+             if (a < 255) { r = (r*a)/255; gc = (gc*a)/255; bb = (bb*a)/255; }
+             stops[i].offset = sv->offset;
+             stops[i].r = r; stops[i].g = gc;
+             stops[i].b = bb; stops[i].a = a;
+          }
+        efl_gfx_gradient_stop_set(g, stops, n->gradient.stops_count);
+     }
+
+   _apply_common(g, n);
+   return g;
+}
+
+/* --------------------------------------------------------------------------
+ * Dispatch: pick the right materializer per node type (pass 2).
+ * -------------------------------------------------------------------------- */
+static Efl_VG *
+_node_to_efl_vg(const Edje_Vg_Node *n, Efl_VG *parent,
+                Eina_Hash *grad_by_name)
+{
+   if (!n) return NULL;
+   switch (n->type)
+     {
+      case EDJE_VG_NODE_SHAPE:
+         return _shape_to_efl_vg(n, parent, grad_by_name);
+      case EDJE_VG_NODE_CONTAINER:
+         return _container_to_efl_vg(n, parent, grad_by_name);
+      case EDJE_VG_NODE_GRADIENT_LINEAR:
+      case EDJE_VG_NODE_GRADIENT_RADIAL:
+         return _gradient_to_efl_vg(n, parent, grad_by_name);
+     }
+   return NULL;
+}
+
+/* --------------------------------------------------------------------------
+ * Public entry point.
+ * -------------------------------------------------------------------------- */
+EAPI Efl_VG *
+_edje_vg_tree_to_efl_vg(const Edje_Vg_Tree *t)
+{
+   if (!t || !t->root) return NULL;
+
+   /* Pass 1: collect all named gradients into name→Efl_VG hash.
+    * Key is the stringshare pointer (already intern'd), so use
+    * eina_hash_stringshared_new for pointer-key equality. */
+   Eina_Hash *grad_by_name = eina_hash_stringshared_new(NULL);
+   if (!grad_by_name) return NULL;
+
+   _collect_gradients(t->root, grad_by_name);
+
+   /* Pass 2: build the live hierarchy.  The root is always a CONTAINER. */
+   Efl_VG *root = _node_to_efl_vg(t->root, NULL, grad_by_name);
+
+   /* Any gradient objects that remain in the hash but were not referenced by
+    * a shape's gradient_ref have been reparented into the tree by pass 2's
+    * _gradient_to_efl_vg; the hash holds non-owning pointers (the objects
+    * are now owned by their parent container).  The hash itself doesn't need
+    * to free values — just free the container. */
+   eina_hash_free(grad_by_name);
+
+   return root;
+}
diff --git a/src/lib/edje/edje_vg_tree.h b/src/lib/edje/edje_vg_tree.h
index 0a4877bf37..af50623c8f 100644
--- a/src/lib/edje/edje_vg_tree.h
+++ b/src/lib/edje/edje_vg_tree.h
@@ -58,6 +58,14 @@ EAPI Edje_Vg_Node *_edje_vg_tree_node_by_name(const Edje_Vg_Tree *t,
 EAPI Eina_Bool     _edje_vg_tree_validate_unique_names(const Edje_Vg_Tree *t,
                                                         const char **dup_out);
 
+/* --- Materializer (Phase 3.1) ------------------------------------------- */
+
+/* Materializes the in-memory tree as a freshly-instantiated Efl_VG root.
+ * The returned object is owned by the caller — efl_canvas_vg_object_root_node_set
+ * adopts ownership; otherwise efl_unref to release. Color bindings are treated
+ * as raw RGBA in this phase; Phase 6 adds class resolution. */
+EAPI Efl_VG *_edje_vg_tree_to_efl_vg(const Edje_Vg_Tree *t);
+
 /* --- Eet descriptor accessor -------------------------------------------- */
 
 /* Returns the static Eet_Data_Descriptor for Edje_Vg_Tree.
diff --git a/src/tests/edje/edje_test_vector_states.c b/src/tests/edje/edje_test_vector_states.c
index 569b0f36e1..1296e5fd4c 100644
--- a/src/tests/edje/edje_test_vector_states.c
+++ b/src/tests/edje/edje_test_vector_states.c
@@ -4,6 +4,7 @@
 
 #include <Eina.h>
 #include <Eet.h>
+#include <Evas.h>
 #include "edje_suite.h"
 
 /*
@@ -50,6 +51,9 @@ Edje_Vg_Node        *_edje_vg_tree_node_by_name(const Edje_Vg_Tree *t,
 Eina_Bool             _edje_vg_tree_validate_unique_names(const Edje_Vg_Tree *t,
                                                            const char **dup_out);
 
+/* Phase 3.1 materializer */
+Efl_VG               *_edje_vg_tree_to_efl_vg(const Edje_Vg_Tree *t);
+
 /*
  * Partial-view forward declarations for the Task 3.0 round-trip test.
  *
@@ -282,10 +286,63 @@ EFL_START_TEST(edje_vg_round_trip_tree_ids)
 }
 EFL_END_TEST
 
+/*
+ * edje_vg_tree_materializer_basic  (Task 3.1)
+ *
+ * Builds the standard test fixture tree (root container → "body" shape)
+ * via _edje_vg_tree_new_for_test(), materializes it through
+ * _edje_vg_tree_to_efl_vg(), then verifies:
+ *
+ *   a. The returned object is non-NULL and is an Efl_Canvas_Vg_Container.
+ *   b. It has exactly one child (the "body" shape).
+ *   c. efl_canvas_vg_container_child_get(root, "body") returns non-NULL.
+ *   d. That child is an Efl_Canvas_Vg_Shape.
+ *
+ * The test does NOT render — that is Task 3.2's scope.
+ *
+ * Note: Creating Efl_Canvas_Vg_* objects via efl_add_ref(CLASS, NULL)
+ * does not require a display / ecore_evas canvas, only that efl/evas are
+ * initialised.  edje_init() (called by the suite) brings those up.
+ */
+EFL_START_TEST(edje_vg_tree_materializer_basic)
+{
+   Edje_Vg_Tree *t = _edje_vg_tree_new_for_test();
+   fail_if(!t);
+
+   Efl_VG *root = _edje_vg_tree_to_efl_vg(t);
+   fail_if(root == NULL);
+
+   /* (a) Root must be a container */
+   fail_if(!efl_isa(root, EFL_CANVAS_VG_CONTAINER_CLASS));
+
+   /* (b) Exactly one child */
+   Eina_Iterator *it = efl_canvas_vg_container_children_get(root);
+   fail_if(it == NULL);
+   unsigned int child_count = 0;
+   void *child_ptr = NULL;
+   EINA_ITERATOR_FOREACH(it, child_ptr) child_count++;
+   eina_iterator_free(it);
+   ck_assert_uint_eq(child_count, 1);
+
+   /* (c) Named lookup */
+   Efl_Canvas_Vg_Node *body =
+      efl_canvas_vg_container_child_get(root, "body");
+   fail_if(body == NULL);
+
+   /* (d) The child is a shape */
+   fail_if(!efl_isa(body, EFL_CANVAS_VG_SHAPE_CLASS));
+
+   /* Cleanup */
+   efl_unref(root);
+   _edje_vg_tree_free(t);
+}
+EFL_END_TEST
+
 void
 edje_test_vector_states(TCase *tc)
 {
    tcase_add_test(tc, edje_vg_tree_eet_roundtrip);
    tcase_add_test(tc, edje_vg_tree_helpers_basic);
    tcase_add_test(tc, edje_vg_round_trip_tree_ids);
+   tcase_add_test(tc, edje_vg_tree_materializer_basic);
 }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to