This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch main
in repository ego.
View the commit online.
commit 988b1707300faae71eb2a72c545e2921f7d95774
Author: [email protected] <[email protected]>
AuthorDate: Wed Apr 1 19:34:28 2026 -0600
feat(ego-gen): add part accessor template with proxy structs
Generate typed part proxy structs and accessor methods for widget
parts declared in Eolian. Each proxy method atomically calls
efl_part + interface method via a C helper. Collision detection
skips parts whose names match existing property bindings.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---
cmd/ego-gen/generator.go | 1 +
cmd/ego-gen/generator_test.go | 23 +++---
cmd/ego-gen/main.go | 18 ++++-
cmd/ego-gen/templates/class.go.tmpl | 151 ++++++++++++++++++++++++++++++++++++
4 files changed, 180 insertions(+), 13 deletions(-)
diff --git a/cmd/ego-gen/generator.go b/cmd/ego-gen/generator.go
index de4a538..e31360f 100644
--- a/cmd/ego-gen/generator.go
+++ b/cmd/ego-gen/generator.go
@@ -322,6 +322,7 @@ func NewGenerator(templateDir, outputDir string) (*Generator, error) {
"isEinaError": IsEinaError,
"isEinaFuture": IsEinaFuture,
"isEinaContent": IsEinaContent,
+ "trimPrefix": strings.TrimPrefix,
}
pattern := filepath.Join(templateDir, "*.tmpl")
diff --git a/cmd/ego-gen/generator_test.go b/cmd/ego-gen/generator_test.go
index 0f1c20f..bf630c5 100644
--- a/cmd/ego-gen/generator_test.go
+++ b/cmd/ego-gen/generator_test.go
@@ -1540,20 +1540,19 @@ func TestGenerateClass_PartAccessor(t *testing.T) {
src := string(content)
- // These will fail until Task 3 adds the template — that's expected.
- // For now, just verify the file is generated without error.
- t.Logf("Generated file length: %d bytes", len(content))
-
- // Check that these are NOT present yet (template hasn't been updated):
- partsPatterns := []string{
+ mustContain := []string{
"type defaultItemIconPart struct",
- "func (o *DefaultItem) Icon()",
+ "parent unsafe.Pointer",
+ "// Icon returns the icon part of DefaultItem.",
+ "func (o *DefaultItem) Icon() defaultItemIconPart",
+ "func (p defaultItemIconPart) Content() unsafe.Pointer",
+ "func (p defaultItemIconPart) SetContent(val efl.Objecter)",
+ "efl_part",
}
- for _, p := range partsPatterns {
- if strings.Contains(src, p) {
- t.Logf("Found part pattern (template already supports parts): %q", p)
- } else {
- t.Logf("Part pattern not yet present (expected, template not updated): %q", p)
+
+ for _, want := range mustContain {
+ if !strings.Contains(src, want) {
+ t.Errorf("missing %q\n%s", want, src)
}
}
}
diff --git a/cmd/ego-gen/main.go b/cmd/ego-gen/main.go
index 6357864..3c3cd0d 100644
--- a/cmd/ego-gen/main.go
+++ b/cmd/ego-gen/main.go
@@ -612,6 +612,9 @@ func buildClassData(c *Class, pkgOptionNames map[string]map[string]bool) (ClassD
}
}
+ // Collect declared parts with collision detection.
+ parts := buildPartsData(c, goName, goNames)
+
// The class-specific .eo.h file name mirrors the C prefix.
// e.g. "Efl.Ui.Button" → cPrefix "efl_ui_button" → "efl_ui_button.eo.h"
eoHeaderFile := cPrefix + ".eo.h"
@@ -817,7 +820,8 @@ func buildClassData(c *Class, pkgOptionNames map[string]map[string]bool) (ClassD
GlobalFunctions: globalFunctions,
CallbackTypes: callbackTypes,
HasCallbacks: len(callbackTypes) > 0,
- HasColor: hasColorProperty(properties) || hasColorMethod(methods),
+ Parts: parts,
+ HasColor: hasColorProperty(properties) || hasColorMethod(methods) || hasColorParts(parts),
HasImageTypes: hasImageTypesProperty(properties) || hasImageTypesMethod(methods) || hasImageTypesIndexed(indexedProperties) || hasImageTypesIterator(iteratorMethods) || hasImageTypesConstructors(constructors) || hasImageTypesConstructors(propertyOptions),
HasTime: hasTimeProperty(properties),
}, nil
@@ -844,6 +848,18 @@ func hasColorMethod(methods []MethodData) bool {
return false
}
+// hasColorParts reports whether any part method uses color.RGBA.
+func hasColorParts(parts []PartData) bool {
+ for _, p := range parts {
+ for _, m := range p.PartMethods {
+ if m.IsColor {
+ return true
+ }
+ }
+ }
+ return false
+}
+
// hasImageTypesProperty reports whether any property uses image.Rectangle or
// image.Point (Eina_Rect or Eina_Position2D), used to decide whether to import
// "image" in the generated file.
diff --git a/cmd/ego-gen/templates/class.go.tmpl b/cmd/ego-gen/templates/class.go.tmpl
index 5c88a34..9238186 100644
--- a/cmd/ego-gen/templates/class.go.tmpl
+++ b/cmd/ego-gen/templates/class.go.tmpl
@@ -125,6 +125,56 @@ extern void {{.GoTrampolineName}}(void *data{{range .Params}}, {{.CType}} {{.GoN
{{- end}}
{{- end}}
+{{- range .Parts}}
+{{- range .PartMethods}}
+{{- if .IsGetter}}
+{{- if .IsString}}
+static const char *{{.CHelper}}(const Eo *obj, const char *part_name) {
+ return {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name));
+}
+{{- else if .IsBool}}
+static Eina_Bool {{.CHelper}}(const Eo *obj, const char *part_name) {
+ return {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name));
+}
+{{- else if .IsColor}}
+static void {{.CHelper}}(const Eo *obj, const char *part_name, int *r, int *g, int *b, int *a) {
+ {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name), r, g, b, a);
+}
+{{- else if .IsEoParam}}
+static Eo *{{.CHelper}}(const Eo *obj, const char *part_name) {
+ return {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name));
+}
+{{- else}}
+static void *{{.CHelper}}(const Eo *obj, const char *part_name) {
+ return (void *)(uintptr_t){{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name));
+}
+{{- end}}
+{{- else if .IsSetter}}
+{{- if .IsString}}
+static void {{.CHelper}}(Eo *obj, const char *part_name, const char *val) {
+ {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name), val);
+}
+{{- else if .IsBool}}
+static void {{.CHelper}}(Eo *obj, const char *part_name, Eina_Bool val) {
+ {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name), val);
+}
+{{- else if .IsColor}}
+static void {{.CHelper}}(Eo *obj, const char *part_name, int r, int g, int b, int a) {
+ {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name), r, g, b, a);
+}
+{{- else if .IsEoParam}}
+static void {{.CHelper}}(Eo *obj, const char *part_name, Eo *val) {
+ {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name), val);
+}
+{{- else}}
+static void {{.CHelper}}(Eo *obj, const char *part_name, void *val) {
+ {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name), val);
+}
+{{- end}}
+{{- end}}
+{{- end}}
+{{- end}}
+
{{- range .Properties}}
{{- if and .HasGet (isPointerType .GoType)}}
// _ego_get_{{.CGetName}} wraps the getter, casting the return value through
@@ -1137,6 +1187,107 @@ func {{$gf.GoName}}({{range $i, $p := $gf.Params}}{{if $i}}, {{end}}{{$p.GoName}
}
{{- end}}
+{{- range $part := .Parts}}
+
+// {{$part.ProxyTypeName}} provides access to the "{{$part.PartName}}" part of {{$.GoName}}.
+type {{$part.ProxyTypeName}} struct {
+ parent unsafe.Pointer
+ name string
+}
+
+// {{$part.GoAccessorName}} returns the {{$part.PartName}} part of {{$.GoName}}.
+func (o *{{$.GoName}}) {{$part.GoAccessorName}}() {{$part.ProxyTypeName}} {
+ return {{$part.ProxyTypeName}}{parent: o.Eo(), name: "{{$part.PartName}}"}
+}
+
+{{- range $m := $part.PartMethods}}
+{{- if $m.IsGetter}}
+{{- if $m.IsString}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}() string {
+ cName := C.CString(p.name)
+ defer C.free(unsafe.Pointer(cName))
+ return C.GoString(C.{{$m.CHelper}}((*C.Eo)(p.parent), cName))
+}
+{{- else if $m.IsBool}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}() bool {
+ cName := C.CString(p.name)
+ defer C.free(unsafe.Pointer(cName))
+ return C.{{$m.CHelper}}((*C.Eo)(p.parent), cName) != 0
+}
+{{- else if $m.IsColor}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}() color.RGBA {
+ cName := C.CString(p.name)
+ defer C.free(unsafe.Pointer(cName))
+ var cr, cg, cb, ca C.int
+ C.{{$m.CHelper}}((*C.Eo)(p.parent), cName, &cr, &cg, &cb, &ca)
+ return color.RGBA{R: uint8(cr), G: uint8(cg), B: uint8(cb), A: uint8(ca)}
+}
+{{- else if $m.IsEoParam}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}() unsafe.Pointer {
+ cName := C.CString(p.name)
+ defer C.free(unsafe.Pointer(cName))
+ return unsafe.Pointer(C.{{$m.CHelper}}((*C.Eo)(p.parent), cName))
+}
+{{- else}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}() {{$m.GoReturnType}} {
+ cName := C.CString(p.name)
+ defer C.free(unsafe.Pointer(cName))
+ return {{$m.GoReturnType}}(C.{{$m.CHelper}}((*C.Eo)(p.parent), cName))
+}
+{{- end}}
+{{- else if $m.IsSetter}}
+{{- if $m.IsString}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}({{$m.GoParamName}} string) {
+ cName := C.CString(p.name)
+ defer C.free(unsafe.Pointer(cName))
+ cVal := C.CString({{$m.GoParamName}})
+ defer C.free(unsafe.Pointer(cVal))
+ C.{{$m.CHelper}}((*C.Eo)(p.parent), cName, cVal)
+}
+{{- else if $m.IsBool}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}({{$m.GoParamName}} bool) {
+ cName := C.CString(p.name)
+ defer C.free(unsafe.Pointer(cName))
+ var cVal C.Eina_Bool
+ if {{$m.GoParamName}} { cVal = 1 }
+ C.{{$m.CHelper}}((*C.Eo)(p.parent), cName, cVal)
+}
+{{- else if $m.IsColor}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}(c color.RGBA) {
+ cName := C.CString(p.name)
+ defer C.free(unsafe.Pointer(cName))
+ C.{{$m.CHelper}}((*C.Eo)(p.parent), cName, C.int(c.R), C.int(c.G), C.int(c.B), C.int(c.A))
+}
+{{- else if $m.IsEoParam}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}({{$m.GoParamName}} efl.Objecter) {
+ cName := C.CString(p.name)
+ defer C.free(unsafe.Pointer(cName))
+ var cVal *C.Eo
+ if {{$m.GoParamName}} != nil { cVal = (*C.Eo)({{$m.GoParamName}}.Eo()) }
+ C.{{$m.CHelper}}((*C.Eo)(p.parent), cName, cVal)
+}
+{{- else}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}({{$m.GoParamName}} {{$m.GoParamType}}) {
+ cName := C.CString(p.name)
+ defer C.free(unsafe.Pointer(cName))
+ C.{{$m.CHelper}}((*C.Eo)(p.parent), cName, {{$m.GoParamName}})
+}
+{{- end}}
+{{- end}}
+{{- end}}
+
+{{- end}}
+
{{- define "paramArg"}}
{{- if .IsEo}}(*C.Eo)({{.GoName}}.Eo())
{{- else if or (needsStringConversion .CType) (isBoolType .CType)}}c{{.GoName}}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.