import json import os import subprocess # TODO: GodotType as class # TODO: callbacks as Callable type_map = { "bool": "bool", "bool*": "Array", "char": "char", "char*": "Array", "const char*": "String", "const float*": "Array", "const ImGuiTableColumnSortSpecs*": "Array", "const ImGuiWindowClass*": "Ref", "double": "double", "double*": "Array", "float": "real_t", "float*": "Array", "ImDrawList*": "Ref", "ImFont*": "int64_t", "ImGuiID": "uint32_t", "ImGuiIO*": "Ref", "ImGuiListClipper*": "Ref", "ImGuiSelectionUserData": "int64_t", "ImGuiStyle*": "Ref", "ImS16": "int16_t", "ImS64": "int64_t", "ImS8": "int8_t", "ImTextureID": "Ref", "ImU16": "uint16_t", "ImU32": "Color", "ImU8": "uint8_t", "ImVec2": "Vector2", "ImVec4": "Color", "ImVector_ImGuiSelectionRequest": "Array", "int": "int", "int*": "Array", "short": "short", "size_t": "int64_t", "void": "void", } # use StringName for these const char* params sn_names = ( "desc_id", "id", "label", "name", "str_id_begin", "str_id_end", "str_id", "tab_or_docked_window_label", ) exclude_funcs = ( "ImGui_ColorConvertFloat4ToU32", "ImGui_ColorConvertHSVtoRGB", "ImGui_ColorConvertU32ToFloat4", "ImGui_CreateContext", "ImGui_DestroyContext", "ImGui_DestroyPlatformWindows", "ImGui_EndFrame", "ImGui_GetColorU32", "ImGui_GetColorU32Ex", "ImGui_GetColorU32ImU32", "ImGui_GetColorU32ImU32Ex", "ImGui_GetColorU32ImVec4", "ImGui_GetCurrentContext", "ImGui_GetIDStr", "ImGui_GetKeyIndex", "ImGui_NewFrame", "ImGui_Render", "ImGui_RenderPlatformWindowsDefault", "ImGui_SetCurrentContext", "ImGui_TextUnformatted", # this is called by Text() "ImGui_TextUnformattedEx", "ImGui_UpdatePlatformWindows", ) include_structs = ( "ImGuiIO", "ImGuiStyle", "ImDrawList", "ImGuiTableColumnSortSpecs", "ImGuiTableSortSpecs", # "ImGuiTextFilter", "ImGuiWindowClass", "ImGuiMultiSelectIO", "ImGuiSelectionRequest", # "ImGuiSelectionBasicStorage", # not useful "ImGuiListClipper", ) constructible_structs = ("ImGuiWindowClass", "ImGuiListClipper") exclude_props = ( "MouseDown", "KeysData", "MouseClickedPos", "MouseClickedTime", "MouseClicked", "MouseDoubleClicked", "MouseClickedCount", "MouseClickedLastCount", "MouseReleased", "MouseDownOwned", "MouseDownOwnedUnlessPopupClose", "MouseDownDuration", "MouseDownDurationPrev", "MouseDragMaxDistanceAbs", "MouseDragMaxDistanceSqr", "KeyMap", "KeysDown", "NavInputs", ) array_types = { "bool*": "bool", "char*": "String", "int*": "int", "float*": "float", "double*": "double", "const float*": "float", } non_bitfield_enums = [] def is_obsolete(j): for cond in j.get("conditionals", ()): if cond["condition"] == "ifndef" and cond["expression"].find("OBSOLETE") != -1: return True return False class Enum: def __init__(self, j): self.orig_name = j["name"] self.obsolete = is_obsolete(j) self.bitfield = self.orig_name.endswith("Flags_") self.vals = [] self.name = self.orig_name.strip("_") if self.name.startswith("ImGui"): self.name = self.name.replace("ImGui", "", 1) elif self.name.startswith("Im"): self.name = self.name.replace("Im", "", 1) ignore_endings = tuple(["COUNT", "_", "BEGIN", "END", "OFFSET", "SIZE"]) for e in j["elements"]: name = e["name"] if not is_obsolete(e) and not name.endswith(ignore_endings): if name.startswith("ImGui"): gdname = name.replace("ImGui", "", 1) elif name.startswith("Im"): gdname = name.replace("Im", "", 1) self.vals.append((gdname, name)) def gen_def(self): rv = f"enum {self.name} {{ " for kv in self.vals: rv += f"{kv[0]} = {kv[1]}, " rv += "};" return [rv] def gen_cast(self): macro = "VARIANT_BITFIELD_CAST" if self.bitfield else "VARIANT_ENUM_CAST" return [f"{macro}(ImGui::Godot::ImGui::{self.name});"] def gen_bindings(self): macro = "BIND_BITFIELD_FLAG" if self.bitfield else "BIND_ENUM_CONSTANT" rv = "" for kv in self.vals: rv += f"{macro}({kv[0]});" return [rv] def __str__(self): return f"Enum {self.name} ({self.orig_name})" class ReturnType: def __init__(self, j): self.orig_type = j["declaration"] self.gdtype = type_map.get(self.orig_type) self.is_struct = self.gdtype and self.gdtype.endswith("Ptr>") if self.gdtype is None: print(f"no type for rt {self.orig_type}") class Param: def __init__(self, j): self.name = j["name"] self.is_array = ( j["is_array"] or self.name == "values" ) # HACK: because is_array not set self.is_varargs = j["is_varargs"] if not self.is_varargs: self.orig_type = j["type"]["declaration"] else: self.orig_type = None self.gdtype = type_map.get(self.orig_type) if self.is_array: self.gdtype = "Array" self.orig_type = self.orig_type[: self.orig_type.find("[")] if self.gdtype == "String" and self.name in sn_names: self.gdtype = "StringName" if self.name == "items_separated_by_zeros": self.gdtype = "Array" dv = j.get("default_value") if dv: dv = dv.replace("ImVec2", "Vector2") dv = dv.replace("ImVec4", "Color") dv = dv.replace("FLT_MIN", "std::numeric_limits::min()") dv = dv.replace("FLT_MAX", "std::numeric_limits::max()") dv = dv.replace("sizeof", "(uint64_t)sizeof") if self.gdtype is not None: dv = dv.replace("NULL", f"{self.gdtype}()") self.dv = dv self.is_struct = self.gdtype and self.gdtype.endswith("Ptr>") if self.gdtype is None: print(f"no type for param {self.orig_type} {self.name}") def gen_decl(self): rv = f"{self.gdtype} {self.name}" # if self.dv: # rv += f" = {self.dv}" return rv def gen_def(self): return f"{self.gdtype} {self.name}" def gen_arg(self, safe_fmt=True): if self.gdtype == "String": if self.dv is not None: return f"{self.name}.ptr() ? {self.name}.utf8().get_data() : nullptr" else: rv = f"{self.name}.utf8().get_data()" if safe_fmt and self.name == "fmt": return f'"%s", {rv}' else: return rv elif self.gdtype == "Vector2": return f"{{static_cast({self.name}.x), static_cast({self.name}.y)}}" elif self.gdtype == "Ref": return f"(ImTextureID){self.name}->get_rid().get_id()" elif self.gdtype == "Color": if self.orig_type == "ImU32": return f"{self.name}.to_abgr32()" else: return f"{{{self.name}.r, {self.name}.g, {self.name}.b, {self.name}.a}}" elif self.gdtype == "StringName": return f"sn_to_cstr({self.name})" elif self.gdtype == "Array": if self.is_array: rv = f"({self.orig_type}*)GdsArray<{self.orig_type}>({self.name})" elif self.name == "items_separated_by_zeros": rv = f"({self.orig_type})GdsZeroArray({self.name})" else: atype = array_types[self.orig_type] rv = f"{self.name}.size() == 0 ? nullptr : " if self.dv else "" rv += f"({self.orig_type})GdsPtr<{atype}>({self.name}" if atype == "String": rv += ", buf_size, label" rv += ")" return rv elif self.orig_type in ["ImFont*"]: # opaque pointers return f"({self.orig_type}){self.name}" elif self.is_struct: return f"{self.name}->_GetPtr()" else: return self.name class Function: def __init__(self, j): self.obsolete = is_obsolete(j) self.orig_name = j["name"] self.name = self.orig_name if self.name.startswith("ImGui_"): self.name = self.name.replace("ImGui_", "", 1) self.rt = ReturnType(j["return_type"]) self.params = [] for ja in j["arguments"]: self.params.append(Param(ja)) for p in self.params: if p.is_varargs or p.orig_type == "va_list": self.params.remove(p) self.valid = ( self.rt.gdtype is not None and not self.obsolete and self.orig_name not in exclude_funcs and not self.orig_name.endswith("V") and not self.orig_name.startswith("ImGuiIO_") and not self.orig_name.startswith("ImFont_") # double underscore = internal and not self.orig_name.startswith("ImDrawList__") ) if self.valid: for p in self.params: if p.gdtype is None: self.valid = False if not self.valid and not self.orig_name in exclude_funcs and not self.obsolete: print(f"skip function {self.orig_name}") def gen_decl(self): return [ f'static {self.rt.gdtype} {self.name}({", ".join(p.gen_decl() for p in self.params)});' ] def gen_def(self): fname = self.orig_name safe_fmt = True if fname == "ImGui_Text": safe_fmt = False fname = "ImGui_TextUnformatted" # do your own formatting fcall = f'::{fname}({", ".join(p.gen_arg(safe_fmt) for p in self.params)})' if self.rt.gdtype in ("Vector2", "Color"): fcall = f"To{self.rt.gdtype}({fcall})" elif self.rt.orig_type in ["ImFont*"]: fcall = f"({self.rt.gdtype}){fcall}" elif self.rt.gdtype in non_bitfield_enums: fcall = f"static_cast<{self.rt.gdtype}>({fcall})" rv = f'{self.rt.gdtype} ImGui::{self.name}({", ".join(p.gen_def() for p in self.params)}) {{' if self.rt.is_struct: rv += f"{self.rt.gdtype} rv;" rv += "rv.instantiate();" rv += f"rv->_SetPtr({fcall});" rv += "return rv" else: if self.rt.gdtype != "void": rv += "return " rv += fcall rv += "; }" return [rv] def gen_bindings(self): rv = f'ClassDB::bind_static_method("ImGui", D_METHOD("{self.name}"' for p in self.params: rv += f', "{p.name}"' rv += f"), &ImGui::{self.name}" for p in self.params: if p.dv is not None: rv += f", DEFVAL({p.dv})" rv += ");" return [rv] class Property: variant_types = { "float": "FLOAT", "Vector2": "VECTOR2", "bool": "BOOL", } array_types = { "ImVec4": "PackedColorArray", } def __init__(self, j, name, struct_name): self.struct_name = struct_name self.name = name self.is_array = j.get("is_array", False) self.is_internal = j.get("is_internal", False) self.orig_type = j["type"]["declaration"] self.valid = False if self.is_internal: return if self.name in exclude_props: return self.gdtype = type_map.get(self.orig_type, None) if self.is_array: self.array_type, self.array_size = self.orig_type[:-1].split("[") self.gdtype = Property.array_types[self.array_type] if self.gdtype == "String": self.gdtype = None try: self.is_const = ( "const" in j["type"]["description"]["inner_type"]["storage_classes"] ) except: self.is_const = False self.valid = self.gdtype is not None if not self.valid: print(f"skip property {self.orig_type} {self.struct_name}::{self.name}") def gen_decl(self): rv = f"{self.gdtype} _Get{self.name}();" rv += f"void _Set{self.name}({self.gdtype} x);" return rv def gen_def(self): # getter rv = f"{self.gdtype} {self.struct_name}::_Get{self.name}() {{" fcall = f"ptr->{self.name}" # TODO: refactor if self.orig_type == "ImVec2": fcall = f"ToVector2({fcall})" elif self.orig_type in ["ImFont*"]: fcall = f"(int64_t){fcall}" elif self.gdtype == "PackedColorArray": fcall = f"ToPackedColorArray({fcall}, {self.array_size})" dv = "{}" if self.gdtype.startswith("BitField"): dv = "0" elif self.gdtype in non_bitfield_enums: fcall = f"static_cast<{self.gdtype}>({fcall})" elif self.orig_type.startswith("ImVector_"): fcall = f"FromImVector({fcall})" elif self.orig_type == "const ImGuiTableColumnSortSpecs*": fcall = f"SpecsToArray(ptr)" rv += f"if (ptr) [[likely]] return {fcall}; else return {dv};" rv += "}" # setter rv += f"void {self.struct_name}::_Set{self.name}({self.gdtype} x) {{" if self.is_const: rv += f'ERR_FAIL_MSG("{self.name} is const");' else: x = "x" if self.orig_type == "ImVec2": x = "{static_cast(x.x), static_cast(x.y)}" elif self.orig_type in ["ImFont*"]: x = "(ImFont*)x" elif self.gdtype in non_bitfield_enums: x = f"static_cast<{self.orig_type}>(x)" elif self.orig_type.startswith("ImVector_"): x = "ToImVector(x)" if self.gdtype == "PackedColorArray": rv += f"FromPackedColorArray(x, ptr->{self.name});" else: rv += f"ptr->{self.name} = {x};" rv += "}" return rv def gen_bindings(self): getter = f"_Get{self.name}" setter = f"_Set{self.name}" rv = f'ClassDB::bind_method(D_METHOD("{getter}"), &{self.struct_name}::{getter});' rv += f'ClassDB::bind_method(D_METHOD("{setter}", "x"), &{self.struct_name}::{setter});' vtype = Property.variant_types.get(self.gdtype, "INT") rv += f'ADD_PROPERTY(PropertyInfo(Variant::{vtype}, "{self.name}"), "{setter}", "{getter}");' return rv class Struct: def __init__(self, j): self.orig_name = j["name"] self.name = self.orig_name + "Ptr" self.valid = self.orig_name in include_structs self.constructible = self.orig_name in constructible_structs self.properties = [] if not self.valid: return for jfield in j["fields"]: prop = Property(jfield, jfield["name"], self.name) if prop.valid: self.properties.append(prop) def gen_decl(self): rv = f"class {self.name} : public RefCounted {{" rv += f"GDCLASS({self.name}, RefCounted);" rv += "protected: static void _bind_methods();" rv += "public:" # needs constructor for non-zero values if self.orig_name == "ImGuiWindowClass": rv += "ImGuiWindowClassPtr() { ptr->ParentViewportId = -1; ptr->DockingAllowUnclassed = true; }" if not self.constructible: rv += f"void _SetPtr({self.orig_name}* p) {{ ptr = p; }}" rv += f"{self.orig_name}* _GetPtr() {{ return ptr; }}" else: rv += f"{self.orig_name}* _GetPtr() {{ return ptr.get(); }}" for prop in self.properties: rv += prop.gen_decl() rv += "private:" if not self.constructible: rv += f"{self.orig_name}* ptr = nullptr;" else: rv += f"std::unique_ptr<{self.orig_name}> ptr = std::make_unique<{self.orig_name}>();" rv += "};" return [rv] def gen_def(self): rv = f"void {self.name}::_bind_methods() {{" for prop in self.properties: rv += prop.gen_bindings() rv += "}" for prop in self.properties: rv += prop.gen_def() return [rv] def gen_bindings(self): return [f"ClassDB::register_class<{self.name}>();"] class JsonParser: array_types = { "bool*": "bool", "char*": "String", "int*": "int", "float*": "float", "double*": "double", } def __init__(self): for s in include_structs: type_map[f"{s}*"] = f"Ref<{s}Ptr>" self.enums = [] self.structs = [] self.funcs = [] self.enum_defs = [] self.enum_casts = [] self.enum_binds = [] self.func_decls = [] self.func_binds = [] self.func_defs = [] self.struct_decls = [] self.struct_decls_fwd = [] self.struct_defs = [] self.struct_binds = [] def write(self): try: os.mkdir("gen") except: pass with open("gen/imgui_bindings.gen.h", "w") as fi: out = ( ["namespace ImGui::Godot {"] + self.struct_decls_fwd + [ "class ImGui : public Object {", " GDCLASS(ImGui, Object);", "protected:", " static void _bind_methods();" "public:", ] + self.enum_defs + self.func_decls + ["};"] + self.struct_decls + ["} // namespace ImGui::Godot"] + self.enum_casts ) fi.write("\n".join(out)) with open("gen/imgui_bindings_impl.gen.h", "w") as fi: out = ( ["void ImGui::_bind_methods() {"] + self.enum_binds + self.struct_binds + self.func_binds + ["}"] + self.struct_defs + self.func_defs ) fi.write("\n".join(out)) def load(self, jdat): enums = [] for je in jdat["enums"]: e = Enum(je) self.enum_defs += e.gen_def() self.enum_casts += e.gen_cast() self.enum_binds += e.gen_bindings() enums.append(e) if e.bitfield: type_map[e.orig_name.strip("_")] = f"BitField" else: type_map[e.orig_name.strip("_")] = f"ImGui::{e.name}" non_bitfield_enums.append(f"ImGui::{e.name}") for js in jdat["structs"]: s = Struct(js) if s.valid: self.structs.append(s) for s in self.structs: self.struct_decls_fwd += [f"class {s.name};"] self.struct_decls += s.gen_decl() self.struct_defs += s.gen_def() self.struct_binds += s.gen_bindings() for jf in jdat["functions"]: f = Function(jf) if f.valid: self.funcs.append(f) for f in self.funcs: self.func_decls += f.gen_decl() self.func_defs += f.gen_def() self.func_binds += f.gen_bindings() def main(): os.makedirs("gen", exist_ok=True) subprocess.call( "python dear_bindings/dear_bindings.py -o gen/cimgui imgui/imgui.h", shell=True ) parser = JsonParser() with open("gen/cimgui.json") as jfi: jdat = json.loads(jfi.read()) parser.load(jdat) parser.write() subprocess.call("clang-format -i gen/imgui_bindings*", shell=True) if __name__ == "__main__": main()