| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- # Part of ImGui Bundle - MIT License - Copyright (c) 2022-2026 Pascal Thomet - https://github.com/pthom/imgui_bundle
- """
- Notebook-specific API for immapp - simplified async GUI execution in Jupyter notebooks.
- This module provides convenient functions for running ImGui apps in Jupyter notebooks:
- - run(): Blocking execution with screenshot output (delegates to patched immapp.run)
- - start(): Non-blocking execution, returns auto-started asyncio.Task
- - stop(): Stop the currently running async GUI
- - is_running(): Check if an async GUI is currently running
- """
- import asyncio
- from typing import Optional
- # from imgui_bundle.immapp import AddOnsParams
- from imgui_bundle.hello_imgui import RunnerParams, SimpleRunnerParams
- # Global state for tracking the current async GUI task
- _current_task: Optional[asyncio.Task] = None
- def is_running() -> bool:
- """Check if an async GUI is currently running.
- Returns:
- True if a GUI started with start() is currently running, False otherwise
- Example:
- if immapp.nb.is_running():
- print("GUI is running")
- else:
- print("No GUI running")
- """
- global _current_task
- return _current_task is not None and not _current_task.done()
- def stop() -> None:
- """Stop the currently running async GUI.
- This sets the app_shall_exit flag, causing the GUI to close gracefully.
- The GUI will clean up and the task will complete.
- Example:
- task = immapp.nb.start(my_gui)
- # ... work in other cells ...
- immapp.nb.stop() # Close the GUI
- """
- if not is_running():
- print("Warning: No GUI is currently running")
- return
- from imgui_bundle import hello_imgui
- hello_imgui.get_runner_params().app_shall_exit = True
- def run(*args, **kwargs):
- """Run an ImGui application in blocking mode with screenshot output.
- This is a convenience wrapper that delegates to the patched immapp.run(),
- which runs the GUI, waits for it to close, and displays a screenshot in
- the notebook cell output.
- Signatures:
- run(runner_params: RunnerParams, addons_params: Optional[AddOnsParams] = None)
- run(simple_params: SimpleRunnerParams, addons_params: Optional[AddOnsParams] = None)
- run(gui_function, window_title="", ..., with_implot=False, ...)
- Example:
- def my_gui():
- imgui.text("Hello from notebook!")
- immapp.nb.run(my_gui, window_title="My App", window_size_auto=True)
- # Screenshot appears in cell output after closing
- Additional parameters to controls the thumbnail screenshot:
- * thumbnail_ratio: (default=1.0)
- You can use it to change the size of the thumbnail.
- Passing 0.5 will create a thumbnail half the width of the window.
- * thumbnail_height: (default=0)
- You can use it to set a fixed height for the thumbnail (in pixels).
- If 0, the height is computed from the app window size.
- (choose only one of the two parameters to control size)
- """
- from imgui_bundle import immapp
- return immapp.run(*args, **kwargs)
- def start(*args, **kwargs) -> asyncio.Task:
- """Start an 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:
- - Light theme for better visibility
- - 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, addons_params: Optional[AddOnsParams] = None)
- start(simple_params: SimpleRunnerParams, addons_params: Optional[AddOnsParams] = None)
- start(gui_function, window_title="", ..., with_implot=False, ...)
- Returns:
- asyncio.Task: The task running the GUI (can be awaited if needed)
- Example:
- def my_gui():
- global freq
- changed, freq = imgui.slider_float("Frequency", freq, 0.1, 10.0)
- # Start the GUI
- task = immapp.nb.start(my_gui, with_implot=True, window_size_auto=True)
- print("GUI started")
- # In another cell: modify variables
- freq = 5.0 # GUI updates immediately!
- # In another cell: close the GUI
- immapp.nb.stop()
- """
- global _current_task
- # Import here to avoid circular imports
- from imgui_bundle import hello_imgui, immapp
- from imgui_bundle.immapp.immapp_notebook import _make_gui_with_light_theme
- # 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...")
- hello_imgui.get_runner_params().app_shall_exit = True
- 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
- addons_params = args[1] if len(args) > 1 else kwargs.get("addons_params", None)
- # Apply notebook-friendly defaults
- # if runner_params.app_window_params.window_geometry.size_auto is None:
- # runner_params.app_window_params.window_geometry.size_auto = True
- # if not runner_params.app_window_params.top_most:
- # runner_params.app_window_params.top_most = True
- # Wrap GUI function with light theme
- if runner_params.callbacks.show_gui is not None:
- original_gui = runner_params.callbacks.show_gui
- runner_params.callbacks.show_gui = _make_gui_with_light_theme(original_gui)
- run_coro_factory = lambda: immapp.run_async(runner_params, addons_params) # noqa: E731
- # Case 2: SimpleRunnerParams
- elif isinstance(first_arg, SimpleRunnerParams):
- simple_params = first_arg
- addons_params = args[1] if len(args) > 1 else kwargs.get("addons_params", None)
- # Apply notebook-friendly defaults
- # if not simple_params.window_size_auto:
- # simple_params.window_size_auto = True
- # if not simple_params.top_most:
- # simple_params.top_most = True
- # Wrap GUI function with light theme
- if simple_params.gui_function is not None:
- original_gui = simple_params.gui_function
- simple_params.gui_function = _make_gui_with_light_theme(original_gui)
- run_coro_factory = lambda: immapp.run_async(simple_params, addons_params) # noqa: E731
- # Case 3: GUI function with keyword arguments
- elif callable(first_arg):
- gui_function = first_arg
- # Wrap GUI function with light theme
- gui_with_theme = _make_gui_with_light_theme(gui_function)
- # 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: immapp.run_async(gui_with_theme, *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
|