Centering buttons (or anything) in a window. #3862
Replies: 4 comments 4 replies
-
Hello Ron, (Discussions (here) or Issues are the right place. The intent was that answers can be better preserved and referred to. In fact i think Discussions are not yet as good as Issues as we cannot label them, but I'm hoping GitHub will bring this change soon). There's unfortunately not a good answer to this question presently: you need to offset the cursor position manually, which requires knowing the size of your widgets. Presently I would generally advise people to not do it unless they absolutely need to. bool ButtonCenteredOnLine(const char* label, float alignment = 0.5f)
{
ImGuiStyle& style = ImGui::GetStyle();
float size = ImGui::CalcTextSize(label).x + style.FramePadding.x * 2.0f;
float avail = ImGui::GetContentRegionAvail().x;
float off = (avail - size) * alignment;
if (off > 0.0f)
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + off);
return ImGui::Button(label);
} Already not very elegant... but the problem become with multiple items, generally spaced out with void AlignForWidth(float width, float alignment = 0.5f)
{
ImGuiStyle& style = ImGui::GetStyle();
float avail = ImGui::GetContentRegionAvail().x;
float off = (avail - width) * alignment;
if (off > 0.0f)
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + off);
} Usage: ImGuiStyle& style = ImGui::GetStyle();
float width = 0.0f;
width += ImGui::CalcTextSize("Hello").x;
width += style.ItemSpacing.x;
width += 150.0f;
width += style.ItemSpacing.x;
width += ImGui::CalcTextSize("World!").x;
AlignForWidth(width);
ImGui::Button("Hello");
ImGui::SameLine();
ImGui::Button("Fixed", ImVec2(150.0f, 0.0f)); // Fixed size
ImGui::SameLine();
ImGui::Button("World"); If your widgets are generated from data it's easy to you but for "quickly written" widget directly expressed in raw code it evidently can become tedious. We could potentially make this automatic for single items on a line, but as the UI is layed out in a single pass we can't do this automatically for more than 1 item per line. The solution I am envisioning in the future is to allow for local opt-in multi-passes. Here's a hypothetical way this could work in the future: // The system underlying would run two passes:
// first pass measure item size without submitting them, then position cursor, second pass let items be submitted
while (ImGui::HorizontalCentredLine())
{
ImGui::Button("Hello");
ImGui::SameLine();
ImGui::Button("Fixed", ImVec2(150.0f, 0.0f)); // Fixed size
ImGui::SameLine();
ImGui::Button("World");
} (not particularly fond of that API but it's too convey the intent) Additionally, Folling wrote a little guide about manual alignment in the past, it doesn't cover everything but it exists :) |
Beta Was this translation helpful? Give feedback.
-
I've managed to come up with a solution that might help with your issue. It's based on calculating the width of the ImGui control first by rendering it off-screen, then adjusting the cursor position based on the calculated size, and finally rendering the actual control. Here's the code: #include "ImGui/imgui.h"
class CenteredControlWrapper {
public:
explicit CenteredControlWrapper(bool result) : result_(result) {}
operator bool() const {
return result_;
}
private:
bool result_;
};
class ControlCenterer {
public:
ControlCenterer(ImVec2 windowSize) : windowSize_(windowSize) {}
template<typename Func>
CenteredControlWrapper operator()(Func control) const {
ImVec2 originalPos = ImGui::GetCursorPos();
// Draw offscreen to calculate size
ImGui::SetCursorPos(ImVec2(-10000.0f, -10000.0f));
control();
ImVec2 controlSize = ImGui::GetItemRectSize();
// Draw at centered position
ImGui::SetCursorPosX(ImVec2((windowSize_.x - controlSize.x) * 0.5f, originalPos.y));
control();
return CenteredControlWrapper(ImGui::IsItemClicked());
}
private:
ImVec2 windowSize_;
};
#define CENTERED_CONTROL(control) ControlCenterer{ImGui::GetWindowSize()}([&]() { control; }) The ControlCenterer class is the main worker here. It takes a function control as a parameter, which is the actual ImGui control to be rendered. It then renders the control off-screen, allowing us to calculate the size without affecting the actual layout. After that, it adjusts the cursor's x position based on the control's size and renders the control again. You can use the CENTERED_CONTROL macro with any ImGui control, for example: if (CENTERED_CONTROL(ImGui::Button("Hello, World!", ImVec2(150, 50)))) {
// Do something here
}
CENTERED_CONTROL(ImGui::Text("Hello, World!")); This solution is more general and can be applied to any ImGui control. Hope this helps! |
Beta Was this translation helpful? Give feedback.
-
used the original source and added multiple arguments so you can add over and below spacings and a vertical offset to the widget class centered_control_wrapper_t
{
public:
explicit centered_control_wrapper_t(bool result) : result_(result) {}
operator bool() const { return result_; }
private:
bool result_;
};
class centered_control_t
{
public:
centered_control_t(ImVec2 window_size, float y = 0.f, float s = 0.f, float su = 0.f) : window_size_(window_size) { y_offset = y; spacing_below = s; spacing_up = su;}
template<typename func>
centered_control_wrapper_t operator()(func control) const
{
centered_control_wrapper_t ccw = centered_control_wrapper_t(false);
ImVec2 original_pos = ImGui::GetCursorPos();
ImGui::SetCursorPos(ImVec2(-10000.0f, -10000.0f));
control();
ImVec2 control_size = ImGui::GetItemRectSize();
ImGui::Dummy(ImVec2(0, spacing_up));
ImGui::SetCursorPos(ImVec2((window_size_.x - control_size.x) * 0.5f, original_pos.y + y_offset));
control();
ImGui::Dummy(ImVec2(0, spacing_below));
return ccw;
}
private:
ImVec2 window_size_;
float y_offset;
float spacing_below;
float spacing_up;
};
/*
* This macros centers in the better way possible, got other function but this one may work
* for the 90% of widgets of ImGui so it's ok, because for some reason with 'ImGui::BeginChild'
* doesn't work properly
*
* In the __VA_ARGS__ which is pointed to the '...' as the second parameter, means that you can
* call the macro with more arguments. It may not look safe but i'll explain briefly below
*
* First arg : function ( No matter the type )
* Second arg : vertical offset to center on
* Third arg : add spacing below the widget
* Fourth arg : add spacing over the widget
*
* Cons : to add only spacing you'll need to still add a y_offset, you can do it with 0.f on the
* second one, to avoid redundancy I made it like that
*/
#define center_(control, ...) centered_control_t{ImGui::GetWindowSize(), __VA_ARGS__}([&]() { control; }) |
Beta Was this translation helpful? Give feedback.
-
I wonder if it would be feasible to add a |
Beta Was this translation helpful? Give feedback.
-
How do I center a button horizontally in a window? I also have two buttons I want to center as a group?
I'm using buttons as my current example but it might apply to any items.
P.S. Apologies if this is the wrong place to ask questions. Not sure where to go with the Discord gone.
Beta Was this translation helpful? Give feedback.
All reactions