ocornut / imgui

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

Overlapping button activation #8054

Closed axeldavy closed 1 month ago

axeldavy commented 1 month ago

Version/Branch of Dear ImGui:

Version 1.91.0, Branch: master

Back-ends:

imgui_impl_opengl3.cpp + imgui_impl_glfw.cpp

Compiler, OS:

GCC, Linux

Full config/build information:

No response

Details:

What I'm trying to achieve

I'm writing a library in Cython to interface with ImGui from Python DearCyGui. The features I would like to provide to the user that makes me open this issue: 1) Having invisible buttons inside a plot (for example in order to be able to click on, or drag user items such as circles, points of a polyline, etc). 2) The user decides which combination of mouse key or keyboard key will trigger a button action (such as dragging, changing item color, etc). 3) If the user creates an invisible button in response to a click (for example to add a point to a polygon), the newly created invisible button should take the activation focus (to be able to drag it without releasing the mouse).

My Issue/Question:

I've managed to make interactible invisible buttons using this code:

    bool InvisibleDrawButton(int uuid, const ImVec2& pos, const ImVec2& size,
                                           ImGuiButtonFlags flags,
                                           bool catch_if_hovered,
                                           bool *out_hovered, bool *out_held)
    {
        ImGuiContext& g = *GImGui;
        ImGuiWindow* window = ImGui::GetCurrentWindow();
        const ImRect bb(pos, pos + size);

        const ImGuiID id = window->GetID(uuid);
        ImGui::KeepAliveID(id);

        // Catch mouse if we are just in front of it
        if (catch_if_hovered && ImGui::IsMouseHoveringRect(bb.Min, bb.Max)) {
            // If we are in front of a window, and the button is not
            // made inside the window (for example viewport front drawlist),
            // the will catch hovering and prevent activation. This is why we
            // need to set HoveredWindow.
            // After we have activation, or if the click initiated outside of any
            // window, this is not needed anymore.
            g.HoveredWindow = window;
            // Replace any item that thought was hovered
            ImGui::SetHoveredID(id);
            // Enable ourselves to catch activation if clicked.
            ImGui::ClearActiveID();
            // Ignore if another item had registered the click for
            // themselves
            flags |= ImGuiButtonFlags_NoTestKeyOwner;
        }

        flags |= ImGuiButtonFlags_AllowOverlap | ImGuiButtonFlags_PressedOnClick;

        bool pressed = ImGui::ButtonBehavior(bb, id, out_hovered, out_held, flags);

        return pressed;
    }

from here: link

The issues I have is that it doesn't work as I intended:

All in all, I don't find a way to do what I wanted with the internal API.

Possibly C could be fixed this way: ButtonBehaviour with ItemAllowOverlap would store which last non-hovered item would have become activated if it were hovered. If the next frame no item is active, the last such item would be considered to become active if IsItemHovered(AllowOverlap) would raise True.

For A and B, there seems to be missing some API to 'steal' button activation on ButtonBehaviour. Programmatically setting the active ID is not enough as the next frame ButtonBehaviour doesn't consider the button active anymore.

Screenshots/Video:

No response

Minimal, Complete and Verifiable Example code:

No response

ocornut commented 1 month ago

That "// Catch mouse if we are just in front of it" code block is utterly strange to me :( I can't begin to be dealing with support of doing things like this sorry. AKA i cannot start a support discussion from a starting point where a pile of weird/tricky stuff have been done.

  1. Having invisible buttons inside a plot (for example in order to be able to click on, or drag user items such as circles, points of a polyline, etc).

Doesn't ImPlot already supports things like that? You could use their facility or try to replicate it.

If the user creates an invisible button in response to a click (for example to add a point to a polygon), the newly created invisible button should take the activation focus (to be able to drag it without releasing the mouse).

In my experience you should often aim to create less InvisibleButton(). Create only one and manage all your logic with it, instead of creating dozens of buttons.

B) Basically from my understanding of ButtonBehaviour, the main issue with what I did, is that when the mouse is clicked at frame T, it is not in the clicked state anymore at frame T+1, thus even if I set my button as active, it wouldn't be held (ButtonBehaviour resets activation if it was programmatically set, and the hovered path doesn't trigger activation). This explains why the plot panning and creating an invisible button in front of the mouse in response to a click is not doing the intended behaviour.

Your explanation seems unusually complex to me. I believe your hack for a starts makes the whole thing confusing. It's not really that complicated.

axeldavy commented 1 month ago

Ok thanks for the reply. I understand this feature is not in your roadmap. Will implement locally something that fits my needs.

ocornut commented 1 month ago

I understand this feature is not in your roadmap.

This is not what I am saying at all.

Will implement locally something that fits my needs.

This is exactly what I would prefer to avoid.

axeldavy commented 1 month ago

This is not what I am saying at all.

Ok, sorry for the misunderstanding.

The InvisibleDrawButton code is indeed a hackish attempt in order to understanding what was going on and find a better solution.

Let me try to give more details to what I have in mind and why DragPoint/Line/Rect do not correspond to my need.

DearCyGui aims to be a Python library written in Cython to provide a flexible GUI. It is RMGUI, in the sense that the objects are created by the user and then the library manages rendering every frame. Internally it uses ImGUI. It is similar to another project: DearPyGui which does the same thing in C++. The difference being that DearCyGui is in Cython, which gives similar performance (Cython is converted to C++), but eases the integration with Python. In particular the user can subclass the DearCyGui objects and customize them.

One of the target audience is scientists. I would like to make it easy for scientists to implement various drawing or image manipulation interactions. In such a scenario, it makes sense to use implot and draw inside the plot, in order to benefit from the already implemented panning and zooming features.

In addition for DearPyGui, a lot of users of the plot features are requesting ways to interact with plots, like being able to display things when the mouse is over a portion of the plot. Or click on a data point to remove it.

I think invisible buttons fit very well the needs of this community because one can use them to interact with various drawn objects inside the plot. It is much less tedious for the user than implementing their own button logic. In addition it is more efficient to implement this logic in C++ or Cython, than Python.

The interactions I have in mind that a user might want to implement are: responding to hovering (display a tooltip, highlight something, etc), responding to clicks (perform an action), and responding to dragging.

As you can see in the code snippet I provided, my InvisibleDrawButton code removes from the default InvisibleButton the Item part, and only keeps the Button logic.

Implot's DragPoint/Line/Rect are too restrictive (little customization, only left click) and thus do not fit my need. I have a working equivalent implementation of them using InvisibleDrawButton.

The advantages of using ImGUI to do the button logic of InvisibleDrawButton is that is natively handles these features:

But to completly fit my needs, I would need two features I described in my first message: