Skip to content

Question: ImGui::RenderXYZ with Position versus ImGui::XYZ with SetCursor(Screen)Pos. #8598

@eXifreXi

Description

@eXifreXi

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:

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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions