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

Hit test customization #1512

Closed
wants to merge 2 commits into from
Closed

Hit test customization #1512

wants to merge 2 commits into from

Conversation

thedmd
Copy link
Contributor

@thedmd thedmd commented Dec 19, 2017

Brief

Add ability to customize hit test for widgets and windows. Former allows to create arbitrary shapes inside of a button rectangle, later to create pass-through areas (holes) in windows. This will make #1503 possible.

I'm opening this PR to open a discussion if such functionality is needed in main branch of ImGui. I would like to hear your opinion on that.

Usage

To use this ability user must implement custom hit function with signature:

typedef bool (*ImGuiHitTestCallback)(const ImVec2& point, const ImVec2& min, const ImVec2& max, void* user_data);

Call PushHitTest/PopHitTest exactly like PushStyleVar before drawing widget.

IMGUI_API void PushHitTest(ImGuiHitTestCallback callback, void* user_data = NULL);
IMGUI_API void PopHitTest();

Example:

ImGui::PushHitTest(circleHitTest);
ImGui::Button("I'm ImGui::Button()!", ImVec2(150, 150));
ImGui::PopHitTest();

Hit function can be set for windows using these:

IMGUI_API void SetNextWindowHitTest(ImGuiHitTestCallback callback, void* user_data = NULL);
IMGUI_API void SetWindowHitTest(const char* name, ImGuiHitTestCallback callback, void* user_data = NULL);

hit_tests

Circle button example:

auto circleHitTest = [](const ImVec2& point, const ImVec2& min, const ImVec2& max, void*)
{
    const auto min_size = ImMin(max.x - min.x, max.y - min.y);
    const auto radius = min_size * 0.5f;
    const auto radius_squared = radius * radius;
    const auto center = (max + min) * 0.5f;
    const auto offset = point - center;
    const auto distance_squared = offset.x * offset.x + offset.y * offset.y;

    return distance_squared < radius_squared;
};

ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 75.0f);
ImGui::PushHitTest(circleHitTest);
bool down = ImGui::Button("I'm ImGui::Button()\n   with rounding\n   and hit test!", ImVec2(150, 150));
ImGui::PopHitTest();
ImGui::PopStyleVar();
static int counter = 0;
if (down)
    ++counter;
ImGui::Text("Clicks: %d", counter);

Hole in a window example:

static ImVec2 emptyRect;
emptyRect = ImGui::GetCursorScreenPos();
ImGui::SetWindowHitTest("Debug##Default", [](const ImVec2& point, const ImVec2& min, const ImVec2& max, void*)
{
    if (point.x >= emptyRect.x && point.y >= emptyRect.y && point.x <= (emptyRect.x + 150.0f) && point.y <= (emptyRect.y + 150.0f))
        return false;

    return true;
});

ImGui::GetWindowDrawList()->AddRect(
    ImGui::GetCursorScreenPos(), ImGui::GetCursorScreenPos() + ImVec2(150, 150), IM_COL32(255, 0, 0, 255));

ImGui::GetWindowDrawList()->AddText(
    ImGui::GetCursorScreenPos() + ImVec2(0, 150 + ImGui::GetStyle().ItemSpacing.y),
    IM_COL32_WHITE, "This is a hole in window.");

@ocornut
Copy link
Owner

ocornut commented Dec 19, 2017 via email

@thedmd
Copy link
Contributor Author

thedmd commented Dec 20, 2017

Sorry for lack of motivation section in PR. I had trouble to remember where I had used this feature, more than a year has passed since I needed it. Since I rebased code to latest ImGui I thought I can share it here.

I used hit test callbacks to implement editor overlay in our engine. That's it overlay. ImGui window is displayed over entire screen. At the top there is a toolbar and editable shapes are over shapes on a 2D scene. Since this is an overlay I needed to pass input events to underlying editor. To do that I used window callback.

Focus for shape editing and gizmos are captured using ImGui::InvisibleButton(), aka. bounding rectangle of a shape. Then hit test callback is used to actually test if a part of shape was hit. I'm utilizing draw lists with arbitrary transformations for rendering (I also added simple ImMatrix consisting of six floats) and InvisibleButton() to handle input.
I can provide some screenshots later to better explain what I'm talking about.

@thedmd thedmd force-pushed the hit-tests branch 3 times, most recently from 82b992c to 9aa6433 Compare December 21, 2017 00:48
@ocornut
Copy link
Owner

ocornut commented Dec 21, 2017

I used hit test callbacks to implement editor overlay in our engine. That's it overlay. ImGui window is displayed over entire screen. At the top there is a toolbar and editable shapes are over shapes on a 2D scene. Since this is an overlay I needed to pass input events to underlying editor. To do that I used window callback.

You can use the ImGuiWindowFlags_NoInputs flag for that on a per window basis.
I'm not sure to understand your setup and why exactly it would need a same window to have a hole (vs using etc. a single non-interactive overlay for display and regular windows for interactive widgets). Do you have a screenshot or mockup of your layout you can share?

I can provide some screenshots later to better explain what I'm talking about.

Yes that would be the most useful.
We maybe could tweaks the internals ButtonBehavior(), ItemHoverable() to make it easier to program non-rectangular hit tests.

@thedmd
Copy link
Contributor Author

thedmd commented Jan 13, 2018

My setup looks like that. ImGui window cover whole screen so I always receive an input. To interact with 'Exit' button (not part of the scene) is ImGui must pass input events to normal editor, window hit test is used to do that. Individual shapes use hit test inside their bounding rects to determine their active area. Yellow rects are handles (custom buttons) to, in this case, manipulate vertices.
Whole thing is layer over regular editor.

