Skip to content
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

Toggle Button? #1537

Open
dbelz opened this issue Jan 5, 2018 · 10 comments

Comments

Projects
None yet
3 participants
@dbelz
Copy link

commented Jan 5, 2018

Hey there,

did ImGui provide a ToggleButton? Haven't found anything related to this. If i haven't saw it sorry

Kind Regards
Daniel

@ocornut

This comment has been minimized.

Copy link
Owner

commented Jan 5, 2018

Hello,
There is a Checkbox but nothing that visually has the appearance of a toggle button. It'd be fairly easy to implement in your side though.

From a design point of view the only barrier to integration in master is deciding how does it fit within the styling system: do we expose new enum colors? And as there's no concept of a preferred language in core imgui it's simpler to not include on/off text. If you implement one locally you can answer those questions more easily.

@ocornut ocornut added the enhancement label Jan 5, 2018

@dbelz

This comment has been minimized.

Copy link
Author

commented Jan 5, 2018

Okay i will try my best.
Just have no experience with imgui (first project with it^^)

Can i somewhere find examples of custom controls / widgets to get an idea how to build it?

@ebachard

This comment has been minimized.

Copy link

commented Jan 5, 2018

Not sure I'm answering the question, but the code below works well for me (the trick is ImGui::IsItemClicked() ):

static bool enable_7m = false;  // default value, the button is disabled 
static float b = 1.0f; //  test whatever color you need from imgui_demo.cpp e.g.
static float c = 0.5f; // 
static int i = 3;

( some code...  )

if (enable_7m == true)
{

    ImGui::PushID(" 7m ");
    ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i/7.0f, b, b));
    ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i/7.0f, b, b));
    ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(i/7.0f, c, c));
    ImGui::Button(" 7m ");
    if (ImGui::IsItemClicked(0))
    {
                        enable_7m = !enable_7m;
    }
    ImGui::PopStyleColor(3);
    ImGui::PopID();
}
else
{
    if (ImGui::Button(" 7m "))
       enable_7m = true;
}

+ add wherever you need in the code (e.g. after some action): 

( some code ... )
enable_7m = false;

HTH
  

[edited] : added the colors, sorry I clicked update too fast ^^ 
@dbelz

This comment has been minimized.

@ocornut

This comment has been minimized.

Copy link
Owner

commented Jan 5, 2018

EDIT typos.

If you are just starting with imgui I'd suggest not going against the current, use Checkbox for now and by the time you decide if imgui is right now you'll find it easy to create your own variation of it.

You can use InvisibleButtion() to detect click and/or hovering + ImDrawList API to draw whatever you need need. If you include imgui_internal.h then you can use ButtonBehavior() with finer amount of features in the flags.

In the code @ebachard posted "the trick is ImGui::IsItemClicked()" is because he cares about toggling the boolean on the mouse PRESS event rather than the release event. This is the sort of finer detail you can access with ButtonBehavior() but I'm not sure it is even desirable. Though using the press event comes with the benefit that you don't need an ID for the item (the ID is needed by button to track the press-release event, but press-only is fully stateless).

I'll post some pseudo code soon.

@ocornut

This comment has been minimized.

Copy link
Owner

commented Jan 5, 2018

Here's a simple proof of concept:

imgui_toggle_button_1

void ToggleButton(const char* str_id, bool* v)
{
    ImVec2 p = ImGui::GetCursorScreenPos();
    ImDrawList* draw_list = ImGui::GetWindowDrawList();

    float height = ImGui::GetFrameHeight();
    float width = height * 1.55f;
    float radius = height * 0.50f;

    if (ImGui::InvisibleButton(str_id, ImVec2(width, height)))
        *v = !*v;
    ImU32 col_bg;
    if (ImGui::IsItemHovered())
        col_bg = *v ? IM_COL32(145+20, 211, 68+20, 255) : IM_COL32(218-20, 218-20, 218-20, 255);
    else
        col_bg = *v ? IM_COL32(145, 211, 68, 255) : IM_COL32(218, 218, 218, 255);

    draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), col_bg, height * 0.5f);
    draw_list->AddCircleFilled(ImVec2(*v ? (p.x + width - radius) : (p.x + radius), p.y + radius), radius - 1.5f, IM_COL32(255, 255, 255, 255));
}

