ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
60.65k stars 10.24k forks source link

Multiple labels for muti-component controls (eg DragFloat3, SliderInt2) #1831

Open OswaldHurlem opened 6 years ago

OswaldHurlem commented 6 years ago

Version/Branch of Dear ImGui: 1.61

My Issue/Question: It would be convenient to be able to pass separate labels corresponding to the individual components of multi-component controls, using some sort of special syntax in either the label or format string. Something like this

ImGui::DragFloat3("Blimp->Rotation#&Ptch#&Yaw#&Roll", &Blimp->Rotation.x)

or this

ImGui::DragFloat3("Blimp->Rotation", &Blimp->Rotation.x, 1.0f, 0.0f, 0.0f, "Ptch: %.3f#&Yaw: %.3f#&Roll: %.3f")

To produce this f92d39927caa13ca1f7ac92cb749fbf0

ocornut commented 6 years ago

I’m torn, I think it would be a bit feature creepy to add another layer of format parsing, you can fairly easily recreate the DragFloatN function and make that custom for now?

OswaldHurlem commented 6 years ago

I can understand that. Just wanted to air my opinion on it. Feel free to close.

volcoma commented 6 years ago

Hi, it is pretty easy to get the desired behavior. I am using this

static void PushMultiItemsWidthsAndLabels(const char* labels[], int components, float w_full)
{
    ImGuiWindow* window = GetCurrentWindow();
    const ImGuiStyle& style = GImGui->Style;
    if(w_full <= 0.0f)
        w_full = GetContentRegionAvailWidth();

    const float w_item_one =
        ImMax(1.0f, (w_full - (style.ItemInnerSpacing.x * 2.0f) * (components - 1)) / (float)components) -
        style.ItemInnerSpacing.x;
    for(int i = 0; i < components; i++)
        window->DC.ItemWidthStack.push_back(w_item_one - CalcTextSize(labels[i]).x);
    window->DC.ItemWidth = window->DC.ItemWidthStack.back();
}

bool DragFloatNEx(const char* labels[], float* v, int components, float v_speed, float v_min, float v_max,
                  const char* display_format, float power)
{
    ImGuiWindow* window = GetCurrentWindow();
    if(window->SkipItems)
        return false;

    ImGuiContext& g = *GImGui;
    bool value_changed = false;
    BeginGroup();

    PushMultiItemsWidthsAndLabels(labels, components, 0.0f);
    for(int i = 0; i < components; i++)
    {
        PushID(labels[i]);
        PushID(i);
        TextUnformatted(labels[i], FindRenderedTextEnd(labels[i]));
        SameLine();
        value_changed |= DragFloat("", &v[i], v_speed, v_min, v_max, display_format, power);
        SameLine(0, g.Style.ItemInnerSpacing.x);
        PopID();
        PopID();
        PopItemWidth();
    }

    EndGroup();

    return value_changed;
}

which is a wrapper on top of dragfloat. if you are fine with the labels being in front of the widget then this may help you. Example usage:

const char* names[] = {"X", "Y", "Z"};
    if(gui::DragFloatNEx(names, &data[0], 3, 0.05f))

will produce something like this:

preview

or you can tweak the function if you want the labels on the right, then instead of the TextUnformatted just fill the DragFloat label param. Hope that helps.

ocornut commented 6 years ago

I think as shown in @OswaldHurlem screenshot, having the labels inside the format string is a better idea. It'll be more compact and look nicer with varying width (such as Pitch/Yaw/Roll). In that case you only need to call PushMultiItemsWidths().

JSandusky commented 6 years ago

I try my damndest to keep labels out of the picture and just echo the information already available (ie. highlight with the axis color, etc):

monogame_imgui-viewport_2018-05-25_18-18-08

The fringe cases (like CSSM splits) are fringe, and probably need something beyond a basic set of spin-boxes anyways.

Code:

// Dupe of DragFloatN with a tweak to add colored lines
bool DragFloatN_Colored(const char* label, float* v, int components, float v_speed, float v_min, float v_max, const char* display_format, float power)
{
    ImGuiWindow* window = GetCurrentWindow();
    if (window->SkipItems)
        return false;

    ImGuiContext& g = *GImGui;
    bool value_changed = false;
    BeginGroup();
    PushID(label);
    PushMultiItemsWidths(components);
    for (int i = 0; i < components; i++)
    {
        static const ImU32 colors[] = {
            0xBB0000FF, // red
            0xBB00FF00, // green
            0xBBFF0000, // blue
            0xBBFFFFFF, // white for alpha?
        };

        PushID(i);
        value_changed |= DragFloat("##v", &v[i], v_speed, v_min, v_max, display_format, power);

        const ImVec2 min = GetItemRectMin();
        const ImVec2 max = GetItemRectMax();
        const float spacing = g.Style.FrameRounding;
        const float halfSpacing = spacing / 2;

        // This is the main change
        window->DrawList->AddLine({ min.x + spacing, max.y - halfSpacing }, { max.x - spacing, max.y - halfSpacing }, colors[i], 4);

        SameLine(0, g.Style.ItemInnerSpacing.x);
        PopID();
        PopItemWidth();
    }
    PopID();

    TextUnformatted(label, FindRenderedTextEnd(label));
    EndGroup();

    return value_changed;
}
Nahor commented 10 months ago

In case someone is interested, here is my version: https://github.com/Nahor/imgui/commit/e0c30aa69899a578a4908a9265717d408e73fa19. This is done with a new flag to inline the label (ImGuiSliderFlags_InlineLabel). I've ported EditColor4 to use that flag as well, as a validation of the concept.

Usage example:

static float f{};
ImGui::DragFloat("Regular", &f, 1.0f, 0.0f, 0.0f, "%.3f", ImGuiSliderFlags_None);
ImGui::DragFloat("Inline", &f, 1.0f, 0.0f, 0.0f, "%.3f", ImGuiSliderFlags_InlineLabel);

ImGui::Separator();
ImGui::Text("Geometry");
ImGui::Indent();

static float pos[3]{-20.0f, 33.0f, 100.0f};
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::SliderFloat("X", pos, -100.0f, 100.0f, "%.3f", ImGuiSliderFlags_InlineLabel);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("Y", pos + 1, -100.0f, 100.0f, "%.3f", ImGuiSliderFlags_InlineLabel);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("Z", pos + 2, -100.0f, 100.0f, "%.3f", ImGuiSliderFlags_InlineLabel);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Text("Position");

static float orient[3]{-20.0f, 180.0f, 60.0f};
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::DragFloat("Pitch", orient, 0.1f, -180.0f, 180.0f, "%.1f", ImGuiSliderFlags_InlineLabel);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("Roll", orient + 1, 0.1f, -180.0f, 180.0f, "%.1f", ImGuiSliderFlags_InlineLabel);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("Yaw", orient + 2, 0.1f, -180.0f, 180.0f, "%.1f", ImGuiSliderFlags_InlineLabel);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Text("Orientation");

ImGui::Unindent();

ImGui::Separator();
static float color[4]{.5f, .3f, .6f, 1.0f};
ImGui::ColorEdit4("Color RGB", color, ImGuiColorEditFlags_Float | ImGuiColorEditFlags_DisplayRGB);
ImGui::ColorEdit3("Color HSV", color, ImGuiColorEditFlags_Uint8 | ImGuiColorEditFlags_DisplayHSV);

And this is what it looks like when used:

https://github.com/ocornut/imgui/assets/1198364/ebaf3fc9-4297-46a0-aa17-1de9df1b8f3b