ocornut / imgui

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

Widgets only disabled when mouse over it #6373

Open dimateos opened 1 year ago

dimateos commented 1 year ago

Version/Branch of DearImGui: Version: 1.89.2 WIP Branch: docking

Back-end/Renderer/Compiler/OS Back-ends: open source QT backend https://github.com/seanchas116/qtimgui Operating System: Windows 10

Full config info ``` Dear ImGui 1.89.2 WIP (18916) -------------------------------- sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20 define: __cplusplus=199711 define: _WIN32 define: _WIN64 define: _MSC_VER=1916 define: _MSVC_LANG=201402 define: IMGUI_HAS_VIEWPORT define: IMGUI_HAS_DOCK -------------------------------- io.BackendPlatformName: qtimgui io.BackendRendererName: NULL io.ConfigFlags: 0x00000040 DockingEnable io.ConfigViewportsNoDecoration io.ConfigInputTextCursorBlink io.ConfigWindowsResizeFromEdges io.ConfigMemoryCompactTimer = 60.0 io.BackendFlags: 0x00000006 HasMouseCursors HasSetMousePos -------------------------------- io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,64 io.DisplaySize: 1920.00,1082.00 io.DisplayFramebufferScale: 1.00,1.00 -------------------------------- style.WindowPadding: 8.00,8.00 style.WindowBorderSize: 1.00 style.FramePadding: 4.00,3.00 style.FrameRounding: 8.00 style.FrameBorderSize: 0.00 style.ItemSpacing: 8.00,4.00 style.ItemInnerSpacing: 4.00,4.00 ```

My Issue/Question:

I was trying to cancel the user interaction with a slider whenever the value goes above a certain value. A quick hack I used was to disable the region for a frame the next frame. Probably there are better ways to do it with the API? Other option is to send a mouse up event from the backend I guess.

This worked fine but only whenever the cursor was over the widget on the disabled frame. A GIF for context:

viewer_plnbTi1mpH

Testing out a bit more I found that the user can keep interacting with disabled widgets in general as long as they are not touched. Just wanted to point it out as I did not find any comment about it.

Thanks for the lib!

Screenshots/Video

The region is disabled whenever the value is over 1.0 but the user can keep interacting as long as the disabled widget is not touched. Note that I did not used IsItemDeactivatedAfterEdit so the interaction is also canceled while manually inputting the value.

viewer_prABUepnzu

Standalone, minimal, complete and verifiable example: (see https://github.com/ocornut/imgui/issues/2261)

if (ImGui::Begin("Test", nullptr)) {
    static float f = 0.5;
    static bool disabled = false;

    if (disabled) ImGui::BeginDisabled();
    //ImGui::BeginDisabled(disabled); // same

    ImGui::SliderFloat("f value", &f, 0, 2);
    if (disabled) ImGui::EndDisabled();

    disabled = f > 1;
    if (ImGui::Button("reset f")) f = 0.5;
}
ImGui::End();
ocornut commented 1 year ago

I was trying to cancel the user interaction with a slider whenever the value goes above a certain value.

That seems quite unusual. Why would you want to do that? Do you expect the interaction to brutally stop? How can the user resume operation once it is disabled? Note that it won't magically lock the value to e.g. 1.0 but only the value during the frame the value exceeded (so it could be >1.0f). What do you expect to happen?

You can use if (GetActiveID() == id) { ClearActiveID(); } to forcefully disable an action, but without more context the intent seems a little bizarre to me.

dimateos commented 1 year ago

For context, it was the first idea I came up with to restrain the slider handle within a smaller range in relation to other sliders (the outer ranges shared for all of them). Later I linked the values instead of brutally stopping the interaction as you said. Not perfect as there is a frame delay when updating the values of sliders above. I thought or redrawing all to remove the latency but probably too complicated for a detail.

viewer_QS0jWHZM0G

Anyway, I brought up the fact that disabled widgets only stop interaction on mouse over for a more general discussion and documentation. I did not find a related issue, but now I found https://github.com/ocornut/imgui/issues/3985 which seems related.

Why would you want to do that?

  • Maybe some condition in a game/app disables a section of the menu, would be weird to let the user keep interacting with draggable widgets when the mouse is away from them. In my case I think that stopping the interaction instead of linking the values is also an ok solution that could work with ClearActiveID() and setting the value to the expected limit.

Do you expect the interaction to brutally stop? How can the user resume operation once it is disabled?

  • Yes? basically the same as if the user had released the click, the same as what is happening when the disabled region is being touched. In an game/app some external condition would decide when to enable it again.

Note that it won't magically lock the value to e.g. 1.0 but only the value during the frame the value exceeded (so it could be >1.0f). What do you expect to happen?

  • Yep, the example was a demo. Binding the disabled state to the slider value was a way to ensure that the region got disabled while the widget was being interacted with, the exact value was not important. I would not expect imgui to restore the value like when the user exits an input field with ESC, which I guess works in an entirely different way.

Overall I think being able to keep dragging disabled widgets is quite an edge case and probably not worth it if it adds performance, and definitely would not prioritize it either.

Thanks your work! cheers

ocornut commented 1 year ago

For context, it was the first idea I came up with to restrain the slider handle within a smaller range in relation to other sliders (the outer ranges shared for all of them).

I suppose in theory we could add optional "clamping" values in SliderBehavior() that would be distinct from visible min/max range. However I believe the solution you found is much nicer for the end user.

Anyway, I brought up the fact that disabled widgets only stop interaction on mouse over

I agree this is something to address in theory, but being overwhelmed with tasks I would tend to avoid over-designing and solving edge cases like this if it doesn't address a concrete issue. So for now I would ignore it, and we may get back to it in the future when someone has a concrete problem. If clarifying/fixing that alone would fix your problem I would do it, but I don't think it is the right solution to your problem:

Do you expect the interaction to brutally stop? How can the user resume operation once it is disabled? Yes? basically the same as if the user had released the click, the same as what is happening when the disabled region is being touched. In an game/app some external condition would decide when to enable it again.

And my question "How can the user resume operation once it is disabled?" is important and not fully answered here: if the intent it clamping, in your specific context you would surely want to allow the user to immediately click to move back in the opposite direction? Which the disabling solution wouldn't allow.

dimateos commented 1 year ago

I suppose in theory we could add optional "clamping" values in SliderBehavior() that would be distinct from visible min/max range. However I believe the solution you found is much nicer for the end user.

That would be pretty nice for sliders that need to have the same context/scale but a different min/max. Should also fix the visual 1-frame latency I mentioned with the bottom slider moving the ones above. I would add an option to visually mark the real min/max within the range but that starts to sound like a custom widget. Probably similar to a range slider https://github.com/ocornut/imgui/issues/76 but with a single value.

I agree this is something to address in theory, but being overwhelmed with tasks I would tend to avoid over-designing and solving edge cases like this if it doesn't address a concrete issue. So for now I would ignore it, and we may get back to it in the future when someone has a concrete problem.

I think you are doing great managing your tasks and I agree with ignoring it for now :) Indeed I don't disable other regions of the menu based on some app state at the momment. About the theoretical clamping in SliderBehavior() I also understand if you would leave it for the end user.

if the intent it clamping, in your specific context you would surely want to allow the user to immediately click to move back in the opposite direction? Which the disabling solution wouldn't allow.

Thats totally true, the underlaying intention is clamping and the user should be able to move it in the other direction. So the proposed internal clamping values would be the real solution: the handle would stop there but the user would keep interacting with the widget.

Feel free to rename the issue for more context or open a new one if you want to focus on the slider behavior, thanks a lot!