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 b2f309420954eeca36b7bbed18f0ba2f3a2ef706
Author: [email protected] <[email protected]>
AuthorDate: Tue Mar 31 15:47:47 2026 -0600
feat(ego-gen): add struct-return methods and color method params
Generate C decomposition wrappers for methods returning known
structs by value. Detect (r,g,b,a) color patterns in method
in/out params and map to color.RGBA. Unblocks textgrid palette,
gesture, calc, cursor, scene, and path methods.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
cmd/ego-gen/generator.go | 19 +++--
cmd/ego-gen/generator_test.go | 49 +++++++++++++
cmd/ego-gen/main.go | 136 ++++++++++++++++++++++++++----------
cmd/ego-gen/templates/class.go.tmpl | 68 +++++++++++++++++-
4 files changed, 227 insertions(+), 45 deletions(-)
diff --git a/cmd/ego-gen/generator.go b/cmd/ego-gen/generator.go
index 3ac986f..83d3fc4 100644
--- a/cmd/ego-gen/generator.go
+++ b/cmd/ego-gen/generator.go
@@ -63,13 +63,18 @@ type ConstructorData struct {
// MethodData describes a single method on an EFL class.
type MethodData struct {
- GoName string
- CName string
- Params []ParamData
- ReturnType string // Go type, empty if void
- ReturnCType string
- OutParams []OutParamData // out-param struct returns
- CallbackParams []CallbackParamData
+ GoName string
+ CName string
+ Params []ParamData
+ ReturnType string // Go type, empty if void
+ ReturnCType string
+ OutParams []OutParamData // out-param struct returns
+ CallbackParams []CallbackParamData
+ IsStructReturn bool // true when method returns a known struct by value
+ StructReturnInfo *StructInfo // populated when IsStructReturn
+ IsColor bool // true when method has (r,g,b,a) color in-params or out-params
+ HasColorOut bool // true when color is in the out-params (returns color.RGBA)
+ HasColorIn bool // true when color is in the in-params (accepts color.RGBA)
}
// ParamData describes a single parameter of a method.
diff --git a/cmd/ego-gen/generator_test.go b/cmd/ego-gen/generator_test.go
index 36e8b0a..ca08f21 100644
--- a/cmd/ego-gen/generator_test.go
+++ b/cmd/ego-gen/generator_test.go
@@ -1405,3 +1405,52 @@ func TestGenerateClass_ColorProperty(t *testing.T) {
}
}
}
+
+func TestGenerateClass_StructReturnMethod(t *testing.T) {
+ g, outDir := newTestGenerator(t)
+ data := ClassData{
+ PackageName: "layout",
+ GoName: "Calc",
+ EolianName: "Efl.Layout.Calc",
+ CClassName: "EFL_LAYOUT_CALC_CLASS",
+ CClassGetFunc: "efl_layout_calc_class_get",
+ CPrefix: "efl_layout_calc",
+ EOHeaderFile: "efl_layout_calc.eo.h",
+ IsAbstract: true,
+ Methods: []MethodData{
+ {
+ GoName: "CalcSizeMin",
+ CName: "efl_layout_calc_size_min",
+ ReturnType: "efl.Size2D",
+ ReturnCType: "Eina_Size2D",
+ IsStructReturn: true,
+ StructReturnInfo: &StructInfo{
+ GoType: "efl.Size2D",
+ CType: "Eina_Size2D",
+ GoFields: []string{"W", "H"},
+ CFields: []string{"w", "h"},
+ CFieldTypes: []string{"int", "int"},
+ },
+ },
+ },
+ }
+ if err := g.GenerateClass(data); err != nil {
+ t.Fatalf("GenerateClass: %v", err)
+ }
+ outFile := filepath.Join(outDir, "layout", "calc.go")
+ content, err := os.ReadFile(outFile)
+ if err != nil {
+ t.Fatalf("not found: %v", err)
+ }
+ src := string(content)
+ mustContain := []string{
+ "_ego_ret_struct_efl_layout_calc_size_min",
+ "func (o *Calc) CalcSizeMin() efl.Size2D",
+ "efl.Size2D{",
+ }
+ 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 aeb7fa6..105b42c 100644
--- a/cmd/ego-gen/main.go
+++ b/cmd/ego-gen/main.go
@@ -33,14 +33,9 @@ var classBlocklist = map[string]bool{
// or have signatures that are incompatible with simple cgo wrappers. Entries
// are the full C function name as returned by Eolian (e.g. "efl_access_action_do").
var methodBlocklist = map[string]bool{
- // efl.Layout.Calc — return struct by value; cannot be converted to unsafe.Pointer by cgo
- "efl_layout_calc_size_min": true,
- "efl_layout_calc_parts_extends": true,
// efl.Layout.Signal — callback params handled via callback trampoline generation.
- // efl.Access.Text — character_extents_get and range_extents_get take an extra Eina_Rect* out-param
- // that the generator does not see from Eolian (struct-valued out-param pattern).
- "efl_access_text_character_extents_get": true,
- "efl_access_text_range_extents_get": true,
+ // efl.Access.Text — character_extents_get and range_extents_get: out-param struct handled via known struct support.
+ // efl.Canvas.Scene — image_max_size getter: out-param struct handled via known struct support.
// efl.Access.Object — non-Eo first arg or global function (incompatible with Eo-receiver generator)
"efl_access_object_event_emit": true,
"efl_access_object_event_handler_add": true,
@@ -67,13 +62,11 @@ var methodBlocklist = map[string]bool{
"efl_text_formatter_attribute_clear": true,
// efl.ThreadIO — callback params handled via callback trampoline generation.
// efl.Input.Pointer — position setters now handled via known struct support.
- // efl.Text_Cursor.Object — returns Eina_Rect by value (struct return not supported in indexed property)
- "efl_text_cursor_object_lower_cursor_geometry_get": true,
- "efl_text_cursor_object_cursor_geometry_get": true,
- // efl.Gfx.Buffer — extra out-param (stride); struct-valued return
- "efl_gfx_buffer_map": true,
- "efl_gfx_buffer_span_get": true,
- "efl_gfx_buffer_managed_get": true,
+ // efl.Text_Cursor.Object — struct-return methods now handled via struct-return support.
+ // efl.Gfx.Buffer — extra out-param (stride); struct-valued return with extra stride param
+ "efl_gfx_buffer_map": true,
+ "efl_gfx_buffer_span_get": true,
+ // efl.Gfx.Buffer — managed_get: handled via struct-return support.
// efl.Gfx.Buffer — size setter now handled via known struct support.
// efl.Gfx.Entity — geometry setter takes Eina_Rect by value
// position_set and size_set now handled via known struct support.
@@ -197,18 +190,14 @@ var methodBlocklist = map[string]bool{
"efl_gesture_recognizer_recognize": true,
// efl.Canvas.Gesture — hotspot setter now handled via known struct support.
// efl.Input_Text — imdata setter now handled via slice property support.
- // efl.Canvas.Scene — pointer_position has extra out-param
- "efl_canvas_scene_pointer_position_get": true,
- // efl.Canvas.Textgrid — multiple out-params for palette colors
- "efl_canvas_textgrid_palette_get": true,
- "efl_canvas_textgrid_palette_set": true,
+ // efl.Canvas.Scene — pointer_position: out-param struct handled via known struct support.
+ // efl.Canvas.Textgrid — palette get/set: color out-param/in-param handled via color support.
// efl.Layout.Group — size_min/size_max now handled via known struct support.
// efl.Screen — returns now handled via known struct support.
// efl.Canvas.Gesture — momentum properties return Eina_Vector2, now handled via struct support.
// efl.Gfx.Hint — combined getters/setters now handled via known struct support.
- // efl.Io.Queue — returns Eina_Slice struct by value
- "efl_io_queue_slice_get": true,
- // efl.Text_Cursor.Object — returns Eina_Rect, now handled via known struct support.
+ // efl.Io.Queue — returns Eina_Slice: handled via struct-return support.
+ // efl.Text_Cursor.Object — returns Eina_Rect, now handled via struct-return support.
// efl.Access.Object — efl_access_object_access_root_get is a global function but
// Eolian reports it as a property getter; block it here to prevent the Eo-receiver
// property wrapper from being emitted (it is also in globalFunctionSet for the method path).
@@ -224,13 +213,8 @@ var methodBlocklist = map[string]bool{
// globalFunctionSet check.
"efl_animation_default_duration_get": true,
"efl_animation_default_duration_set": true,
- // efl.Canvas.GestureTouch — methods that return Eina_Vector2 by value; cgo cannot
- // convert struct return values to unsafe.Pointer.
- "efl_gesture_touch_delta": true,
- "efl_gesture_touch_distance": true,
- // efl.Canvas.Scene — image_max_size getter takes a Eina_Size2D out-param in addition
- // to returning Eina_Bool; the generator only sees the bool return and omits the out-param.
- "efl_canvas_scene_image_max_size_get": true,
+ // efl.Canvas.GestureTouch — methods returning Eina_Vector2: handled via struct-return support.
+ // efl.Canvas.Scene — image_max_size getter: Eina_Size2D out-param handled via known struct support.
// efl.Ui.Theme — efl_ui_theme_default_get is a global function but Eolian reports it
// as a property getter; block it here to prevent the Eo-receiver wrapper.
"efl_ui_theme_default_get": true,
@@ -910,7 +894,7 @@ func buildClassData(c *Class, pkgOptionNames map[string]map[string]bool) (ClassD
GlobalFunctions: globalFunctions,
CallbackTypes: callbackTypes,
HasCallbacks: len(callbackTypes) > 0,
- HasColor: hasColorProperty(properties),
+ HasColor: hasColorProperty(properties) || hasColorMethod(methods),
}, nil
}
@@ -925,6 +909,16 @@ func hasColorProperty(props []PropertyData) bool {
return false
}
+// hasColorMethod reports whether any method in the slice has IsColor set.
+func hasColorMethod(methods []MethodData) bool {
+ for _, m := range methods {
+ if m.IsColor {
+ return true
+ }
+ }
+ return false
+}
+
// deduplicateIteratorStructTypes returns one representative IteratorMethodData
// per distinct StructCType among methods with ElemIsStruct == true. This is
// used to generate one set of C field-accessor helpers per struct type, rather
@@ -960,6 +954,36 @@ func isColorPattern(vals []MultiValueData) bool {
return true
}
+// isOutParamColorPattern reports whether a slice of OutParamData represents an
+// EFL (r, g, b, a int) color tuple coming from out-parameters.
+func isOutParamColorPattern(ops []OutParamData) bool {
+ if len(ops) != 4 {
+ return false
+ }
+ names := [4]string{"r", "g", "b", "a"}
+ for i, op := range ops {
+ if op.GoName != names[i] || op.GoType != "int" {
+ return false
+ }
+ }
+ return true
+}
+
+// isInParamColorPattern reports whether a slice of ParamData (exactly 4 entries)
+// represents an EFL (r, g, b, a int) color tuple as input parameters.
+func isInParamColorPattern(ps []ParamData) bool {
+ if len(ps) != 4 {
+ return false
+ }
+ names := [4]string{"r", "g", "b", "a"}
+ for i, p := range ps {
+ if p.GoName != names[i] || p.GoType != "int" {
+ return false
+ }
+ }
+ return true
+}
+
// isSimpleCgoType reports whether a C type can be used directly in a cgo call
// as a plain integer/float value. These are the types that can safely appear as
// multiple return values or multiple parameters without needing any C wrapper.
@@ -1247,14 +1271,52 @@ func buildMethodData(f *Function, cPrefix string) MethodData {
}
}
+ // Detect struct-return: when the return C type is a known non-slice struct,
+ // generate a C decomposition wrapper instead of trying to cast the return
+ // value through uintptr_t (which cgo does not support for aggregate types).
+ var isStructReturn bool
+ var structReturnInfo *StructInfo
+ if returnCType != "" {
+ if si := GetStructInfo(returnCType); si != nil && !si.IsSlice {
+ isStructReturn = true
+ structReturnInfo = si
+ returnType = si.GoType
+ }
+ }
+
+ // Detect color out-param pattern: if outParams is exactly 4 entries forming
+ // an (r, g, b, a int) color tuple, collapse them to a color.RGBA return.
+ var isColor, hasColorOut, hasColorIn bool
+ if len(outParams) == 4 && isOutParamColorPattern(outParams) {
+ isColor = true
+ hasColorOut = true
+ outParams = nil // collapse out-params into the color return
+ }
+
+ // Detect color in-param pattern: if the last 4 params form (r, g, b, a int),
+ // collapse them into a single color.RGBA parameter.
+ if !isColor && len(params) >= 4 {
+ last4 := params[len(params)-4:]
+ if isInParamColorPattern(last4) {
+ isColor = true
+ hasColorIn = true
+ params = params[:len(params)-4] // remove the r,g,b,a params
+ }
+ }
+
return MethodData{
- GoName: goName,
- CName: cName,
- Params: params,
- ReturnType: returnType,
- ReturnCType: returnCType,
- OutParams: outParams,
- CallbackParams: callbackParams,
+ GoName: goName,
+ CName: cName,
+ Params: params,
+ ReturnType: returnType,
+ ReturnCType: returnCType,
+ OutParams: outParams,
+ CallbackParams: callbackParams,
+ IsStructReturn: isStructReturn,
+ StructReturnInfo: structReturnInfo,
+ IsColor: isColor,
+ HasColorOut: hasColorOut,
+ HasColorIn: hasColorIn,
}
}
diff --git a/cmd/ego-gen/templates/class.go.tmpl b/cmd/ego-gen/templates/class.go.tmpl
index 0ca1d69..66d2fc4 100644
--- a/cmd/ego-gen/templates/class.go.tmpl
+++ b/cmd/ego-gen/templates/class.go.tmpl
@@ -254,6 +254,18 @@ static void _ego_outparam_{{$m.CName}}(Eo *obj{{range $i, $p := $m.Params}}, {{i
{{- end}}
{{- end}}
{{- end}}
+{{- range $m := .Methods}}
+{{- if $m.IsStructReturn}}
+// _ego_ret_struct_{{$m.CName}} wraps the method, decomposing the returned struct
+// into individual out-params for safe cgo consumption.
+static void _ego_ret_struct_{{$m.CName}}(Eo *obj{{range $m.Params}}, {{if needsStringConversion .CType}}const char *{{else if isBoolType .CType}}Eina_Bool{{else if .IsEo}}Eo *{{else if isPointerType .GoType}}void *{{else}}{{.CType}}{{end}} {{.GoName}}{{end}}{{range $i, $f := $m.StructReturnInfo.CFields}}, {{index $m.StructReturnInfo.CFieldTypes $i}} *out_{{$f}}{{end}}) {
+ {{$m.StructReturnInfo.CType}} _r = {{$m.CName}}(obj{{range $m.Params}}, {{.GoName}}{{end}});
+{{- range $i, $f := $m.StructReturnInfo.CFields}}
+ *out_{{$f}} = _r{{$m.StructReturnInfo.CAccessPrefix}}.{{$f}};
+{{- end}}
+}
+{{- end}}
+{{- end}}
{{- range .Events}}
// _ego_ev_{{.CEventName}} returns the event description pointer using the EFL
// macro, avoiding a conflicting extern declaration for the symbol name.
@@ -488,7 +500,61 @@ func (o *{{$.GoName}}) {{$prop.GoSetter}}(val {{if $prop.IsEo}}efl.Objecter{{els
{{- end}}
{{- range .Methods}}
-{{- if .CallbackParams}}
+{{- if .IsStructReturn}}
+{{- $m := .}}
+
+// {{$m.GoName}} calls the {{$m.CName}} method on {{$.GoName}}.
+func (o *{{$.GoName}}) {{$m.GoName}}({{range $i, $p := $m.Params}}{{if $i}}, {{end}}{{$p.GoName}} {{if $p.IsEo}}efl.Objecter{{else}}{{$p.GoType}}{{end}}{{end}}) {{$m.StructReturnInfo.GoType}} {
+{{- range $m.Params}}
+{{- if needsStringConversion .CType}}
+ c{{.GoName}} := C.CString({{.GoName}})
+ defer C.free(unsafe.Pointer(c{{.GoName}}))
+{{- else if isBoolType .CType}}
+ var c{{.GoName}} C.{{cTypeToCgo .CType}}
+ if {{.GoName}} { c{{.GoName}} = 1 }
+{{- end}}
+{{- end}}
+{{- range $i, $f := $m.StructReturnInfo.CFields}}
+ var c{{$f}} C.{{index $m.StructReturnInfo.CFieldTypes $i}}
+{{- end}}
+ C._ego_ret_struct_{{$m.CName}}((*C.Eo)(o.Eo()){{range $m.Params}}, {{template "paramArg" .}}{{end}}{{range $m.StructReturnInfo.CFields}}, &c{{.}}{{end}})
+ return {{$m.StructReturnInfo.GoType}}{ {{- range $i, $gf := $m.StructReturnInfo.GoFields}}{{if $i}}, {{end}}{{$gf}}: {{if eq (index $m.StructReturnInfo.CFieldTypes $i) "double"}}float64{{else}}int{{end}}(c{{index $m.StructReturnInfo.CFields $i}}){{end -}} }
+}
+{{- else if .HasColorOut}}
+{{- $m := .}}
+
+// {{$m.GoName}} calls the {{$m.CName}} method on {{$.GoName}}.
+func (o *{{$.GoName}}) {{$m.GoName}}({{range $i, $p := $m.Params}}{{if $i}}, {{end}}{{$p.GoName}} {{if $p.IsEo}}efl.Objecter{{else}}{{$p.GoType}}{{end}}{{end}}) color.RGBA {
+{{- range $m.Params}}
+{{- if needsStringConversion .CType}}
+ c{{.GoName}} := C.CString({{.GoName}})
+ defer C.free(unsafe.Pointer(c{{.GoName}}))
+{{- else if isBoolType .CType}}
+ var c{{.GoName}} C.{{cTypeToCgo .CType}}
+ if {{.GoName}} { c{{.GoName}} = 1 }
+{{- end}}
+{{- end}}
+ var cr, cg, cb, ca C.int
+ C.{{$m.CName}}((*C.Eo)(o.Eo()){{range $m.Params}}, {{template "paramArg" .}}{{end}}, &cr, &cg, &cb, &ca)
+ return color.RGBA{R: uint8(cr), G: uint8(cg), B: uint8(cb), A: uint8(ca)}
+}
+{{- else if .HasColorIn}}
+{{- $m := .}}
+
+// {{$m.GoName}} calls the {{$m.CName}} method on {{$.GoName}}.
+func (o *{{$.GoName}}) {{$m.GoName}}({{range $i, $p := $m.Params}}{{if $i}}, {{end}}{{$p.GoName}} {{if $p.IsEo}}efl.Objecter{{else}}{{$p.GoType}}{{end}}{{end}}{{if $m.Params}}, {{end}}c color.RGBA) {
+{{- range $m.Params}}
+{{- if needsStringConversion .CType}}
+ c{{.GoName}} := C.CString({{.GoName}})
+ defer C.free(unsafe.Pointer(c{{.GoName}}))
+{{- else if isBoolType .CType}}
+ var c{{.GoName}} C.{{cTypeToCgo .CType}}
+ if {{.GoName}} { c{{.GoName}} = 1 }
+{{- end}}
+{{- end}}
+ C.{{$m.CName}}((*C.Eo)(o.Eo()){{range $m.Params}}, {{template "paramArg" .}}{{end}}, C.int(c.R), C.int(c.G), C.int(c.B), C.int(c.A))
+}
+{{- else if .CallbackParams}}
{{- $m := .}}
// {{$m.GoName}} calls the {{$m.CName}} method on {{$.GoName}}.
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.