ocornut / imgui

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

High resolution dragging mode for float sliders? #180

Open unpacklo opened 9 years ago

unpacklo commented 9 years ago

I was wondering if imgui already supports some method of high resolution dragging on sliders?

An issue I encounter frequently is having a slider which might have a large range and the default dragging increments are very high (or alternatively, the window is sized so that the slider is quite small so the dragging precision drops dramatically). Most of the time this is OK, but sometimes I want to move at much smaller increments and the sliding mechanism is much more natural than having to type in values directly.

One such thing I've seen somewhere (don't remember where) is allowing the user to hold a modifier key (such as control or alt) while dragging which enables the high resolution dragging mode and the slider then starts to move at a smaller increment.

I think this would be a really useful feature if imgui doesn't already have it! Let me know what you think.

-Dale Kim

ocornut commented 9 years ago

Yes, I am very unhappy with the current sliders so they will be replaced! Basically we need to rework the slider to not use absolute positioning but a system of relative increment (which will then allow speed scaling via modifiers). It will also allow them to fit in much tighter space, the slider can be removed all together and just be the size of a button displaying the value. Other ideas include using a rotating system like AntTweakBar did. This is why I started adding dragging primitives but I didn't get around to rewrite either widgets instead of sliders yet. Btw slider won't go away they still have use, but they shouldn't be the most common widget.

ocornut commented 9 years ago

OK I'm working on this now. I also made it that when you CTRL+Click on it changes into a text input box.

The tricky part is defining the API entry point(s) for potentially desired options without cluttering:

I'm not sure yet how to handle those possibility. Obviously a function with 8 parameters is probably not the right way to go.

ocornut commented 9 years ago

Also what would be a good name for this widget. DragFloat() ? Maybe min/max can come via an different name:

DragFloat() vs DragFloatRange() ?

unpacklo commented 9 years ago

Is it necessary to specify the fast step? My thinking on this was that it works just like the current sliders. You would then only need to specify a slow step. It also seems like we could make this optional as well and default to some fraction of the fast/normal step, but I feel like this could fail at some number ranges (I'm not familiar with how you're computing the final slider values).

I think +/- buttons wouldn't be necessary. Maybe it makes it more obvious that you can move at some specific step, but seems like the finer resolution dragging slider would cover the same cases.

As for naming, well, that's the hardest of them all! DragFloat() doesn't seem too bad too me. Are you thinking of replacing SliderFloat() outright or have these be two separate widgets living side-by-side?

ocornut commented 9 years ago

My idea for steps is that the default, maybe -1.0f would automatically be like 100 and 0.01 when modifiers are held. So it's ok to have them if they are final parameters I suppose. Or fast could be the inverse the slow. But yes I agree they not be so necessary by default. Especially with CTRL+Click always available.

Won't replace the slider no, but will launch a giant lobbying/marketing campaign to advise people to use the new widget.

ocornut commented 9 years ago

The slow/fast factors could also be part of the imgui state, so by default they don't show in the simple API (and you can always have a helper to push/pop them for you).

ocornut commented 9 years ago

Proposal

bool     DragFloat(const char* label, float* v, float v_step = 1.0f, float v_min = -FLT_MAX, float v_max = FLT_MAX, const char* display = "%.3f");
bool     DragInt(const char* label, int* v, int v_step = 1, int v_min = INT_MIN, int v_max = INT_MAX, const char* display = "%.0f");

With global state to adjust slow/fast step ratios. Note that I think I'll be renaming 'display_format' parameter to 'display' to emphasis the fact that you can include prefix/suffix there.

DragFloat("", &f, 0, 1.0f, 10000.0f, "%.3f coins")

unpacklo commented 9 years ago

I was actually going to suggest exactly this as a way to reduce the number of parameters. As long as the default step sizes are intuitive, I definitely think this is the way to go.

On Apr 3, 2015, at 3:19 AM, omar notifications@github.com wrote:

The slow/fast factors could also be part of the imgui state, so by default they don't show in the simple API (and you can always have a helper to push/pop them for you).

\ Reply to this email directly or view it on GitHub.

ocornut commented 9 years ago

I currently have two variants of each (uncommited)

bool DragFloat(const char* label, float* v, float v_step = 1.0f, float v_min = -FLT_MAX, float v_max = FLT_MAX, const char* display_format = "%.3f");
bool DragFloat(const char* label, float* v, float v_step, const char* display_format);

