diff --git a/bindings/imgui_bundle/demos_cpp/sandbox/sandbox_immapp_manual_render.cpp b/bindings/imgui_bundle/demos_cpp/sandbox/sandbox_immapp_manual_render.cpp new file mode 100644 index 00000000..235cf952 --- /dev/null +++ b/bindings/imgui_bundle/demos_cpp/sandbox/sandbox_immapp_manual_render.cpp @@ -0,0 +1,70 @@ +#include "immapp/immapp.h" +#include "imgui_md_wrapper/imgui_md_wrapper.h" +#include "hello_imgui/hello_imgui.h" +#include "imgui.h" + + +void Gui() +{ + ImGuiMd::RenderUnindented(R"( + # Sandbox + + Lorem ipsum dolor sit amet, consectetur adipiscing elit + )"); + if (ImGui::Button("Save")) + { + // Save something + } + ImGui::SameLine(); + if (ImGui::Button("Load")) + { + // Load something + } +} + +int main(int, char **) +{ + + //ImmApp::Run(runnerParams, addonsParams); + + { + HelloImGui::RunnerParams runnerParams; + runnerParams.callbacks.ShowGui = Gui; + ImmApp::AddOnsParams addonsParams; + addonsParams.withMarkdown = true; + + ImmApp::ManualRender::SetupFromRunnerParams(runnerParams, addonsParams); + while(!HelloImGui::GetRunnerParams()->appShallExit) + ImmApp::ManualRender::Render(); + ImmApp::ManualRender::TearDown(); + } + + { + ImmApp::ManualRender::SetupFromGuiFunction( + Gui, + "Sandbox", // windowTitle + false, // windowSizeAuto + false, // windowRestorePreviousGeometry + ImmApp::DefaultWindowSize, // windowSize + 10.f, // fpsIdle + false, // withImplot + true, // withMarkdown + false // withNodeEditor + ); + while(!HelloImGui::GetRunnerParams()->appShallExit) + ImmApp::ManualRender::Render(); + ImmApp::ManualRender::TearDown(); + } + + { + HelloImGui::SimpleRunnerParams runnerParams; + runnerParams.guiFunction = Gui; + ImmApp::AddOnsParams addonsParams; + addonsParams.withMarkdown = true; + ImmApp::ManualRender::SetupFromSimpleRunnerParams(runnerParams, addonsParams); + while(!HelloImGui::GetRunnerParams()->appShallExit) + ImmApp::ManualRender::Render(); + ImmApp::ManualRender::TearDown(); + } + +} \ No newline at end of file diff --git a/bindings/imgui_bundle/demos_python/sandbox/sandbox_immapp_manual_render.py b/bindings/imgui_bundle/demos_python/sandbox/sandbox_immapp_manual_render.py new file mode 100644 index 00000000..044f86da --- /dev/null +++ b/bindings/imgui_bundle/demos_python/sandbox/sandbox_immapp_manual_render.py @@ -0,0 +1,29 @@ +from imgui_bundle import immapp, hello_imgui, imgui_md, imgui + + +def gui(): + imgui_md.render_unindented(""" + # Sandbox + + Lorem ipsum dolor sit amet, consectetur adipiscing elit + """) + if imgui.button("Save"): + # Save something + pass + imgui.same_line() + if imgui.button("Load"): + # Load something + pass + + +def main(): + immapp.manual_render.setup_from_gui_function(gui) + while not hello_imgui.get_runner_params().app_shall_exit: + immapp.manual_render.render() + immapp.manual_render.tear_down() + + +if __name__ == "__main__": + main() + main() + main() diff --git a/bindings/imgui_bundle/immapp/__init__.py b/bindings/imgui_bundle/immapp/__init__.py index 9a20bbbc..cef0398f 100644 --- a/bindings/imgui_bundle/immapp/__init__.py +++ b/bindings/imgui_bundle/immapp/__init__.py @@ -1,4 +1,5 @@ # Part of ImGui Bundle - MIT License - Copyright (c) 2022-2023 Pascal Thomet - https://github.com/pthom/imgui_bundle +from imgui_bundle import _imgui_bundle as _native_bundle from imgui_bundle._imgui_bundle import immapp_cpp as immapp_cpp # type: ignore from imgui_bundle._imgui_bundle.immapp_cpp import ( # type: ignore clock_seconds, @@ -32,7 +33,7 @@ # runner_params.callbacks.default_icon_font = hello_imgui.DefaultIconFont.font_awesome6 from imgui_bundle.immapp import icons_fontawesome_4 as icons_fontawesome_4 from imgui_bundle.immapp import icons_fontawesome_6 as icons_fontawesome_6 -from imgui_bundle.immapp import icons_fontawesome_4 as icons_fontawesome # v4 +from imgui_bundle.immapp import icons_fontawesome_4 as icons_fontawesome # Icons font awesome v4 from imgui_bundle.immapp.immapp_utils import ( static as static, @@ -46,6 +47,9 @@ SimpleRunnerParams as SimpleRunnerParams, ) + +manual_render = _native_bundle.immapp_cpp.manual_render + __all__ = [ "clock_seconds", "default_node_editor_context", @@ -69,6 +73,7 @@ "RunnerParams", "SimpleRunnerParams", "snippets", + "manual_render", "begin_plot_in_node_editor", "end_plot_in_node_editor", "show_resizable_plot_in_node_editor", diff --git a/bindings/imgui_bundle/immapp/__init__.pyi b/bindings/imgui_bundle/immapp/__init__.pyi index d21848d5..0fbcf65b 100644 --- a/bindings/imgui_bundle/immapp/__init__.pyi +++ b/bindings/imgui_bundle/immapp/__init__.pyi @@ -10,6 +10,7 @@ from .immapp_cpp import ( run_with_markdown as run_with_markdown, AddOnsParams as AddOnsParams, snippets as snippets, + manual_render as manual_render, begin_plot_in_node_editor as begin_plot_in_node_editor, end_plot_in_node_editor as end_plot_in_node_editor, show_resizable_plot_in_node_editor as show_resizable_plot_in_node_editor, diff --git a/bindings/imgui_bundle/immapp/immapp_cpp.pyi b/bindings/imgui_bundle/immapp/immapp_cpp.pyi index 66cb99d0..069b8b15 100644 --- a/bindings/imgui_bundle/immapp/immapp_cpp.pyi +++ b/bindings/imgui_bundle/immapp/immapp_cpp.pyi @@ -303,8 +303,87 @@ def delete_node_editor_settings(runner_params: HelloImGui.RunnerParams) -> None: pass # #endif -# } +# + +# =========================== HelloImGui::ManualRender ================================== +# @@md#HelloImGui::ManualRender + +# @@md + +# +class manual_render: # Proxy class that introduces typings for the *submodule* manual_render + pass # (This corresponds to a C++ namespace. All method are static!) + """ namespace ManualRender""" + # Immapp::ManualRender is a namespace that groups functions, allowing fine-grained control over the rendering process: + # - It is customizable like Immapp::Run: initialize it with `RunnerParams` and `AddOnsParams`. + # - `ManualRender::Render()` will render the application for one frame: + # - Ensure that `ManualRender::Render()` is triggered regularly (e.g., through a loop or other mechanism) + # to maintain responsiveness. This method must be called on the main thread. + + @staticmethod + def setup_from_runner_params( + runner_params: HelloImGui.RunnerParams, + add_ons_params: Optional[AddOnsParams] = None, + ) -> None: + """Initializes the rendering with the full customizable `RunnerParams`. + This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + A distinct copy of `RunnerParams` is stored internally. + --- + Python bindings defaults: + If addOnsParams is None, then its default value will be: AddOnsParams() + """ + pass + + @staticmethod + def setup_from_simple_runner_params( + simple_params: HelloImGui.SimpleRunnerParams, + add_ons_params: Optional[AddOnsParams] = None, + ) -> None: + """Initializes the rendering with `SimpleRunnerParams`. + This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + --- + Python bindings defaults: + If addOnsParams is None, then its default value will be: AddOnsParams() + """ + pass + + @staticmethod + def setup_from_gui_function( + gui_function: VoidFunction, + window_title: str = "", + window_size_auto: bool = False, + window_restore_previous_geometry: bool = False, + window_size: Optional[ScreenSize] = None, + fps_idle: float = 10.0, + with_implot: bool = False, + with_markdown: bool = False, + with_node_editor: bool = False, + with_tex_inspect: bool = False, + with_node_editor_config: Optional[NodeEditorConfig] = None, + with_markdown_options: Optional[ImGuiMd.MarkdownOptions] = None, + ) -> None: + """Initializes the renderer with a simple GUI function and additional parameters. + This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + --- + Python bindings defaults: + If windowSize is None, then its default value will be: DefaultWindowSize + """ + pass + + @staticmethod + def render() -> None: + """Renders the current frame. Should be called regularly to maintain the application's responsiveness.""" + pass + + @staticmethod + def tear_down() -> None: + """Tears down the renderer and releases all associated resources. + This will release the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + After calling `TearDown()`, the InitFromXXX can be called with new parameters. + """ + pass +# #################### #################### #################### #################### diff --git a/external/immapp/bindings/pybind_immapp_cpp.cpp b/external/immapp/bindings/pybind_immapp_cpp.cpp index 814f499b..5acf5b28 100644 --- a/external/immapp/bindings/pybind_immapp_cpp.cpp +++ b/external/immapp/bindings/pybind_immapp_cpp.cpp @@ -284,7 +284,79 @@ void py_init_module_immapp_cpp(nb::module_& m) nb::arg("runner_params"), "DeleteNodeEditorSettings deletes the json file for the node editor settings."); // #endif - // } + // + + { // + nb::module_ pyNsManualRender = m.def_submodule("manual_render", "namespace ManualRender"); + pyNsManualRender.def("setup_from_runner_params", + [](const HelloImGui::RunnerParams & runnerParams, const std::optional & addOnsParams = std::nullopt) + { + auto SetupFromRunnerParams_adapt_mutable_param_with_default_value = [](const HelloImGui::RunnerParams & runnerParams, const std::optional & addOnsParams = std::nullopt) + { + + const ImmApp::AddOnsParams& addOnsParams_or_default = [&]() -> const ImmApp::AddOnsParams { + if (addOnsParams.has_value()) + return addOnsParams.value(); + else + return ImmApp::AddOnsParams(); + }(); + + ImmApp::ManualRender::SetupFromRunnerParams(runnerParams, addOnsParams_or_default); + }; + + SetupFromRunnerParams_adapt_mutable_param_with_default_value(runnerParams, addOnsParams); + }, + nb::arg("runner_params"), nb::arg("add_ons_params") = nb::none(), + " Initializes the rendering with the full customizable `RunnerParams`.\n This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.).\n A distinct copy of `RunnerParams` is stored internally.\n---\nPython bindings defaults:\n If addOnsParams is None, then its default value will be: AddOnsParams()"); + + pyNsManualRender.def("setup_from_simple_runner_params", + [](const HelloImGui::SimpleRunnerParams & simpleParams, const std::optional & addOnsParams = std::nullopt) + { + auto SetupFromSimpleRunnerParams_adapt_mutable_param_with_default_value = [](const HelloImGui::SimpleRunnerParams & simpleParams, const std::optional & addOnsParams = std::nullopt) + { + + const ImmApp::AddOnsParams& addOnsParams_or_default = [&]() -> const ImmApp::AddOnsParams { + if (addOnsParams.has_value()) + return addOnsParams.value(); + else + return ImmApp::AddOnsParams(); + }(); + + ImmApp::ManualRender::SetupFromSimpleRunnerParams(simpleParams, addOnsParams_or_default); + }; + + SetupFromSimpleRunnerParams_adapt_mutable_param_with_default_value(simpleParams, addOnsParams); + }, + nb::arg("simple_params"), nb::arg("add_ons_params") = nb::none(), + " Initializes the rendering with `SimpleRunnerParams`.\n This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.).\n---\nPython bindings defaults:\n If addOnsParams is None, then its default value will be: AddOnsParams()"); + + pyNsManualRender.def("setup_from_gui_function", + [](const VoidFunction & guiFunction, const std::string & windowTitle = "", bool windowSizeAuto = false, bool windowRestorePreviousGeometry = false, const std::optional & windowSize = std::nullopt, float fpsIdle = 10.f, bool withImplot = false, bool withMarkdown = false, bool withNodeEditor = false, bool withTexInspect = false, const std::optional & withNodeEditorConfig = std::nullopt, const std::optional & withMarkdownOptions = std::nullopt) + { + auto SetupFromGuiFunction_adapt_mutable_param_with_default_value = [](const VoidFunction & guiFunction, const std::string & windowTitle = "", bool windowSizeAuto = false, bool windowRestorePreviousGeometry = false, const std::optional & windowSize = std::nullopt, float fpsIdle = 10.f, bool withImplot = false, bool withMarkdown = false, bool withNodeEditor = false, bool withTexInspect = false, const std::optional & withNodeEditorConfig = std::nullopt, const std::optional & withMarkdownOptions = std::nullopt) + { + + const ScreenSize& windowSize_or_default = [&]() -> const ScreenSize { + if (windowSize.has_value()) + return windowSize.value(); + else + return DefaultWindowSize; + }(); + + ImmApp::ManualRender::SetupFromGuiFunction(guiFunction, windowTitle, windowSizeAuto, windowRestorePreviousGeometry, windowSize_or_default, fpsIdle, withImplot, withMarkdown, withNodeEditor, withTexInspect, withNodeEditorConfig, withMarkdownOptions); + }; + + SetupFromGuiFunction_adapt_mutable_param_with_default_value(guiFunction, windowTitle, windowSizeAuto, windowRestorePreviousGeometry, windowSize, fpsIdle, withImplot, withMarkdown, withNodeEditor, withTexInspect, withNodeEditorConfig, withMarkdownOptions); + }, + nb::arg("gui_function"), nb::arg("window_title") = "", nb::arg("window_size_auto") = false, nb::arg("window_restore_previous_geometry") = false, nb::arg("window_size") = nb::none(), nb::arg("fps_idle") = 10.f, nb::arg("with_implot") = false, nb::arg("with_markdown") = false, nb::arg("with_node_editor") = false, nb::arg("with_tex_inspect") = false, nb::arg("with_node_editor_config") = nb::none(), nb::arg("with_markdown_options") = nb::none(), + " Initializes the renderer with a simple GUI function and additional parameters.\n This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.).\n---\nPython bindings defaults:\n If windowSize is None, then its default value will be: DefaultWindowSize"); + + pyNsManualRender.def("render", + ImmApp::ManualRender::Render, "Renders the current frame. Should be called regularly to maintain the application's responsiveness."); + + pyNsManualRender.def("tear_down", + ImmApp::ManualRender::TearDown, " Tears down the renderer and releases all associated resources.\n This will release the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.).\n After calling `TearDown()`, the InitFromXXX can be called with new parameters."); + } // //////////////////// //////////////////// diff --git a/external/immapp/immapp/runner.cpp b/external/immapp/immapp/runner.cpp index af65f59f..b77c33a9 100644 --- a/external/immapp/immapp/runner.cpp +++ b/external/immapp/immapp/runner.cpp @@ -48,11 +48,8 @@ namespace ImmApp ImmAppContext gImmAppContext; - - void Run(HelloImGui::RunnerParams& runnerParams, const AddOnsParams& addOnsParams_) + static void Priv_Setup(HelloImGui::RunnerParams& runnerParams, AddOnsParams& addOnsParams) { - AddOnsParams addOnsParams = addOnsParams_; - // create implot context if required #ifdef IMGUI_BUNDLE_WITH_IMPLOT if (addOnsParams.withImplot) @@ -139,7 +136,7 @@ namespace ImmApp runnerParams.callbacks.PostInit = HelloImGui::SequenceFunctions( fn_ImGuiTextInspect_Init, runnerParams.callbacks.PostInit - ); + ); } // Modify before-exit: DeInit ImGuiTexInspect @@ -155,7 +152,7 @@ namespace ImmApp runnerParams.callbacks.BeforeExit = HelloImGui::SequenceFunctions( fn_ImGuiTextInspect_DeInit, runnerParams.callbacks.BeforeExit - ); + ); } } #endif @@ -166,10 +163,10 @@ namespace ImmApp runnerParams.callbacks.BeforeExit, ImmVision::ClearTextureCache); #endif + } - - HelloImGui::Run(runnerParams); - + void Priv_Teardown(AddOnsParams& addOnsParams) + { #ifdef IMGUI_BUNDLE_WITH_IMPLOT if (addOnsParams.withImplot) ImPlot::DestroyContext(); @@ -186,7 +183,14 @@ namespace ImmApp if (addOnsParams.withMarkdown || addOnsParams.withMarkdownOptions.has_value()) ImGuiMd::DeInitializeMarkdown(); + } + void Run(HelloImGui::RunnerParams& runnerParams, const AddOnsParams& addOnsParams_) + { + AddOnsParams addOnsParams = addOnsParams_; + Priv_Setup(runnerParams, addOnsParams); + HelloImGui::Run(runnerParams); + Priv_Teardown(addOnsParams); } void Run(const HelloImGui::SimpleRunnerParams& simpleParams, const AddOnsParams& addOnsParams) @@ -354,4 +358,108 @@ namespace ImmApp } #endif + + +// ========================= ManualRender ==================================================== + +namespace ManualRender // namespace ImmApp::ManualRender +{ + // Enumeration to track the current state of the ManualRenderer + enum class RendererStatus + { + NotInitialized, + Initialized, + }; + RendererStatus sCurrentStatus = RendererStatus::NotInitialized; + + static AddOnsParams sAddOnsParams; + + // Changes the current status to Initialized if it was NotInitialized, + // otherwise raises an error (assert or exception) + void TrySwitchToInitialized() + { + if (sCurrentStatus == RendererStatus::Initialized) + IM_ASSERT(false && "ImmApp::ManualRender::SetupFromXXX() cannot be called while already initialized. Call TearDown() first."); + sCurrentStatus = RendererStatus::Initialized; + } + + // Changes the current status to NotInitialized if it was Initialized, + // otherwise raises an error (assert or exception) + void TrySwitchToNotInitialized() + { + if (sCurrentStatus == RendererStatus::NotInitialized) + IM_ASSERT(false && "ImmApp::ManualRender::TearDown() cannot be called while not initialized."); + sCurrentStatus = RendererStatus::NotInitialized; + } + + + void SetupFromRunnerParams(const HelloImGui::RunnerParams& runnerParams, const AddOnsParams& addOnsParams) + { + TrySwitchToInitialized(); + sAddOnsParams = addOnsParams; + HelloImGui::RunnerParams runnerParamsCopy = runnerParams; + Priv_Setup(runnerParamsCopy, sAddOnsParams); + HelloImGui::ManualRender::SetupFromRunnerParams(runnerParamsCopy); + } + + void SetupFromSimpleRunnerParams(const HelloImGui::SimpleRunnerParams& simpleParams, const AddOnsParams& addOnsParams) + { + HelloImGui::RunnerParams runnerParams = simpleParams.ToRunnerParams(); + SetupFromRunnerParams(runnerParams, addOnsParams); + } + + void SetupFromGuiFunction( + const VoidFunction& guiFunction, + const std::string& windowTitle, + bool windowSizeAuto, + bool windowRestorePreviousGeometry, + const ScreenSize& windowSize, + float fpsIdle, + + // AddOnsParams below: + bool withImplot, + bool withMarkdown, + bool withNodeEditor, + bool withTexInspect, +#ifdef IMGUI_BUNDLE_WITH_IMGUI_NODE_EDITOR + const std::optional& withNodeEditorConfig, +#endif + const std::optional & withMarkdownOptions + ) + { + HelloImGui::SimpleRunnerParams simpleRunnerParams; + simpleRunnerParams.guiFunction = guiFunction; + simpleRunnerParams.windowTitle = windowTitle; + simpleRunnerParams.windowSizeAuto = windowSizeAuto; + simpleRunnerParams.windowRestorePreviousGeometry = windowRestorePreviousGeometry; + simpleRunnerParams.windowSize = windowSize; + simpleRunnerParams.fpsIdle = fpsIdle; + + AddOnsParams addOnsParams; + addOnsParams.withImplot = withImplot; + addOnsParams.withMarkdown = true; + addOnsParams.withNodeEditor = withNodeEditor; + addOnsParams.withTexInspect = withTexInspect; +#ifdef IMGUI_BUNDLE_WITH_IMGUI_NODE_EDITOR + addOnsParams.withNodeEditorConfig = withNodeEditorConfig; +#endif + addOnsParams.withMarkdownOptions = withMarkdownOptions; + + SetupFromSimpleRunnerParams(simpleRunnerParams, addOnsParams); + } + + void Render() + { + HelloImGui::ManualRender::Render(); + } + + void TearDown() + { + TrySwitchToNotInitialized(); + HelloImGui::ManualRender::TearDown(); + Priv_Teardown(sAddOnsParams); + sAddOnsParams = AddOnsParams(); + } +} // namespace ManualRender + } // namespace ImmApp diff --git a/external/immapp/immapp/runner.h b/external/immapp/immapp/runner.h index 5260aed5..1b913fdc 100644 --- a/external/immapp/immapp/runner.h +++ b/external/immapp/immapp/runner.h @@ -174,4 +174,58 @@ namespace ImmApp void DeleteNodeEditorSettings(const HelloImGui::RunnerParams& runnerParams); #endif + + +// =========================== HelloImGui::ManualRender ================================== +// @@md#HelloImGui::ManualRender + + namespace ManualRender + { + // Immapp::ManualRender is a namespace that groups functions, allowing fine-grained control over the rendering process: + // - It is customizable like Immapp::Run: initialize it with `RunnerParams` and `AddOnsParams`. + // - `ManualRender::Render()` will render the application for one frame: + // - Ensure that `ManualRender::Render()` is triggered regularly (e.g., through a loop or other mechanism) + // to maintain responsiveness. This method must be called on the main thread. + + // Initializes the rendering with the full customizable `RunnerParams`. + // This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + // A distinct copy of `RunnerParams` is stored internally. + void SetupFromRunnerParams(const HelloImGui::RunnerParams& runnerParams, const AddOnsParams& addOnsParams = AddOnsParams()); + + // Initializes the rendering with `SimpleRunnerParams`. + // This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + void SetupFromSimpleRunnerParams(const HelloImGui::SimpleRunnerParams& simpleParams, const AddOnsParams& addOnsParams = AddOnsParams()); + + // Initializes the renderer with a simple GUI function and additional parameters. + // This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + void SetupFromGuiFunction( + const VoidFunction& guiFunction, + const std::string& windowTitle = "", + bool windowSizeAuto = false, + bool windowRestorePreviousGeometry = false, + const ScreenSize& windowSize = DefaultWindowSize, + float fpsIdle = 10.f, + + // AddOnsParams below: + bool withImplot = false, + bool withMarkdown = false, + bool withNodeEditor = false, + bool withTexInspect = false, +#ifdef IMGUI_BUNDLE_WITH_IMGUI_NODE_EDITOR + const std::optional& withNodeEditorConfig = std::nullopt, +#endif + const std::optional & withMarkdownOptions = std::nullopt + ); + + // Renders the current frame. Should be called regularly to maintain the application's responsiveness. + void Render(); + + // Tears down the renderer and releases all associated resources. + // This will release the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + // After calling `TearDown()`, the InitFromXXX can be called with new parameters. + void TearDown(); + } // namespace ManualRender + +// @@md + } // namespace ImmApp