From 66cd2a02f954a0f1eddf9013900b2388dd133a4d Mon Sep 17 00:00:00 2001 From: Pascal Thomet Date: Tue, 14 May 2024 13:56:28 +0200 Subject: [PATCH] Add InputTextResizable --- src/hello_imgui/hello_imgui_widgets.h | 53 +++++++- src/hello_imgui/impl/hello_imgui_widgets.cpp | 118 +++++++++++++++++- .../hello_imgui_demodocking.main.cpp | 37 ++++-- 3 files changed, 192 insertions(+), 16 deletions(-) diff --git a/src/hello_imgui/hello_imgui_widgets.h b/src/hello_imgui/hello_imgui_widgets.h index b72dc061..0308555a 100644 --- a/src/hello_imgui/hello_imgui_widgets.h +++ b/src/hello_imgui/hello_imgui_widgets.h @@ -3,7 +3,8 @@ #include "imgui.h" #include #include - +#include +#include namespace HelloImGui { @@ -13,6 +14,8 @@ namespace HelloImGui void EndGroupColumn(); // calls ImGui::EndGroup() + ImGui::SameLine() + // @@md#WidgetWithResizeHandle + // WidgetWithResizeHandle: adds a resize handle to a widget // Example usage with ImPlot: // void gui() @@ -35,4 +38,52 @@ namespace HelloImGui std::optional onItemHovered = std::nullopt ); + // @@md + + + // -------------------------------------------------------------------------------------------- + + // @@md#InputTextResizable + + // `InputTextResizable`: displays a resizable text input widget + // + // The `InputTextResizable` widget allows you to create a text input field that can be resized by the user. + // It supports both single-line and multi-line text input. + // Note: the size of the widget is expressed in em units. + // **Usage example:** + // C++: + // ```cpp + // // Somewhere in the application state + // (static) InputTextData textInput("My text", true, ImVec2(10, 3)); + // // In the GUI function + // bool changed = InputTextResizable("Label", &textInput); + // ``` + // Python: + // ```python + // # Somewhere in the application state + // text_input = hello_imgui.InputTextData("My text", multiline=True, size_em=ImVec2(10, 3)) + // # In the GUI function + // changed, text_input = hello_imgui.InputTextResizable("Label", text_input) + // ``` + struct InputTextData + { + std::string Text; + bool Multiline = false; + ImVec2 SizeEm = ImVec2(0, 0); + + InputTextData(const std::string& text = "", bool multiline = false, ImVec2 size_em = ImVec2(0, 0)) : Text(text), Multiline(multiline), SizeEm(size_em) {} + }; + bool InputTextResizable(const char* label, InputTextData* textInput); + + // Serialization for InputTextData + // ------------------------------- + // to/from dict + using DictTypeInputTextData = std::map>; + DictTypeInputTextData InputTextDataToDict(const InputTextData& data); + InputTextData InputTextDataFromDict(const DictTypeInputTextData& dict); + // to/from string + std::string InputTextDataToString(const InputTextData& data); + InputTextData InputTextDataFromString(const std::string& str); + + // @@md } diff --git a/src/hello_imgui/impl/hello_imgui_widgets.cpp b/src/hello_imgui/impl/hello_imgui_widgets.cpp index c606749d..55c91f09 100644 --- a/src/hello_imgui/impl/hello_imgui_widgets.cpp +++ b/src/hello_imgui/impl/hello_imgui_widgets.cpp @@ -1,7 +1,10 @@ #define IMGUI_DEFINE_MATH_OPERATORS #include "hello_imgui/hello_imgui_widgets.h" +#include "hello_imgui/dpi_aware.h" #include "imgui.h" +#include "imgui_stdlib.h" #include "imgui_internal.h" +#include "nlohmann/json.hpp" #include @@ -57,7 +60,6 @@ namespace HelloImGui onItemHovered.value()(); } - float em = ImGui::GetFontSize(), size = em * handleSizeEm; ImVec2 widget_bottom_right = ImGui::GetItemRectMax(); @@ -82,19 +84,26 @@ namespace HelloImGui // Color ImU32 color = ImGui::GetColorU32(ImGuiCol_Button); if (ImGui::IsMouseHoveringRect(zone.Min, zone.Max)) + { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNWSE); color = ImGui::GetColorU32(ImGuiCol_ButtonHovered); + } if (resizingState->Resizing) + { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNWSE); color = ImGui::GetColorU32(ImGuiCol_ButtonActive); + } ImGui::GetWindowDrawList()->AddTriangleFilled(br, bl, tr, color); + if (mouseInZoneBeforeAfter) + if (!resizingState->Resizing) { if (wasMouseJustClicked && mouseInZoneBeforeAfter) { if (onItemResized.has_value() && onItemResized) onItemResized.value()(); - ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNWSE); resizingState->Resizing = true; } } @@ -113,7 +122,6 @@ namespace HelloImGui } else { - ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow); resizingState->Resizing = false; } } @@ -121,4 +129,106 @@ namespace HelloImGui return widget_size; } -} \ No newline at end of file + + static void setDefaultSizeIfNone(InputTextData* textInput) + { + if (textInput->SizeEm.x == 0 && textInput->SizeEm.y == 0) + { + float defaultWidth = 15.f; + float defaultHeight = 5.f; + if (textInput->Multiline) + textInput->SizeEm = ImVec2(defaultWidth, defaultHeight); + else + textInput->SizeEm = ImVec2(defaultWidth, 0); + } + } + + bool InputTextResizable(const char* label, InputTextData* textInput) + { + setDefaultSizeIfNone(textInput); + + static std::string hash_hash = "##"; + + bool labelIsHidden = (label[0] == '#' && label[1] == '#'); + std::string inputLabel = labelIsHidden ? label : hash_hash + label; + std::string visibleLabel = labelIsHidden ? std::string(label).substr(2) : label; + + bool changed = false; + + auto gui_cb = [&]() + { + if (textInput->Multiline) + { + changed = ImGui::InputTextMultiline(inputLabel.c_str(), &textInput->Text, HelloImGui::EmToVec2(textInput->SizeEm)); + } + else + { + ImGui::SetNextItemWidth(HelloImGui::EmSize(textInput->SizeEm.x)); + changed = ImGui::InputText(inputLabel.c_str(), &textInput->Text); + } + }; + + ImVec2 newSizePixels = HelloImGui::WidgetWithResizeHandle(label, gui_cb, 0.8f); + ImVec2 newSize = HelloImGui::PixelsToEm(newSizePixels); + if (textInput->Multiline) + textInput->SizeEm = newSize; + else + textInput->SizeEm.x = newSize.x; + + if (!labelIsHidden) + { + ImGui::SameLine(); + ImGui::Text("%s", visibleLabel.c_str()); + } + + return changed; + } + + // Serialization to/from dict + DictTypeInputTextData InputTextDataToDict(const InputTextData& data) + { + return { + {"Text", data.Text}, + {"Multiline", data.Multiline}, + {"SizeEm_x", data.SizeEm.x}, + {"SizeEm_y", data.SizeEm.y} + }; + } + + InputTextData InputTextDataFromDict(const DictTypeInputTextData& dict) + { + InputTextData result; + if (dict.find("Text") != dict.end()) + result.Text = std::get(dict.at("Text")); + if (dict.find("Multiline") != dict.end()) + result.Multiline = std::get(dict.at("Multiline")); + if (dict.find("SizeEm_x") != dict.end()) + result.SizeEm.x = std::get(dict.at("Size_x")); + if (dict.find("SizeEm_y") != dict.end()) + result.SizeEm.y = std::get(dict.at("Size_y")); + return result; + } + + // Serialization to/from string using JSON + std::string InputTextDataToString(const InputTextData& data) + { + nlohmann::json j = { + {"Text", data.Text}, + {"Multiline", data.Multiline}, + {"SizeEm_x", data.SizeEm.x}, + {"SizeEm_y", data.SizeEm.y} + }; + return j.dump(); + } + + InputTextData InputTextDataFromString(const std::string& str) + { + nlohmann::json j = nlohmann::json::parse(str); + InputTextData result; + result.Text = j["Text"]; + result.Multiline = j["Multiline"]; + result.SizeEm.x = j["SizeEm_x"]; + result.SizeEm.y = j["SizeEm_y"]; + return result; + } +} diff --git a/src/hello_imgui_demos/hello_imgui_demodocking/hello_imgui_demodocking.main.cpp b/src/hello_imgui_demos/hello_imgui_demodocking/hello_imgui_demodocking.main.cpp index 2de557b1..a11d0363 100644 --- a/src/hello_imgui_demos/hello_imgui_demodocking/hello_imgui_demodocking.main.cpp +++ b/src/hello_imgui_demos/hello_imgui_demodocking/hello_imgui_demodocking.main.cpp @@ -15,6 +15,7 @@ It demonstrates how to: #include "hello_imgui/hello_imgui.h" #include "hello_imgui/renderer_backend_options.h" #include "hello_imgui/icons_font_awesome_6.h" +#include "nlohmann/json.hpp" #include "imgui.h" #include "imgui_stdlib.h" #include "imgui_internal.h" @@ -41,7 +42,12 @@ It demonstrates how to: ////////////////////////////////////////////////////////////////////////// struct MyAppSettings { - std::string name = "Test"; + HelloImGui::InputTextData motto = HelloImGui::InputTextData( + "Hello, Dear ImGui\n" + "Unleash your creativity!\n", + true, // multiline + ImVec2(14.f, 3.f) // initial size (in em) + ); int value = 10; }; @@ -109,17 +115,25 @@ void LoadFonts(AppState& appState) // This is called by runnerParams.callbacks.L // Warning, the save/load function below are quite simplistic! std::string MyAppSettingsToString(const MyAppSettings& myAppSettings) { - std::stringstream ss; - ss << myAppSettings.name << "\n"; - ss << myAppSettings.value; - return ss.str(); + using namespace nlohmann; + json j; + j["motto"] = HelloImGui::InputTextDataToString(myAppSettings.motto); + j["value"] = myAppSettings.value; + return j.dump(); } MyAppSettings StringToMyAppSettings(const std::string& s) { - std::stringstream ss(s); MyAppSettings myAppSettings; - ss >> myAppSettings.name; - ss >> myAppSettings.value; + using namespace nlohmann; + try { + json j = json::parse(s); + myAppSettings.motto = HelloImGui::InputTextDataFromString(j["motto"].get()); + myAppSettings.value = j["value"]; + } + catch (json::exception& e) + { + HelloImGui::Log(HelloImGui::LogLevel::Error, "Error while parsing user settings: %s", e.what()); + } return myAppSettings; } @@ -215,13 +229,14 @@ void DemoUserSettings(AppState& appState) ImGui::PushFont(appState.TitleFont->font); ImGui::Text("User settings"); ImGui::PopFont(); ImGui::BeginGroup(); ImGui::SetNextItemWidth(HelloImGui::EmSize(7.f)); - ImGui::InputText("Name", &appState.myAppSettings.name); - ImGui::SetNextItemWidth(HelloImGui::EmSize(7.f)); + + ImGui::SliderInt("Value", &appState.myAppSettings.value, 0, 100); + HelloImGui::InputTextResizable("Motto", &appState.myAppSettings.motto); + ImGui::Text("(this text widget is resizable)"); ImGui::EndGroup(); if (ImGui::IsItemHovered()) ImGui::SetTooltip("The values below are stored in the application settings ini file and restored at startup"); - } void DemoRocket(AppState& appState)