feat: Add Go (cgo) bindings for Dear ImGui + example

bindings/go/:
- imgui_bridge.h/.cpp: Thin C API bridge (extern C) wrapping C++ ImGui
- imgui.go: Go cgo package (CreateContext, NewFrame, Begin, Button,
  SliderFloat, SliderInt, Checkbox, ColorEdit3, Text, Combo, etc.)
- go.mod: Module definition
- README.md: Full docs with usage, building, and API reference

examples/example_golang/:
- main.go: Null-mode example showing ImGui from Go
- go.mod: Module with replace directive for local bindings
- Makefile: Build/run/clean targets

Go 1.26.5 verified working on this host. CGO_ENABLED=1 required
(plus a C++ compiler like g++ for the bridge compilation).
This commit is contained in:
Turtle Dove 2026-07-14 23:15:33 -05:00
parent 42b5a2b2a2
commit 1cd495b522
8 changed files with 664 additions and 0 deletions

90
bindings/go/README.md Normal file
View File

@ -0,0 +1,90 @@
# Go Bindings for Dear ImGui (imgui_xl)
This directory provides Go (cgo) bindings for Dear ImGui via a thin C API bridge.
## Architecture
```
imgui.go → Go package (cgo wrapper)
imgui_bridge.h → C API declarations (extern "C")
imgui_bridge.cpp → C API implementation (wraps C++ ImGui)
go.mod → Go module definition
```
The Go package calls C functions via cgo, which in turn call the C++ ImGui API through the bridge layer.
## Prerequisites
- **Go 1.21+** (with `CGO_ENABLED=1`)
- **C++ compiler** (g++ or clang++) on PATH
## Usage
```go
package main
import (
"fmt"
imgui "github.com/Turtle-PB/imgui_xl/bindings/go"
)
func main() {
imgui.CreateContext()
defer imgui.DestroyContext()
imgui.StyleColorsDark()
imgui.NewFrame()
visible := imgui.Begin("Hello, Go!", nil)
if visible {
imgui.Text("Hello from Go!")
imgui.Text(fmt.Sprintf("FPS: %.1f", imgui.GetFramerate()))
}
imgui.End()
imgui.Render()
}
```
## Building
```bash
# Set up the Go module (from the example directory)
cd examples/example_golang
go mod tidy
# Build (requires C++ compiler)
CGO_ENABLED=1 go build -o imgui_go_example
# Run
./imgui_go_example
```
## Available Functions
### Core
- `CreateContext()`, `DestroyContext()`
- `NewFrame()`, `Render()`
- `Version()`, `GetFramerate()`, `GetTime()`, `GetFrameCount()`
- `GetDisplaySize()`
### Style
- `StyleColorsDark()`, `StyleColorsLight()`, `StyleColorsClassic()`
### Windows
- `Begin(name, &open)`, `End()`
- `ShowDemoWindow(&open)`
### Widgets
- `Text(s)`, `TextColored(r,g,b,a,s)`, `BulletText(s)`
- `Button(label, w, h)`, `Checkbox(label, &v)`
- `SliderFloat(label, &v, min, max)`, `SliderInt(label, &v, min, max)`
- `ColorEdit3(label, &col)`, `InputText(label, &buf, size)`
- `Combo(label, &current, items, count)`
### Layout
- `SameLine(offset, spacing)`, `Separator()`, `Spacing()`, `NewLine()`
- `PushItemWidth(w)`, `PopItemWidth()`
## License
MIT — same as Dear ImGui

3
bindings/go/go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/Turtle-PB/imgui_xl/bindings/go
go 1.21

245
bindings/go/imgui.go Normal file
View File

