hello_imgui_nb.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. """Notebook convenience API for hello_imgui.
  2. This module provides non-blocking GUI execution for Jupyter notebooks using hello_imgui.
  3. It's similar to immapp.nb but without AddOnsParams support.
  4. Functions:
  5. run() - Blocking mode (delegates to hello_imgui.run)
  6. start() - Non-blocking async mode with notebook-friendly defaults
  7. stop() - Stop the running GUI
  8. is_running() - Check if GUI is running
  9. """
  10. import asyncio
  11. from typing import Optional
  12. # Track the current running task
  13. _current_task: Optional[asyncio.Task] = None
  14. def is_running() -> bool:
  15. """Check if a GUI is currently running.
  16. Returns:
  17. bool: True if a GUI task is active and not done, False otherwise.
  18. Example:
  19. if hello_imgui.nb.is_running():
  20. print("GUI is active")
  21. """
  22. global _current_task
  23. return _current_task is not None and not _current_task.done()
  24. def stop() -> None:
  25. """Stop the currently running GUI by setting app_shall_exit flag.
  26. This is a convenience function that sets the exit flag for the running GUI.
  27. The GUI will close on its next render cycle.
  28. Example:
  29. hello_imgui.nb.stop() # Close the current GUI
  30. """
  31. if is_running():
  32. try:
  33. from imgui_bundle import hello_imgui
  34. hello_imgui.get_runner_params().app_shall_exit = True
  35. except Exception:
  36. pass # Ignore if runner params not available
  37. else:
  38. print("Warning: No GUI is currently running")
  39. def run(*args, **kwargs):
  40. """Run a hello_imgui application in blocking mode.
  41. This is a convenience wrapper that delegates to hello_imgui.run(),
  42. which runs the GUI and waits for it to close.
  43. Note: Unlike immapp.run, this does NOT automatically add light theme
  44. or screenshot functionality (hello_imgui doesn't have those features).
  45. Signatures:
  46. run(runner_params: RunnerParams)
  47. run(simple_params: SimpleRunnerParams)
  48. run(gui_function, window_title="", ...)
  49. Example:
  50. def my_gui():
  51. imgui.text("Hello from notebook!")
  52. hello_imgui.nb.run(my_gui, window_title="My App")
  53. # Blocks until window closed
  54. """
  55. from imgui_bundle import hello_imgui
  56. return hello_imgui.run(*args, **kwargs)
  57. def start(*args, **kwargs) -> asyncio.Task:
  58. """Start a hello_imgui application in non-blocking mode for interactive notebook use.
  59. This function starts a GUI that runs asynchronously, allowing you to:
  60. - Continue executing other notebook cells while the GUI is open
  61. - Modify variables from other cells and see updates in real-time
  62. - Run multiple sequential GUIs (close one before starting another)
  63. The function automatically applies notebook-friendly defaults:
  64. - window_size_auto=True (if not specified)
  65. - top_most=True (if not specified)
  66. If a GUI is already running, it will be automatically stopped with a warning.
  67. Signatures:
  68. start(runner_params: RunnerParams) -> asyncio.Task
  69. start(simple_params: SimpleRunnerParams) -> asyncio.Task
  70. start(gui_function, window_title="", window_size_auto=True, ...) -> asyncio.Task
  71. Args:
  72. *args: RunnerParams, SimpleRunnerParams, or gui_function
  73. **kwargs: Additional parameters when using gui_function signature
  74. Returns:
  75. asyncio.Task: The task running the GUI
  76. Example:
  77. counter = {"value": 0}
  78. def my_gui():
  79. imgui.text(f"Counter: {counter['value']}")
  80. if imgui.button("Increment"):
  81. counter["value"] += 1
  82. hello_imgui.nb.start(my_gui, window_title="Counter")
  83. # In another cell:
  84. counter["value"] = 100 # Updates in real-time!
  85. # To stop:
  86. hello_imgui.nb.stop()
  87. """
  88. global _current_task
  89. # Import here to avoid circular imports
  90. from imgui_bundle import hello_imgui
  91. RunnerParams = hello_imgui.RunnerParams # type: ignore
  92. SimpleRunnerParams = hello_imgui.SimpleRunnerParams # type: ignore
  93. # Capture the previous task (if any) and signal it to exit. We do NOT
  94. # block here: actually waiting for it to finish must happen on the event
  95. # loop, inside the new task — otherwise we'd freeze the loop and the
  96. # previous task could never reach its tear_down().
  97. previous_task: Optional[asyncio.Task] = None
  98. if is_running():
  99. print("Warning: A GUI is already running. Stopping it first...")
  100. try:
  101. hello_imgui.get_runner_params().app_shall_exit = True
  102. except Exception:
  103. pass
  104. previous_task = _current_task
  105. # Determine which signature is being used and build the run_async coroutine
  106. if len(args) >= 1:
  107. first_arg = args[0]
  108. # Case 1: RunnerParams
  109. if isinstance(first_arg, RunnerParams):
  110. runner_params = first_arg
  111. run_coro_factory = lambda: hello_imgui.run_async(runner_params) # noqa: E731
  112. # Case 2: SimpleRunnerParams
  113. elif isinstance(first_arg, SimpleRunnerParams):
  114. simple_params = first_arg
  115. run_coro_factory = lambda: hello_imgui.run_async(simple_params) # noqa: E731
  116. # Case 3: GUI function with keyword arguments
  117. elif callable(first_arg):
  118. gui_function = first_arg
  119. # Apply notebook-friendly defaults to kwargs
  120. if "window_size_auto" not in kwargs and "window_size" not in kwargs:
  121. kwargs["window_size_auto"] = True
  122. extra_args = args[1:]
  123. run_coro_factory = lambda: hello_imgui.run_async(gui_function, *extra_args, **kwargs) # noqa: E731
  124. else:
  125. raise TypeError(f"First argument must be RunnerParams, SimpleRunnerParams, or a callable GUI function, got {type(first_arg)}")
  126. else:
  127. raise TypeError("start() requires at least one argument")
  128. async def _drain_then_run():
  129. if previous_task is not None:
  130. try:
  131. await previous_task
  132. except Exception:
  133. pass # Don't let a previous failure mask the new launch
  134. await run_coro_factory()
  135. _current_task = asyncio.create_task(_drain_then_run())
  136. return _current_task