May not be very useful but this is easily specify the display format without the min/max.

heroboy commented 9 years ago

I suggest you can look at the Unity's FloatField

The mouse can drag the lable of the input field to change the float value. I think this is very easy to use.

ocornut commented 9 years ago

Yes, I suppose that's very similar.

The difference are:

ocornut commented 9 years ago

Committed a first version that you can try. Not totally happy with it yet and will likely change the behavior some more.

// Widgets: Drags (tip: ctrl+click on a drag box to input text)
bool DragFloat(const char* label, float* v, float v_step = 1.0f, float v_min = -FLT_MAX, float v_max = FLT_MAX, const char* display_format = "%.3f");
bool DragFloat(const char* label, float* v, float v_step, const char* display_format);
bool DragInt(const char* label, int* v, int v_step = 1, int v_min = -0x7fffffff-1, int v_max = 0x7fffffff, const char* display_format = "%.0f");
bool DragInt(const char* label, int* v, int v_step, const char* display_format = "%.0f");

Let me know how that works.

ocornut commented 9 years ago

( Side topic: I'm experimenting with generalizing frame coloring when hovering any widget or when active, the same way DragFloat() does. It works but however raise a few problems:

)

Also need to spend some time redesigning ShowTestWindow before there's too many things in there. I don't want to remove anything (in fact we should add more examples) but the presentation may need to change.

unpacklo commented 9 years ago

I just tried them out and it's a decent first version. It did take me a second to realize that only horizontal mouse movement affected it. Would probably help to have a unique graphic for this widget to make it easily identifiable that it's a DragFloat.

I also found myself wanting to see the widget as a normal slider if the DragFloat was bounded so I can have some context as to what the extremes are and where I am in relation to them. It might just be because I'm so used to working with the sliders. We use imgui right now to handle live editing of some game data, and in theory, a lot of our float values can be "unbounded". However, for all practical purposes, they fall within a reasonable range so they work OK with sliders.

The case that would be solved better with these DragFloats are those where the range is bounded only on one side. We do have quite a few of these (like specifying time durations) and we've had to force them into the sliders and place arbitrary limits on either the min or the max and just size them accordingly to the most common ranges.

ocornut commented 9 years ago

Very good points. I can see a case for drawing the visual marker, but also use range as reference while being able to get out of the range if you need. May have to redesign and break the current DragFloat() interface.

I'm starting to understand why you suggested they could replace sliders altogether.

unpacklo commented 9 years ago

Ah, yes, sorry I wasn't more clear!

The way I originally envisioned it was that the current sliders would just be modified to be able to step at a much smaller increment when you hit another key while fiddling with the slider. Nevertheless, we would still find the DragFloat or something like it very useful for the case of bounding on only one side of the number line.

It's actually kind of a pain right now when we implement our data editing gui and have a float to expose. Usually there's a clear bound on min or max, but not always both, so we need to spend some time fiddling around to see what a good range is and hope that we set the range well enough for the tuning work. We're not always right, so sometimes we get requests to resize the range.

Something that might be worth looking into is looking at #76 and see if maybe we can kill a few birds with one mega stone? Might be too much in one widget... but customizable ranges + a high resolution mode would solve a few issues we're seeing!

heroboy commented 9 years ago

After tried,I feel the "step/pixel" movement speed is too hard to set value accurately.Maybe add a speed parameter? And I think the min max parameters are not necessary, because sometimes I want clamp and sometimes I want wrap(e.g. for angle).

ocornut commented 9 years ago

OK it looks like I may need to rewrite that (and rewrite Slider as well) to latch the initial value on press and go relative from there when clicking on the little box, including speed scale and non-linear curve shenanigans. From there we can use the bounds as visual reference but allow the user to get past them, it's a matter of figuring out the right API after the feature is coded in.

ocornut commented 9 years ago

@heroboy have you tried holding ALT to slow down the speed ?

ocornut commented 9 years ago

Fixed commit below, didn't commit the right hunks (my github ui acts weird?) I also renamed v_step to v_speed, which is a more accurate name.

ocornut commented 9 years ago

Unsure how to visualize the current value of a drag box similarly to a slider, while uniquely identifying them.

