// Instructions to build & bind: // // jai generate.jai - compile // We don’t ship the Dear ImGui source with the compiler. Download a Dear ImGui release and update this path if you want to re-generate the bindings. IMGUI_PATH :: "../imgui-source"; // v1.92.9-docking (WIP) SDL3_INCLUDE_PATH :: "src/include"; // Used for SDL v3.2.14 FREETYPE_INCLUDE_PATH :: "src/include/freetype-2.12.1"; DECLARATIONS_TO_OMIT :: string.[ // We have custom declarations for these in module.jai: "ImVec2", "ImVec4", "ImColor", "ImVector", "ImDrawCallback", // Jai has its own, better "String_Builder" "ImGuiTextBuffer", "ImDrawCallback_ResetRenderState", // casting an integer to a function pointer is not supported in Jai. ]; macos_arm64_version_arg := "-mmacos-version-min=11.0"; // Earliest version that supports arm64; macOS is arm64 only #run,stallable { set_build_options_dc(.{do_output=false}); root_options := get_build_options(); args := root_options.compile_time_command_line; if !generate_imgui_bindings(args) { compiler_set_workspace_status(.FAILED); } } generate_imgui_bindings :: (args: [] string) -> bool { compile := array_find(args, "compile"); compile_debug := array_find(args, "debug"); compile_and_gen_backend_sdl3_gpu3 := array_find(args, "backend_sdl3_gpu3"); compile_and_gen_freetype := array_find(args, "freetype"); if compile { imgui_src_files: [..] string; array_add(*imgui_src_files, tprint("%/imgui.cpp", IMGUI_PATH)); array_add(*imgui_src_files, tprint("%/imgui_widgets.cpp", IMGUI_PATH)); array_add(*imgui_src_files, tprint("%/imgui_draw.cpp", IMGUI_PATH)); array_add(*imgui_src_files, tprint("%/imgui_tables.cpp", IMGUI_PATH)); // array_add(*imgui_src_files, tprint("%/imgui_demo.cpp", IMGUI_PATH)); success := true; #if OS == .WINDOWS { make_directory_if_it_does_not_exist("windows"); success &&= build_cpp_dynamic_lib( "windows/ImGui", ..imgui_src_files, extra=.[ "/DIMGUI_API=__declspec(dllexport)", "/DIMGUI_USE_WCHAR32", ], debug=compile_debug, ); // We set IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS. Otherwise all users of the ImGui module have to link against "imm32". success &&= build_cpp_static_lib( "windows/ImGui.static", ..imgui_src_files, extra=.[ "/DIMGUI_API=__declspec(dllexport)", "/DIMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS", "/DIMGUI_USE_WCHAR32", ], debug=compile_debug, ); } else #if OS == .LINUX { make_directory_if_it_does_not_exist("linux"); // success &&= build_cpp_dynamic_lib("linux/ImGui", ..imgui_src_files, debug=compile_debug, extra=.["-DIMGUI_USE_WCHAR32"]); // @Hack: Is -fno-threadsafe-statics really the way to go? Without it, we get linker errors when using the static library // due to "__cxa_guard_acquire" et al not being defined. The library users probably need to link agains libc++ or something. But do we really want to enforce that? success &&= build_cpp_static_lib("linux/ImGui", ..imgui_src_files, extra = .["-fno-exceptions", "-fno-rtti", "-fno-threadsafe-statics", "-fPIC", "-DIMGUI_USE_WCHAR32"], debug=compile_debug); } else #if OS == .MACOS { #import "Process"; make_directory_if_it_does_not_exist("macos"); success &&= build_cpp_dynamic_lib("macos/ImGui", ..imgui_src_files, extra = .["-arch", "arm64", "-std=c++20", macos_arm64_version_arg, "-DIMGUI_USE_WCHAR32"], debug=compile_debug); // @Hack: Is -fno-threadsafe-statics really the way to go? Without it, we get linker errors when using the static library // due to "__cxa_guard_acquire" et al not being defined. The library users probably need to link agains libc++ or something. But do we really want to enforce that? success &&= build_cpp_static_lib( "macos/ImGui", ..imgui_src_files, extra = .["-arch", "arm64", "-std=c++20", "-fno-exceptions", "-fno-rtti", "-fno-threadsafe-statics", macos_arm64_version_arg, "-DIMGUI_USE_WCHAR32"], debug=compile_debug); } else { assert(false, "this OS is not supported, check the procedure % for details\n", #procedure_name()); } if !success return false; } // We return if we generate a backend because bindings generator crashes if we do more than one generation per run if compile_and_gen_backend_sdl3_gpu3 { success := compile_and_generate_bindings_backend_sdl3_gpu3(compile_debug); return success; } if compile_and_gen_freetype { success := compile_and_generate_bindings_freetype(compile_debug); return success; } defer { deinit(*enums_for_typedefs); array_reset(*begin_end_string_args); } output_filename: string; opts: Generate_Bindings_Options; { using opts; #if OS == .WINDOWS { array_add(*libraries, .{filename="ImGui.static"}); array_add(*library_search_paths, "windows"); output_filename = "windows/windows.jai"; generate_library_declarations = false; footer = #string EOF imgui :: #library,no_dll "ImGui.static"; EOF } else { #if OS == .LINUX { array_add(*library_search_paths, "linux"); } else #if OS == .MACOS { array_add(*library_search_paths, "macos"); } else { assert(false); } footer = FOOTER_UNIX; output_filename = "unix.jai"; array_add(*libraries, .{filename="ImGui"}); generate_library_declarations = false; } array_add(*include_paths, IMGUI_PATH); array_add(*source_files, tprint("%/imgui.h", IMGUI_PATH)); array_add(*extra_clang_arguments, "-x", "c++", "-DWIN32_LEAN_AND_MEAN", "-DIMGUI_USE_WCHAR32"); array_add(*flatten_namespaces, "ImGui"); array_add(*strip_prefixes, "ImGui"); auto_detect_enum_prefixes = false; log_stripped_declarations = false; generate_compile_time_struct_checks = false; strip_flags |= .INLINED_FUNCTIONS; // "inline" functions are not present in the static library. visitor = imgui_visitor; get_func_args_for_printing = imgui_get_function_arg_for_printing; } success := generate_bindings(opts, output_filename); return success; } compile_and_generate_bindings_backend_sdl3_gpu3 :: (compile_debug: bool) -> bool { success := false; array_add(*prefixes_to_keep, "ImGui_ImplSDL3_", "ImGui_ImplSDLGPU3_"); src_files := string.[ tprint("%/backends/imgui_impl_sdl3.cpp", IMGUI_PATH), tprint("%/backends/imgui_impl_sdlgpu3.cpp", IMGUI_PATH), ]; // Generate binaries #if OS == .WINDOWS { make_directory_if_it_does_not_exist("backends/windows", recursive=true); // We set IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS. Otherwise all users of the ImGui module have to link against "imm32". success = build_cpp_static_lib("backends/windows/ImGui_sdl3_gpu3.static", ..src_files, extra = .["/DIMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS", "/DIMGUI_USE_WCHAR32", tprint("/I%", IMGUI_PATH), tprint("/I%", SDL3_INCLUDE_PATH)], debug=compile_debug); if !success return false; } else #if OS == .MACOS { #import "Process"; make_directory_if_it_does_not_exist("backends/macos", recursive=true); // @Hack: Is -fno-threadsafe-statics really the way to go? Without it, we get linker errors when using the static library // due to "__cxa_guard_acquire" et al not being defined. The library users probably need to link agains libc++ or something. But do we really want to enforce that? success = build_cpp_static_lib("backends/macos/ImGui_sdl3_gpu3", ..src_files, extra = .["-arch", "arm64", "-std=c++20", "-fno-exceptions", "-fno-rtti", "-fno-threadsafe-statics", "-DIMGUI_USE_WCHAR32", macos_arm64_version_arg, tprint("-I%", IMGUI_PATH), tprint("-I%", SDL3_INCLUDE_PATH)], debug=compile_debug); assert(success, "build_cpp_static_lib('backends/macos/ImGui_sdl3_gpu3'"); } else #if OS == .LINUX { #import "Process"; make_directory_if_it_does_not_exist("backends/linux", recursive=true); // @Hack: Is -fno-threadsafe-statics really the way to go? Without it, we get linker errors when using the static library // due to "__cxa_guard_acquire" et al not being defined. The library users probably need to link agains libc++ or something. But do we really want to enforce that? success = build_cpp_static_lib("backends/linux/ImGui_sdl3_gpu3", ..src_files, extra = .["-std=c++20", "-fno-exceptions", "-fno-rtti", "-fno-threadsafe-statics", "-DIMGUI_USE_WCHAR32", tprint("-I%", IMGUI_PATH), tprint("-I%", SDL3_INCLUDE_PATH)], debug=compile_debug); assert(success, "build_cpp_static_lib('backends/linux/ImGui_sdl3_gpu3'"); } // Generate bindings output_filename: string; opts: Generate_Bindings_Options; { using opts; #if OS == .WINDOWS { array_add(*library_search_paths, "backends/windows"); array_add(*libraries, .{filename="ImGui_sdl3_gpu3.static"}); output_filename = "backends/windows/windows_sdl3_gpu3.jai"; generate_library_declarations = false; footer = #string EOF imgui_sdl3_gpu3 :: #library,no_dll "ImGui_sdl3_gpu3.static"; EOF } else { // "inline" functions are not present in the static library. strip_flags |= .INLINED_FUNCTIONS; #if OS == .LINUX { array_add(*library_search_paths, "backends/linux"); } else #if OS == .MACOS { array_add(*library_search_paths, "backends/macos"); // For some reason generation on macos doesn't find // the library and so strips the functions we want. // // So for now we avoid stripping them and then do the horrible hack // below to replace '#foreign __UnknownLib' and '#elsewhere __UnknownLib' // with '#foreign imgui_sdl3_gpu3' strip_flags &= ~.SYMBOLS_WITH_UNKNOWN_FOREIGN_LIBS; } else { assert(false); } array_add(*libraries, .{filename="ImGui_sdl3_gpu3"}); output_filename = "backends/unix_sdl3_gpu3.jai"; generate_library_declarations = false; footer = #string END #if OS == .MACOS imgui_sdl3_gpu3 :: #library,no_dll "macos/ImGui_sdl3_gpu3"; #if OS == .LINUX imgui_sdl3_gpu3 :: #library,no_dll "linux/ImGui_sdl3_gpu3"; END } array_add(*include_paths, IMGUI_PATH, SDL3_INCLUDE_PATH); array_add(*source_files, tprint("%/backends/imgui_impl_sdl3.h", IMGUI_PATH), tprint("%/backends/imgui_impl_sdlgpu3.h", IMGUI_PATH)); array_add(*extra_clang_arguments, "-x", "c++", "-DWIN32_LEAN_AND_MEAN", "-DIMGUI_USE_WCHAR32"); array_add(*flatten_namespaces, "ImGui"); array_add(*strip_prefixes, "ImGui", "Imgui"); auto_detect_enum_prefixes = false; log_stripped_declarations = false; generate_compile_time_struct_checks = false; visitor = imgui_visitor; get_func_args_for_printing = imgui_get_function_arg_for_printing; } success = generate_bindings(opts, output_filename); // Horrible hack but... #if OS == .MACOS { file_path := "backends/unix_sdl3_gpu3.jai"; defer { if !success { log("failed to replace invalid library name from generated MacOS bindings at '%'. You will have to manually replace '#foreign __UnknownLib' and '#elsewhere __UnknownLib' with '#foreign imgui_sdl3_gpu3' and '#elsewhere imgui_sdl3_gpu3'", file_path, flags=.ERROR); } } contents, success= := read_entire_file(file_path); if success { defer free(contents); replace1 := replace(contents, "#foreign __UnknownLib", "#foreign imgui_sdl3_gpu3"); defer free(replace1); replace2 := replace(replace1, "#elsewhere __UnknownLib", "#elsewhere imgui_sdl3_gpu3"); defer free(replace2); success = write_entire_file(file_path, replace2); } } return success; } compile_and_generate_bindings_freetype :: (compile_debug: bool) -> bool { success := false; array_add(*prefixes_to_keep, "ImGuiFreeType", "ImGui_ImplFreeType"); src_files := string.[ tprint("%/misc/freetype/imgui_freetype.cpp", IMGUI_PATH), ]; // Generate binaries #if OS == .WINDOWS { make_directory_if_it_does_not_exist("misc/freetype/windows", recursive=true); success = build_cpp_static_lib("misc/freetype/windows/ImGui_freetype.static", ..src_files, extra = .[ tprint("/I%", IMGUI_PATH), tprint("/I%", FREETYPE_INCLUDE_PATH), tprint("/I%", join(IMGUI_PATH, "/misc/freetype",, temp)), "/DIMGUI_USE_WCHAR32", ], debug=compile_debug); if !success return false; } else #if OS == .LINUX { #import "Process"; make_directory_if_it_does_not_exist("misc/freetype/linux"); success = build_cpp_static_lib("misc/freetype/linux/ImGui_freetype", ..src_files, extra = .[ tprint("-I%", IMGUI_PATH), tprint("-I%", FREETYPE_INCLUDE_PATH), tprint("-I%", join(IMGUI_PATH, "/misc/freetype",, temp)), "-fno-exceptions", "-fno-rtti", "-fno-threadsafe-statics", "-fPIC", "-DIMGUI_USE_WCHAR32", ], debug=compile_debug); assert(success, "build_cpp_static_lib('misc/freetype/linux/ImGui_freetype'"); } else #if OS == .MACOS { #import "Process"; make_directory_if_it_does_not_exist("misc/freetype/macos", recursive=true); success = build_cpp_static_lib("misc/freetype/macos/ImGui_freetype", ..src_files, extra = .[ "-arch", "arm64", "-std=c++20", "-fno-exceptions", "-fno-rtti", "-fno-threadsafe-statics", macos_arm64_version_arg, tprint("-I%", IMGUI_PATH), tprint("-I%", FREETYPE_INCLUDE_PATH), join("-I", IMGUI_PATH, "/misc/freetype",, temp), "-DIMGUI_USE_WCHAR32", ], debug=compile_debug); assert(success, "build_cpp_static_lib('misc/freetype/macos/ImGui_freetype'"); } else { assert(false, "this OS is not supported"); } if !success return false; // Generate bindings output_filename: string; opts: Generate_Bindings_Options; { using opts; #if OS == .WINDOWS { array_add(*library_search_paths, "misc/freetype/windows"); array_add(*libraries, .{filename="ImGui_freetype.static"}); output_filename = "misc/freetype/windows/windows_freetype.jai"; generate_library_declarations = false; footer = #string EOF imgui_freetype :: #library,no_dll "ImGui_freetype.static"; EOF } else { // "inline" functions are not present in the static library. strip_flags |= .INLINED_FUNCTIONS; #if OS == .LINUX { array_add(*library_search_paths, "misc/freetype/linux"); } else #if OS == .MACOS { array_add(*library_search_paths, "misc/freetype/macos"); // Same reason as SDL backend — macOS generator sometimes fails to find the lib strip_flags &= ~.SYMBOLS_WITH_UNKNOWN_FOREIGN_LIBS; } else { assert(false); } array_add(*libraries, .{filename="ImGui_freetype"}); output_filename = "misc/freetype/unix_freetype.jai"; generate_library_declarations = false; footer = #string END #if OS == .MACOS imgui_freetype :: #library,no_dll "macos/ImGui_freetype"; #if OS == .LINUX imgui_freetype :: #library,no_dll "linux/ImGui_freetype"; END } array_add(*include_paths, IMGUI_PATH, join(IMGUI_PATH, "/misc/freetype"), FREETYPE_INCLUDE_PATH); array_add(*source_files, tprint("%/misc/freetype/imgui_freetype.h", IMGUI_PATH) ); array_add(*extra_clang_arguments, "-x", "c++", "-DWIN32_LEAN_AND_MEAN", "-DIMGUI_USE_WCHAR32"); array_add(*strip_prefixes, "ImGui", "Imgui"); auto_detect_enum_prefixes = false; log_stripped_declarations = false; generate_compile_time_struct_checks = false; visitor = imgui_visitor; get_func_args_for_printing = imgui_get_function_arg_for_printing; } success = generate_bindings(opts, output_filename); // Horrible macOS hack #if OS == .MACOS { file_path := "misc/freetype/unix_freetype.jai"; defer { if !success { log("failed to replace invalid library name from generated MacOS bindings at '%'. You will have to manually replace '#foreign __UnknownLib' and '#elsewhere __UnknownLib' with '#foreign imgui_freetype' and '#elsewhere imgui_freetype'", file_path, flags=.ERROR); } } contents, read_ok := read_entire_file(file_path); if read_ok { defer free(contents); replace1 := replace(contents, "#foreign __UnknownLib", "#foreign imgui_freetype"); defer free(replace1); replace2 := replace(replace1, "#elsewhere __UnknownLib", "#elsewhere imgui_freetype"); defer free(replace2); success = write_entire_file(file_path, replace2); } } return success; } begin_end_string_args: [..]*Declaration; enums_for_typedefs: Table(string, *Enum); prefixes_to_keep: [..]string; imgui_visitor :: (decl: *Declaration, parent_decl: *Declaration) -> Declaration_Visit_Result { // The parent check is because if we have a struct to keep we want // everything inside it to stay even if the names aren't to keep, and // so we only apply this check on top-level declarations. if prefixes_to_keep.count && !parent_decl { found := false; for prefixes_to_keep { if begins_with(decl.name, it) { found = true; break; } } if !found { decl.decl_flags |= .OMIT_FROM_OUTPUT; return .STOP; } } // ImGui has "typedef ints" for each enum. but we want to just use the enums directly, // so we get nice type safety, and .unary dots, etc. // // Look for declarations inside functions (i.e., arguments) or structs (i.e., fields) get_associated_enum_name :: (name: string) -> string { if name.count > 1 && name[name.count - 1] != #char "_" { return tprint("%_", name); } return name; } if !parent_decl { if array_find(DECLARATIONS_TO_OMIT, decl.name) { decl.decl_flags |= .OMIT_FROM_OUTPUT; return .STOP; } if decl.name == "ImGuiContext" { decl.output_name = decl.name; // Don’t shorten ImGuiContext to Context as this would conflict } if decl.kind == .TYPEDEF { old_name := decl.name; if old_name { new_name := get_associated_enum_name(old_name); for context.generator.global_scope.members { if it.kind != .ENUM || it.name != new_name continue; en := cast(*Enum)it; // Don't output the integer typedef decl.decl_flags |= .OMIT_FROM_OUTPUT; // Make sure the enums like "FocusFlags_" get outputted as "FocusFlags" if en.output_name.count > 2 && en.output_name[en.output_name.count - 1] == #char "_" { en.output_name.count -= 1; } // Keep the comment, if there is one: if decl.comment.text && !en.comment.text { en.comment = decl.comment; } table_add(*enums_for_typedefs, old_name, en); break; } } } } // Look for function arguments with "typedef" types. if parent_decl && (parent_decl.kind == .FUNCTION || parent_decl.kind == .STRUCT) && decl.kind == .DECLARATION && decl.type.type_of_typedef != null { old_name := decl.type.type_of_typedef.name; if old_name { found, en := table_find(*enums_for_typedefs, old_name); if found { change_type_to_enum(decl, en); } } } if decl.kind == .ENUM { // ImGui has lots of enums that should be enum flags. Luckily, they all end in "…Flags_". if ends_with(decl.name, "Flags_") { en := cast(*Enum)decl; en.flags |= .IS_ENUM_FLAGS; en.flags |= .VALUES_IN_HEX; } } if decl.kind == .FUNCTION { func := cast(*Function)decl; type := func.type.type_of_function; // ImGui also has functions with pairs of arguments like "text_begin" and "text_end" // for the pointers to the beginning and end of a string. We'll generate wrappers for // those function which take a jai string. // // Here we collect pointers to "_begin" arguments, and use them in // get_func_args_for_printing below. for type.arguments { if it_index == 0 || !ends_with(it.name, "_end") continue; arg_type := find_underlying_type(it.type); if !arg_type.pointer_to || !(arg_type.pointer_to.number_flags & ._8BIT) continue; // Only convert *u8 ranges to string name_part := slice(it.name, 0, it.name.count - "_end".count); if !name_part continue; // the first arg may appear as "text_begin" or just "text" name_part_with_begin := tprint("%_begin", name_part); prev_arg := type.arguments[it_index - 1]; if prev_arg.name == name_part || prev_arg.name == name_part_with_begin { array_add(*begin_end_string_args, prev_arg); func.decl_flags |= .NEEDS_ARGUMENT_WRAPPER; } } } return .RECURSE; } // Modify function with _begin and _end string arguments as they get printed. // See the note above about "text_begin". imgui_get_function_arg_for_printing :: (func: *Function, type: *Function_Type, mode: Arg_Print_Mode) -> []*Declaration { args: [..]*Declaration; for type.arguments { if array_find(begin_end_string_args, it) { if mode == { case .OUTER_WRAPPER; // For the outer wrapper, remove any "_begin" part of the argument, and // change its type to a jai string arg_copy := New(Declaration); arg_copy.* = it.*; arg_copy.type = context.generator.type_def_jai_string; if ends_with(it.name, "_begin") { arg_copy.output_name = slice(it.name, 0, it.name.count - "_begin".count); } array_add(*args, arg_copy); it_index += 1; // skip the next argument-we collpase the _begin and _end args into one string arg continue; case .INNER_WRAPPER_CALL; // For the inner wrapper function call, we pass the incoming arg // as two arguments: "jai_string.data, jai_string.data + jai_string.count" -- corresponding // to the _begin and _end parts. base_arg_name := it.name; if ends_with(it.name, "_begin") { base_arg_name = slice(it.name, 0, it.name.count - "_begin".count); } arg_copy := New(Declaration); arg_copy.* = it.*; arg_copy.output_name = tprint("%.data", base_arg_name); array_add(*args, arg_copy); arg_copy_2 := New(Declaration); arg_copy_2.* = it.*; arg_copy_2.output_name = tprint("%.data + %.count", base_arg_name, base_arg_name); array_add(*args, arg_copy_2); it_index += 1; // skip the next arg, since we already added a second one above continue; } } array_add(*args, it); } return args; } #import "Basic"; #import "Bindings_Generator"; #import "BuildCpp"; #import "Compiler"; #import "File"; #import "Hash_Table"; #import "String"; FOOTER_UNIX :: #string END #if OS == .MACOS imgui :: #library,no_dll "macos/ImGui"; #if OS == .LINUX imgui :: #library,no_dll "linux/ImGui"; END