@ -0,0 +1,245 @@
// Package imgui provides Go bindings for Dear ImGui via cgo.
// It uses a thin C bridge (imgui_bridge.h/.cpp) to call the C++ ImGui API.
//
// Requirements:
// - C++ compiler (g++ or clang++) for building the bridge
// - CGO_ENABLED=1
//
// Build:
// CGO_ENABLED=1 go build
//
// Copyright (c) 2024 OwlLogics / imgui_xl
// Licensed under MIT
package imgui
/*
#cgo CXXFLAGS: -std=c++11 -I${SRCDIR}/../../.. -I${SRCDIR}/../../../backends
#cgo LDFLAGS: -lstdc++ -lm
// Include the C bridge header
#include "imgui_bridge.h"
// We need to compile the bridge .cpp via cgo's CXX support.
// The bridge .cpp includes all of ImGui's implementation.
*/
import "C"
import (
"unsafe"
)
// Version returns the compiled ImGui version string.
func Version() string {
return C.GoString(C.ig_GetVersion())
}
// CreateContext creates the ImGui context.
func CreateContext() {
C.ig_CreateContext()
}
// DestroyContext destroys the current ImGui context.
func DestroyContext() {
C.ig_DestroyContext()
}
// NewFrame starts a new ImGui frame.
func NewFrame() {
C.ig_NewFrame()
}
// Render finalizes the frame and prepares draw data.
func Render() {
C.ig_Render()
}
// StyleColorsDark sets the dark color scheme.
func StyleColorsDark() {
C.ig_StyleColorsDark()
}
// StyleColorsLight sets the light color scheme.
func StyleColorsLight() {
C.ig_StyleColorsLight()
}
// StyleColorsClassic sets the classic color scheme.
func StyleColorsClassic() {
C.ig_StyleColorsClassic()
}
// ShowDemoWindow shows the ImGui demo window.
func ShowDemoWindow(open *bool) {
if open != nil {
v := 0
if *open {
v = 1
}
C.ig_ShowDemoWindow(&v)
*open = v != 0
} else {
C.ig_ShowDemoWindow(nil)
}
}
// GetFramerate returns the current FPS.
func GetFramerate() float32 {
return float32(C.ig_GetFramerate())
}
// GetTime returns the elapsed time since initialization.
func GetTime() float32 {
return float32(C.ig_GetTime())
}
// GetFrameCount returns the current frame count.
func GetFrameCount() int {
return int(C.ig_GetFrameCount())
}
// GetDisplaySize returns the display width and height.
func GetDisplaySize() (float32, float32) {
var w, h C.float
C.ig_GetDisplaySize(&w, &h)
return float32(w), float32(h)
}
// --- Window functions ---
// Begin pushes a new window to the stack.
// Returns true if the window is visible (not collapsed).
func Begin(name string, open *bool) bool {
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
var pOpen *C.int
if open != nil {
v := 0
if *open {
v = 1
}
pOpen = &v
defer func() { *open = v != 0 }()
}
return C.ig_Begin(cName, pOpen) != 0
}
// End closes the current window.
func End() {
C.ig_End()
}
// SameLine places the next widget on the same line.
func SameLine(offset, spacing float32) {
C.ig_SameLine(C.float(offset), C.float(spacing))
}
// Separator draws a horizontal separator.
func Separator() {
C.ig_Separator()
}
// Spacing adds vertical spacing.
func Spacing() {
C.ig_Spacing()
}
// --- Widget functions ---
// Text displays a text label.
func Text(s string) {
cStr := C.CString(s)
defer C.free(unsafe.Pointer(cStr))
C.ig_Text(cStr)
}
// TextColored displays colored text.
func TextColored(r, g, b, a float32, s string) {
cStr := C.CString(s)
defer C.free(unsafe.Pointer(cStr))
C.ig_TextColored(C.float(r), C.float(g), C.float(b), C.float(a), cStr)
}
// Button displays a button. Returns true if clicked.
func Button(label string, width, height float32) bool {
cStr := C.CString(label)
defer C.free(unsafe.Pointer(cStr))
return C.ig_Button(cStr, C.float(width), C.float(height)) != 0
}
// Checkbox displays a checkbox.
func Checkbox(label string, v *bool) bool {
cStr := C.CString(label)
defer C.free(unsafe.Pointer(cStr))
var vC C.int
if *v {
vC = 1
}
ret := C.ig_Checkbox(cStr, &vC) != 0
*v = vC != 0
return ret
}
// SliderFloat displays a float slider.
func SliderFloat(label string, v *float32, vMin, vMax float32) bool {
cStr := C.CString(label)
defer C.free(unsafe.Pointer(cStr))
return C.ig_SliderFloat(cStr, (*C.float)(v), C.float(vMin), C.float(vMax)) != 0
}
// SliderInt displays an integer slider.
func SliderInt(label string, v *int, vMin, vMax int) bool {
cStr := C.CString(label)
defer C.free(unsafe.Pointer(cStr))
return C.ig_SliderInt(cStr, (*C.int)(v), C.int(vMin), C.int(vMax)) != 0
}
// ColorEdit3 displays a 3-component color editor.
func ColorEdit3(label string, col *[3]float32) bool {
cStr := C.CString(label)
defer C.free(unsafe.Pointer(cStr))
return C.ig_ColorEdit3(cStr, (*C.float)(unsafe.Pointer(col))) != 0
}
// InputText displays a text input field.
func InputText(label string, buf *string, bufSize int) bool {
cLabel := C.CString(label)
defer C.free(unsafe.Pointer(cLabel))
// Allocate a C buffer
cBuf := C.malloc(C.size_t(bufSize))
defer C.free(cBuf)
C.memcpy(cBuf, unsafe.Pointer(C.CString(*buf)), C.size_t(len(*buf)+1))
ret := C.ig_InputText(cLabel, (*C.char)(cBuf), C.int(bufSize)) != 0
*buf = C.GoString((*C.char)(cBuf))
return ret
}
// Combo displays a dropdown combo box.
func Combo(label string, current *int, items string, itemCount int) bool {
cLabel := C.CString(label)
defer C.free(unsafe.Pointer(cLabel))
cItems := C.CString(items)
defer C.free(unsafe.Pointer(cItems))
return C.ig_Combo(cLabel, (*C.int)(current), cItems, C.int(itemCount)) != 0
}
// BulletText displays a bulleted text item.
func BulletText(s string) {
cStr := C.CString(s)
defer C.free(unsafe.Pointer(cStr))
C.ig_BulletText(cStr)
}
// NewLine inserts a new line.
func NewLine() {
C.ig_NewLine()
}
// PushItemWidth sets the width for the next widget.
func PushItemWidth(width float32) {
C.ig_PushItemWidth(C.float(width))
}
// PopItemWidth restores the previous item width.
func PopItemWidth() {
C.ig_PopItemWidth()
}

