__init__.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. # Part of ImGui Bundle - MIT License - Copyright (c) 2022-2026 Pascal Thomet - https://github.com/pthom/imgui_bundle
  2. from imgui_bundle import _imgui_bundle as _native_bundle
  3. from imgui_bundle._imgui_bundle import immapp_cpp as immapp_cpp # type: ignore
  4. from imgui_bundle._imgui_bundle.immapp_cpp import ( # type: ignore
  5. clock_seconds,
  6. default_node_editor_context,
  7. default_node_editor_config,
  8. delete_node_editor_settings,
  9. has_node_editor_settings,
  10. node_editor_settings_location,
  11. em_size,
  12. em_to_vec2,
  13. pixels_to_em,
  14. pixel_size_to_em,
  15. run,
  16. run_with_markdown,
  17. AddOnsParams,
  18. snippets,
  19. begin_plot_in_node_editor,
  20. end_plot_in_node_editor,
  21. show_resizable_plot_in_node_editor,
  22. show_resizable_plot_in_node_editor_em,
  23. widget_with_resize_handle_in_node_editor,
  24. widget_with_resize_handle_in_node_editor_em,
  25. )
  26. # Note: to enable font awesome 6:
  27. # runner_params.callbacks.default_icon_font = hello_imgui.DefaultIconFont.font_awesome6
  28. from imgui_bundle.immapp import icons_fontawesome_4 as icons_fontawesome_4
  29. from imgui_bundle.immapp import icons_fontawesome_6 as icons_fontawesome_6
  30. from imgui_bundle.immapp import icons_fontawesome_4 as icons_fontawesome # Icons font awesome v4
  31. from imgui_bundle.immapp.immapp_utils import (
  32. static as static,
  33. run_anon_block as run_anon_block,
  34. )
  35. from imgui_bundle.immapp import immapp_code_utils
  36. from imgui_bundle._imgui_bundle.hello_imgui import ( # type: ignore
  37. RunnerParams as RunnerParams,
  38. SimpleRunnerParams as SimpleRunnerParams,
  39. )
  40. # Import async support
  41. from imgui_bundle.immapp.run_async_overloads import run_async as run_async
  42. # Import notebook convenience API
  43. from imgui_bundle.immapp import nb as nb
  44. manual_render = _native_bundle.immapp_cpp.manual_render
  45. __all__ = [
  46. "clock_seconds",
  47. "default_node_editor_context",
  48. "default_node_editor_config",
  49. "delete_node_editor_settings",
  50. "has_node_editor_settings",
  51. "node_editor_settings_location",
  52. "em_size",
  53. "em_to_vec2",
  54. "pixels_to_em",
  55. "pixel_size_to_em",
  56. "run",
  57. "run_async",
  58. "run_with_markdown",
  59. "AddOnsParams",
  60. "icons_fontawesome", # v4
  61. "icons_fontawesome_4",
  62. "icons_fontawesome_6",
  63. "static",
  64. "run_anon_block",
  65. "RunnerParams",
  66. "SimpleRunnerParams",
  67. "snippets",
  68. "manual_render",
  69. "begin_plot_in_node_editor",
  70. "end_plot_in_node_editor",
  71. "show_resizable_plot_in_node_editor",
  72. "show_resizable_plot_in_node_editor_em",
  73. "widget_with_resize_handle_in_node_editor",
  74. "widget_with_resize_handle_in_node_editor_em",
  75. "immapp_code_utils",
  76. "nb",
  77. ]
  78. def run_nb(*args, **kwargs):
  79. """run_nb: alias for immapp.run, kept for backward compatibility.
  80. Was intended to be used in Jupyter notebooks. Now immapp.run is patched to work in notebooks.
  81. """
  82. return run(*args, **kwargs)
  83. __all__.append("run_nb")
  84. def render_markdown_doc_panel(doc: str, height_em: float = 20.0) -> None:
  85. """Render a markdown documentation panel with a light theme, inside a resizable child window.
  86. Useful for showing docstrings or documentation at the top of a demo.
  87. Args:
  88. doc: markdown string to render (will be unindented automatically)
  89. height_em: height of the panel in em units
  90. """
  91. from imgui_bundle import imgui, imgui_md, hello_imgui
  92. tweaked_theme = hello_imgui.ImGuiTweakedTheme()
  93. tweaked_theme.theme = hello_imgui.ImGuiTheme_.gray_variations
  94. tweaked_theme.tweaks.rounding = 0.0
  95. hello_imgui.push_tweaked_theme(tweaked_theme)
  96. size = hello_imgui.em_to_vec2(0, height_em)
  97. # Note: with ResizeY, ImGui uses the size only on the very first frame
  98. # (it saves/restores the resized height in the ini file after that)
  99. imgui.begin_child("##doc", size,
  100. imgui.ChildFlags_.borders | imgui.ChildFlags_.resize_y)
  101. imgui_md.render_unindented(doc)
  102. imgui.end_child()
  103. imgui.new_line()
  104. hello_imgui.pop_tweaked_theme()
  105. __all__.append("render_markdown_doc_panel")
  106. def download_url_bytes(url: str) -> bytes:
  107. """Download data from a URL. Works on both desktop (urllib) and Pyodide (sync XMLHttpRequest).
  108. Returns the downloaded bytes, or empty bytes on failure.
  109. Args:
  110. url: the URL to download from
  111. """
  112. from imgui_bundle import __bundle_pyodide__
  113. if __bundle_pyodide__:
  114. try:
  115. from pyodide.code import run_js # type: ignore
  116. # Sync XHR doesn't support responseType='arraybuffer'.
  117. # Instead, download as base64 text and decode in Python.
  118. _fetch_fn = run_js("""
  119. (function(url) {
  120. var xhr = new XMLHttpRequest();
  121. xhr.open('GET', url, false);
  122. xhr.overrideMimeType('text/plain; charset=x-user-defined');
  123. xhr.send();
  124. if (xhr.status === 200) {
  125. var binary = '';
  126. var bytes = xhr.responseText;
  127. for (var i = 0; i < bytes.length; i++) {
  128. binary += String.fromCharCode(bytes.charCodeAt(i) & 0xff);
  129. }
  130. return btoa(binary);
  131. } else {
  132. return null;
  133. }
  134. })
  135. """)
  136. result = _fetch_fn(url)
  137. if result is not None and len(result) > 0:
  138. import base64
  139. return base64.b64decode(result)
  140. except Exception as e:
  141. import logging
  142. logging.getLogger("immapp").warning("Failed to download %s: %s", url, e)
  143. return b""
  144. else:
  145. import urllib.request
  146. try:
  147. req = urllib.request.Request(url, headers={"User-Agent": "imgui_bundle/1.0"})
  148. with urllib.request.urlopen(req, timeout=10) as resp:
  149. return resp.read()
  150. except Exception as e:
  151. import logging
  152. logging.getLogger("immapp").warning("Failed to download %s: %s", url, e)
  153. return b""
  154. __all__.append("download_url_bytes")
  155. async def download_url_bytes_async(url: str) -> bytes:
  156. """Download data from a URL asynchronously.
  157. On Pyodide: uses pyfetch (non-blocking, lets the browser breathe).
  158. On desktop: uses urllib in a thread (non-blocking for the event loop).
  159. Usage:
  160. # In Pyodide (top-level await):
  161. data = await immapp.download_url_bytes_async("https://example.com/image.png")
  162. # On desktop:
  163. data = asyncio.run(immapp.download_url_bytes_async("https://..."))
  164. Args:
  165. url: the URL to download from
  166. """
  167. from imgui_bundle import __bundle_pyodide__
  168. if __bundle_pyodide__:
  169. try:
  170. from pyodide.http import pyfetch # type: ignore
  171. resp = await pyfetch(url)
  172. return await resp.bytes()
  173. except Exception as e:
  174. import logging
  175. logging.getLogger("immapp").warning("Failed to download %s: %s", url, e)
  176. return b""
  177. else:
  178. import asyncio
  179. # Run sync download in a thread to avoid blocking the event loop
  180. return await asyncio.to_thread(download_url_bytes, url)
  181. __all__.append("download_url_bytes_async")