Skip to content

Commit

Permalink
Add InputTextResizable
Browse files Browse the repository at this point in the history
  • Loading branch information
pthom committed May 14, 2024
1 parent 908e2fb commit 66cd2a0
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 16 deletions.
53 changes: 52 additions & 1 deletion src/hello_imgui/hello_imgui_widgets.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
#include "imgui.h"
#include <optional>
#include <functional>

#include <map>
#include <variant>

namespace HelloImGui
{
Expand All @@ -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()
Expand All @@ -35,4 +38,52 @@ namespace HelloImGui
std::optional<VoidFunction> 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<std::string, std::variant<std::string, bool, float>>;
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
}
118 changes: 114 additions & 4 deletions src/hello_imgui/impl/hello_imgui_widgets.cpp
Original file line number Diff line number Diff line change
@@ -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 <unordered_map>

Expand Down Expand Up @@ -57,7 +60,6 @@ namespace HelloImGui
onItemHovered.value()();
}


float em = ImGui::GetFontSize(), size = em * handleSizeEm;
ImVec2 widget_bottom_right = ImGui::GetItemRectMax();

Expand All @@ -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;
}
}
Expand All @@ -113,12 +122,113 @@ namespace HelloImGui
}
else
{
ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
resizingState->Resizing = false;
}
}

return widget_size;
}

}

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<std::string>(dict.at("Text"));
if (dict.find("Multiline") != dict.end())
result.Multiline = std::get<bool>(dict.at("Multiline"));
if (dict.find("SizeEm_x") != dict.end())
result.SizeEm.x = std::get<float>(dict.at("Size_x"));
if (dict.find("SizeEm_y") != dict.end())
result.SizeEm.y = std::get<float>(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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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;
};

Expand Down Expand Up @@ -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<std::string>());
myAppSettings.value = j["value"];
}
catch (json::exception& e)
{
HelloImGui::Log(HelloImGui::LogLevel::Error, "Error while parsing user settings: %s", e.what());
}
return myAppSettings;
}

Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 66cd2a0

Please sign in to comment.