ocornut / imgui

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

Toggle Button? #1537

Open LegendaryB opened 6 years ago

LegendaryB commented 6 years ago

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 commented 6 years ago

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.

LegendaryB commented 6 years ago

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 commented 6 years ago

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 ^^ 
LegendaryB commented 6 years ago

Searching for something like this:

https://articulate-heroes.s3.amazonaws.com/uploads/rte/xriqtlyw_Jeff%20Kortenbosch%20v1-toggle-buttons-sliders-elearning.png

ocornut commented 6 years ago

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 commented 6 years ago

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:

LegendaryB commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

@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.

LegendaryB commented 6 years ago

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

Kind Regards, Daniel

moebiussurfing commented 4 years ago

hey, is there any toggle button snippet around that uses the same normal style that a button? (square box with text label and global styles)

moebiussurfing commented 4 years ago
> 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):

sorry, this above snippet seems to look fine to me.

L4ZZA commented 4 years ago

What's the progress on this one? @ocornut

frink commented 4 years ago

Is this planned for eventual inclusion?

xerosic commented 3 years ago

how can i make the border smoother? it's a bit "rough" and i want to try to make it look better.

moebiussurfing commented 3 years ago

how can i make the border smoother? it's a bit "rough" and i want to try to make it look better.

@XanaxOG , you can draw a colored border to a widget like this:

ImVec4 colorBorder{ 1,1,1,0.5 };
float lineWidth = 2.0;

ImGui::PushStyleColor(ImGuiCol_Border, colorBorder);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, lineWidth);

// draw button

ImGui::PopStyleColor();
ImGui::PopStyleVar(1);
xerosic commented 3 years ago

how can i make the border smoother? it's a bit "rough" and i want to try to make it look better.

@XanaxOG , you can draw a colored border to a widget like this:

ImVec4 colorBorder{ 1,1,1,0.5 };
float lineWidth = 2.0;

ImGui::PushStyleColor(ImGuiCol_Border, colorBorder);
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, lineWidth);

// draw button

ImGui::PopStyleColor();
ImGui::PopStyleVar(1);

It doesn't really work still seems kinda rough, especially the button border

ghost commented 3 years ago

I added some code minimization and the colors of the toggle are the same as the theme's colors:

Screenshot 202101 Screenshot 202102

void ToggleButton(const char* str_id, bool* v)
{
    ImVec4* colors = ImGui::GetStyle().Colors;
    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;
    ImGuiContext& gg = *GImGui;
    float ANIM_SPEED = 0.085f;
    if (gg.LastActiveId == gg.CurrentWindow->GetID(str_id))// && g.LastActiveIdTimer < ANIM_SPEED)
        float t_anim = ImSaturate(gg.LastActiveIdTimer / ANIM_SPEED);
    if (ImGui::IsItemHovered())
        draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), ImGui::GetColorU32(*v ? colors[ImGuiCol_ButtonActive] : ImVec4(0.78f, 0.78f, 0.78f, 1.0f)), height * 0.5f);
    else
        draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), ImGui::GetColorU32(*v ? colors[ImGuiCol_Button] : ImVec4(0.85f, 0.85f, 0.85f, 1.0f)), height * 0.50f);
    draw_list->AddCircleFilled(ImVec2(p.x + radius + (*v ? 1 : 0) * (width - radius * 2.0f), p.y + radius), radius - 1.5f, IM_COL32(255, 255, 255, 255));
}
pinojojo commented 2 years ago

I feel that this circle is a bit aliased, even when anti-aliasing has been enabled on in the imgui::style. Does anyone know of a good solution to make the round edge not that jaggle?

moebiussurfing commented 2 years ago

@pinojojo , did you tried to change the circle segments resolution?

ashifolfi commented 2 years ago

can there be a version that isn't circular and has customizable corner radius? I can't figure out how to replace the circle portion with a rounded square properly and I don't want a strange fully rounded toggle button in my ui

EDIT: I ended up finally figuring it out. For anyone who wants the non circle version:

(I used nerdtronik's comment as a base for this and only changed it enough to not use a circle for the knob in the slider)

obligatory image showcasing the edit in action image

inline void ToggleButton(const char* str_id, bool* v)
{
    ImVec4* colors = ImGui::GetStyle().Colors;
    ImVec2 p = ImGui::GetCursorScreenPos();
    ImDrawList* draw_list = ImGui::GetWindowDrawList();

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

    ImGui::InvisibleButton(str_id, ImVec2(width, height));
    if (ImGui::IsItemClicked()) *v = !*v;
    ImGuiContext& gg = *GImGui;
    float ANIM_SPEED = 0.085f;
    if (gg.LastActiveId == gg.CurrentWindow->GetID(str_id))// && g.LastActiveIdTimer < ANIM_SPEED)
        float t_anim = ImSaturate(gg.LastActiveIdTimer / ANIM_SPEED);
    if (ImGui::IsItemHovered())
        draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), ImGui::GetColorU32(*v ? colors[ImGuiCol_ButtonActive] : ImVec4(0.78f, 0.78f, 0.78f, 1.0f)), height * rounding);
    else
        draw_list->AddRectFilled(p, ImVec2(p.x + width, p.y + height), ImGui::GetColorU32(*v ? colors[ImGuiCol_Button] : ImVec4(0.85f, 0.85f, 0.85f, 1.0f)), height * rounding);

    ImVec2 center = ImVec2(radius + (*v ? 1 : 0) * (width - radius * 2.0f), radius);
    draw_list->AddRectFilled(ImVec2((p.x + center.x) - 9.0f, p.y + 1.5f),
        ImVec2((p.x + (width / 2) + center.x) - 9.0f, p.y + height - 1.5f), IM_COL32(255, 255, 255, 255), height * rounding);
}

0.5f rounding doesn't give a perfect circle at the moment but I'm sure that should be easy to fix

EDIT 2: it appears that the animation might be broken as well strangely? I didn't really touch anything related to animation so I'm not sure what's going on there

nitz commented 2 years ago

Hello all,

I took some inspiration from this thread and from the way ImGui::Checkbox() is handled, and pulled it all together into a small package.

First, I come bearing gifs:

imgui_toggle preview

And here's the repo if you're looking to dive in: :octocat: cmdwtf/imgui_toggle

After taking a look at what you'd all done in this thread, put together a small API that I felt came across in the most Dear ImGui way. It also implemented a majority of the features you all showed off here: Theme (and override) colors, rounding vs square toggles, animations, and label-less toggles.

I went a bit further by also making the squircling (both for the knob and frame) adjustable.

In addition, since I was drawing inspiration from the Checkbox(), I went ahead and added label (and thus label interactivity) functionality, as that was something I certainly needed for my use case. Naturally the label can be hidden as normal.

Currently the major shortcomings are those discussed about the dependency on LastActiveIdTimer for animation, and those associated with not being part of Dear ImGui proper: no named style colors (and thus borrowing from buttons and some colors hardcoded.)

I've added some advanced configuration to expose a lot of behavior and styling options; gif is updated!

Feedback is always appreciated, and I hope you're able to make use of it in your projects!

slajerek commented 2 years ago

Cool! Thank you, this definitely will be helpful!

iBenji commented 1 year ago

Also here another way with the ImGui::Button

bool isButtonPressed = false; //Global Variable

if (menu) {
ImGui::NewFrame();
ImGui::Begin("Some Main Menu Frame Name");

  if (ImGui::Button("ButtonName")
          {
              isButtonPressed = !isButtonPressed;
          }

          if (isButtonPressed ) {
              //Here could be your any imgui frames, windows codes.
          }

ImGui::End();
ImGui::Render();
}