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.