diff --git a/bindings/imgui_bundle/__init__.py b/bindings/imgui_bundle/__init__.py
index 926fa26a..d0edb1da 100644
--- a/bindings/imgui_bundle/__init__.py
+++ b/bindings/imgui_bundle/__init__.py
@@ -151,6 +151,12 @@ def has_submodule(submodule_name):
from imgui_bundle.pyodide_patch_runners import pyodide_do_patch_runners
pyodide_do_patch_runners()
+#
+# Jupyter notebook: patch hello_imgui.run and immapp.run to work with Jupyter notebook
+#
+from imgui_bundle.notebook_patch_runners import notebook_do_patch_runners_if_needed # noqa: E402
+notebook_do_patch_runners_if_needed()
+
#
# Override assets folder
diff --git a/bindings/imgui_bundle/immapp/immapp_notebook.py b/bindings/imgui_bundle/immapp/immapp_notebook.py
index 3f6f145a..972e24d8 100644
--- a/bindings/imgui_bundle/immapp/immapp_notebook.py
+++ b/bindings/imgui_bundle/immapp/immapp_notebook.py
@@ -1,27 +1,28 @@
# Part of ImGui Bundle - MIT License - Copyright (c) 2022-2023 Pascal Thomet - https://github.com/pthom/imgui_bundle
# mypy: disable_error_code=no-untyped-call
-from typing import Optional, Callable, Tuple
+from typing import Callable, Tuple
from imgui_bundle import immapp, hello_imgui
-import imgui_bundle
GuiFunction = Callable[[], None]
ScreenSize = Tuple[int, int]
-def run_nb(
- gui_function: GuiFunction,
- window_title: str = "",
- window_size_auto: bool = True,
- window_restore_previous_geometry: bool = False,
- window_size: ScreenSize = (0, 0),
- fps_idle: float = 10.0,
- with_implot: bool = True,
- with_markdown: bool = True,
- with_node_editor: bool = True,
- thumbnail_height: int = 0,
- thumbnail_ratio: float = 0.0,
- run_id: Optional[str] = None,
+def _make_gui_with_light_theme(gui_function: GuiFunction) -> GuiFunction:
+ @immapp.static(was_theme_set=False)
+ def inner():
+ static = inner
+ if not static.was_theme_set:
+ hello_imgui.apply_theme(hello_imgui.ImGuiTheme_.white_is_white)
+ static.was_theme_set = True
+ gui_function()
+ return inner
+
+
+def _run_app_function_and_display_image_in_notebook(
+ app_function: GuiFunction, # A function that runs the app entirely
+ thumbnail_height: int = 0,
+ thumbnail_ratio: float = 0.0,
) -> None:
"""ImguiBundle app runner for jupyter notebook
@@ -38,32 +39,6 @@ def run_nb(
import cv2 # type: ignore
import PIL.Image # pip install pillow
from IPython.display import display # type: ignore
- from IPython.core.display import HTML # type: ignore
-
- def run_app():
- nonlocal window_size_auto
- if window_size[0] > 0 and window_size[1] > 0:
- window_size_auto = False
-
- @immapp.static(was_theme_set=False)
- def gui_with_light_theme():
- static = gui_with_light_theme
- if not static.was_theme_set:
- hello_imgui.apply_theme(hello_imgui.ImGuiTheme_.white_is_white)
- static.was_theme_set = True
- gui_function()
-
- immapp.run(
- gui_function=gui_with_light_theme,
- window_title=window_title,
- window_size_auto=window_size_auto,
- window_restore_previous_geometry=window_restore_previous_geometry,
- window_size=window_size,
- fps_idle=fps_idle,
- with_implot=with_implot,
- with_markdown=with_markdown,
- with_node_editor=with_node_editor,
- )
def make_thumbnail(image):
resize_ratio = 1.0
@@ -109,7 +84,7 @@ def run_app_and_display_thumb():
nonlocal thumbnail_ratio
from imgui_bundle import hello_imgui
- run_app()
+ app_function()
app_image = hello_imgui.final_app_window_screenshot()
scale = hello_imgui.final_app_window_screenshot_framebuffer_scale()
@@ -119,39 +94,48 @@ def run_app_and_display_thumb():
thumbnail = make_thumbnail(app_image)
display_image(thumbnail)
- def display_run_button():
- html_code = f"""
-
-
- """
- display(HTML(html_code))
-
- def display_app_with_run_button(run_id):
- """Experiment displaying a "run" button in the notebook below the screenshot. Disabled as of now
- If using this, it would be possible to run the app only if the user clicks on the Run button
- (and not during normal cell execution).
- """
- if run_id is None:
- run_app_and_display_thumb()
- else:
- if hasattr(imgui_bundle, "JAVASCRIPT_RUN_ID"):
- print(
- "imgui_bundle.JAVASCRIPT_RUN_ID="
- + imgui_bundle.JAVASCRIPT_RUN_ID
- + "{run_id=}"
- )
- if imgui_bundle.JAVASCRIPT_RUN_ID == run_id:
- run_app_and_display_thumb()
- else:
- print("imgui_bundle: no JAVASCRIPT_RUN_ID")
-
- # display_app_with_run_button(run_id)
run_app_and_display_thumb()
+
+
+def run_nb(
+ gui_function: GuiFunction,
+ window_title: str = "",
+ window_size_auto: bool = True,
+ window_restore_previous_geometry: bool = False,
+ window_size: ScreenSize = (0, 0),
+ fps_idle: float = 10.0,
+ with_implot: bool = True,
+ with_markdown: bool = True,
+ with_node_editor: bool = True,
+ thumbnail_height: int = 0,
+ thumbnail_ratio: float = 0.0,
+) -> None:
+ """ImguiBundle app runner for jupyter notebook
+
+ This runner is able to:
+ * run an ImGui app from a jupyter notebook
+ * display a thumbnail of the app + a "run" button in the cell output
+
+ The GUI will be rendered with a white theme.
+ If window_size is left as its default value (0, 0), then the window will autosize.
+
+ thumbnail_height and thumbnail_ratio control the size of the screenshot that is displayed after execution.
+ """
+ def run_app():
+ nonlocal window_size_auto
+ if window_size[0] > 0 and window_size[1] > 0:
+ window_size_auto = False
+
+ immapp.run(
+ gui_function=_make_gui_with_light_theme(gui_function),
+ window_title=window_title,
+ window_size_auto=window_size_auto,
+ window_restore_previous_geometry=window_restore_previous_geometry,
+ window_size=window_size,
+ fps_idle=fps_idle,
+ with_implot=with_implot,
+ with_markdown=with_markdown,
+ with_node_editor=with_node_editor,
+ )
+
+ _run_app_function_and_display_image_in_notebook(run_app, thumbnail_height, thumbnail_ratio)
diff --git a/bindings/imgui_bundle/notebook_patch_runners.py b/bindings/imgui_bundle/notebook_patch_runners.py
new file mode 100644
index 00000000..9fc6f031
--- /dev/null
+++ b/bindings/imgui_bundle/notebook_patch_runners.py
@@ -0,0 +1,54 @@
+"""Patch the immapp and hello_imgui runners for Jupyter notebook.
+- Will display a screenshot of the final app state in the notebook output.
+- Will use a white theme for the GUI.
+- Will make the window autosize by default.
+- Will patch hello_imgui.run and immapp.run
+"""
+
+def is_in_notebook() -> bool:
+ try:
+ import sys
+ return 'ipykernel' in sys.modules and 'IPython' in sys.modules
+ except ImportError:
+ return False
+
+
+def notebook_do_patch_runners_if_needed() -> None:
+ if not is_in_notebook():
+ return
+
+ from imgui_bundle import immapp, hello_imgui
+ from imgui_bundle.immapp.immapp_notebook import _run_app_function_and_display_image_in_notebook
+
+ def patch_runner(run_backup):
+ def patched_run(*args, **kwargs):
+ # Are we using hello_imgui.RunnerParams, or are we using raw parameters (i.e. a gui_function + other parameters)?
+ use_gui_function = (len(args) >= 1 and callable(args[0])) or "gui_function" in kwargs
+
+ # Set window_size_auto to True if not set, to make the window smaller and reduce the size of the screenshot
+ if use_gui_function and "window_size" not in kwargs and "window_size_auto" not in kwargs:
+ kwargs["window_size_auto"] = True
+
+ # If using a gui function, patch it so that it uses a white theme
+ if use_gui_function:
+ gui_function = args[0] if len(args) >= 1 else kwargs.get("gui_function", None)
+ if gui_function:
+ from imgui_bundle.immapp.immapp_notebook import _make_gui_with_light_theme
+ gui_function_with_light_theme = _make_gui_with_light_theme(gui_function)
+ if "gui_function" in kwargs:
+ kwargs["gui_function"] = gui_function_with_light_theme
+ else:
+ args = (gui_function_with_light_theme, *args[1:])
+
+ # define a function that will run the full app, then run this function
+ # via _run_app_function_and_display_image_in_notebook
+ def app_function():
+ run_backup(*args, **kwargs)
+ _run_app_function_and_display_image_in_notebook(app_function)
+ return patched_run
+
+ immapp_run_backup = immapp.run
+ immapp.run = patch_runner(immapp_run_backup)
+
+ hello_imgui_run_backup = hello_imgui.run
+ hello_imgui.run = patch_runner(hello_imgui_run_backup)