View File

@ -0,0 +1,102 @@
// imgui_bridge.cpp — Thin C API bridge implementation for Dear ImGui
// Copyright (c) 2024 OwlLogics / imgui_xl
// Licensed under MIT
#include "imgui.h"
#include "imgui_bridge.h"
// Compile the full ImGui implementation here
#define IMGUI_IMPLEMENTATION
#include "imgui.cpp"
#include "imgui_demo.cpp"
#include "imgui_draw.cpp"
#include "imgui_tables.cpp"
#include "imgui_widgets.cpp"
// === Core ===
extern "C" void ig_CreateContext(void) { ImGui::CreateContext(); }
extern "C" void ig_DestroyContext(void) { ImGui::DestroyContext(); }
extern "C" void* ig_GetIO(void) { return (void*)&ImGui::GetIO(); }
extern "C" void* ig_GetStyle(void) { return (void*)&ImGui::GetStyle(); }
extern "C" void ig_NewFrame(void) { ImGui::NewFrame(); }
extern "C" void ig_Render(void) { ImGui::Render(); }
extern "C" void* ig_GetDrawData(void) { return (void*)ImGui::GetDrawData(); }
extern "C" const char* ig_GetVersion(void) { return ImGui::GetVersion(); }
// === Style ===
extern "C" void ig_StyleColorsDark(void) { ImGui::StyleColorsDark(); }
extern "C" void ig_StyleColorsLight(void) { ImGui::StyleColorsLight(); }
extern "C" void ig_StyleColorsClassic(void) { ImGui::StyleColorsClassic(); }
// === Windows ===
extern "C" int ig_Begin(const char* name, int* p_open) {
return ImGui::Begin(name, p_open ? (bool*)p_open : nullptr) ? 1 : 0;
}
extern "C" void ig_End(void) { ImGui::End(); }
extern "C" int ig_BeginChild(const char* str_id, float w, float h, int border) {
return ImGui::BeginChild(str_id, ImVec2(w, h), border != 0) ? 1 : 0;
}
extern "C" void ig_EndChild(void) { ImGui::EndChild(); }
// === Widgets ===
extern "C" void ig_Text(const char* fmt) { ImGui::Text("%s", fmt); }
extern "C" void ig_TextColored(float r, float g, float b, float a, const char* fmt) {
ImGui::TextColored(ImVec4(r, g, b, a), "%s", fmt);
}
extern "C" void ig_BulletText(const char* fmt) { ImGui::BulletText("%s", fmt); }
extern "C" int ig_Button(const char* label, float w, float h) {
return ImGui::Button(label, ImVec2(w, h)) ? 1 : 0;
}
extern "C" int ig_InvisibleButton(const char* str_id, float w, float h) {
return ImGui::InvisibleButton(str_id, ImVec2(w, h)) ? 1 : 0;
}
extern "C" int ig_Checkbox(const char* label, int* v) {
bool b = *v != 0;
int ret = ImGui::Checkbox(label, &b) ? 1 : 0;
*v = b ? 1 : 0;
return ret;
}
extern "C" int ig_SliderFloat(const char* label, float* v, float v_min, float v_max) {
return ImGui::SliderFloat(label, v, v_min, v_max) ? 1 : 0;
}
extern "C" int ig_SliderInt(const char* label, int* v, int v_min, int v_max) {
return ImGui::SliderInt(label, v, v_min, v_max) ? 1 : 0;
}
extern "C" int ig_ColorEdit3(const char* label, float* col) {
return ImGui::ColorEdit3(label, col) ? 1 : 0;
}
extern "C" int ig_ColorEdit4(const char* label, float* col, int flags) {
return ImGui::ColorEdit4(label, col, flags) ? 1 : 0;
}
extern "C" int ig_InputText(const char* label, char* buf, int buf_size) {
return ImGui::InputText(label, buf, (size_t)buf_size) ? 1 : 0;
}
extern "C" int ig_Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int items_count) {
return ImGui::Combo(label, current_item, items_separated_by_zeros, items_count) ? 1 : 0;
}
// === Layout ===
extern "C" void ig_SameLine(float offset, float spacing) { ImGui::SameLine(offset, spacing); }
extern "C" void ig_Separator(void) { ImGui::Separator(); }
extern "C" void ig_Spacing(void) { ImGui::Spacing(); }
extern "C" void ig_NewLine(void) { ImGui::NewLine(); }
extern "C" void ig_PushItemWidth(float w) { ImGui::PushItemWidth(w); }
extern "C" void ig_PopItemWidth(void) { ImGui::PopItemWidth(); }
// === Drawing ===
extern "C" void ig_ShowDemoWindow(int* p_open) { ImGui::ShowDemoWindow(p_open ? (bool*)p_open : nullptr); }
// === Metrics / Info ===
extern "C" float ig_GetFramerate(void) { return ImGui::GetIO().Framerate; }
extern "C" float ig_GetTime(void) { return (float)ImGui::GetTime(); }
extern "C" int ig_GetFrameCount(void) { return ImGui::GetFrameCount(); }
// === Display ===
extern "C" void ig_GetDisplaySize(float* w, float* h) {
ImVec2 size = ImGui::GetIO().DisplaySize;
*w = size.x; *h = size.y;
}
extern "C" void ig_GetDisplayFramebufferScale(float* x, float* y) {
ImVec2 scale = ImGui::GetIO().DisplayFramebufferScale;
*x = scale.x; *y = scale.y;
}

