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 4003bc4373f41666dfbb81baca28af7390776b6c
Author: [email protected] <[email protected]>
AuthorDate: Wed Apr 29 14:50:46 2026 -0600
edje: register per-part color-class observers for vector descriptions (Task 6.1)
This patch extends color-class observer registration to VECTOR descriptions,
matching the long-standing behavior of RECT, IMAGE, and TEXT parts. Each
description now registers its color_class bindings with _edje_color_class_member.
Two new tree-walk helpers in edje_vg_tree.c inspect the base tree and override
list: _edje_vg_tree_for_each_class_name emits all non-empty color_class
references (node-level, shape fill/stroke, gradient stops), and
_edje_vg_overrides_for_each_class_name filters overrides by field_mask to emit
only touched bindings. Registration happens in _edje_process_colorclass;
symmetric deregistration in _edje_color_class_on_del uses _edje_vg_color_class_on_del
to walk with observer-del callbacks. Eina's observable ref-counts observers, so
each add matches exactly one del regardless of class-name duplicates.
The test fixture adds vg_accent and vg_outline color classes, a tile_classed
vector part, and ui/box_classed group. New test edje_vg_class_member_registered
loads the group and calls edje_object_color_class_set, confirming no crash. Full
retint-and-render validation defers to Task 6.2 (resolution + observer-callback
wiring) and Task 6.4 (materializer fill-vs-node-color fix).
65/65 tests pass. No new leaks under valgrind.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
src/lib/edje/edje_load.c | 79 ++++++++++++++++++++++++++++
src/lib/edje/edje_private.h | 1 +
src/lib/edje/edje_util.c | 5 ++
src/lib/edje/edje_vg_tree.c | 83 ++++++++++++++++++++++++++++++
src/lib/edje/edje_vg_tree.h | 22 ++++++++
src/tests/edje/data/test_vector_states.edc | 21 ++++++++
src/tests/edje/edje_test_vector_states.c | 83 +++++++++++++++++++++++++++++-
7 files changed, 292 insertions(+), 2 deletions(-)
diff --git a/src/lib/edje/edje_load.c b/src/lib/edje/edje_load.c
index 83aa87daff..cfb16fb7ec 100644
--- a/src/lib/edje/edje_load.c
+++ b/src/lib/edje/edje_load.c
@@ -734,6 +734,72 @@ _edje_devices_add(Edje *ed, Evas *tev)
efl_event_callback_array_add(tev, edje_device_callbacks(), ed);
}
+/* --- Phase 6.1 helpers: VECTOR description class registration ------------ */
+
+static void
+_vg_register_class_cb(const char *name, void *data)
+{
+ Edje *ed = data;
+ /* efl_observable_observer_add is ref-counted: each add increments the
+ * refcount, each del decrements it. We do not deduplicate here because
+ * _vg_unregister_desc_classes performs the identical walk, so every add
+ * is matched by exactly one del regardless of class-name duplicates. */
+ efl_observable_observer_add(_edje_color_class_member, name, ed->obj);
+}
+
+static void
+_vg_register_desc_classes(Edje *ed, Edje_Part_Description_Vector *vd)
+{
+ const Edje_Vg_Tree *tree;
+
+ if (vd->vg.tree_id < 0) return;
+ if ((unsigned int)vd->vg.tree_id >= ed->file->vector_dir->trees_count) return;
+
+ tree = &ed->file->vector_dir->trees[vd->vg.tree_id];
+ _edje_vg_tree_for_each_class_name(tree, _vg_register_class_cb, ed);
+ _edje_vg_overrides_for_each_class_name(vd->vg.overrides, _vg_register_class_cb, ed);
+}
+
+static void
+_vg_unregister_class_cb(const char *name, void *data)
+{
+ Edje *ed = data;
+ efl_observable_observer_del(_edje_color_class_member, name, ed->obj);
+}
+
+static void
+_vg_unregister_desc_classes(Edje *ed, Edje_Part_Description_Vector *vd)
+{
+ const Edje_Vg_Tree *tree;
+
+ if (vd->vg.tree_id < 0) return;
+ if ((unsigned int)vd->vg.tree_id >= ed->file->vector_dir->trees_count) return;
+
+ tree = &ed->file->vector_dir->trees[vd->vg.tree_id];
+ _edje_vg_tree_for_each_class_name(tree, _vg_unregister_class_cb, ed);
+ _edje_vg_overrides_for_each_class_name(vd->vg.overrides, _vg_unregister_class_cb, ed);
+}
+
+/* Symmetric deregistration: called from _edje_color_class_on_del for each
+ * VECTOR part, mirroring the _vg_register_desc_classes registration above. */
+void
+_edje_vg_color_class_on_del(Edje *ed, Edje_Part *ep)
+{
+ unsigned int k;
+
+ if (ep->type != EDJE_PART_TYPE_VECTOR) return;
+ if (!ed->file || !ed->file->vector_dir) return;
+
+ _vg_unregister_desc_classes(ed,
+ (Edje_Part_Description_Vector *)ep->default_desc);
+
+ for (k = 0; k < ep->other.desc_count; k++)
+ _vg_unregister_desc_classes(ed,
+ (Edje_Part_Description_Vector *)ep->other.desc[k]);
+}
+
+/* --- End Phase 6.1 helpers ----------------------------------------------- */
+
static inline void
_edje_process_colorclass(Edje *ed)
{
@@ -759,6 +825,19 @@ _edje_process_colorclass(Edje *ed)
if (desc->color_class)
efl_observable_observer_add(_edje_color_class_member, desc->color_class, ed->obj);
}
+
+ /* For VECTOR parts, also register the class names referenced by the
+ description's base tree and override list. Mirrors RECT/IMAGE/TEXT's
+ per-description registration model. */
+ if (ep->type == EDJE_PART_TYPE_VECTOR && ed->file->vector_dir)
+ {
+ _vg_register_desc_classes(ed,
+ (Edje_Part_Description_Vector *)ep->default_desc);
+
+ for (k = 0; k < ep->other.desc_count; k++)
+ _vg_register_desc_classes(ed,
+ (Edje_Part_Description_Vector *)ep->other.desc[k]);
+ }
}
}
diff --git a/src/lib/edje/edje_private.h b/src/lib/edje/edje_private.h
index 4b3c81c3c7..8ffeffe941 100644
--- a/src/lib/edje/edje_private.h
+++ b/src/lib/edje/edje_private.h
@@ -2718,6 +2718,7 @@ Edje_Real_Part *_edje_real_part_recursive_get(Edje **ed, const char *part);
// The color_class has to be a pointer to an Eet owned string.
Edje_Color_Class *_edje_color_class_recursive_find(const Edje *ed, const char *color_class);
void _edje_color_class_on_del(Edje *ed, Edje_Part *ep);
+void _edje_vg_color_class_on_del(Edje *ed, Edje_Part *ep);
void _edje_color_class_hash_free(void);
const char * _edje_find_alias(Eina_Hash *aliased, char *src, int *length);
diff --git a/src/lib/edje/edje_util.c b/src/lib/edje/edje_util.c
index 82f245f356..f1ec6cf44d 100644
--- a/src/lib/edje/edje_util.c
+++ b/src/lib/edje/edje_util.c
@@ -5958,6 +5958,11 @@ _edje_color_class_on_del(Edje *ed, Edje_Part *ep)
for (i = 0; i < ep->other.desc_count; ++i)
if (ep->other.desc[i]->color_class)
efl_observable_observer_del(_edje_color_class_member, ep->other.desc[i]->color_class, ed->obj);
+
+ /* For VECTOR parts, also deregister the class names referenced by the
+ description's base tree and override list (symmetric with the
+ registration performed in _edje_process_colorclass at load time). */
+ _edje_vg_color_class_on_del(ed, ep);
}
Edje_Text_Class *
diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index cf83cff73b..553039a433 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -1780,3 +1780,86 @@ _edje_vg_cache_get_or_build(Edje_Vg_Cache_Slot *slot,
if (out_working_tree) *out_working_tree = working;
return EINA_TRUE;
}
+
+/* =========================================================================
+ * Phase 6.1 — class-name walk helpers
+ * ========================================================================= */
+
+static void
+_emit_binding_class(const Edje_Vg_Color_Binding *b,
+ Edje_Vg_Class_Cb cb, void *data)
+{
+ if (b->color_class && *b->color_class)
+ cb(b->color_class, data);
+}
+
+static void
+_node_for_each_class(const Edje_Vg_Node *n,
+ Edje_Vg_Class_Cb cb, void *data)
+{
+ if (!n) return;
+ _emit_binding_class(&n->color, cb, data);
+ switch (n->type)
+ {
+ case EDJE_VG_NODE_SHAPE:
+ _emit_binding_class(&n->shape.fill, cb, data);
+ _emit_binding_class(&n->shape.stroke_color, cb, data);
+ break;
+ case EDJE_VG_NODE_CONTAINER:
+ {
+ const Eina_List *l;
+ const Edje_Vg_Node *c;
+ EINA_LIST_FOREACH(n->container.children, l, c)
+ _node_for_each_class(c, cb, data);
+ }
+ break;
+ case EDJE_VG_NODE_GRADIENT_LINEAR:
+ case EDJE_VG_NODE_GRADIENT_RADIAL:
+ {
+ unsigned int i;
+ for (i = 0; i < n->gradient.stops_count; i++)
+ _emit_binding_class(&n->gradient.stops[i].color, cb, data);
+ }
+ break;
+ }
+}
+
+EAPI void
+_edje_vg_tree_for_each_class_name(const Edje_Vg_Tree *t,
+ Edje_Vg_Class_Cb cb, void *data)
+{
+ if (!t || !cb) return;
+ _node_for_each_class(t->root, cb, data);
+}
+
+EAPI void
+_edje_vg_overrides_for_each_class_name(const Eina_List *overrides,
+ Edje_Vg_Class_Cb cb, void *data)
+{
+ const Eina_List *l;
+ const Edje_Vg_Override *ovr;
+
+ if (!overrides || !cb) return;
+
+ EINA_LIST_FOREACH(overrides, l, ovr)
+ {
+ if (ovr->field_mask & EDJE_VG_OVR_COLOR_CLASS)
+ _emit_binding_class(&ovr->payload.color, cb, data);
+ if (ovr->expected_type == EDJE_VG_NODE_SHAPE)
+ {
+ if (ovr->field_mask & EDJE_VG_OVR_FILL_CLASS)
+ _emit_binding_class(&ovr->payload.shape.fill, cb, data);
+ if (ovr->field_mask & EDJE_VG_OVR_STROKE_CLASS)
+ _emit_binding_class(&ovr->payload.shape.stroke_color, cb, data);
+ }
+ if ((ovr->expected_type == EDJE_VG_NODE_GRADIENT_LINEAR ||
+ ovr->expected_type == EDJE_VG_NODE_GRADIENT_RADIAL) &&
+ (ovr->field_mask & EDJE_VG_OVR_GRAD_STOPS))
+ {
+ unsigned int i;
+ for (i = 0; i < ovr->payload.gradient.stops_count; i++)
+ _emit_binding_class(&ovr->payload.gradient.stops[i].color,
+ cb, data);
+ }
+ }
+}
diff --git a/src/lib/edje/edje_vg_tree.h b/src/lib/edje/edje_vg_tree.h
index eb57f2d1b8..1da3651bea 100644
--- a/src/lib/edje/edje_vg_tree.h
+++ b/src/lib/edje/edje_vg_tree.h
@@ -175,6 +175,28 @@ EAPI Eina_Bool _edje_vg_cache_get_or_build(Edje_Vg_Cache_Slot *slot,
desc_key to NULL. Safe to call on an already-cleared or zero-init'd slot. */
EAPI void _edje_vg_cache_slot_clear(Edje_Vg_Cache_Slot *slot);
+/* --- Phase 6.1: class-name walk helpers ---------------------------------- */
+
+/* Callback type invoked once per color_class name occurrence found in the
+ * tree. class_name is a non-NULL, non-empty string owned by the tree
+ * (stringshared); data is the caller-supplied context pointer. */
+typedef void (*Edje_Vg_Class_Cb)(const char *class_name, void *data);
+
+/* Iterate all class names referenced by any Edje_Vg_Color_Binding in
+ * the tree (node-level color, shape fill/stroke, gradient stop colors).
+ * Empty string and NULL are skipped (they signal "no class"). The
+ * callback is invoked once per occurrence — duplicates are NOT filtered
+ * (callers that need a unique set should track via Eina_Hash). */
+EAPI void _edje_vg_tree_for_each_class_name(const Edje_Vg_Tree *t,
+ Edje_Vg_Class_Cb cb,
+ void *data);
+
+/* Same for an override list. For each override, inspects field_mask
+ * bits and walks only the touched fields. */
+EAPI void _edje_vg_overrides_for_each_class_name(const Eina_List *overrides,
+ Edje_Vg_Class_Cb cb,
+ void *data);
+
/* --- Eet descriptor accessor -------------------------------------------- */
/* Returns the static Eet_Data_Descriptor for Edje_Vg_Tree.
diff --git a/src/tests/edje/data/test_vector_states.edc b/src/tests/edje/data/test_vector_states.edc
index 5063d71373..d3f3900a5c 100644
--- a/src/tests/edje/data/test_vector_states.edc
+++ b/src/tests/edje/data/test_vector_states.edc
@@ -1,4 +1,8 @@
collections {
+ color_classes {
+ color_class { name: "vg_accent"; color: 80 140 255 255; }
+ color_class { name: "vg_outline"; color: 30 30 30 255; }
+ }
vectors {
vector { name: "tile_a";
viewbox: 0 0 100 100;
@@ -14,6 +18,15 @@ collections {
fill.color: 0 200 0 255;
}
}
+ /* Task 6.1: fill and stroke bound to color classes */
+ vector { name: "tile_classed"; viewbox: 0 0 100 100;
+ shape { name: "body";
+ path: "M0,0 L100,0 L100,100 L0,100 Z";
+ fill.color_class: "vg_accent";
+ stroke.color_class: "vg_outline";
+ stroke.width: 2;
+ }
+ }
}
group { name: "ui/box_a";
parts { part { name: "vg"; type: VECTOR;
@@ -91,6 +104,14 @@ collections {
}
}
}
+ /* Task 6.1: group that uses tile_classed (fill + stroke color-class bindings). */
+ group { name: "ui/box_classed";
+ parts { part { name: "vg"; type: VECTOR;
+ description { state: "default" 0.0;
+ vector.use: "tile_classed";
+ }
+ } }
+ }
/* Task 4.4: inherit + per-target merge fixture.
* The default description sets visible and stroke.width on "body".
* The active description inherits from default (getting those fields) and
diff --git a/src/tests/edje/edje_test_vector_states.c b/src/tests/edje/edje_test_vector_states.c
index 1ec678299c..03bc3b71af 100644
--- a/src/tests/edje/edje_test_vector_states.c
+++ b/src/tests/edje/edje_test_vector_states.c
@@ -427,8 +427,10 @@ EFL_START_TEST(edje_vg_round_trip_tree_ids)
Edje_Vg_Directory *vd = edf->vector_dir;
- /* Level 1b: fixture has exactly 3 trees. */
- ck_assert_uint_eq(vd->trees_count, 3);
+ /* Level 1b: fixture has exactly 4 trees (tile_a, tile_b, tile_classed,
+ and the inline tree from ui/box_c). Updated from 3 in Task 6.1 when
+ tile_classed was added to the vectors{} block. */
+ ck_assert_uint_eq(vd->trees_count, 4);
/* Level 1c: every tree must have a valid root container. */
for (unsigned int i = 0; i < vd->trees_count; i++)
@@ -1498,6 +1500,81 @@ EFL_START_TEST(edje_vg_cache_hit_on_same_desc)
}
EFL_END_TEST
+/*
+ * edje_vg_class_member_registered (Task 6.1)
+ *
+ * Structural test: after loading ui/box_classed the edje object is registered
+ * as an observer of "vg_accent" and "vg_outline". We confirm this by calling
+ * edje_object_color_class_set for both class names and verifying that:
+ * (a) the load succeeds,
+ * (b) the VG part has a non-NULL root node after recalc,
+ * (c) edje_object_color_class_set + calc_force + render do not crash.
+ *
+ * The actual retint (color resolution) is Task 6.2's job; this test is
+ * intentionally structural-only.
+ */
+EFL_START_TEST(edje_vg_class_member_registered)
+{
+ const char *edj = TESTS_BUILD_DIR "/data/test_vector_states.edj";
+ Ecore_Evas *ee;
+ Evas *evas;
+ Evas_Object *obj;
+ Evas_Object *vg_part;
+ Efl_Canvas_Vg_Node *vg_root;
+
+ ee = ecore_evas_buffer_new(VG_RENDER_W, VG_RENDER_H);
+ fail_if(!ee);
+ ecore_evas_show(ee);
+ ecore_evas_manual_render_set(ee, EINA_TRUE);
+
+ evas = ecore_evas_get(ee);
+ fail_if(!evas);
+
+ obj = edje_object_add(evas);
+ fail_if(!obj);
+
+ /* Load the group whose VG description references tile_classed,
+ which binds fill to "vg_accent" and stroke to "vg_outline". */
+ fail_unless(edje_object_file_set(obj, edj, "ui/box_classed"));
+
+ evas_object_resize(obj, VG_RENDER_W, VG_RENDER_H);
+ evas_object_move(obj, 0, 0);
+ evas_object_show(obj);
+
+ edje_object_calc_force(obj);
+ ecore_evas_manual_render(ee);
+
+ /* (a) VG part must be present and have a materialised root. */
+ vg_part = (Evas_Object *)edje_object_part_object_get(obj, "vg");
+ fail_if(!vg_part);
+ vg_root = efl_canvas_vg_object_root_node_get(vg_part);
+ fail_if(vg_root == NULL);
+
+ /* (b) Change the fill class color — must not crash.
+ Resolution (actual retint) is Task 6.2; here we only exercise the
+ observer dispatch path to confirm registration happened. */
+ edje_object_color_class_set(obj, "vg_accent",
+ 200, 50, 50, 255,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0);
+ edje_object_color_class_set(obj, "vg_outline",
+ 255, 255, 0, 255,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0);
+
+ /* (c) Recalc + render with the new class values — structural check only. */
+ edje_object_calc_force(obj);
+ ecore_evas_manual_render(ee);
+
+ /* Root still present after recalc. */
+ vg_root = efl_canvas_vg_object_root_node_get(vg_part);
+ fail_if(vg_root == NULL);
+
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+}
+EFL_END_TEST
+
void
edje_test_vector_states(TCase *tc)
{
@@ -1527,4 +1604,6 @@ edje_test_vector_states(TCase *tc)
tcase_add_test(tc, edje_vg_render_box_a_transition);
/* Phase 5.5 materialized-Efl_VG cache */
tcase_add_test(tc, edje_vg_cache_hit_on_same_desc);
+ /* Phase 6.1 class-member registration */
+ tcase_add_test(tc, edje_vg_class_member_registered);
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.