| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- """Notebook convenience API for hello_imgui.
- This module provides non-blocking GUI execution for Jupyter notebooks using hello_imgui.
- It's similar to immapp.nb but without AddOnsParams support.
- Functions:
- run() - Blocking mode (delegates to hello_imgui.run)
- start() - Non-blocking async mode with notebook-friendly defaults
- stop() - Stop the running GUI
- is_running() - Check if GUI is running
- """
- import asyncio
- from typing import Optional
- # Track the current running task
- _current_task: Optional[asyncio.Task] = None
- def is_running() -> bool:
- """Check if a GUI is currently running.
- Returns:
- bool: True if a GUI task is active and not done, False otherwise.
- Example:
- if hello_imgui.nb.is_running():
- print("GUI is active")
- """
- global _current_task
- return _current_task is not None and not _current_task.done()
- def stop() -> None:
- """Stop the currently running GUI by setting app_shall_exit flag.
- This is a convenience function that sets the exit flag for the running GUI.
- The GUI will close on its next render cycle.
- Example:
- hello_imgui.nb.stop() # Close the current GUI
- """
- if is_running():
- try:
- from imgui_bundle import hello_imgui
- hello_imgui.get_runner_params().app_shall_exit = True
- except Exception:
- pass # Ignore if runner params not available
- else:
- print("Warning: No GUI is currently running")
- def run(*args, **kwargs):
- """Run a hello_imgui application in blocking mode.
- This is a convenience wrapper that delegates to hello_imgui.run(),
- which runs the GUI and waits for it to close.
- Note: Unlike immapp.run, this does NOT automatically add light theme
- or screenshot functionality (hello_imgui doesn't have those features).
- Signatures:
- run(runner_params: RunnerParams)
- run(simple_params: SimpleRunnerParams)
- run(gui_function, window_title="", ...)
- Example:
- def my_gui():
- imgui.text("Hello from notebook!")
- hello_imgui.nb.run(my_gui, window_title="My App")
- # Blocks until window closed
- """
- from imgui_bundle import hello_imgui
- return hello_imgui.run(*args, **kwargs)
- def start(*args, **kwargs) -> asyncio.Task:
- """Start a hello_imgui application in non-blocking mode for interactive notebook use.
- This function starts a GUI that runs asynchronously, allowing you to:
- - Continue executing other notebook cells while the GUI is open
- - Modify variables from other cells and see updates in real-time
- - Run multiple sequential GUIs (close one before starting another)
- The function automatically applies notebook-friendly defaults:
- - window_size_auto=True (if not specified)
- - top_most=True (if not specified)
- If a GUI is already running, it will be automatically stopped with a warning.
- Signatures:
- start(runner_params: RunnerParams) -> asyncio.Task
- start(simple_params: SimpleRunnerParams) -> asyncio.Task
- start(gui_function, window_title="", window_size_auto=True, ...) -> asyncio.Task
- Args:
- *args: RunnerParams, SimpleRunnerParams, or gui_function
- **kwargs: Additional parameters when using gui_function signature
- Returns:
- asyncio.Task: The task running the GUI
- Example:
- counter = {"value": 0}
- def my_gui():
- imgui.text(f"Counter: {counter['value']}")
- if imgui.button("Increment"):
- counter["value"] += 1
- hello_imgui.nb.start(my_gui, window_title="Counter")
- # In another cell:
- counter["value"] = 100 # Updates in real-time!
- # To stop:
- hello_imgui.nb.stop()
- """
- global _current_task
- # Import here to avoid circular imports
- from imgui_bundle import hello_imgui
- RunnerParams = hello_imgui.RunnerParams # type: ignore
- SimpleRunnerParams = hello_imgui.SimpleRunnerParams # type: ignore
- # Capture the previous task (if any) and signal it to exit. We do NOT
- # block here: actually waiting for it to finish must happen on the event
- # loop, inside the new task — otherwise we'd freeze the loop and the
- # previous task could never reach its tear_down().
- previous_task: Optional[asyncio.Task] = None
- if is_running():
- print("Warning: A GUI is already running. Stopping it first...")
- try:
- hello_imgui.get_runner_params().app_shall_exit = True
- except Exception:
- pass
- previous_task = _current_task
- # Determine which signature is being used and build the run_async coroutine
- if len(args) >= 1:
- first_arg = args[0]
- # Case 1: RunnerParams
- if isinstance(first_arg, RunnerParams):
- runner_params = first_arg
- run_coro_factory = lambda: hello_imgui.run_async(runner_params) # noqa: E731
- # Case 2: SimpleRunnerParams
- elif isinstance(first_arg, SimpleRunnerParams):
- simple_params = first_arg
- run_coro_factory = lambda: hello_imgui.run_async(simple_params) # noqa: E731
- # Case 3: GUI function with keyword arguments
- elif callable(first_arg):
- gui_function = first_arg
- # Apply notebook-friendly defaults to kwargs
- if "window_size_auto" not in kwargs and "window_size" not in kwargs:
- kwargs["window_size_auto"] = True
- extra_args = args[1:]
- run_coro_factory = lambda: hello_imgui.run_async(gui_function, *extra_args, **kwargs) # noqa: E731
- else:
- raise TypeError(f"First argument must be RunnerParams, SimpleRunnerParams, or a callable GUI function, got {type(first_arg)}")
- else:
- raise TypeError("start() requires at least one argument")
- async def _drain_then_run():
- if previous_task is not None:
- try:
- await previous_task
- except Exception:
- pass # Don't let a previous failure mask the new launch
- await run_coro_factory()
- _current_task = asyncio.create_task(_drain_then_run())
- return _current_task
|