From 1cd495b5224b20aa1fbf088e800440af7d18953b Mon Sep 17 00:00:00 2001 From: Turtle Dove Date: Tue, 14 Jul 2026 23:15:33 -0500 Subject: [PATCH] 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). --- bindings/go/README.md | 90 ++++++++++++ bindings/go/go.mod | 3 + bindings/go/imgui.go | 245 +++++++++++++++++++++++++++++++ bindings/go/imgui_bridge.cpp | 102 +++++++++++++ bindings/go/imgui_bridge.h | 74 ++++++++++ examples/example_golang/Makefile | 38 +++++ examples/example_golang/go.mod | 7 + examples/example_golang/main.go | 105 +++++++++++++ 8 files changed, 664 insertions(+) create mode 100644 bindings/go/README.md create mode 100644 bindings/go/go.mod create mode 100644 bindings/go/imgui.go create mode 100644 bindings/go/imgui_bridge.cpp create mode 100644 bindings/go/imgui_bridge.h create mode 100644 examples/example_golang/Makefile create mode 100644 examples/example_golang/go.mod create mode 100644 examples/example_golang/main.go diff --git a/bindings/go/README.md b/bindings/go/README.md new file mode 100644 index 000000000..d02eff418 --- /dev/null +++ b/bindings/go/README.md @@ -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, ¤t, items, count)` + +### Layout +- `SameLine(offset, spacing)`, `Separator()`, `Spacing()`, `NewLine()` +- `PushItemWidth(w)`, `PopItemWidth()` + +## License + +MIT — same as Dear ImGui diff --git a/bindings/go/go.mod b/bindings/go/go.mod new file mode 100644 index 000000000..7105d6890 --- /dev/null +++ b/bindings/go/go.mod @@ -0,0 +1,3 @@ +module github.com/Turtle-PB/imgui_xl/bindings/go + +go 1.21 diff --git a/bindings/go/imgui.go b/bindings/go/imgui.go new file mode 100644 index 000000000..fa882b314 --- /dev/null +++ b/bindings/go/imgui.go @@ -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() +} diff --git a/bindings/go/imgui_bridge.cpp b/bindings/go/imgui_bridge.cpp new file mode 100644 index 000000000..0f44a102a --- /dev/null +++ b/bindings/go/imgui_bridge.cpp @@ -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; +} diff --git a/bindings/go/imgui_bridge.h b/bindings/go/imgui_bridge.h new file mode 100644 index 000000000..b1cfd2b31 --- /dev/null +++ b/bindings/go/imgui_bridge.h @@ -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 diff --git a/examples/example_golang/Makefile b/examples/example_golang/Makefile new file mode 100644 index 000000000..f0c807522 --- /dev/null +++ b/examples/example_golang/Makefile @@ -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 diff --git a/examples/example_golang/go.mod b/examples/example_golang/go.mod new file mode 100644 index 000000000..dd64b6778 --- /dev/null +++ b/examples/example_golang/go.mod @@ -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 diff --git a/examples/example_golang/main.go b/examples/example_golang/main.go new file mode 100644 index 000000000..762ef44f6 --- /dev/null +++ b/examples/example_golang/main.go @@ -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") +}