imgui
imgui copied to clipboard
Question: ImGui::RenderXYZ with Position versus ImGui::XYZ with SetCursor(Screen)Pos.
Version/Branch of Dear ImGui:
Version 1.91.8, Branch: docking
Back-ends:
Unreal Engine
Compiler, OS:
Windows 10 + MSVC 2022 14.38.33144
Full config/build information:
Dear ImGui 1.91.8 (19180)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=202002
define: IMGUI_DISABLE_OBSOLETE_FUNCTIONS
define: IMGUI_DISABLE_WIN32_FUNCTIONS
define: IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS
define: IMGUI_DISABLE_DEFAULT_ALLOCATORS
define: _WIN32
define: _WIN64
define: _MSC_VER=1938
define: _MSVC_LANG=202002
define: IMGUI_HAS_VIEWPORT
define: IMGUI_HAS_DOCK
--------------------------------
io.BackendPlatformName: NULL
io.BackendRendererName: NULL
io.ConfigFlags: 0x00004483
NavEnableKeyboard
NavEnableGamepad
DockingEnable
ViewportsEnable
DpiEnableScaleViewports
io.ConfigViewportsNoDecoration
io.ConfigNavMoveSetMousePos
io.ConfigNavCaptureKeyboard
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x0000140E
HasMouseCursors
HasSetMousePos
PlatformHasViewports
RendererHasVtxOffset
RendererHasViewports
--------------------------------
io.Fonts: 6 fonts, Flags: 0x00000000, TexSize: 2048,2048
io.DisplaySize: 2560.00,1400.00
io.DisplayFramebufferScale: 1.00,1.00
--------------------------------
style.WindowPadding: 8.00,8.00
style.WindowBorderSize: 1.00
style.FramePadding: 4.00,2.00
style.FrameRounding: 4.00
style.FrameBorderSize: 1.00
style.ItemSpacing: 6.00,4.00
style.ItemInnerSpacing: 6.00,6.00
Details:
My Issue/Question:
Hello there,
I'm currently trying to understand when I should use functions like ImGui::Text and when I should use functions like ImGui::RenderTextClipped.
I did see that the comment above the RenderXYZ functions suggests not using them, but it felt a bit more natural to fall back to them when creating "custom widgets".
I created my own TextPill widget, which is just text with a background behind it. It follows a similar setup to ImGui::Text.
Here is the code for it (note: Dear ImGui is integrated into UE in this case and thus uses some UE-specific types, such as FLinearColor or FMargin):
void ImGui::UnformattedTextPill(const FLinearColor& PillColor, const FMargin& Padding, const float Rounding, const char* Text, const char* TextEnd)
{
TextPillEx(PillColor, Padding, Rounding, Text, TextEnd);
}
void ImGui::TextPill(const FLinearColor& PillColor, const FMargin& Padding, const float Rounding, const char* Format, ...)
{
va_list Args;
va_start(Args, Format);
TextPillV(PillColor, Padding, Rounding, Format, Args);
va_end(Args);
}
void ImGui::TextPillV(const FLinearColor& PillColor, const FMargin& Padding, const float Rounding, const char* Format, const va_list Args)
{
const char *Text, *TextEnd;
ImFormatStringToTempBufferV(&Text, &TextEnd, Format, Args);
TextPillEx(PillColor, Padding, Rounding, Text, TextEnd);
}
void ImGui::TextPillEx(const FLinearColor& PillColor, const FMargin& Padding, const float Rounding, const char* Text, const char* TextEnd)
{
const ImGuiWindow* const Window = ImGui::GetCurrentWindow();
if (Window->SkipItems)
{
return;
}
const ImGuiContext& CurrentContext = *ImGui::GetCurrentContext();
const ImGuiStyle& Style = CurrentContext.Style;
if (Text == TextEnd)
{
Text = TextEnd = "";
}
if (TextEnd == nullptr)
{
TextEnd = Text + strlen(Text);
}
const ImGuiID PillID = ImGui::GetID(Text);
const ImVec2 TextSize = ImGui::CalcTextSize(Text, TextEnd);
const ImVec2 PillPos = ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y - Padding.Top);
const float Width = CurrentContext.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth ? CurrentContext.NextItemData.Width : TextSize.x + Padding.Left + Padding.Right;
const ImVec2 PillSize = ImVec2(Width, TextSize.y + Padding.Top + Padding.Bottom);
ImGui::ItemSize(PillSize, 0);
const ImRect PillRect = ImRect(PillPos.x, PillPos.y, PillPos.x + PillSize.x, PillPos.y + PillSize.y);
if (!ImGui::ItemAdd(PillRect, PillID))
{
return;
}
RenderTextPill(PillRect.Min, PillRect.Max, PillColor, Rounding, Text, &TextSize, &PillRect);
}
void ImGui::RenderTextPill(const ImVec2& PosMin, const ImVec2& PosMax, const FLinearColor& PillColor, const float Rounding, const char* Text, const ImVec2* TextSizeIfKnown, const ImRect* Rect)
{
const ImGuiWindow* const Window = ImGui::GetCurrentWindow();
if (Window->SkipItems)
{
return;
}
const ImGuiContext& CurrentContext = *ImGui::GetCurrentContext();
const ImGuiStyle& Style = CurrentContext.Style;
ImGui::RenderFrame(PosMin, PosMax, ImGui::GetColorU32(PillColor),true, Rounding);
const ImVec2 Center = ImVec2(
PosMin.x + (PosMax.x - PosMin.x) * .5f,
PosMin.y + (PosMax.y - PosMin.y) * .5f
);
const char* TextEnd = Text + strlen(Text);
const ImVec2 TextSize = TextSizeIfKnown ? *TextSizeIfKnown : ImGui::CalcTextSize(Text, TextEnd);
const ImVec2 TextPosMin = ImVec2(Center.x - TextSize.x * .5f, Center.y - TextSize.y * .5f);
const ImVec2 TextPosMax = ImVec2(Center.x + TextSize.x * .5f, Center.y + TextSize.y * .5f);
ImGui::RenderTextClipped(TextPosMin, TextPosMax, Text, TextEnd, &TextSize, Style.SelectableTextAlign, Rect);
}
This works fine so far and satisfies the requirements I have for it. Using ImGui::Text and whatever the equivalent for RenderFrame is doesn't feel correct here, because this is its own "text widget" aka "Item" with its own "ID" and it has no content beyond that.
Now, I created a command palette, where I draw a somewhat complex "widget" per command entry. I will refrain from posting the code here, as that will be a bit too much to show, but I can show an image:
The entries are the two bottom elements with "Open Test Window" written in them.
Each entry has its own custom ItemID, similar to the TextPill. I originally called ImGui::TextPill and ImGui::Text for the elements, and positioned them via ImGui::SetCursorScreenPos inside the background frame. However, I then went ahead and started using the ImGui::RenderTextPill and ImGui::RenderTextClipped instead and constructed the whole entry via the render functions.
The benefit of that is that the CursorScreenPos remains unchanged, and the single ImGui::ItemSize call of the command entry ensures the Cursor is placed below it, ready for the next command entry. On top of that, I don't have any additional Item (ItemIDs) generated from the 2 TextPill elements and the Text element, and the command entry is one single Item.
Now, looking into the imgui_demo.cpp file, I feel like I'm pretty much alone with that idea, and I should have stuck to the ImGui::TextPill and ImGui::Text functions and simply positioned the Cursor by hand.
--
I can't find much guidance on when I should choose to construct my widgets via RenderXYZ calls, and when to use the XYZ calls directly and set the Cursor position. Using the XYZ calls directly is simpler due to not having to calculate the TextSize (in most cases) and the PosEnd, but it feels nasty to move the cursor around so much, especially if one forgets to account for any item spacing or so.
I also don't think that each XYZ element has a matching Render function for it. So, as soon as I want to create something that only works via its XYZ function and requires an ID, I will have to fall back to the Cursor anyway.
It would be really awesome if someone could share a rule of thumb with me here. I'm still pretty new to the whole system. I mainly used it to create debugging windows by using the non-render functions in the past.
Cheers!
I'm currently trying to understand when I should use functions like ImGui::Text and when I should use functions like ImGui::RenderTextClipped.
For others and for the records (but you know that):
Text()is a public API widget, it fulfill layout requirement such as moving the cursor, extending window contents areas.RenderTextClipped()is lower-level undocumented internal API and only render text, it's not an item/widget per se.
I did see that the comment above the RenderXYZ functions suggests not using them, but it felt a bit more natural to fall back to them when creating "custom widgets". This works fine so far and satisfies the requirements I have for it. Using ImGui::Text and whatever the equivalent for RenderFrame is doesn't feel correct here, because this is its own "text widget" aka "Item" with its own "ID" and it has no content beyond that.
That's reasonable. Your question is entirely legitimate.
Basically imgui_demo.cpp doesn't promote using very fancy custom widgets. Officially the public-API way would be to use e.g. InvisibleButton() + a mixture of direct ImDrawList calls + potentially higher level ImGui calls, depending on the nature of the widget.
Official widgets don't do this to composite basic widgets (e.g. a Button() is not InvisibleButton() + Text()) because it would be too slow and add too much cognitive noise in various ways. The performance stuff are not going to matter much if you have 50-100 complex widgets but if you have 10k then the public API way of doing thing isn't going to be great.
Unfortunately I cannot be opening too many things to the public API until they are better designed. e.g. the end goal is that we have much better lower-level text API in e.g. ImDrawList:: or whreever, that handle clipping, ellipsis, alignment etc. But we don't so pragmatically speaking if you want to create custom widgets you need to be picking things here and there between those two worlds, which is exactly what you are doing now. Do what works for you.
- public api: more guarantee or not breaking with updates. possibly more limited, more awkward, slightly slower.
- internal api: less guarantee of not breaking. you take more more responsibilities.
Now, looking into the imgui_demo.cpp file, I feel like I'm pretty much alone with that idea, and I should have stuck to the ImGui::TextPill and ImGui::Text functions and simply positioned the Cursor by hand.
You are not alone but it's indeed not particularly showcased in imgui_demo.cpp. A good portion of third-party extensions are likely sitting on those imgui_internal.h helpers. For the most common internal helpers, I am putting a reasonable effort at not breaking their API until I can come up with something better.
Hey there,
I never found the time to respond to your reply properly.
Knowing how much work it is to reply to a large community, especially with questions ranging from 'read the docs' simple to 'that will take me my entire afternoon to respond to' complex, I really appreciate your reply.
--
For others and for the records (but you know that):
Thanks for clarifying this once more.
Basically imgui_demo.cpp doesn't promote using very fancy custom widgets. Officially the public-API way would be to use e.g. InvisibleButton() + a mixture of direct ImDrawList calls + potentially higher level ImGui calls, depending on the nature of the widget.
That makes sense. I will have a look around if I can find some 'unofficial' demo material (aka repositories that do fancy custom widgets) and see if I can get a better understanding of when to utilize what.
The performance stuff are not going to matter much if you have 50-100 complex widgets but if you have 10k then the public API way of doing thing isn't going to be great.
Are there any performance profiling resources to see what counts as "complex"? I'll have a look around to see if I can find some resources on this.
Mainly wondering if I could get a general "feeling" for when to avoid using the public API from the get-go. 10 to 10.000 is a large step, and I'm currently unsure what kind of Dear ImGui widget would count as "complex".
I assume that there is also a unique overhead to be accounted for when working with an immediate setup, as other UI frameworks, e.g., Slate of Unreal Engine, have the benefit of performing more expensive computations only when the UI actively changes. Slate in particular even has a "(Global) Invalidation" mode, in which it renders the UI elements only once, snapshots them, and provides them almost like an image until an element in it is dirtied.
Unfortunately I cannot be opening too many things to the public API until they are better designed.
That's fair. I'm unfamiliar with the roadmap/timeline of development on Dear ImGui...
e.g. the end goal is that we have much better lower-level text API in e.g. ImDrawList:: or whreever, that handle clipping, ellipsis, alignment etc. But we don't
... so here I'm not sure if this is something that is actively being worked on or could take a couple of years.
I think I won't need an answer for that, though, because if I understand correctly, you are mostly working on this alone, plus having some contributors via PRs. I had a look through some of the "Help Wanted" and "Funding" pages and will see if I find the time to reach out via my company.
--
All in all, your reply helped me not feel as if I'm doing something wrong. I will take your suggestion to "Do what works for you." and see how far I can get with that.
Cheers!