View File

@ -0,0 +1,74 @@
// imgui_bridge.h — Thin C API bridge for Dear ImGui
// This file provides C-callable wrappers around the C++ Dear ImGui API.
// It allows languages like Go (via cgo), Rust, Python, etc. to use ImGui.
//
// Compile with: g++ -c imgui_bridge.cpp -I.. -I../backends -DIMGUI_IMPLEMENTATION
// The resulting .o can be linked from any C-compatible FFI.
//
// Copyright (c) 2024 OwlLogics / imgui_xl
// Licensed under MIT
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// === Core ===
void ig_CreateContext(void);
void ig_DestroyContext(void);
void* ig_GetIO(void); // Returns ImGuiIO* as void*
void* ig_GetStyle(void); // Returns ImGuiStyle* as void*
void ig_NewFrame(void);
void ig_Render(void);
void* ig_GetDrawData(void); // Returns ImDrawData* as void*
const char* ig_GetVersion(void);
// === Style ===
void ig_StyleColorsDark(void);
void ig_StyleColorsLight(void);
void ig_StyleColorsClassic(void);
// === Windows ===
int ig_Begin(const char* name, int* p_open); // Returns bool (1=visible)
void ig_End(void);
int ig_BeginChild(const char* str_id, float w, float h, int border); // bool
void ig_EndChild(void);
// === Widgets ===
void ig_Text(const char* fmt);
void ig_TextColored(float r, float g, float b, float a, const char* fmt);
void ig_BulletText(const char* fmt);
int ig_Button(const char* label, float w, float h); // bool
int ig_InvisibleButton(const char* str_id, float w, float h); // bool
int ig_Checkbox(const char* label, int* v); // bool, v is bool*
int ig_SliderFloat(const char* label, float* v, float v_min, float v_max); // bool
int ig_SliderInt(const char* label, int* v, int v_min, int v_max); // bool
int ig_ColorEdit3(const char* label, float* col); // bool, col is float[3]
int ig_ColorEdit4(const char* label, float* col, int flags); // bool, col is float[4]
int ig_InputText(const char* label, char* buf, int buf_size); // bool
int ig_Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int items_count); // bool
// === Layout ===
void ig_SameLine(float offset, float spacing);
void ig_Separator(void);
void ig_Spacing(void);
void ig_NewLine(void);
void ig_PushItemWidth(float w);
void ig_PopItemWidth(void);
// === Drawing ===
void ig_ShowDemoWindow(int* p_open);
// === Metrics / Info ===
float ig_GetFramerate(void);
float ig_GetTime(void);
int ig_GetFrameCount(void);
// === Display ===
void ig_GetDisplaySize(float* w, float* h);
void ig_GetDisplayFramebufferScale(float* x, float* y);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,38 @@
# Makefile for the Go ImGui example
# Requirements: g++ (or clang++), Go 1.21+, CGO_ENABLED=1
#
# Usage:
# make build - Compile the Go example
# make run - Build and run
# make clean - Remove build artifacts
#
# Copyright (c) 2024 OwlLogics / imgui_xl
# Licensed under MIT
GO = go
CXX = g++
IMGUI_DIR = ../..
BRIDGE_DIR = $(IMGUI_DIR)/bindings/go
CGO_ENABLED = 1
GOFLAGS = -trimpath
OUTPUT = imgui_go_example
.PHONY: all build run clean vet fmt
all: build
build:
CGO_ENABLED=$(CGO_ENABLED) $(GO) build -o $(OUTPUT) . 2>&1 || echo "Build requires a C++ compiler (g++ or clang++) on PATH"
run: build
./$(OUTPUT)
vet:
$(GO) vet ./...
fmt:
$(GO) fmt ./...
clean:
rm -f $(OUTPUT) *.o *.exe

View File

@ -0,0 +1,7 @@
module github.com/Turtle-PB/imgui_xl/examples/example_golang
go 1.21
require github.com/Turtle-PB/imgui_xl/bindings/go v0.0.0
replace github.com/Turtle-PB/imgui_xl/bindings/go => ../../bindings/go

View File

@ -0,0 +1,105 @@
// Package main demonstrates using Dear ImGui from Go.
// This example runs ImGui in a headless "null" mode — it creates a context,
// runs 20 frames of rendering, and prints the output.
//
// For a full graphical example, pair this with a Go windowing library
// (e.g., GLFW, SDL2) and a renderer backend (OpenGL3, Vulkan).
//
// Build requirements:
// - C++ compiler (g++ or clang++) on PATH
// - CGO_ENABLED=1 (default when a C compiler is available)
//
// Build:
// cd examples/example_golang
// CGO_ENABLED=1 go build -o imgui_go_example
// ./imgui_go_example
//
// Copyright (c) 2024 OwlLogics / imgui_xl
// Licensed under MIT
package main
import (
"fmt"
imgui "github.com/Turtle-PB/imgui_xl/bindings/go"
)
func main() {
fmt.Println("=== Dear ImGui Go Example ===")
fmt.Printf("ImGui Version: %s\n", imgui.Version())
fmt.Println()
// Create context
imgui.CreateContext()
defer imgui.DestroyContext()
// Use dark theme
imgui.StyleColorsDark()
// Run 20 frames in "null" mode (no rendering output)
showDemo := true
showAnother := false
floatVal := 0.0
counter := 0
for frame := 0; frame < 20; frame++ {
imgui.NewFrame()
// Show the ImGui demo window
imgui.ShowDemoWindow(&showDemo)
// Create a custom window
visible := imgui.Begin("Hello from Go!", &showDemo)
if visible {
imgui.Text(fmt.Sprintf("Frame: %d", frame))
imgui.Text(fmt.Sprintf("FPS: %.1f", imgui.GetFramerate()))
imgui.Separator()
imgui.Checkbox("Another Window", &showAnother)
f32Val := float32(floatVal)
if imgui.SliderFloat("Float", &f32Val, 0.0, 1.0) {
floatVal = float64(f32Val)
}
if imgui.Button("Click Me", 0, 0) {
counter++
fmt.Printf(" Button clicked! Count: %d\n", counter)
}
imgui.SameLine(0, 4)
imgui.Text(fmt.Sprintf("counter = %d", counter))
}
imgui.End()
// Show another window toggled by checkbox
if showAnother {
visible := imgui.Begin("Another Window", &showAnother)
if visible {
imgui.Text("Hello from the second window!")
imgui.Text("This window can be closed via the checkbox.")
}
imgui.End()
}
imgui.Render()
if frame == 0 || frame == 9 || frame == 19 {
w, h := imgui.GetDisplaySize()
fmt.Printf("Frame %2d: Render complete (display: %.0fx%.0f, FPS: %.1f)\n",
frame, w, h, imgui.GetFramerate())
}
}
fmt.Println()
fmt.Println("=== Done! ===")
fmt.Println("20 frames rendered in null mode.")
fmt.Println()
fmt.Println("To build a graphical Go app with ImGui:")
fmt.Println(" 1. Install a Go windowing lib: go get github.com/go-gl/glfw/v3.3/glfw")
fmt.Println(" 2. Install an OpenGL Go lib: go get github.com/go-gl/gl/v4.1-core/gl")
fmt.Println(" 3. Create a window, init OpenGL,")
fmt.Println(" call imgui.CreateContext() + ImGui_ImplOpenGL3_Init()")
fmt.Println(" 4. Loop: poll events -> NewFrame -> draw UI -> Render -> RenderDrawData")
}