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 4cf920e6a197df5c645a95b9c0b9fd35211b2603
Author: [email protected] <[email protected]>
AuthorDate: Thu Apr 30 16:53:51 2026 -0600
edje: emit vector trees and overrides in edje_decc (Phase 8.1)
Implement source code generation for named vector trees and per-shape
overrides via both edje_edit and edje_decc source-generation pipelines.
This unblocks edje_decc decompilation: without these emitters, edje_decc
would silently produce incomplete EDC files for any theme using named
vector trees (Mode A trees referenced via vector.use:), and the result
would not recompile. The feature was fully functional at runtime but
broken for round-trip workflows.
Changes:
- Add Edje_Vg_Tree.name field (stringshare) to track toplevel tree
names across serialization. Eet-descriptor updated for backward
compatibility: loading older .edj files reads it as NULL.
- Emit vectors {} block in both _edje_edit_source_generate (edje_edit
API) and _edje_generate_source (edje_decc/edje_edit_source_save).
Named trees are emitted at top level; anonymous trees stay inline.
- Per-description vector support: emit vector.use: "name" for Mode A
references, vector { } inline blocks for Mode B anonymous trees,
and sparse per-node overrides with color_class bindings.
- VG emission helpers for shapes, gradients, transforms, and fill/
stroke properties using EDC syntax (cap/join/fill-rule enums).
The test suite adds edje_vg_source_gen_round_trip to verify both
pipelines emit structural substrings (vectors {, vector.use:, vector.shape,
color_class:), catching regressions in either path. This caught a real
bug during development where vectors {}} was only hooked into one pipeline.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
src/bin/edje/edje_cc_handlers.c | 3 +
src/lib/edje/edje_data.c | 1 +
src/lib/edje/edje_edit.c | 476 +++++++++++++++++++++++++++++++
src/lib/edje/edje_private.h | 1 +
src/lib/edje/edje_vg_tree.c | 2 +
src/tests/edje/edje_test_vector_states.c | 109 ++++++-
6 files changed, 591 insertions(+), 1 deletion(-)
diff --git a/src/bin/edje/edje_cc_handlers.c b/src/bin/edje/edje_cc_handlers.c
index 79df0860d3..91379ed2e8 100644
--- a/src/bin/edje/edje_cc_handlers.c
+++ b/src/bin/edje/edje_cc_handlers.c
@@ -1846,6 +1846,9 @@ st_vectors_vector_name(void)
if (eina_hash_find(toplevel_vg_trees, name))
error_and_abort(NULL, "Duplicate vector tree name '%s' (VEC001)", name);
eina_hash_add(toplevel_vg_trees, name, current_vg_tree);
+ /* Store the name inside the tree so the decompiler can recover it. */
+ if (current_vg_tree->name) eina_stringshare_del(current_vg_tree->name);
+ current_vg_tree->name = eina_stringshare_add(name);
free(name);
}
diff --git a/src/lib/edje/edje_data.c b/src/lib/edje/edje_data.c
index c0208e494d..5f7426b13a 100644
--- a/src/lib/edje/edje_data.c
+++ b/src/lib/edje/edje_data.c
@@ -570,6 +570,7 @@ _edje_edd_init(void)
/* Edje_Vg_Tree */
EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Edje_Vg_Tree);
_edje_edd_edje_vg_tree = eet_data_descriptor_file_new(&eddc);
+ EET_DATA_DESCRIPTOR_ADD_BASIC(_edje_edd_edje_vg_tree, Edje_Vg_Tree, "name", name, EET_T_STRING);
EET_DATA_DESCRIPTOR_ADD_BASIC(_edje_edd_edje_vg_tree, Edje_Vg_Tree, "vbx", vbx, EET_T_DOUBLE);
EET_DATA_DESCRIPTOR_ADD_BASIC(_edje_edd_edje_vg_tree, Edje_Vg_Tree, "vby", vby, EET_T_DOUBLE);
EET_DATA_DESCRIPTOR_ADD_BASIC(_edje_edd_edje_vg_tree, Edje_Vg_Tree, "vbw", vbw, EET_T_DOUBLE);
diff --git a/src/lib/edje/edje_edit.c b/src/lib/edje/edje_edit.c
index e4a5b4a1da..d54671d657 100644
--- a/src/lib/edje/edje_edit.c
+++ b/src/lib/edje/edje_edit.c
@@ -11,6 +11,7 @@
#define EFL_CANVAS_GROUP_PROTECTED
#include "edje_private.h"
+#include "edje_vg_tree.h"
#include "canvas/evas_canvas_eo.h"
@@ -12683,6 +12684,9 @@ static const char *aspect_mode[] = {"NONE", "NEITHER", "HORIZONTAL", "VERTICAL",
static Eina_Bool
_edje_generate_source_of_group(Edje *ed, Edje_Part_Collection_Directory_Entry *pce, Eina_Strbuf *buf);
+static void
+ _edje_generate_source_of_vectors_block(const Edje_File *ef, Eina_Strbuf *buf);
+
static Eina_Bool
_edje_generate_source_of_style(Edje *ed, const char *name, Eina_Strbuf *buf);
@@ -13051,6 +13055,10 @@ _edje_edit_source_generate(Evas_Object *obj, Eina_Bool without_global_data)
}
+ /* Emit toplevel named vector trees (Mode A) */
+ if (!without_global_data)
+ _edje_generate_source_of_vectors_block(ed->file, buf);
+
/* print the main code of group collections */
if (!without_global_data) BUF_APPEND(I0 "collections {\n");
/* if sounds were found, print them */
@@ -13999,6 +14007,467 @@ _edje_generate_source_state_relative(Edje *ed,
}
}
+/* =========================================================================
+ * VG source generation — Phase 8.1
+ * ========================================================================= */
+
+static void _edje_vg_emit_node(const Edje_Vg_Node *n, Eina_Strbuf *buf, int depth);
+
+static const char *
+_vg_cap_str(Efl_Gfx_Cap c)
+{
+ switch (c)
+ {
+ case EFL_GFX_CAP_ROUND: return "ROUND";
+ case EFL_GFX_CAP_SQUARE: return "SQUARE";
+ default: return "BUTT";
+ }
+}
+
+static const char *
+_vg_join_str(Efl_Gfx_Join j)
+{
+ switch (j)
+ {
+ case EFL_GFX_JOIN_ROUND: return "ROUND";
+ case EFL_GFX_JOIN_BEVEL: return "BEVEL";
+ default: return "MITER";
+ }
+}
+
+static const char *
+_vg_fill_rule_str(Efl_Gfx_Fill_Rule r)
+{
+ return (r == EFL_GFX_FILL_RULE_ODD_EVEN) ? "EVEN_ODD" : "WINDING";
+}
+
+static const char *
+_vg_spread_str(Efl_Gfx_Gradient_Spread s)
+{
+ switch (s)
+ {
+ case EFL_GFX_GRADIENT_SPREAD_REFLECT: return "REFLECT";
+ case EFL_GFX_GRADIENT_SPREAD_REPEAT: return "REPEAT";
+ default: return "PAD";
+ }
+}
+
+/* Build indentation string for a given tree depth. The toplevel vectors {}
+ * block uses I1 base; each nesting level inside a node block adds one more
+ * I1. Maximum of eight levels should cover any realistic tree. */
+static const char *
+_vg_node_indent(int depth)
+{
+ /* depth 0 = inside top-level vector{}: I2; depth 1 = I3, etc. */
+ static const char *table[] = { I2, I3, I4, I5, I6, I7, I7, I7 };
+ if (depth < 0) depth = 0;
+ if (depth > 7) depth = 7;
+ return table[depth];
+}
+
+static void
+_edje_vg_emit_xform(const Edje_Vg_Transform *x, Eina_Strbuf *buf, const char *indent)
+{
+ Eina_Bool ret = EINA_TRUE;
+ if (x->has_matrix)
+ {
+ BUF_APPENDF("%stransform.matrix: %.6g %.6g %.6g %.6g %.6g %.6g %.6g %.6g %.6g;\n",
+ indent,
+ x->m[0], x->m[1], x->m[2],
+ x->m[3], x->m[4], x->m[5],
+ x->m[6], x->m[7], x->m[8]);
+ }
+ else
+ {
+ if (x->tx != 0.0 || x->ty != 0.0)
+ BUF_APPENDF("%stransform.translate: %.6g %.6g;\n", indent, x->tx, x->ty);
+ if (x->angle != 0.0)
+ BUF_APPENDF("%stransform.rotate: %.6g;\n", indent, x->angle);
+ if (x->sx != 1.0 || x->sy != 1.0)
+ BUF_APPENDF("%stransform.scale: %.6g %.6g;\n", indent, x->sx, x->sy);
+ }
+ (void)ret;
+}
+
+static void
+_edje_vg_emit_shape_node(const Edje_Vg_Node *n, Eina_Strbuf *buf,
+ const char *indent, const char *inner)
+{
+ Eina_Bool ret = EINA_TRUE;
+ if (n->shape.path)
+ BUF_APPENDF("%spath: \"%s\";\n", inner, n->shape.path);
+
+ /* fill */
+ if (n->shape.fill.color_class || n->shape.fill.r || n->shape.fill.g ||
+ n->shape.fill.b || n->shape.fill.a)
+ {
+ if (n->shape.fill.color_class)
+ {
+ BUF_APPENDF("%sfill {\n", indent);
+ if (n->shape.fill.r || n->shape.fill.g || n->shape.fill.b || n->shape.fill.a)
+ BUF_APPENDF("%scolor: %d %d %d %d;\n", inner, n->shape.fill.r,
+ n->shape.fill.g, n->shape.fill.b, n->shape.fill.a);
+ BUF_APPENDF("%scolor_class: \"%s\";\n", inner, n->shape.fill.color_class);
+ BUF_APPENDF("%s}\n", indent);
+ }
+ else
+ BUF_APPENDF("%sfill.color: %d %d %d %d;\n", indent,
+ n->shape.fill.r, n->shape.fill.g, n->shape.fill.b, n->shape.fill.a);
+ }
+ if (n->shape.gradient_ref)
+ BUF_APPENDF("%sfill.gradient: \"%s\";\n", indent, n->shape.gradient_ref);
+
+ /* stroke */
+ {
+ Eina_Bool has_stroke = (n->shape.stroke_color.r || n->shape.stroke_color.g ||
+ n->shape.stroke_color.b || n->shape.stroke_color.a ||
+ n->shape.stroke_color.color_class ||
+ n->shape.stroke_width != 0.0 ||
+ n->shape.stroke_dash_count > 0 ||
+ n->shape.stroke_cap != EFL_GFX_CAP_BUTT ||
+ n->shape.stroke_join != EFL_GFX_JOIN_MITER);
+ if (has_stroke)
+ {
+ BUF_APPENDF("%sstroke {\n", indent);
+ if (n->shape.stroke_color.color_class)
+ {
+ if (n->shape.stroke_color.r || n->shape.stroke_color.g ||
+ n->shape.stroke_color.b || n->shape.stroke_color.a)
+ BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+ n->shape.stroke_color.r, n->shape.stroke_color.g,
+ n->shape.stroke_color.b, n->shape.stroke_color.a);
+ BUF_APPENDF("%scolor_class: \"%s\";\n", inner, n->shape.stroke_color.color_class);
+ }
+ else if (n->shape.stroke_color.r || n->shape.stroke_color.g ||
+ n->shape.stroke_color.b || n->shape.stroke_color.a)
+ BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+ n->shape.stroke_color.r, n->shape.stroke_color.g,
+ n->shape.stroke_color.b, n->shape.stroke_color.a);
+ if (n->shape.stroke_width != 0.0)
+ BUF_APPENDF("%swidth: %.6g;\n", inner, n->shape.stroke_width);
+ if (n->shape.stroke_cap != EFL_GFX_CAP_BUTT)
+ BUF_APPENDF("%scap: %s;\n", inner, _vg_cap_str(n->shape.stroke_cap));
+ if (n->shape.stroke_join != EFL_GFX_JOIN_MITER)
+ BUF_APPENDF("%sjoin: %s;\n", inner, _vg_join_str(n->shape.stroke_join));
+ if (n->shape.stroke_dash_count > 0)
+ {
+ unsigned int di;
+ BUF_APPENDF("%sdash:", inner);
+ for (di = 0; di < n->shape.stroke_dash_count; di++)
+ BUF_APPENDF(" %.6g", n->shape.stroke_dash[di]);
+ BUF_APPEND(";\n");
+ }
+ BUF_APPENDF("%s}\n", indent);
+ }
+ }
+
+ if (n->shape.fill_rule != EFL_GFX_FILL_RULE_WINDING)
+ BUF_APPENDF("%sfill_rule: %s;\n", indent, _vg_fill_rule_str(n->shape.fill_rule));
+ (void)ret;
+}
+
+static void
+_edje_vg_emit_gradient_node(const Edje_Vg_Node *n, Eina_Strbuf *buf,
+ const char *indent, const char *inner)
+{
+ Eina_Bool ret = EINA_TRUE;
+ if (n->gradient.spread != EFL_GFX_GRADIENT_SPREAD_PAD)
+ BUF_APPENDF("%sspread: %s;\n", indent, _vg_spread_str(n->gradient.spread));
+ if (n->gradient.is_radial)
+ {
+ BUF_APPENDF("%scenter: %.6g %.6g;\n", indent, n->gradient.x0, n->gradient.y0);
+ BUF_APPENDF("%sfocal: %.6g %.6g;\n", indent, n->gradient.x1, n->gradient.y1);
+ BUF_APPENDF("%sradius: %.6g;\n", indent, n->gradient.radius);
+ }
+ else
+ {
+ BUF_APPENDF("%sstart: %.6g %.6g;\n", indent, n->gradient.x0, n->gradient.y0);
+ BUF_APPENDF("%send: %.6g %.6g;\n", indent, n->gradient.x1, n->gradient.y1);
+ }
+ if (n->gradient.stops && n->gradient.stops_count > 0)
+ {
+ unsigned int si;
+ for (si = 0; si < n->gradient.stops_count; si++)
+ {
+ const Edje_Vg_Stop *st = &n->gradient.stops[si];
+ BUF_APPENDF("%sstop {\n", indent);
+ BUF_APPENDF("%soffset: %.6g;\n", inner, st->offset);
+ if (st->color.color_class)
+ {
+ BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+ st->color.r, st->color.g, st->color.b, st->color.a);
+ BUF_APPENDF("%scolor_class: \"%s\";\n", inner, st->color.color_class);
+ }
+ else
+ BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+ st->color.r, st->color.g, st->color.b, st->color.a);
+ BUF_APPENDF("%s}\n", indent);
+ }
+ }
+ (void)ret;
+}
+
+/* Emit one node and, for containers, recurse into children.
+ * depth 0 = directly inside a vector{} root; depth 1 = inside a container. */
+static void
+_edje_vg_emit_node(const Edje_Vg_Node *n, Eina_Strbuf *buf, int depth)
+{
+ Eina_Bool ret = EINA_TRUE;
+ const char *indent = _vg_node_indent(depth);
+ const char *inner = _vg_node_indent(depth + 1);
+ const char *kw;
+
+ switch (n->type)
+ {
+ case EDJE_VG_NODE_SHAPE: kw = "shape"; break;
+ case EDJE_VG_NODE_CONTAINER: kw = "container"; break;
+ case EDJE_VG_NODE_GRADIENT_LINEAR: kw = "gradient_linear"; break;
+ case EDJE_VG_NODE_GRADIENT_RADIAL: kw = "gradient_radial"; break;
+ default: kw = "shape"; break;
+ }
+
+ BUF_APPENDF("%s%s {\n", indent, kw);
+ if (n->name)
+ BUF_APPENDF("%sname: \"%s\";\n", inner, n->name);
+ if (!n->visible)
+ BUF_APPENDF("%svisible: 0;\n", inner);
+ if (n->color.r != 255 || n->color.g != 255 || n->color.b != 255 || n->color.a != 255 ||
+ n->color.color_class)
+ {
+ if (n->color.color_class)
+ {
+ BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+ n->color.r, n->color.g, n->color.b, n->color.a);
+ BUF_APPENDF("%scolor_class: \"%s\";\n", inner, n->color.color_class);
+ }
+ else
+ BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+ n->color.r, n->color.g, n->color.b, n->color.a);
+ }
+ _edje_vg_emit_xform(&n->xform, buf, inner);
+
+ if (n->type == EDJE_VG_NODE_SHAPE)
+ _edje_vg_emit_shape_node(n, buf, indent, inner);
+ else if (n->type == EDJE_VG_NODE_GRADIENT_LINEAR ||
+ n->type == EDJE_VG_NODE_GRADIENT_RADIAL)
+ _edje_vg_emit_gradient_node(n, buf, inner, _vg_node_indent(depth + 2));
+ else if (n->type == EDJE_VG_NODE_CONTAINER)
+ {
+ Eina_List *l;
+ Edje_Vg_Node *child;
+ EINA_LIST_FOREACH(n->container.children, l, child)
+ _edje_vg_emit_node(child, buf, depth + 1);
+ }
+
+ BUF_APPENDF("%s}\n", indent);
+ (void)ret;
+}
+
+/* Emit the full inline vector{} body for a tree (viewbox + root children). */
+static void
+_edje_vg_emit_tree_body(const Edje_Vg_Tree *t, Eina_Strbuf *buf, int child_depth)
+{
+ Eina_Bool ret = EINA_TRUE;
+ BUF_APPENDF("%sviewbox: %.6g %.6g %.6g %.6g;\n",
+ _vg_node_indent(child_depth - 1),
+ t->vbx, t->vby, t->vbw, t->vbh);
+ if (t->root)
+ {
+ Eina_List *l;
+ Edje_Vg_Node *child;
+ EINA_LIST_FOREACH(t->root->container.children, l, child)
+ _edje_vg_emit_node(child, buf, child_depth);
+ }
+ (void)ret;
+}
+
+/* Emit a single override block. Sparse emit — only fields in field_mask. */
+static void
+_edje_vg_emit_override(const Edje_Vg_Override *ovr, Eina_Strbuf *buf,
+ const char *indent, const char *inner)
+{
+ Eina_Bool ret = EINA_TRUE;
+ const char *kw;
+ switch (ovr->expected_type)
+ {
+ case EDJE_VG_NODE_SHAPE: kw = "vector.shape"; break;
+ case EDJE_VG_NODE_CONTAINER: kw = "vector.container"; break;
+ case EDJE_VG_NODE_GRADIENT_LINEAR:
+ case EDJE_VG_NODE_GRADIENT_RADIAL: kw = "vector.gradient"; break;
+ default: kw = "vector.shape"; break;
+ }
+
+ BUF_APPENDF("%s%s \"%s\" {\n", indent, kw, ovr->target_name);
+
+ const Edje_Vg_Node *p = &ovr->payload;
+
+ if (ovr->field_mask & EDJE_VG_OVR_VISIBLE)
+ BUF_APPENDF("%svisible: %d;\n", inner, p->visible);
+ if (ovr->field_mask & EDJE_VG_OVR_COLOR_RAW)
+ BUF_APPENDF("%scolor: %d %d %d %d;\n", inner,
+ p->color.r, p->color.g, p->color.b, p->color.a);
+ if (ovr->field_mask & EDJE_VG_OVR_COLOR_CLASS)
+ BUF_APPENDF("%scolor_class: \"%s\";\n", inner,
+ p->color.color_class ? p->color.color_class : "");
+ if (ovr->field_mask & EDJE_VG_OVR_TRANSFORM)
+ _edje_vg_emit_xform(&p->xform, buf, inner);
+
+ if (ovr->expected_type == EDJE_VG_NODE_SHAPE)
+ {
+ if (ovr->field_mask & EDJE_VG_OVR_PATH)
+ BUF_APPENDF("%spath: \"%s\";\n", inner, p->shape.path ? p->shape.path : "");
+ if (ovr->field_mask & EDJE_VG_OVR_FILL_RAW)
+ BUF_APPENDF("%sfill.color: %d %d %d %d;\n", inner,
+ p->shape.fill.r, p->shape.fill.g, p->shape.fill.b, p->shape.fill.a);
+ if (ovr->field_mask & EDJE_VG_OVR_FILL_CLASS)
+ BUF_APPENDF("%sfill.color_class: \"%s\";\n", inner,
+ p->shape.fill.color_class ? p->shape.fill.color_class : "");
+ if (ovr->field_mask & EDJE_VG_OVR_STROKE_RAW)
+ BUF_APPENDF("%sstroke.color: %d %d %d %d;\n", inner,
+ p->shape.stroke_color.r, p->shape.stroke_color.g,
+ p->shape.stroke_color.b, p->shape.stroke_color.a);
+ if (ovr->field_mask & EDJE_VG_OVR_STROKE_CLASS)
+ BUF_APPENDF("%sstroke.color_class: \"%s\";\n", inner,
+ p->shape.stroke_color.color_class ? p->shape.stroke_color.color_class : "");
+ if (ovr->field_mask & EDJE_VG_OVR_STROKE_WIDTH)
+ BUF_APPENDF("%sstroke.width: %.6g;\n", inner, p->shape.stroke_width);
+ if (ovr->field_mask & EDJE_VG_OVR_STROKE_CAP)
+ BUF_APPENDF("%sstroke.cap: %s;\n", inner, _vg_cap_str(p->shape.stroke_cap));
+ if (ovr->field_mask & EDJE_VG_OVR_STROKE_JOIN)
+ BUF_APPENDF("%sstroke.join: %s;\n", inner, _vg_join_str(p->shape.stroke_join));
+ if ((ovr->field_mask & EDJE_VG_OVR_STROKE_DASH) &&
+ p->shape.stroke_dash && p->shape.stroke_dash_count > 0)
+ {
+ unsigned int di;
+ BUF_APPENDF("%sstroke.dash:", inner);
+ for (di = 0; di < p->shape.stroke_dash_count; di++)
+ BUF_APPENDF(" %.6g", p->shape.stroke_dash[di]);
+ BUF_APPEND(";\n");
+ }
+ if (ovr->field_mask & EDJE_VG_OVR_FILL_RULE)
+ BUF_APPENDF("%sfill_rule: %s;\n", inner, _vg_fill_rule_str(p->shape.fill_rule));
+ }
+ else if (ovr->expected_type == EDJE_VG_NODE_GRADIENT_LINEAR ||
+ ovr->expected_type == EDJE_VG_NODE_GRADIENT_RADIAL)
+ {
+ if (ovr->field_mask & EDJE_VG_OVR_GRAD_SPREAD)
+ BUF_APPENDF("%sspread: %s;\n", inner, _vg_spread_str(p->gradient.spread));
+ if (ovr->field_mask & EDJE_VG_OVR_GRAD_GEOM)
+ {
+ if (p->gradient.is_radial)
+ {
+ BUF_APPENDF("%scenter: %.6g %.6g;\n", inner, p->gradient.x0, p->gradient.y0);
+ BUF_APPENDF("%sfocal: %.6g %.6g;\n", inner, p->gradient.x1, p->gradient.y1);
+ BUF_APPENDF("%sradius: %.6g;\n", inner, p->gradient.radius);
+ }
+ else
+ {
+ BUF_APPENDF("%sstart: %.6g %.6g;\n", inner, p->gradient.x0, p->gradient.y0);
+ BUF_APPENDF("%send: %.6g %.6g;\n", inner, p->gradient.x1, p->gradient.y1);
+ }
+ }
+ if ((ovr->field_mask & EDJE_VG_OVR_GRAD_STOPS) &&
+ p->gradient.stops && p->gradient.stops_count > 0)
+ {
+ unsigned int si;
+ /* Derive stop-content indent one level deeper than the stop{} opener
+ * (inner), matching the pattern in _edje_vg_emit_gradient_node.
+ * Each level is 3 spaces; I2 (6 chars) is depth 0. */
+ const char *stop_inner =
+ _vg_node_indent((int)(strlen(inner) - strlen(I2)) / 3 + 1);
+ for (si = 0; si < p->gradient.stops_count; si++)
+ {
+ const Edje_Vg_Stop *st = &p->gradient.stops[si];
+ BUF_APPENDF("%sstop {\n", inner);
+ BUF_APPENDF("%soffset: %.6g;\n", stop_inner, st->offset);
+ if (st->color.color_class)
+ {
+ BUF_APPENDF("%scolor: %d %d %d %d;\n", stop_inner,
+ st->color.r, st->color.g, st->color.b, st->color.a);
+ BUF_APPENDF("%scolor_class: \"%s\";\n", stop_inner, st->color.color_class);
+ }
+ else
+ BUF_APPENDF("%scolor: %d %d %d %d;\n", stop_inner,
+ st->color.r, st->color.g, st->color.b, st->color.a);
+ BUF_APPENDF("%s}\n", inner);
+ }
+ }
+ }
+
+ BUF_APPENDF("%s}\n", indent);
+ (void)ret;
+}
+
+/* Emit the top-level vectors {} block for all named trees in the file. */
+static void
+_edje_generate_source_of_vectors_block(const Edje_File *ef, Eina_Strbuf *buf)
+{
+ Eina_Bool ret = EINA_TRUE;
+
+ if (!ef->vector_dir || ef->vector_dir->trees_count == 0) return;
+
+ /* Check whether any tree has a name (only named trees go in vectors {}).
+ * Anonymous trees that are only referenced inline have no name. */
+ Eina_Bool any_named = EINA_FALSE;
+ unsigned int i;
+ for (i = 0; i < ef->vector_dir->trees_count; i++)
+ if (ef->vector_dir->trees[i].name) { any_named = EINA_TRUE; break; }
+ if (!any_named) return;
+
+ BUF_APPEND(I0 "vectors {\n");
+ for (i = 0; i < ef->vector_dir->trees_count; i++)
+ {
+ const Edje_Vg_Tree *t = &ef->vector_dir->trees[i];
+ /* sparse emit — unnamed trees belong to inline descriptions, not here */
+ if (!t->name) continue;
+ BUF_APPENDF(I1 "vector { name: \"%s\";\n", t->name);
+ /* child_depth = 1 → uses I2 for viewbox line, I2/I3 for nodes */
+ _edje_vg_emit_tree_body(t, buf, 1);
+ BUF_APPEND(I1 "}\n");
+ }
+ BUF_APPEND(I0 "}\n\n");
+ (void)ret;
+}
+
+/* Emit vector-specific content for a VECTOR part's description. */
+static void
+_edje_generate_source_state_vector(Edje *ed,
+ Edje_Part_Description_Common *pd,
+ Eina_Strbuf *buf)
+{
+ Eina_Bool ret = EINA_TRUE;
+ Edje_Part_Description_Vector *vd = (Edje_Part_Description_Vector *)pd;
+ const Eina_List *l;
+ const Edje_Vg_Override *ovr;
+
+ if (vd->vg.tree_id >= 0 &&
+ ed->file->vector_dir &&
+ (unsigned int)vd->vg.tree_id < ed->file->vector_dir->trees_count)
+ {
+ const Edje_Vg_Tree *t = &ed->file->vector_dir->trees[vd->vg.tree_id];
+ if (t->name)
+ {
+ /* Mode A: reference a named toplevel tree */
+ BUF_APPENDF(I5 "vector.use: \"%s\";\n", t->name);
+ }
+ else
+ {
+ /* Mode B anonymous: emit inline vector{} block */
+ BUF_APPEND(I5 "vector {\n");
+ _edje_vg_emit_tree_body(t, buf, 5);
+ BUF_APPEND(I5 "}\n");
+ }
+ }
+
+ /* Emit per-node overrides — sparse emit, only fields in field_mask. */
+ EINA_LIST_FOREACH(vd->vg.overrides, l, ovr)
+ _edje_vg_emit_override(ovr, buf, I5, I6);
+
+ (void)ret;
+}
+
+/* end VG source generation — Phase 8.1 */
+
static void
_edje_generate_source_state_image(Edje_Edit *eed, Evas_Object *obj,
const char *part, const char *state, double value,
@@ -15475,6 +15944,10 @@ _edje_generate_source_of_state(Evas_Object *obj, const char *part, const char *s
if (rp->part->type == EDJE_PART_TYPE_IMAGE)
_edje_generate_source_state_image(eed, obj, part, state, value, pd, inherit_pd, buf) ;
+ //Vector
+ if (rp->part->type == EDJE_PART_TYPE_VECTOR)
+ _edje_generate_source_state_vector(ed, pd, buf);
+
//Proxy
if (rp->part->type == EDJE_PART_TYPE_PROXY)
_edje_generate_source_state_proxy(ed, pd, inherit_pd, buf);
@@ -16215,6 +16688,9 @@ _edje_generate_source(Evas_Object *obj)
}
}
+ /* Vectors — emit named trees for edje_decc and full-source paths */
+ _edje_generate_source_of_vectors_block(ed->file, buf);
+
/* Styles */
if ((ll = edje_edit_styles_list_get(obj)))
{
diff --git a/src/lib/edje/edje_private.h b/src/lib/edje/edje_private.h
index 8ffeffe941..f678bbd498 100644
--- a/src/lib/edje/edje_private.h
+++ b/src/lib/edje/edje_private.h
@@ -774,6 +774,7 @@ struct _Edje_Vg_Node {
};
struct _Edje_Vg_Tree {
+ const char *name; /* tree name in the toplevel vectors {} block; NULL for inline/anonymous */
double vbx, vby, vbw, vbh; /* viewbox */
Edje_Vg_Node *root; /* always a CONTAINER */
};
diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index 370e0ea41b..26f7a09848 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -201,6 +201,7 @@ EAPI void
_edje_vg_tree_free_fields(Edje_Vg_Tree *t)
{
if (!t) return;
+ if (t->name) { eina_stringshare_del(t->name); t->name = NULL; }
_edje_vg_node_free(t->root);
t->root = NULL;
}
@@ -587,6 +588,7 @@ _edje_vg_tree_dup(const Edje_Vg_Tree *t)
Edje_Vg_Tree *dst = calloc(1, sizeof(Edje_Vg_Tree));
if (!dst) return NULL;
+ dst->name = eina_stringshare_ref(t->name);
dst->vbx = t->vbx;
dst->vby = t->vby;
dst->vbw = t->vbw;
diff --git a/src/tests/edje/edje_test_vector_states.c b/src/tests/edje/edje_test_vector_states.c
index 16696b2c84..2aa247f1f2 100644
--- a/src/tests/edje/edje_test_vector_states.c
+++ b/src/tests/edje/edje_test_vector_states.c
@@ -13,6 +13,9 @@
#include <Ecore_Eo.h>
#include <Ecore_Evas.h>
#include "edje_suite.h"
+/* Phase 8.1: source-generation round-trip test needs the Edje_Edit API. */
+#define EDJE_EDIT_IS_UNSTABLE_AND_I_KNOW_ABOUT_IT
+#include <Edje_Edit.h>
/*
* Forward declarations for internal VG-tree helpers defined in
@@ -38,7 +41,7 @@ typedef enum {
* A partial view struct is safe for accessing only those fields.
* DO NOT access any other fields through this view. */
struct _Edje_Vg_Node { const char *name; Edje_Vg_Node_Type type; /* opaque tail */ };
-struct _Edje_Vg_Tree { double vbx, vby, vbw, vbh; Edje_Vg_Node *root; };
+struct _Edje_Vg_Tree { const char *name; double vbx, vby, vbw, vbh; Edje_Vg_Node *root; };
/* Phase 1 helpers */
Edje_Vg_Tree *_edje_vg_tree_new_for_test(void);
@@ -2416,6 +2419,108 @@ EFL_START_TEST(edje_vg_diag_vec004_werror_escalation)
}
EFL_END_TEST
+/*
+ * edje_vg_source_gen_round_trip (Phase 8.1)
+ *
+ * Loads test_vector_states.edj via the Edje_Edit API, calls
+ * edje_edit_source_generate() on a VECTOR part group and verifies
+ * the emitted source contains:
+ * - "vectors {" — the top-level vectors {} block
+ * - "vector.use:" — Mode A reference in the default state
+ * - "vector.shape" — an override block in the hover state
+ * - "color_class:" — the vg_accent binding from tile_classed
+ *
+ * The stronger recompile-and-compare check is skipped here because
+ * we have no reliable in-tree edje_cc path from within the test
+ * binary (spawning an external process would require knowing the
+ * install prefix). The structural string checks are sufficient to
+ * prove the emitter fires the correct code paths.
+ */
+EFL_START_TEST(edje_vg_source_gen_round_trip)
+{
+ const char *edj = TESTS_BUILD_DIR "/data/test_vector_states.edj";
+ Ecore_Evas *ee;
+ Evas *evas;
+ Evas_Object *edit_obj;
+ const char *src;
+
+ ee = ecore_evas_buffer_new(100, 100);
+ fail_if(!ee);
+ evas = ecore_evas_get(ee);
+ fail_if(!evas);
+
+ /* Load the hover group (uses tile_a + override on "body") via Edje_Edit.
+ * Any vector group works; this one exercises both vector.use and override. */
+ edit_obj = edje_edit_object_add(evas);
+ fail_if(!edit_obj);
+ fail_unless(edje_object_file_set(edit_obj, edj, "ui/box_a_hover"));
+
+ src = ""
+ fail_if(!src);
+
+ ck_assert_msg(strstr(src, "vectors {") != NULL,
+ "Source must contain top-level 'vectors {' block");
+ ck_assert_msg(strstr(src, "vector.use:") != NULL,
+ "Source must contain 'vector.use:' in default description");
+ ck_assert_msg(strstr(src, "vector.shape") != NULL,
+ "Source must contain 'vector.shape' override block in hover description");
+
+ edje_edit_string_free(src);
+ evas_object_del(edit_obj);
+ ecore_evas_free(ee);
+
+ /* Second check: load the classed group and confirm color_class: is emitted. */
+ ee = ecore_evas_buffer_new(100, 100);
+ fail_if(!ee);
+ evas = ecore_evas_get(ee);
+ fail_if(!evas);
+
+ edit_obj = edje_edit_object_add(evas);
+ fail_if(!edit_obj);
+ fail_unless(edje_object_file_set(edit_obj, edj, "ui/box_classed"));
+
+ src = ""
+ fail_if(!src);
+
+ ck_assert_msg(strstr(src, "color_class:") != NULL,
+ "Source for box_classed must contain 'color_class:' binding");
+
+ edje_edit_string_free(src);
+ evas_object_del(edit_obj);
+ ecore_evas_free(ee);
+
+ /* Third check: edje_edit_full_source_generate exercises _edje_generate_source
+ * (the path used by edje_decc and edje_edit_source_save). Verify the same
+ * structural substrings are present — this would fail if the vectors block
+ * hook were missing from that pipeline. */
+ ee = ecore_evas_buffer_new(100, 100);
+ fail_if(!ee);
+ evas = ecore_evas_get(ee);
+ fail_if(!evas);
+
+ edit_obj = edje_edit_object_add(evas);
+ fail_if(!edit_obj);
+ fail_unless(edje_object_file_set(edit_obj, edj, "ui/box_a_hover"));
+
+ {
+ char *full_src = edje_edit_full_source_generate(edit_obj);
+ fail_if(!full_src);
+
+ ck_assert_msg(strstr(full_src, "vectors {") != NULL,
+ "full_source_generate must contain top-level 'vectors {' block");
+ ck_assert_msg(strstr(full_src, "vector.use:") != NULL,
+ "full_source_generate must contain 'vector.use:' in default description");
+ ck_assert_msg(strstr(full_src, "vector.shape") != NULL,
+ "full_source_generate must contain 'vector.shape' override block");
+
+ free(full_src);
+ }
+
+ evas_object_del(edit_obj);
+ ecore_evas_free(ee);
+}
+EFL_END_TEST
+
void
edje_test_vector_states(TCase *tc)
{
@@ -2469,4 +2574,6 @@ edje_test_vector_states(TCase *tc)
tcase_add_test(tc, edje_vg_diag_vec004_topology_mismatch);
tcase_add_test(tc, edje_vg_diag_vec004_no_false_positive);
tcase_add_test(tc, edje_vg_diag_vec004_werror_escalation);
+ /* Phase 8.1 source-generation round-trip */
+ tcase_add_test(tc, edje_vg_source_gen_round_trip);
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.