ocornut / imgui

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

Adding a step size to DragScalar in addition to v_speed #8026

Open simonvanbernem opened 2 weeks ago

simonvanbernem commented 2 weeks ago

Version/Branch of Dear ImGui:

Version 1.91.1, Branch: docking

Back-ends:

custom DX11 + Win32

Compiler, OS:

Windows 10 + MSVC 2022

Full config/build information:

No response

Details:

In my game, I have a layout editor where I use DragScalar to edit floats. There are two common use cases:

Outliers that are not multiples of 0.1 and 0.05, or 5 and 10 respectively must still be able to be specificed.

I currently don't see a way to make sure that, when dragging a DragScalar widget, the change happens in the desired granualrity, while happening at the correct speed. I could set v_speed to 5 e.g. and that would result in a granularity of 5, but then the rate of change is way too high.

What I want is to tell the widget is "Change the value by 3 per 10 pixels, while only allowing multiples of 5 as values". I am aware that I can get this behavior for multiples of ten by using a format string with that number of digits, and then having the widget round internally, but that doesn't work for 0.05 or 5, and having different precision format strings also looks kind of bad in my case, so I want this to be separate.

I tried to do this myself by rounding to the nearest multiple after the widget changed the value, but that doesn't work: The widget seems to not remember the initial value when dragging, so the manual rounding resets it every frame and no change happens, unless you drag far enough in a single frame, that the rounded value will be different from the previous one. Additionally, the widget displays the non-rounded value, so it will look like you are changing things, before it reverts on the next frame.

Ideally, I would want DragScalar to take a "granularity" or "step_size" input, that took care of this behavior. A rounding callback would also work.

As for a workaround:

Could I pas a format string that contains just the original value like "0.005", not even a format specifier, so that the widget would not show change where there isn't any? The value shown would have one frame of delay then, but that would be fine for me.

And for the rounding to work, I would need to store the changed value in a proxy somewhere and only apply it to the real value when it is different including rounding. Could I make imgui allocate some memory associated with the widget for that, so that I don't have to worry about memory management on my side?

So basically I'm thinking this:

Screenshots/Video:

No response

Minimal, Complete and Verifiable Example code:

No response

ocornut commented 2 weeks ago

This has been an open problem #1815 #1183 #7082 I haven't been able to spend the time to find a good solution for it yet. Perhaps the best solution is to add a callback in the lower-level calls in imgui_internal.h so at least there's a solution that doesn't involve cluttering public API right away. A callback would be more general purpose than merely a step.

simonvanbernem commented 2 weeks ago

I looked at the issues you linked, and the workarounds / custom implementations there are all made for SliderFloat, which doesn't work for DragScalar, I believe. For example, the code from last years issue about this (https://github.com/ocornut/imgui/issues/7082#issue-2021551661):

static float foobar = 0.0f;
if (ImGui::SliderFloat("Foobar", &foobar, 0.0f, 15.0f, "%.0f")) {
    foobar = std::round(foobar / 3.0f) * 3.0f;
}
ImGui::Text("%f", foobar);

If this was DragScalar instead of SliderFloat and it took me 3 frames to drag the value from 0 to 3, then rounding it each frame would completely break it, because each frame, DragScalar would change the value from 0 to 1, and then the rounding would reset it to 0. This works in the Slider widget, because I guess the slider widgets calculates the output value by the absolute position of the slider, while the Drag widget only knows about the difference to the last frame. And if the difference to the last frame is not big enough, then it will always be rounded away.

Do you have a suggestion on how to adapt this workaround so that it also works for DragScalar (which prefferably wouldn't involve me manually adding more static variables for each use of this widget)?

simonvanbernem commented 2 weeks ago

I agree that a callback would be the most complete solution. That way one could even do stuff like allow only powers of two and so on.