diff --git a/docs/lang/articles/visualization/ggui.md b/docs/lang/articles/visualization/ggui.md index fc662630b40a1..7ae9c54b072e6 100644 --- a/docs/lang/articles/visualization/ggui.md +++ b/docs/lang/articles/visualization/ggui.md @@ -437,6 +437,22 @@ with gui.sub_window("Sub Window", x=10, y=10, width=300, height=100): color = gui.color_edit_3("name2", color) ``` +### Locking a subwindow + +You can control whether GGUI subwindows dimensions can be manipulated using these flags. +They all default to `True`: + +- `movable`: Allow users to drag the subwindow. If False, position can be updated programmatically. +- `resizable`: Allow users to resize the subwindow. If `False`, size can be updated programmatically. +- `collapsible`: Allow users to collapse the subwindow by clicking its title bar. + +```python +# Window that is fully locked +with gui.sub_window("Locked subwindow", 0.1, 0.1, 0.3, 0.2, + resizable=False, movable=False, collapsible=False): + gui.text("Locked!") +``` + ## Show a window Call `show()` to show a window. diff --git a/python/taichi/ui/imgui.py b/python/taichi/ui/imgui.py index 570a815526001..bb333d8eb9aad 100644 --- a/python/taichi/ui/imgui.py +++ b/python/taichi/ui/imgui.py @@ -13,7 +13,7 @@ def __init__(self, gui) -> None: self.gui = gui @contextmanager - def sub_window(self, name, x, y, width, height): + def sub_window(self, name, x, y, width, height, movable=True, resizable=True, collapsible=True): """Creating a context manager for subwindow. Note: @@ -26,19 +26,25 @@ def sub_window(self, name, x, y, width, height): corner of the subwindow, relative to the full window. width (float): The width of the subwindow relative to the full window. height (float): The height of the subwindow relative to the full window. + movable (bool): Whether the window can be moved by the user. \ + When False, position can only be changed programmatically. + resizable (bool): Whether the window can be resized by the user. \ + When False, size can only be changed programmatically. + collapsible (bool): Whether the window can be collapsed by the user. \ + When False, the collapse button is disabled. Example:: >>> with gui.sub_window(name, x, y, width, height) as g: >>> g.text("Hello, World!") """ - self.begin(name, x, y, width, height) + self.begin(name, x, y, width, height, movable, resizable, collapsible) try: yield self finally: self.end() - def begin(self, name, x, y, width, height): + def begin(self, name, x, y, width, height, movable=True, resizable=True, collapsible=True): """Creates a subwindow that holds imgui widgets. All widget function calls (e.g. `text`, `button`) after the `begin` @@ -51,8 +57,14 @@ def begin(self, name, x, y, width, height): corner of the subwindow, relative to the full window. width (float): The width of the subwindow relative to the full window. height (float): The height of the subwindow relative to the full window. + movable (bool): Whether the window can be moved by the user. \ + When False, position can only be changed programmatically. + resizable (bool): Whether the window can be resized by the user. \ + When False, size can only be changed programmatically. + collapsible (bool): Whether the window can be collapsed by the user. \ + When False, the collapse button is disabled. """ - self.gui.begin(name, x, y, width, height) + self.gui.begin(name, x, y, width, height, movable, resizable, collapsible) def end(self): """End the description of the current subwindow.""" diff --git a/taichi/python/export_ggui.cpp b/taichi/python/export_ggui.cpp index 965c54de70b01..098a7a0d8652c 100644 --- a/taichi/python/export_ggui.cpp +++ b/taichi/python/export_ggui.cpp @@ -60,8 +60,15 @@ py::array_t mat4_to_nparray(glm::mat4 mat) { struct PyGui { GuiBase *gui; // not owned - void begin(std::string name, float x, float y, float width, float height) { - gui->begin(name, x, y, width, height); + void begin(std::string name, + float x, + float y, + float width, + float height, + bool movable = true, + bool resizable = true, + bool collapsible = true) { + gui->begin(name, x, y, width, height, movable, resizable, collapsible); } void end() { gui->end(); diff --git a/taichi/ui/common/gui_base.h b/taichi/ui/common/gui_base.h index a7d296cbe7b06..37ef19cd5e5d4 100644 --- a/taichi/ui/common/gui_base.h +++ b/taichi/ui/common/gui_base.h @@ -10,7 +10,10 @@ class GuiBase { float x, float y, float width, - float height) = 0; + float height, + bool movable = true, + bool resizable = true, + bool collapsible = true) = 0; virtual void end() = 0; virtual void text(const std::string &text) = 0; virtual void text(const std::string &text, glm::vec3 color) = 0; diff --git a/taichi/ui/ggui/gui.cpp b/taichi/ui/ggui/gui.cpp index f5bc10833880b..d6592fcd4fc7f 100644 --- a/taichi/ui/ggui/gui.cpp +++ b/taichi/ui/ggui/gui.cpp @@ -145,13 +145,49 @@ void Gui::begin(const std::string &name, float x, float y, float width, - float height) { + float height, + bool movable, + bool resizable, + bool collapsible) { if (!initialized()) { return; } - ImGui::SetNextWindowPos(ImVec2(abs_x(x), abs_y(y)), ImGuiCond_Once); - ImGui::SetNextWindowSize(ImVec2(abs_x(width), abs_y(height)), ImGuiCond_Once); - ImGui::Begin(name.c_str()); + + // Update window dimensions when locked, so programmatic updates use current + // window size + if ((!movable || !resizable) && app_context_->config.show_window) { +#ifdef ANDROID + widthBeforeDPIScale = + (int)ANativeWindow_getWidth(app_context_->taichi_window()); + heightBeforeDPIScale = + (int)ANativeWindow_getHeight(app_context_->taichi_window()); +#else + glfwGetWindowSize(app_context_->taichi_window(), &widthBeforeDPIScale, + &heightBeforeDPIScale); +#endif + } + + // Set position: Always if locked, Once if unlocked + ImGuiCond pos_cond = movable ? ImGuiCond_Once : ImGuiCond_Always; + ImGui::SetNextWindowPos(ImVec2(abs_x(x), abs_y(y)), pos_cond); + + // Set size: Always if locked, Once if unlocked + ImGuiCond size_cond = resizable ? ImGuiCond_Once : ImGuiCond_Always; + ImGui::SetNextWindowSize(ImVec2(abs_x(width), abs_y(height)), size_cond); + + // Build window flags + ImGuiWindowFlags flags = 0; + if (!movable) { + flags |= ImGuiWindowFlags_NoMove; + } + if (!resizable) { + flags |= ImGuiWindowFlags_NoResize; + } + if (!collapsible) { + flags |= ImGuiWindowFlags_NoCollapse; + } + + ImGui::Begin(name.c_str(), nullptr, flags); is_empty_ = false; } void Gui::end() { diff --git a/taichi/ui/ggui/gui.h b/taichi/ui/ggui/gui.h index e54314e570f7a..05f97ba2d5067 100644 --- a/taichi/ui/ggui/gui.h +++ b/taichi/ui/ggui/gui.h @@ -33,7 +33,10 @@ class TI_DLL_EXPORT Gui final : public GuiBase { float x, float y, float width, - float height) override; + float height, + bool movable = true, + bool resizable = true, + bool collapsible = true) override; void end() override; void text(const std::string &text) override; void text(const std::string &text, glm::vec3 color) override; diff --git a/taichi/ui/ggui/gui_metal.h b/taichi/ui/ggui/gui_metal.h index 039c87fe9bc3a..8955c2ae2eb0c 100644 --- a/taichi/ui/ggui/gui_metal.h +++ b/taichi/ui/ggui/gui_metal.h @@ -27,7 +27,10 @@ class TI_DLL_EXPORT GuiMetal final : public GuiBase { float x, float y, float width, - float height) override; + float height, + bool movable = true, + bool resizable = true, + bool collapsible = true) override; void end() override; void text(const std::string &text) override; void text(const std::string &text, glm::vec3 color) override; diff --git a/taichi/ui/ggui/gui_metal.mm b/taichi/ui/ggui/gui_metal.mm index 61cbfb1b7c5b5..be1cb39ed523c 100644 --- a/taichi/ui/ggui/gui_metal.mm +++ b/taichi/ui/ggui/gui_metal.mm @@ -55,10 +55,36 @@ float GuiMetal::abs_y(float y) { return y * heightBeforeDPIScale; } void GuiMetal::begin(const std::string &name, float x, float y, float width, - float height) { - ImGui::SetNextWindowPos(ImVec2(abs_x(x), abs_y(y)), ImGuiCond_Once); - ImGui::SetNextWindowSize(ImVec2(abs_x(width), abs_y(height)), ImGuiCond_Once); - ImGui::Begin(name.c_str()); + float height, bool movable, bool resizable, + bool collapsible) { + // Update window dimensions when locked, so programmatic updates use current + // window size + if ((!movable || !resizable) && app_context_->config.show_window) { + glfwGetWindowSize(app_context_->taichi_window(), &widthBeforeDPIScale, + &heightBeforeDPIScale); + } + + // Set position: Always if locked, Once if unlocked + ImGuiCond pos_cond = movable ? ImGuiCond_Once : ImGuiCond_Always; + ImGui::SetNextWindowPos(ImVec2(abs_x(x), abs_y(y)), pos_cond); + + // Set size: Always if locked, Once if unlocked + ImGuiCond size_cond = resizable ? ImGuiCond_Once : ImGuiCond_Always; + ImGui::SetNextWindowSize(ImVec2(abs_x(width), abs_y(height)), size_cond); + + // Build window flags + ImGuiWindowFlags flags = 0; + if (!movable) { + flags |= ImGuiWindowFlags_NoMove; + } + if (!resizable) { + flags |= ImGuiWindowFlags_NoResize; + } + if (!collapsible) { + flags |= ImGuiWindowFlags_NoCollapse; + } + + ImGui::Begin(name.c_str(), nullptr, flags); is_empty_ = false; } void GuiMetal::end() { ImGui::End(); }