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.

Reply via email to