ocornut / imgui

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

How do you change shift fast tweak drag behavior? #6762

Open elanhickler opened 1 year ago

elanhickler commented 1 year ago

Edit: For future readers, here's my change to the code to disable changing v_speed. Note that it is not recommended to change the library code. imgui_widgets.cpp function DragBehaviorT

    // Default tweak speed
    if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX))
        v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);

    // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a
    // difference with our precision settings
    float adjust_delta = 0.0f;
    if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() &&
        IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) {
        adjust_delta = g.IO.MouseDelta[axis];
    } else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) {
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
        adjust_delta                = GetNavTweakPressedAmount(axis); // * tweak_factor;
        v_speed                     = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
    }
    adjust_delta *= v_speed;

Hi, new here. I am very out of my depth trying to use this library but I'm determined because of how much potential I see. I am having trouble understanding the philosophy and workflow. I am mostly interested in sliders and knobs behaviors because I develop audio plugins. The way that knobs/sliders currently work in imgui is quite far from the audio industry standard so I am trying to write out the perfect code for an industry-standard knob both in function and in end-programmer customizability and slider and will definitely submit it when it's done. I'm also trying to stick to programming style of Imgui...

Dear ImGui 1.89.7 (18971)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=202002
define: _WIN32
define: _WIN64
define: _MSC_VER=1933
define: _MSVC_LANG=202002
define: __clang_version__=15.0.2 
--------------------------------
io.BackendPlatformName: imgui_impl_glfw
io.BackendRendererName: imgui_impl_vulkan
io.ConfigFlags: 0x00000003
 NavEnableKeyboard
 NavEnableGamepad
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x0000000E
 HasMouseCursors
 HasSetMousePos
 RendererHasVtxOffset
--------------------------------
io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,64
io.DisplaySize: 1280.00,720.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: 0.00
style.FrameBorderSize: 0.00
style.ItemSpacing: 8.00,4.00
style.ItemInnerSpacing: 4.00,4.00

I'm proud that I made an initial attempt at figuring this out, I found a line of code.

/// Line 2261 of imgui_widgets.cpp
/// I'm guessing this is what is responsible for shift drag behavior that I'm encountering.
const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);

and my test code:

/// out of context code just taking the speed value and modifying it down before calling Imgui functions.
float speedMod = speed;
if (IsKeyDown(ImGuiMod_Shift) || IsKeyDown(ImGuiMod_Ctrl)) {
    speedMod = 0.0001;
}

QUESTION (finally a question): How do I bypass the shift drag behavior? Because my code actually works! The only problem is that Imgui takes my speedMod and modifies it again. If only Imgui would not do that, I'd be golden here.

elanhickler commented 1 year ago

ah ok, the line 2261 is for game pads. The above line 2251 is for mouse.

GamingMinds-DanielC commented 1 year ago

There is currently no clean way to deactivate the modifiers. Mmaybe having these multiplicators configurable instead of hardcoded might be a good idea.

If you don't mind small hacks, you could wrap the widget in your own custom widget function, get the context, backup IO.KeyAlt and IO.KeyShift, clear them, call the original widget function and restore the key states. You probably already have a wrapper that contains your own speed mod, so you could add the state manipulation there.

elanhickler commented 1 year ago

The library needs a default and a way to disable it. Built-in configurability is not an option here.

Consider the code:

if (IsKeyDown(ImGuiMod_Alt)) {
    v_speed *= 2.0;
} else if (IsKeyDown(ImGuiMod_Shift) && IsKeyDown(ImGuiMod_Ctrl)) {
    v_speed *= 0.0001;
} else if (IsKeyDown(ImGuiMod_Ctrl)) {
    v_speed *= 0.01;
} else if (IsKeyDown(ImGuiMod_Shift)) {
    v_speed *= 0.1;
}

I like this code. But the sky is the limit with how to handle this. Do you want to have speed based on min/max value? Do you want it to be based on size of the widget? Do you want it to be a fixed value? Based on desired precision? Based on a unique equation? Based on multiple or single key modifiers or both? How about mousewheel? How about mousewheel + shift, mousewheel + ctrl, mousewheel + shift + ctrl + alt? What about right-click? What about customizing for specific widgets? The options are vast. Leave it up to the programmer.

GamingMinds-DanielC commented 1 year ago

The library needs a default and a way to disable it. Built-in configurability is not an option here.

Configurable multipliers would be a way to disable it, by just setting them to 1.0f this feature is effectively disabled. Therefore very much an option.

ocornut commented 1 year ago

Part of this answer also applies to #6763. There's a tricky line to walk between providing lots of options, and making it more favorable for users to create their own widgets. One of my underlying wish has often been that people should be encouraged to create their own custom widgets, you'll find it is actually quite simple. But it looks trickier because over time our widgets have accumulated small options and features and support for edge cases.

If you look at the images in the first post of #4722 they are all experiments made with Dear ImGui. My aim was/is to keep improving the internals helpers so that making custom widgets become attractive, but there's also that tension between improving internals vs keeping them stable so they can relied on. I don't have a good answer.

Maybe checking e.g. imgui-knobs (see wiki https://github.com/ocornut/imgui/wiki/Useful-Extensions#knobs) can help come to the realization that it is worthwhile to consider making your widgets (and if you can design them and program them the way dear imgui does it, you are encouraged to share them).

On this very specific matter you asked about:

Expose parameters?

Here's the underlying logic:

// Default tweak speed
if (v_speed == 0.0f && is_clamped && (v_max - v_min < FLT_MAX))
    v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);

// Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
float adjust_delta = 0.0f;
if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
{
    adjust_delta = g.IO.MouseDelta[axis];
    if (g.IO.KeyAlt)
        adjust_delta *= 1.0f / 100.0f;
    if (g.IO.KeyShift)
        adjust_delta *= 10.0f;
}
else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
{
    const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
    const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
    const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
    const float tweak_factor = tweak_slow ? 1.0f / 1.0f : tweak_fast ? 10.0f : 1.0f;
    adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
    v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
}
adjust_delta *= v_speed;

The logic is not overly complex but rather custom, arbitrary and opinionated. You will also find that SliderBehaviorT() also has keyboard/gamepad logic which is very different from the above.

If you start to try to expose enough values to make all the parameters of that logic surface, then you are stuck with a locked logic. I think in that situation we'd both have a more complex system AND we are stuck to move forward. If you have an elegant idea to make some parameter surface I am open to it.

But, in this specific case, now that we have access to ImGuiSliderFlags (didn't exist when code above was written), my proposal is to add a flag to disable keymods-based scaling. Then you can use this flag, and provide the speed yourself.

PS: as a minor suggestion, if you ought to frequently make all those mod checks, you can read io.KeyMods directly instead of making calls:

ImGuiKeyChord mods = GetIO().KeyMods;
if (mods & ImGuiMod_Alt)
    v_speed *= 2.0;
else if ((mods & (ImGuiMod_Shift | ImGuiMod_Ctrl)) == (ImGuiMod_Shift | ImGuiMod_Ctrl))
    v_speed *= 0.0001;
else if (mods & ImGuiMod_Ctrl)
    v_speed *= 0.01;
else if (mods & ImGuiMod_Shift)
    v_speed *= 0.1;