__init__.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. # Part of ImGui Bundle - MIT License - Copyright (c) 2022-2026 Pascal Thomet - https://github.com/pthom/imgui_bundle
  2. import os
  3. import sys
  4. from imgui_bundle._imgui_bundle import __bundle_submodules_available__, __bundle_submodules_disabled__, __bundle_pyodide__ # type: ignore
  5. from imgui_bundle._imgui_bundle import __version__, __build_number__, compilation_time
  6. from types import ModuleType
  7. from typing import Union, Tuple, List, overload
  8. def has_submodule(submodule_name: str) -> bool:
  9. return submodule_name in __bundle_submodules_available__
  10. def _publish(name: str, module: ModuleType) -> None:
  11. """Expose a native submodule under `imgui_bundle.<name>` in sys.modules.
  12. Enables `from imgui_bundle.<name> import ...` in addition to the
  13. existing `from imgui_bundle import <name>`. Without this, the native
  14. module's fully-qualified name stays `imgui_bundle._imgui_bundle.<name>`,
  15. which is what `sys.modules` keys on — the public path isn't registered.
  16. """
  17. sys.modules[f"imgui_bundle.{name}"] = module
  18. def info() -> str:
  19. """Return information about imgui_bundle: version, compilation time, and available submodules.
  20. Returns:
  21. str: Formatted string with package information
  22. Example:
  23. >>> import imgui_bundle
  24. >>> print(imgui_bundle.info())
  25. ImGui Bundle v1.92.6
  26. Compiled on Jan 26 2026 at 15:49:15
  27. Available submodules (16): imgui, imgui.internal, imgui.backends, ...
  28. """
  29. comp_time = compilation_time()
  30. # Format submodules list
  31. submodules_str = ", ".join(__bundle_submodules_available__)
  32. info_str = f"ImGui Bundle v{__version__} buil {__build_number__}\n"
  33. info_str += f"{comp_time}\n"
  34. info_str += f"Available submodules ({len(__bundle_submodules_available__)}): {submodules_str}\n"
  35. if len(__bundle_submodules_disabled__) > 0:
  36. disabled_str = ", ".join(__bundle_submodules_disabled__)
  37. info_str += f"Disabled submodules ({len(__bundle_submodules_disabled__)}): {disabled_str}"
  38. return info_str
  39. def _is_pydantic_v2_available() -> bool:
  40. from importlib import metadata
  41. try:
  42. version_str: str = metadata.version("pydantic")
  43. except metadata.PackageNotFoundError:
  44. return False
  45. major: int = int(version_str.split(".")[0])
  46. return major >= 2
  47. __all__ = [
  48. "__version__",
  49. "__build_number__",
  50. "compilation_time",
  51. "info",
  52. "has_submodule",
  53. "__bundle_submodules_available__",
  54. "__bundle_submodules_disabled__",
  55. "__bundle_pyodide__"
  56. ]
  57. #
  58. # Import native submodules
  59. #
  60. if has_submodule("imgui"):
  61. from imgui_bundle._imgui_bundle import imgui as imgui
  62. _publish("imgui", imgui)
  63. # Let Python resolve `imgui_bundle.imgui.<submodule>` against the source
  64. # dir (e.g. test_engine_checks.py), since the C++ submodule shadows it.
  65. imgui.__path__ = [os.path.join(os.path.dirname(__file__), "imgui")]
  66. _publish("imgui.internal", imgui.internal)
  67. _publish("imgui.backends", imgui.backends)
  68. if has_submodule("imgui.test_engine"):
  69. _publish("imgui.test_engine", imgui.test_engine)
  70. # Catch Python exceptions in test_func/gui_func/teardown_func so they
  71. # become logged test failures instead of nanobind::python_error ->
  72. # std::terminate (which would crash the whole process).
  73. from imgui_bundle import _imgui_test_engine_python_safety # noqa: F401
  74. _imgui_test_engine_python_safety.install(imgui.test_engine)
  75. from imgui_bundle._imgui_bundle.imgui import ImVec2, ImVec4, ImColor, FLT_MIN, FLT_MAX # noqa: F401
  76. from imgui_bundle.im_col32 import IM_COL32 # noqa: F401, E402
  77. from imgui_bundle import imgui_ctx as imgui_ctx # noqa: E402
  78. ImVec2Like = Union[ImVec2, Tuple[int | float, int | float], List[int | float]]
  79. ImVec4Like = Union[ImVec4, Tuple[int | float, int | float, int | float, int | float], List[int | float]]
  80. imgui.ImVec2Like = ImVec2Like
  81. imgui.ImVec4Like = ImVec4Like
  82. __all__.extend([
  83. "imgui",
  84. "ImVec2",
  85. "ImVec2Like",
  86. "ImVec4Like",
  87. "ImVec4",
  88. "ImColor",
  89. "FLT_MIN",
  90. "FLT_MAX",
  91. "IM_COL32",
  92. "imgui_ctx",
  93. ])
  94. # Em sizing utilities (DPI-independent sizing)
  95. def em_size(v: float = 1.0) -> float:
  96. """Returns a size in pixels corresponding to `v` em units.
  97. 1 em = current font size (ImGui::GetFontSize()).
  98. Use this for DPI-independent sizing.
  99. """
  100. return imgui.get_font_size() * v
  101. @overload
  102. def em_to_vec2(x: float, y: float) -> ImVec2: ...
  103. @overload
  104. def em_to_vec2(v: ImVec2Like) -> ImVec2: ...
  105. def em_to_vec2(x, y=None) -> ImVec2:
  106. """Returns an ImVec2 sized in em units (multiples of font size).
  107. Can be called as:
  108. em_to_vec2(3.0, 2.0) -> ImVec2 of 3em x 2em
  109. em_to_vec2((3.0, 2.0)) -> same, from tuple
  110. em_to_vec2(ImVec2(3.0, 2.0)) -> same, from ImVec2
  111. """
  112. font_size = imgui.get_font_size()
  113. if y is None:
  114. return ImVec2(font_size * x[0], font_size * x[1])
  115. return ImVec2(font_size * x, font_size * y)
  116. __all__.extend(["em_size", "em_to_vec2"])
  117. # Patch after imgui v1.90.9, where
  118. # the enum ImGuiDir_ was renamed to ImGuiDir and ImGuiSortDirection_ was renamed to ImGuiSortDirection
  119. # this enables old python to continue to work
  120. imgui.Dir_ = imgui.Dir
  121. imgui.SortDirection_ = imgui.SortDirection
  122. # Pydantic types are lazily imported via __getattr__ below,
  123. # so that pydantic can be installed after imgui_bundle is first imported
  124. # (e.g. in Pyodide where packages are installed incrementally).
  125. __all__.extend([
  126. "imgui_pydantic",
  127. "ImVec4_Pydantic",
  128. "ImVec2_Pydantic",
  129. "ImColor_Pydantic",
  130. ])
  131. if has_submodule("hello_imgui"):
  132. from imgui_bundle._imgui_bundle import hello_imgui as hello_imgui
  133. _publish("hello_imgui", hello_imgui)
  134. __all__.extend(["hello_imgui"])
  135. if has_submodule("implot"):
  136. from imgui_bundle._imgui_bundle import implot as implot
  137. _publish("implot", implot)
  138. __all__.extend(["implot"])
  139. # Flag types for ImPlot
  140. implot.LineFlags = int # see implot.LineFlags_
  141. implot.ScatterFlags = int # see implot.ScatterFlags_
  142. implot.StairsFlags = int # see implot.StairsFlags_
  143. implot.ShadedFlags = int # see implot.ShadedFlags_
  144. implot.BarsFlags = int # see implot.BarsFlags_
  145. implot.PieChartFlags = int # see implot.PieChartFlags_
  146. implot.HistogramFlags = int # see implot.HistogramFlags_
  147. if has_submodule("implot3d"):
  148. from imgui_bundle._imgui_bundle import implot3d as implot3d
  149. _publish("implot3d", implot3d)
  150. __all__.extend(["implot3d"])
  151. if has_submodule("imgui_color_text_edit"):
  152. from imgui_bundle._imgui_bundle import imgui_color_text_edit as imgui_color_text_edit
  153. _publish("imgui_color_text_edit", imgui_color_text_edit)
  154. __all__.extend(["imgui_color_text_edit"])
  155. if has_submodule("imgui_node_editor"):
  156. from imgui_bundle._imgui_bundle import imgui_node_editor as imgui_node_editor
  157. _publish("imgui_node_editor", imgui_node_editor)
  158. from imgui_bundle import imgui_node_editor_ctx as imgui_node_editor_ctx # noqa: E402
  159. __all__.extend(["imgui_node_editor", "imgui_node_editor_ctx"])
  160. if has_submodule("imgui_knobs"):
  161. from imgui_bundle._imgui_bundle import imgui_knobs as imgui_knobs
  162. _publish("imgui_knobs", imgui_knobs)
  163. __all__.extend(["imgui_knobs"])
  164. if has_submodule("im_file_dialog"):
  165. from imgui_bundle._imgui_bundle import im_file_dialog as im_file_dialog
  166. _publish("im_file_dialog", im_file_dialog)
  167. __all__.extend(["im_file_dialog"])
  168. if has_submodule("imspinner"):
  169. from imgui_bundle._imgui_bundle import imspinner as imspinner
  170. _publish("imspinner", imspinner)
  171. __all__.extend(["imspinner"])
  172. if has_submodule("imgui_md"):
  173. from imgui_bundle._imgui_bundle import imgui_md as imgui_md
  174. _publish("imgui_md", imgui_md)
  175. __all__.extend(["imgui_md"])
  176. # Register a hook so that initialize_markdown() automatically sets up URL image download support
  177. def _on_initialize_markdown(options):
  178. if options.callbacks.on_download_data is None:
  179. from imgui_bundle._imgui_md_image_loader import _get_download_function
  180. options.callbacks.on_download_data = _get_download_function()
  181. imgui_md._set_on_initialize_markdown_callback(_on_initialize_markdown)
  182. if has_submodule("immvision"):
  183. from imgui_bundle._imgui_bundle import immvision as immvision
  184. _publish("immvision", immvision)
  185. __all__.extend(["immvision"])
  186. if has_submodule("imguizmo"):
  187. from imgui_bundle._imgui_bundle import imguizmo as imguizmo
  188. _publish("imguizmo", imguizmo)
  189. __all__.extend(["imguizmo"])
  190. if has_submodule("imgui_tex_inspect"):
  191. from imgui_bundle._imgui_bundle import imgui_tex_inspect as imgui_tex_inspect
  192. _publish("imgui_tex_inspect", imgui_tex_inspect)
  193. __all__.extend(["imgui_tex_inspect"])
  194. if has_submodule("imgui_toggle"):
  195. from imgui_bundle._imgui_bundle import imgui_toggle as imgui_toggle
  196. _publish("imgui_toggle", imgui_toggle)
  197. __all__.extend(["imgui_toggle"])
  198. if has_submodule("portable_file_dialogs"):
  199. from imgui_bundle._imgui_bundle import portable_file_dialogs as portable_file_dialogs
  200. _publish("portable_file_dialogs", portable_file_dialogs)
  201. __all__.extend(["portable_file_dialogs"])
  202. if has_submodule("imgui_command_palette"):
  203. from imgui_bundle._imgui_bundle import imgui_command_palette as imgui_command_palette
  204. _publish("imgui_command_palette", imgui_command_palette)
  205. __all__.extend(["imgui_command_palette"])
  206. if has_submodule("imcoolbar"):
  207. from imgui_bundle._imgui_bundle import im_cool_bar as im_cool_bar
  208. _publish("im_cool_bar", im_cool_bar)
  209. __all__.extend(["im_cool_bar"])
  210. if has_submodule("nanovg"):
  211. from imgui_bundle._imgui_bundle import nanovg as nanovg
  212. _publish("nanovg", nanovg)
  213. __all__.extend(["nanovg"])
  214. if has_submodule("im_anim"):
  215. from imgui_bundle._imgui_bundle import im_anim as im_anim
  216. _publish("im_anim", im_anim)
  217. __all__.extend(["im_anim"])
  218. if has_submodule("imgui_explorer"):
  219. from imgui_bundle._imgui_bundle import imgui_explorer as imgui_explorer
  220. _publish("imgui_explorer", imgui_explorer)
  221. __all__.extend(["imgui_explorer"])
  222. if has_submodule("imgui_microtex"):
  223. from imgui_bundle._imgui_bundle import imgui_microtex as imgui_microtex
  224. _publish("imgui_microtex", imgui_microtex)
  225. __all__.extend(["imgui_microtex"])
  226. if has_submodule("webgl"):
  227. from imgui_bundle._imgui_bundle import webgl as webgl
  228. _publish("webgl", webgl)
  229. __all__.extend(["webgl"])
  230. if has_submodule("immapp_cpp"): # immapp is a Python wrapper around immapp_cpp
  231. from imgui_bundle import immapp as immapp
  232. # Note: to enable font awesome 6:
  233. # runner_params.callbacks.default_icon_font = hello_imgui.DefaultIconFont.font_awesome6
  234. from imgui_bundle.immapp import icons_fontawesome_4 as icons_fontawesome_4
  235. from imgui_bundle.immapp import icons_fontawesome_4 as icons_fontawesome # v4 # noqa: F401
  236. from imgui_bundle.immapp import icons_fontawesome_6 as icons_fontawesome_6
  237. __all__.extend(["immapp", "icons_fontawesome_4", "icons_fontawesome", "icons_fontawesome_6"])
  238. #
  239. # Import Python submodules
  240. #
  241. if has_submodule("immvision"):
  242. from imgui_bundle import imgui_fig as imgui_fig
  243. __all__.extend(["imgui_fig"])
  244. # Glfw setup:
  245. # By importing imgui_bundle.glfw_utils, we make sure that glfw provided by pip will use our glfw dynamic library.
  246. # (imgui_bundle.glfw_utils will call _set_glfw_pip_search_path automatically)
  247. if has_submodule("with_glfw"):
  248. from imgui_bundle._glfw_set_search_path import _glfw_set_search_path
  249. _glfw_set_search_path()
  250. from imgui_bundle import glfw_utils as glfw_utils # noqa: E402
  251. #
  252. # Pyodide: patch hello_imgui.run and immapp.run to work with Pyodide
  253. #
  254. if __bundle_pyodide__ and has_submodule("hello_imgui") and has_submodule("immapp_cpp"):
  255. from imgui_bundle.pyodide_patch_runners import pyodide_do_patch_runners
  256. pyodide_do_patch_runners()
  257. # Jupyter notebook: patch hello_imgui.run and immapp.run to work with Jupyter notebook
  258. # run will display a screenshot of the final app state in the notebook output
  259. # Note: the immapp.nb submodule provides more complete notebook support (async support, etc.)
  260. if has_submodule("hello_imgui") and has_submodule("immapp_cpp") and not __bundle_pyodide__:
  261. from imgui_bundle.notebook_patch_runners import notebook_do_patch_runners_if_needed # noqa: E402
  262. notebook_do_patch_runners_if_needed()
  263. from imgui_bundle._patch_runners_add_save_screenshot_param import patch_runners_add_save_screenshot_param # noqa: E402
  264. patch_runners_add_save_screenshot_param()
  265. #
  266. # Add async support to hello_imgui
  267. #
  268. if has_submodule("hello_imgui"):
  269. from imgui_bundle.hello_imgui_run_async import run_async as _hello_imgui_run_async
  270. hello_imgui.run_async = _hello_imgui_run_async # type: ignore
  271. # Add notebook convenience API
  272. from imgui_bundle import hello_imgui_nb as _hello_imgui_nb_module
  273. hello_imgui.nb = _hello_imgui_nb_module # type: ignore
  274. THIS_DIR = os.path.dirname(__file__)
  275. hello_imgui.override_assets_folder(THIS_DIR + "/assets")
  276. def register_demos_assets_folder() -> None:
  277. """Add demos_assets/ to hello_imgui assets search path.
  278. Call this from demos that need additional images (house.jpg, bear_transparent.png, etc.)
  279. beyond the default assets (world.png, fonts)."""
  280. from imgui_bundle import __bundle_pyodide__
  281. import os as _os
  282. import logging as _logging
  283. _log = _logging.getLogger("imgui_bundle")
  284. if __bundle_pyodide__:
  285. _log.warning("register_demos_assets_folder will not work in Pyodide")
  286. return
  287. _demos_assets = _os.path.join(_os.path.dirname(__file__), "demos_assets")
  288. _demos_assets = _os.path.normpath(_demos_assets)
  289. if _os.path.isdir(_demos_assets):
  290. from imgui_bundle import hello_imgui as _hello_imgui
  291. _hello_imgui.add_assets_search_path(_demos_assets)
  292. _log.debug("Registered demos assets folder: %s", _demos_assets)
  293. else:
  294. _log.warning("demos_assets/ not found at %s", _demos_assets)
  295. __all__.extend(["register_demos_assets_folder"])
  296. # Lazy import for pydantic types: deferred so that pydantic can be installed
  297. # after imgui_bundle is first imported (e.g. in Pyodide).
  298. _PYDANTIC_NAMES = {"ImVec2_Pydantic", "ImVec4_Pydantic", "ImColor_Pydantic", "imgui_pydantic"}
  299. def __getattr__(name: str):
  300. if name in _PYDANTIC_NAMES:
  301. if not _is_pydantic_v2_available():
  302. raise ImportError(
  303. f"'{name}' requires pydantic v2+. Install it with: pip install pydantic"
  304. )
  305. import importlib
  306. _mod = importlib.import_module("imgui_bundle.imgui_pydantic")
  307. # Cache into module globals so __getattr__ is not called again
  308. globals()["imgui_pydantic"] = _mod
  309. globals()["ImVec2_Pydantic"] = _mod.ImVec2_Pydantic
  310. globals()["ImVec4_Pydantic"] = _mod.ImVec4_Pydantic
  311. globals()["ImColor_Pydantic"] = _mod.ImColor_Pydantic
  312. return globals()[name]
  313. raise AttributeError(f"module 'imgui_bundle' has no attribute {name!r}")