There are lots of subtleties that makes it more work to provide a default one:

  • Where do we define the colors (do we have 4 extra enums in the style system?)
  • Do we want to react on PRESS or PRESS-RELEASE? If using the earlier we could ditch the identifier.
  • Do we want a version that displays a label, if so how/where.
  • Is the rendering quality good enough?
  • Do we want to add animation? We need to store a last-time-clicked + identifier for that, which is easy to introduce in imgui internals if we keep a single buffer for it, so clicking too fast to trigger multiple animation simultaneously would skip the earlier animation. Otherwise we can provide better storage but imgui typically tries to avoid non-constant time algorithm so it'd be better be done on user side.
@dbelz

This comment has been minimized.

Copy link
Author

commented Jan 5, 2018

Wow cool!

I have one more question: What is the str_id? At my side its not toggling lets say. I think its because of this id. As i said im really an beginner in this topic :/

ocornut added a commit that referenced this issue Jan 5, 2018

@ocornut

This comment has been minimized.

Copy link
Owner

commented Jan 5, 2018

Please read the FAQ about identifiers, also make sure you understand how imgui lets you manage your state (the boolean has to be persistent on your side).

ID: For interaction that cannot be fully processed in one frame, during ImGui needs to identify widgets somehow. It uses an ID stack and typically at the top of the ID stack we use the label widget. Here this widget doesn't display anything but to use InvisibleButton() we still need an identifier somehow.

Note that we only need this identifier because the standard way to process mouse UI clicks is to require both the mouse click and the mouse release to be hovering the widget. Those events don't happen at the same time and we need to identify the button to compare if the mouse click happened over the same object at the mouse release.

If we are happy with triggering the toggle on mouse click only, that can be processed immediately and not require an identifier at all. In the code above you could entirely remove str_id, pass a dummy string to InvisibleButton() and then instead of reading the return value of InvisibleButtion(), instead use the return value of IsItemClicked().

Below: This variation also animate the toggle position and background color, but it is using the internal API and a new field I have just added to track the activation time.

imgui_toggle_button_2

#include "imgui_internal.h"

void ToggleButton(const char* str_id, bool* v)
{
    ImVec2 p = ImGui::GetCursorScreenPos();
    ImDrawList* draw_list = ImGui::GetWindowDrawList();

    float height = ImGui::GetFrameHeight();
    float width = height * 1.55f;
    float radius = height * 0.50f;

    ImGui::InvisibleButton(str_id, ImVec2(width, height));
    if (ImGui::IsItemClicked())
        *v = !*v;

    float t = *v ? 1.0f : 0.0f;

    ImGuiContext& g = *GImGui;
    float ANIM_SPEED = 0.08f;
    if (g.LastActiveId == g.CurrentWindow->GetID(str_id))// && g.LastActiveIdTimer < ANIM_SPEED)
    {
        float t_anim = ImSaturate(g.LastActiveIdTimer / ANIM_SPEED);
        t = *v ? (t_anim) : (1.0f - t_anim);
    }

    ImU32 col_bg;
    if (ImGui::IsItemHovered())
        col_bg = ImGui::GetColorU32(ImLerp(ImVec4(0.78f, 0.78f, 0.78f, 1.0f), ImVec4(0.64f, 0.83f, 0.34f, 1.0f), t));
    else
        col_bg = ImGui::GetColorU32(ImLerp(ImVec4(0.85f, 0.85f, 0.85f, 1.0f), ImVec4(0.56f, 0.83f, 0.26f, 1.0f), t));

    draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), col_bg, height * 0.5f);
    draw_list->AddCircleFilled(ImVec2(p.x + radius + t * (width - radius * 2.0f), p.y + radius), radius - 1.5f, IM_COL32(255, 255, 255, 255));
}
@ebachard

This comment has been minimized.

Copy link

commented Jan 5, 2018

@ocornut

Thanks a lot for the new example, and thanks a lot for your explanations. I'm glad to learn such lessons, about being extremely precise with controls, and I'll keep the issue in my list.

@dbelz

This comment has been minimized.

Copy link
Author

commented Jan 5, 2018

@ocornut and @ebachard thanks for the fast help. I like this

Kind Regards,
Daniel

ocornut added a commit that referenced this issue Jan 5, 2018

Revert "Internals: Added LastActiveId, LastActiveIdTimer. (#1537)" Wi…
…ll come up with a better design later.

This reverts commit 007f403.

ocornut added a commit that referenced this issue May 28, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.