image

Due to release schedule I had, it took a while to get back to the topic.

I think widget test hit function can be useful for advanced users. InvisibleButton() + hit test can be used to model any non-rect widget without diving into internal api.

@ocornut
Copy link
Owner

ocornut commented Jan 13, 2018 via email

@thedmd
Copy link
Contributor Author

thedmd commented Jan 14, 2018

I don't have a button covering whole window. I have a window covering whole screen to be able to place buttons in arbitrary positions. Either that or I need to create windows instead buttons, but I read this is not recommended. As far as I know I cannot place widgets without window.

@ocornut
Copy link
Owner

ocornut commented Jan 14, 2018

But where are the imgui buttons in that screenshots? Aren't they only the ones the top rectangular bar?
Or do you use imgui buttons for all those geometrical edition?

@thedmd
Copy link
Contributor Author

thedmd commented Jan 20, 2018

Buttons are on top bar, over each green and reddish shape and yellow manipulators.

Yes, I'm using buttons for geometry edition since they are the easiest way to capture input (hover, mouse clicks, active state, etc.).

Sorry for slow response time.

@ocornut
Copy link
Owner

ocornut commented Oct 2, 2018

@thedmd Do you use the window hit test for something other than creating one rectangular hole?
(I'm looking at doing something similar for the purpose of the docking system)

@thedmd
Copy link
Contributor Author

thedmd commented Oct 2, 2018

On screenschot above every shape was a part of the window so cut out shape is more complex than an rectangle. To keep sanity in check I'm drawing rectangular widgets on window and with custom hit test applied to them. So window hit test callback check only rectangles and if one is hit, widget hit test is perfomed.

Since code allow you to set a callback you can implement any test both for window and widget. There is not code which deal specifically with rectangles only in this PR if that's what you're looking for.

I'm still keeping code alive and can update PR if you want that.

@ocornut
Copy link
Owner

ocornut commented Oct 2, 2018

I'm asking because your implementation requires the window HitData callback/data to be in scope and usable by the time we reach the next call NewFrame(), which is a very unusual case of opaque user data crossing frame boundaries.

As for widgets themselves, I think expanding and promoting the creation of custom widgets would be a more preferable direction. Currently, the signature ItemHoverable() doesn't obviously allow it, but if we reworked this the user writing custom widgets wouldn't need callback.
Note however that you can trick by calling ItemHoverable(ImRect(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX), id) and then perform your hit test if that returns true. It's mostly awkward because lots of custom widgets don't use ItemHoverable directly, but ButtonBehavior.

@thedmd
Copy link
Contributor Author

thedmd commented Oct 2, 2018

I thought about window hit test callback as its state, like position or size. If callback need additional data to work it is user decision to store such. You're right this is not usual for user data to keep data for next frame. Yet I do not know simpler solution.
That being said, there may be an alternative solution to the problem. What if window can be build from rectangles instead? By default one rectangle can be pushed covering whole window making it opaque. For pass-through window this rectangle will not be pushed. Each widget rectangle will make a union with already pushed ones determining opaque area. For default case every rectangle will be swallowed by one pushed by window not adding an extra overhead. I wonder if opaque area information is actually needed, maybe just flag that window was hit is enough to make it transparent?
With that widget hit test approach will be enough to shape opaque area to user liking.

I didn't thought of using ItemHoverable(). This should be enough to make custom shaped widgets inside opaque rectangle. But this will not support window transparency. As far as I know ItemHoverable() still depends on IsMouseHoveringRect() which test only rectangles. Which brings us back to the widget hit test callback.

@ocornut
Copy link
Owner

ocornut commented Oct 3, 2018

I agree there's lots of interesting to do. Right now I need to solve a specific case, with the additional tricky bit where for docking even though we want to dig a hole through a windows for regular inputs, the docking overlay itself needs to ignore that hole. So if we treat this in a generic fashion, those callback or shape data may need to be carry a little more specific data, needing further design (maybe we have multiple HitTest query or information for multiple "layer" ?).

Or in IM always-submit-style maybe the solution is just that in my specific case I can stop submitting the hole when user is holding a window payload. In this case we expect the data to be always resubmitted (in your current PR the WindowHitTest data is not consumed/reset in Begin, but left as-is, that a 1 line change).

Don't worry about keeping this PR rebased (unless you need it yourself), the code is self-explanatory and a good reference as-is.

ocornut added a commit that referenced this pull request Jul 29, 2020
Mernion pushed a commit to Mernion/imgui that referenced this pull request Aug 3, 2020
Mernion pushed a commit to Mernion/imgui that referenced this pull request Aug 3, 2020
@thedmd
Copy link
Contributor Author

thedmd commented Nov 28, 2020

This PR does not outlived its expectations. ImGui right now has an ability to add "hole" into the window, which is what I needed.
It may be revived in some shape, since there is no replacement for fine-grinned hit test for widgets. Hitmaps for widgets - maybe some day. :)

@thedmd thedmd closed this Nov 28, 2020
@thedmd
Copy link
Contributor Author

thedmd commented Aug 19, 2022

Rebased on v1.89 WIP (18808)

Out of the blue I updated branch with hit test code. Latest incarnation is more up to ImGui standard. Both item level and window level hit test functions make these possible:

2022-08-20.00-18-36.mp4

@Lioncky
Copy link

Lioncky commented Sep 13, 2023

I agree with you, hope for this issue open again

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants