-
-
Notifications
You must be signed in to change notification settings - Fork 10.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generic Box() component #5944
Comments
You would need to store that state somewhere. Please note instantiating and using a new Splitter every frame is prohibitively costly. We never do such thing in Dear ImGui codebase, as a rule of thumb Dear ImGui should never allocate on idle frames, only occasionally on growth/opening-something frames. My plan for e.g. an upcoming BeginSelectable()/EndSelectable() which should hold being drawn thousands of times a frame, is to claim vertices/indices ahead of time and then fill them (or reposition/recolor them) during the End operation. This works easily for AddRectFilled() without-rounding which is a known number of vertices, but more other shapes may not be as easier as vertices count may vary. Perhaps this feature will need to introduce ImDrawFlags to specify drawing techniques which are more deterministic. |
Thank you for the feedback. How would you recommend implementing my box widget without instantiating and using a new Splitter every frame? |
@ocornut Are you suggesting that somehow I can issue the |
Keep a splitter around and reuse it across frames.
Yes that's my suggestion for AddRectFilled().. I suggested it may not be as easy to do for other shapes, but non-rounded AddRect() should also work and use a deterministic number of indices/vertices. The first pass you may record drawlist positions, claim PrimReserve(), second pass sort of seek back into position, add AddXXX primitives, and restore position. That's essentially a super specialized version of splitter. I would need to experiment with that and see if I can actually bundle it into a shared helper. We already use |
I took a look at I think for now, I will simply declare static ImDrawListSplitter splitter{}; which should solve the "using a new Splitter every frame is prohibitively costly" issue. The side effect is that Boxes are no longer nestable but that is not an issue with my specific use case. |
I wanted to close the loop and provide the "final" code with the following features:
void Box(Modifier const &iModifier,
std::function<void()> const &iBoxContent,
ImDrawListSplitter *iSplitter = nullptr)
{
// Implementation note: this is made static because of this https://github.com/ocornut/imgui/issues/5944#issuecomment-1333930454
// "using a new Splitter every frame is prohibitively costly".
static ImDrawListSplitter kSplitter{};
auto hasBackground = !ColorIsTransparent(iModifier.fBackgroundColor);
auto hasBorder = !ColorIsTransparent(iModifier.fBorderColor);
ImDrawList *drawList{};
if(hasBackground || hasBorder)
{
drawList = ImGui::GetWindowDrawList();
if(!iSplitter)
iSplitter = &kSplitter;
// split draw list in 2
iSplitter->Split(drawList, 2);
// first we draw in channel 1 to render iBoxContent (will be on top)
iSplitter->SetCurrentChannel(drawList, 1);
}
auto min = ImGui::GetCursorScreenPos();
// account for padding left/top
ImGui::SetCursorScreenPos(min + ImVec2{iModifier.fPadding.w, iModifier.fPadding.x});
ImGui::BeginGroup();
{
iBoxContent();
ImGui::EndGroup();
}
// account for padding right/bottom
auto max = ImGui::GetItemRectMax() + ImVec2{iModifier.fPadding.y, iModifier.fPadding.z};
if(drawList)
{
// second we draw the rectangle and border in channel 0 (will be below)
iSplitter->SetCurrentChannel(drawList, 0);
// draw the background
if(hasBackground)
drawList->AddRectFilled(min, max, iModifier.fBackgroundColor);
// draw the border
if(hasBorder)
drawList->AddRect(min, max, iModifier.fBorderColor);
// merge the 2 draw lists
iSplitter->Merge(drawList);
}
// reposition the cursor (top left) and render a "dummy" box of the correct size so that it occupies
// the proper amount of space
ImGui::SetCursorScreenPos(min);
ImGui::Dummy(max - min);
} Here is an example of usage (with nesting): // constexpr ImU32 kWhiteColor = ...;
// constexpr ImU32 kBoxColor = ...;
// constexpr ImU32 kNestedBoxColor = ...;
Box(Modifier{}.padding(10.0f).backgroundColor(kBoxColor).borderColor(kWhiteColor), []{
static ImDrawListSplitter kNestedSplitter{}; // note how this is static!
ImGui::Text("Inside before");
ReGui::Box(Modifier{}.padding(15.0f).backgroundColor(kNestedBoxColor), [] {
ImGui::Text("Nested");
},
&kNestedSplitter); // passing the splitter to allow for nesting
ImGui::Text("Inside after");
});
which renders like this: |
Thanks for the snippet @ypujante I was trying to use this as a Card UI, so had few questions.
Box(Modifier{}.padding(10.0f).backgroundColor(kBoxColor).borderColor(kWhiteColor), [] {
ImGui::Text("Header");
ImGui::Separator(); // <= This extends outside the Box
ImGui::Text("Content");
}); I am relatively new to ImGui so checking if you have any tips to fix it. |
constexpr bool ColorIsTransparent(ImU32 iColor)
{
return (iColor & IM_COL32_A_MASK) == 0;
} I think the issue with |
Thank you. |
I have created a generic Box component (inspired by jetpack compose) for the purpose of:
It works great as can be seen in this image
Here is the code I have:
It works great, but of course it is a very different api style from the rest of ImGui. I should have instead a
BeginBox(Modifier)
andEndBox()
api instead. The reason why I was not able to follow the same API is that in theEndBox()
call I need information that is provided in theBeginBox
call (like the colors and padding). Is there any recommended way on how I could do that (I guess I would need some form of "generic" PushMyVar api)?The text was updated successfully, but these errors were encountered: