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 7c7f9cf505a3dc269abccf36f122d7f83a1a01ec
Author: [email protected] <[email protected]>
AuthorDate: Wed Apr 29 15:09:46 2026 -0600
edje: resolve color-class bindings at cache rebuild (Phase 6.2)
Wire color-class resolution into Phase 5.5's materialized-Efl_VG cache path.
When _edje_vg_cache_get_or_build rebuilds, _edje_vg_tree_resolve_colors walks
all Edje_Vg_Color_Binding nodes (fill, stroke, gradient stops) and multiplies
raw RGBA × class RGBA / 255, then clears the class_class field so subsequent
passes (lerp, materializer) see only resolved values per spec §6.5.
Two invalidation paths ensure cache clears when classes change: (1) observer
path in edje_smart.c when _edje_color_class_member fires, (2) direct setter
path in edje_util.c for edje_object_color_class_set. Both mark classes_dirty
on VECTOR parts. Initialize stroke_color default to (255,255,255,255) so
class-only stroke bindings (no explicit stroke.color) resolve non-zero.
New test edje_vg_runtime_class_retint verifies end-to-end: loads ui/box_classed,
checks initial stroke = (30,30,30,255), calls edje_object_color_class_set to
red, recalcs, asserts stroke retints to (255,0,0,255). Skips fill assertion
pending Task 6.4 (materializer fill-vs-node-color bug).
66/66 tests pass; no valgrind leaks.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
src/lib/edje/edje_calc.c | 4 +-
src/lib/edje/edje_smart.c | 17 +++++
src/lib/edje/edje_util.c | 5 ++
src/lib/edje/edje_vg_tree.c | 66 +++++++++++++++++++
src/lib/edje/edje_vg_tree.h | 15 +++++
src/tests/edje/edje_test_vector_states.c | 110 +++++++++++++++++++++++++++++--
6 files changed, 209 insertions(+), 8 deletions(-)
diff --git a/src/lib/edje/edje_calc.c b/src/lib/edje/edje_calc.c
index 07b03be821..5630a5775e 100644
--- a/src/lib/edje/edje_calc.c
+++ b/src/lib/edje/edje_calc.c
@@ -3312,7 +3312,7 @@ _edje_vector_recalc_apply(Edje *ed, Edje_Real_Part *ep, Edje_Calc_Params *p3 EIN
/* Build or fetch the chosen description's cache slot (A side). */
if (!_edje_vg_cache_get_or_build(&rpv->cache_chosen,
- chosen_desc, base_a,
+ ed, chosen_desc, base_a,
chosen_desc->vg.overrides,
rpv->classes_dirty,
&efl_a, &working_a))
@@ -3336,7 +3336,7 @@ _edje_vector_recalc_apply(Edje *ed, Edje_Real_Part *ep, Edje_Calc_Params *p3 EIN
/* Build or fetch the param2 description's cache slot (B side). */
if (_edje_vg_cache_get_or_build(&rpv->cache_param2,
- next_desc, base_b,
+ ed, next_desc, base_b,
next_desc->vg.overrides,
rpv->classes_dirty,
&efl_b, &working_b))
diff --git a/src/lib/edje/edje_smart.c b/src/lib/edje/edje_smart.c
index d0d794790b..fac96d1e1c 100644
--- a/src/lib/edje/edje_smart.c
+++ b/src/lib/edje/edje_smart.c
@@ -502,6 +502,23 @@ _efl_canvas_layout_efl_observer_update(Eo *obj EINA_UNUSED, Edje *ed, Efl_Object
ed->all_part_change = EINA_TRUE;
#endif
}
+
+ /* Phase 6.2: VECTOR parts use a materialized-Efl_VG cache (Phase 5.5)
+ that needs explicit invalidation on color-class change. Set
+ classes_dirty on every VECTOR part so the cache rebuild path in
+ _edje_vector_recalc_apply re-resolves class-bound colors. */
+ if (obs == _edje_color_class_member)
+ {
+ unsigned int _i;
+ for (_i = 0; _i < ed->table_parts_size; _i++)
+ {
+ Edje_Real_Part *_rp = ed->table_parts[_i];
+ if (_rp && _rp->part &&
+ _rp->part->type == EDJE_PART_TYPE_VECTOR &&
+ _rp->typedata.vector)
+ _rp->typedata.vector->classes_dirty = EINA_TRUE;
+ }
+ }
else if (obs == _edje_text_class_member)
{
if (data == ed)
diff --git a/src/lib/edje/edje_util.c b/src/lib/edje/edje_util.c
index f1ec6cf44d..a0e51c0b1e 100644
--- a/src/lib/edje/edje_util.c
+++ b/src/lib/edje/edje_util.c
@@ -943,6 +943,11 @@ _efl_canvas_layout_efl_gfx_color_class_color_class_set(Eo *obj EINA_UNUSED, Edje
(rp->typedata.swallow->swallowed_object))
efl_gfx_color_class_set(rp->typedata.swallow->swallowed_object,
color_class, layer, r, g, b, a);
+ /* Phase 6.2: invalidate VECTOR part caches so class-bound colors
+ are re-resolved against the updated registry on next recalc. */
+ if (rp->part->type == EDJE_PART_TYPE_VECTOR &&
+ rp->typedata.vector)
+ rp->typedata.vector->classes_dirty = EINA_TRUE;
}
_edje_recalc(ed);
diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index 553039a433..8f4eaf3064 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -240,6 +240,8 @@ _edje_vg_node_new(Edje_Vg_Node_Type type)
{
n->shape.fill.r = n->shape.fill.g =
n->shape.fill.b = n->shape.fill.a = 255;
+ n->shape.stroke_color.r = n->shape.stroke_color.g =
+ n->shape.stroke_color.b = n->shape.stroke_color.a = 255;
n->shape.stroke_width = 1.0;
n->shape.stroke_cap = EFL_GFX_CAP_BUTT;
n->shape.stroke_join = EFL_GFX_JOIN_MITER;
@@ -1729,6 +1731,7 @@ _edje_vg_cache_slot_clear(Edje_Vg_Cache_Slot *slot)
EAPI Eina_Bool
_edje_vg_cache_get_or_build(Edje_Vg_Cache_Slot *slot,
+ const Edje *ed,
const void *desc_key,
const Edje_Vg_Tree *base,
const Eina_List *overrides,
@@ -1765,6 +1768,11 @@ _edje_vg_cache_get_or_build(Edje_Vg_Cache_Slot *slot,
_edje_vg_tree_apply_override(working, ovr);
}
+ /* Resolve class-bound colors before materialization. The cached
+ working_tree carries no remaining class references; subsequent
+ passes (lerp, materialize) see only resolved RGBA per spec §6.5. */
+ _edje_vg_tree_resolve_colors(working, ed);
+
Efl_VG *efl_vg = _edje_vg_tree_to_efl_vg(working);
if (!efl_vg)
{
@@ -1781,6 +1789,64 @@ _edje_vg_cache_get_or_build(Edje_Vg_Cache_Slot *slot,
return EINA_TRUE;
}
+/* =========================================================================
+ * Phase 6.2 — class color resolution
+ * ========================================================================= */
+
+static void
+_resolve_binding(Edje_Vg_Color_Binding *cb, const Edje *ed)
+{
+ if (!cb || !cb->color_class || !*cb->color_class) return;
+
+ Edje_Color_Class *cc = _edje_color_class_recursive_find(ed, cb->color_class);
+ if (cc)
+ {
+ cb->r = (cb->r * cc->r) / 255;
+ cb->g = (cb->g * cc->g) / 255;
+ cb->b = (cb->b * cc->b) / 255;
+ cb->a = (cb->a * cc->a) / 255;
+ }
+ /* Hit or miss: clear so subsequent passes see resolved RGBA. */
+ eina_stringshare_replace(&cb->color_class, "");
+}
+
+static void
+_resolve_node_recursive(Edje_Vg_Node *n, const Edje *ed)
+{
+ if (!n) return;
+ _resolve_binding(&n->color, ed);
+ switch (n->type)
+ {
+ case EDJE_VG_NODE_SHAPE:
+ _resolve_binding(&n->shape.fill, ed);
+ _resolve_binding(&n->shape.stroke_color, ed);
+ break;
+ case EDJE_VG_NODE_CONTAINER:
+ {
+ Eina_List *l;
+ Edje_Vg_Node *c;
+ EINA_LIST_FOREACH(n->container.children, l, c)
+ _resolve_node_recursive(c, ed);
+ }
+ 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++)
+ _resolve_binding(&n->gradient.stops[i].color, ed);
+ }
+ break;
+ }
+}
+
+EAPI void
+_edje_vg_tree_resolve_colors(Edje_Vg_Tree *working, const Edje *ed)
+{
+ if (!working || !ed) return; /* No-op when ed is NULL — used by tests */
+ _resolve_node_recursive(working->root, ed);
+}
+
/* =========================================================================
* Phase 6.1 — class-name walk helpers
* ========================================================================= */
diff --git a/src/lib/edje/edje_vg_tree.h b/src/lib/edje/edje_vg_tree.h
index 1da3651bea..af3e09a074 100644
--- a/src/lib/edje/edje_vg_tree.h
+++ b/src/lib/edje/edje_vg_tree.h
@@ -164,6 +164,7 @@ EAPI Edje_Vg_Tree *_edje_vg_tree_lerp(const Edje_Vg_Tree *a,
out_efl_vg and out_working_tree may each be NULL if the caller does not need
them. */
EAPI Eina_Bool _edje_vg_cache_get_or_build(Edje_Vg_Cache_Slot *slot,
+ const Edje *ed,
const void *desc_key,
const Edje_Vg_Tree *base,
const Eina_List *overrides,
@@ -175,6 +176,20 @@ 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.2: class color resolution ----------------------------------- */
+
+/* Walk the working tree; for every Edje_Vg_Color_Binding whose
+ * color_class is non-NULL/non-empty, look up the class via
+ * _edje_color_class_recursive_find(ed, name). On hit, multiply the
+ * binding's raw RGBA componentwise (resolved.r = base.r * C.r / 255).
+ * Then clear color_class to "" (empty stringshare) so subsequent passes
+ * (lerp / materialize) see only resolved RGBA.
+ *
+ * Empty string is the "no class" sentinel — left untouched. Class
+ * not found: clear the binding's class so subsequent recalcs don't keep
+ * looking it up. No-op when working or ed is NULL. */
+EAPI void _edje_vg_tree_resolve_colors(Edje_Vg_Tree *working, const Edje *ed);
+
/* --- Phase 6.1: class-name walk helpers ---------------------------------- */
/* Callback type invoked once per color_class name occurrence found in the
diff --git a/src/tests/edje/edje_test_vector_states.c b/src/tests/edje/edje_test_vector_states.c
index 03bc3b71af..486b9c828c 100644
--- a/src/tests/edje/edje_test_vector_states.c
+++ b/src/tests/edje/edje_test_vector_states.c
@@ -128,8 +128,10 @@ typedef struct
} Test_Vg_Cache_Slot;
/* The cache helpers take Edje_Vg_Cache_Slot* from the library's perspective,
- * but since both structs have identical layout, we cast freely. */
+ * but since both structs have identical layout, we cast freely.
+ * ed is passed as NULL in unit tests — resolve short-circuits on NULL ed. */
Eina_Bool _edje_vg_cache_get_or_build(void *slot,
+ const void *ed,
const void *desc_key,
const Edje_Vg_Tree *base,
const void *overrides,
@@ -1463,8 +1465,9 @@ EFL_START_TEST(edje_vg_cache_hit_on_same_desc)
Efl_VG *vg1 = NULL;
const Edje_Vg_Tree *wt1 = NULL;
- /* First call: cold miss — must build and return valid objects. */
- fail_if(!_edje_vg_cache_get_or_build(&slot, key_a, base, NULL,
+ /* First call: cold miss — must build and return valid objects.
+ NULL ed: resolve short-circuits (no class names in test tree). */
+ fail_if(!_edje_vg_cache_get_or_build(&slot, NULL, key_a, base, NULL,
EINA_FALSE, &vg1, &wt1));
fail_if(!vg1);
fail_if(!wt1);
@@ -1473,7 +1476,7 @@ EFL_START_TEST(edje_vg_cache_hit_on_same_desc)
const Edje_Vg_Tree *wt2 = NULL;
/* Second call with same key: cache hit — same pointer returned. */
- fail_if(!_edje_vg_cache_get_or_build(&slot, key_a, base, NULL,
+ fail_if(!_edje_vg_cache_get_or_build(&slot, NULL, key_a, base, NULL,
EINA_FALSE, &vg2, &wt2));
ck_assert_ptr_eq(vg1, vg2);
ck_assert_ptr_eq(wt1, wt2);
@@ -1481,14 +1484,14 @@ EFL_START_TEST(edje_vg_cache_hit_on_same_desc)
Efl_VG *vg3 = NULL;
/* Third call with different key: cache miss — fresh object. */
- fail_if(!_edje_vg_cache_get_or_build(&slot, key_b, base, NULL,
+ fail_if(!_edje_vg_cache_get_or_build(&slot, NULL, key_b, base, NULL,
EINA_FALSE, &vg3, NULL));
fail_if(vg3 == vg1);
Efl_VG *vg4 = NULL;
/* Fourth call: same key_b but classes_dirty forces rebuild. */
- fail_if(!_edje_vg_cache_get_or_build(&slot, key_b, base, NULL,
+ fail_if(!_edje_vg_cache_get_or_build(&slot, NULL, key_b, base, NULL,
EINA_TRUE, &vg4, NULL));
fail_if(vg4 == vg3);
@@ -1575,6 +1578,99 @@ EFL_START_TEST(edje_vg_class_member_registered)
}
EFL_END_TEST
+/*
+ * edje_vg_runtime_class_retint (Task 6.2)
+ *
+ * Runtime color-class resolution test using ui/box_classed (tile_classed):
+ * fill.color_class = "vg_accent" (EDC default: 80 140 255 255)
+ * stroke.color_class = "vg_outline" (EDC default: 30 30 30 255)
+ * Raw binding defaults: 255 255 255 255 (white)
+ *
+ * After initial load+render the materialised stroke should be ~(30, 30, 30, 255)
+ * because resolve does: 255*30/255 = 30 for each channel.
+ *
+ * After edje_object_color_class_set(obj, "vg_outline", 255,0,0,255, ...) + recalc
+ * the stroke should be (255, 0, 0, 255): 255*255/255=255, 255*0/255=0, 255*0/255=0.
+ *
+ * Fill color assertion is skipped per the acknowledged fill-vs-node-color bug
+ * (Task 6.4); stroke has its own independent getter unaffected by that bug.
+ */
+EFL_START_TEST(edje_vg_runtime_class_retint)
+{
+ 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;
+ Efl_Canvas_Vg_Node *body_node;
+ int r, g, b, a;
+
+ 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);
+
+ 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);
+
+ 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);
+ fail_if(!efl_isa(vg_root, EFL_CANVAS_VG_CONTAINER_CLASS));
+
+ body_node = efl_canvas_vg_container_child_get(vg_root, "body");
+ fail_if(body_node == NULL);
+
+ /* Initial stroke: raw (255,255,255,255) * vg_outline (30,30,30,255) / 255 = (30,30,30,255) */
+ efl_gfx_shape_stroke_color_get(body_node, &r, &g, &b, &a);
+ ck_assert_int_eq(r, 30);
+ ck_assert_int_eq(g, 30);
+ ck_assert_int_eq(b, 30);
+ ck_assert_int_eq(a, 255);
+
+ /* Change the outline class to red; observer fires -> classes_dirty -> rebuild. */
+ edje_object_color_class_set(obj, "vg_outline",
+ 255, 0, 0, 255,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0);
+ edje_object_calc_force(obj);
+ ecore_evas_manual_render(ee);
+
+ /* Root pointer may have changed after rematerialization. */
+ vg_root = efl_canvas_vg_object_root_node_get(vg_part);
+ fail_if(vg_root == NULL);
+ fail_if(!efl_isa(vg_root, EFL_CANVAS_VG_CONTAINER_CLASS));
+
+ body_node = efl_canvas_vg_container_child_get(vg_root, "body");
+ fail_if(body_node == NULL);
+
+ /* After change: raw (255,255,255,255) * vg_outline (255,0,0,255) / 255 = (255,0,0,255) */
+ efl_gfx_shape_stroke_color_get(body_node, &r, &g, &b, &a);
+ ck_assert_int_eq(r, 255);
+ ck_assert_int_eq(g, 0);
+ ck_assert_int_eq(b, 0);
+ ck_assert_int_eq(a, 255);
+
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+}
+EFL_END_TEST
+
void
edje_test_vector_states(TCase *tc)
{
@@ -1606,4 +1702,6 @@ edje_test_vector_states(TCase *tc)
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);
+ /* Phase 6.2 runtime color-class resolution */
+ tcase_add_test(tc, edje_vg_runtime_class_retint);
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.