It got me thinking. I was wondering if slider could just be changed to function like DragFloat(), that is, clicking is never setting an absolute value.

Pros: is that slider/drag just become the same thing, and we can have API variants of options to : have no bounds, have bounds but allow to go past them, etc.

Cons: can't set an absolute value in one click, drag is generally slower (even if it's more flexible and precise). Need to decide on a default speed for sliders (e.g. 1 pixel is 1/200 of the input range).

ocornut commented 9 years ago

I made DragFloat() support power curves while applying relative changes and working on both sides of zero. Perhaps someone who is more competent at maths or floating-point issues can review this and tell me if it's total rubbish or if there are edge cases to be wary about:

float v_cur = g.DragCurrentValue;
float delta = (mouse_drag_delta.x - g.DragLastMouseDelta.x) * speed;
if (fabsf(power - 1.0f) > 0.001f)
{
    // Logarithmic curve on both side of 0.0
    float v0_abs = v_cur >= 0.0f ? v_cur : -v_cur;
    float v0_sign = v_cur >= 0.0f ? 1.0f : -1.0f;
    float v1 = powf(v0_abs, 1.0f / power) + (delta * v0_sign);
    float v1_abs = v1 >= 0.0f ? v1 : -v1;
    float v1_sign = v1 >= 0.0f ? 1.0f : -1.0f;          // Crossed sign line
    v_cur = powf(v1_abs, power) * v0_sign * v1_sign;    // Reapply sign
}
else
{
    v_cur += delta;
}
heroboy commented 9 years ago

I tried use ALT to slow down, but it is not what I want. It just decrease the change amount. To set a value,fox example 1.00 (not 0.99,not 1.01), I must move mouse very carefully.

heroboy commented 9 years ago

I have use a horizontal slider on IOS. It use cursor y position to determine the speed. When the cursor move away the slider in y axis, you need move more distance to change the value. It's very interesting. You may reference this one to improve the float slider?

extrawurst commented 9 years ago

To set a value,fox example 1.00 (not 0.99,not 1.01), I must move mouse very carefully.

@heroboy To set a specific value you simply type it in.

I like the DragFloat widget as it is, especially now that you can specify the speed.

heroboy commented 9 years ago

@Extrawurst I want set value to 1.00,2.00,3.00,.... sequentially. I think what I want is snap

extrawurst commented 9 years ago

@heroboy sounds like you want is DragInt...

ocornut commented 9 years ago

Or you can use a format of %.0f to snap float to integer values (whiles using actual floats) But I agree the iOS behavior is interesting. Will think about it.

unpacklo commented 9 years ago

iOS behavior could be nice, I'd have to try it with mouse + keyboard to know for sure. Even on iOS, I don't find the dragging resolution being tied to vertical distance from the dragging element to be that great (but better than nothing). Being locked to the horizontal axis and having no regard for the vertical component is useful in dragging; most of the time I kind of throw my mouse over to get to values quickly and having the resolution constantly change on me seems like it would be annoying.

extrawurst commented 9 years ago

One thing I would like to see in this new widget is: The support that unity brings for similar dragSliders to wrap the mouse pos around to enable infinite dragging, this is really usefull! "wrapping" as in "leaving the window on the left warps the cursor to the right of the window"

ocornut commented 9 years ago

Btw I have released 1.38 as is. Established that the missing desired features (e.g. bounds for clamp different from bounds for display) won't break the current API. They may have to be a different API.

unpacklo commented 9 years ago

We just bumped up to 1.38 today in our game, and though we aren't using the drag floats yet, things are looking good! Will try to find some places to use these and let you know of anything we find as a result...

videovse commented 7 years ago

Hello! Is it possible to disable in project (for some reasons) this feature : CTRL+click slider for text input?

ocornut commented 7 years ago

@CaptNemo999

Is it possible to disable in project (for some reasons) this feature : CTRL+click slider for text input?

No it isn't at the moment. Curious as to why you would need to disable it?

videovse commented 7 years ago

Ok, don't worry. Thank you for your answer. I use float slider as time slider line in video player (as you can see in QuickTime player for example). It's not what one expected from video player - to show text input if user accidentally click it with CTRL. By the way - excellent work, thank you!

ocornut commented 7 years ago

That makes sense. I would suggest you can implement a custom control. SliderFloat() does a lot of subtle stuff you don't really need, a simplified version would be less code.