Inputs: Extra Keys / AddKeyEvent(): bidirectional mapping, basic CI, simplify backends, asserts on misuses, tested backward compat. (#2625, #4858, #2787)

(edit: simplified backends merged into previous commits to make history clearer)
This commit is contained in:
ocornut
2022-01-03 20:52:52 +01:00
parent 3b66929301
commit bf08c13e9b
7 changed files with 236 additions and 154 deletions

213
imgui.cpp
View File

@ -348,7 +348,7 @@ CODE
- The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable.
- Keyboard:
- Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable.
NewFrame() will automatically fill io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays.
NewFrame() will automatically fill io.NavInputs[] based on your io.AddKeyEvent() calls.
- When keyboard navigation is active (io.NavActive + ImGuiConfigFlags_NavEnableKeyboard), the io.WantCaptureKeyboard flag
will be set. For more advanced uses, you may want to read from:
- io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.
@ -386,6 +386,10 @@ CODE
When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files.
You can read releases logs https://github.com/ocornut/imgui/releases for more details.
- 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(). Removed GetKeyIndex(), now unecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details.
- IsKeyPressed(MY_NATIVE_KEY_XXX) -> use IsKeyPressed(ImGuiKey_XXX)
- IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX)
- Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent()
- 2022/01/05 (1.87) - inputs: renamed ImGuiKey_KeyPadEnter to ImGuiKey_KeypadEnter to align with new symbols. Kept redirection enum.
- 2022/01/05 (1.87) - removed io.ImeSetInputScreenPosFn() in favor of more flexible io.SetPlatformImeDataFn(). Removed 'void* io.ImeWindowHandle' in favor of writing to 'void* ImGuiViewport::PlatformHandleRaw'.
- 2022/01/01 (1.87) - commented out redirecting functions/enums names that were marked obsolete in 1.69, 1.70, 1.71, 1.72 (March-July 2019)
@ -923,9 +927,6 @@ static const char* GetClipboardTextFn_DefaultImpl(void* user_data);
static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text);
static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport, ImGuiPlatformImeData* data);
// ImGuiKey <-> user key index mapping functions
static int GetKeyDataIndexInternal(int imgui_key_or_user_key_index);
namespace ImGui
{
// Navigation
@ -1151,8 +1152,9 @@ ImGuiIO::ImGuiIO()
MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);
MouseDragThreshold = 6.0f;
for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;
for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++) KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f;
for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++) { KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f; }
for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f;
BackendUsingLegacyKeyArrays = (ImS8)-1;
}
// Pass in translated ASCII characters for text input.
@ -1233,20 +1235,49 @@ void ImGuiIO::ClearInputKeys()
NavInputsDownDuration[n] = NavInputsDownDurationPrev[n] = -1.0f;
}
// Queue a new key down/up event.
// - ImGuiKey key: Translated key (as in, generally ImGuiKey_A matches the key end-user would use to emit an 'A' character)
// - bool down: Is the key down? use false to signify a key release.
// FIXME: In the current version this is setting key data immediately. This will evolve into a trickling queue.
void ImGuiIO::AddKeyEvent(ImGuiKey key, bool down, int native_keycode, int native_scancode)
void ImGuiIO::AddKeyEvent(ImGuiKey key, bool down)
{
IM_UNUSED(native_keycode);
IM_UNUSED(native_scancode);
int key_index = GetKeyDataIndexInternal(key);
if (key_index < 0)
//if (e->Down) { IMGUI_DEBUG_LOG("AddKeyEvent() Key='%s' %d, NativeKeycode = %d, NativeScancode = %d\n", ImGui::GetKeyName(e->Key), e->Down, e->NativeKeycode, e->NativeScancode); }
if (key == ImGuiKey_None)
return;
IM_ASSERT(ImGui::IsNamedKey(key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values are legacy native key codes which are not accepted by this API.
KeysData[key_index].Down = down;
// Verify that backend isn't mixing up using new io.AddKeyEvent() api and old io.KeysDown[] + io.KeyMap[] data.
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
KeysDown[key_index] = KeysData[key_index].Down;
IM_ASSERT((BackendUsingLegacyKeyArrays == -1 || BackendUsingLegacyKeyArrays == 0) && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!");
if (BackendUsingLegacyKeyArrays == -1)
for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++)
IM_ASSERT(KeyMap[n] == -1 && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!");
#endif
BackendUsingLegacyKeyArrays = 0;
// Write key
const int keydata_index = (key - ImGuiKey_KeysData_OFFSET);
KeysData[keydata_index].Down = down;
}
// [Optional] Call add AddKeyEvent().
// Specify native keycode, scancode + Specify index for legacy <1.87 IsKeyXXX() functions with native indices.
// If you are writing a backend in 2022 or don't use IsKeyXXX() with native values that are not ImGuiKey values, you can avoid calling this.
void ImGuiIO::SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index)
{
IM_ASSERT(ImGui::IsNamedKey(key)); // >= 512
IM_ASSERT(native_legacy_index == -1 || ImGui::IsLegacyKey(native_legacy_index)); // >= 0 && <= 511
IM_UNUSED(native_keycode); // Yet unused
IM_UNUSED(native_scancode); // Yet unused
// Build native->imgui map so old user code can still call key functions with native 0..511 values.
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
const int legacy_key = (native_legacy_index != -1) ? native_legacy_index : native_keycode;
if (ImGui::IsLegacyKey(legacy_key))
KeyMap[legacy_key] = key;
#else
IM_UNUSED(key);
IM_UNUSED(native_legacy_index);
#endif
}
@ -3788,13 +3819,44 @@ static void ImGui::UpdateKeyboardInputs()
// Synchronize io.KeyMods with individual modifiers io.KeyXXX bools
io.KeyMods = GetMergedKeyModFlags();
// Import legacy keys or verify they are not used
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
if (io.BackendUsingLegacyKeyArrays == 0)
{
// Backend used new io.AddKeyEvent() API: Good! Verify that old arrays are never written too.
for (int n = 0; n < IM_ARRAYSIZE(io.KeysDown); n++)
IM_ASSERT(io.KeysDown[n] == false && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!");
}
else
{
if (g.FrameCount == 0)
for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++)
IM_ASSERT(g.IO.KeyMap[n] == -1 && "Backend is not allowed to write to io.KeyMap[0..511]!");
// Build reverse KeyMap (Named -> Legacy)
for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++)
if (io.KeyMap[n] != -1)
{
IM_ASSERT(IsLegacyKey((ImGuiKey)io.KeyMap[n]));
io.KeyMap[io.KeyMap[n]] = n;
}
// Import legacy keys into new ones
for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++)
if (io.KeysDown[n] || io.BackendUsingLegacyKeyArrays == 1)
{
const ImGuiKey key = (ImGuiKey)(io.KeyMap[n] != -1 ? io.KeyMap[n] : n);
IM_ASSERT(io.KeyMap[n] == -1 || IsNamedKey(key));
io.KeysData[key].Down = io.KeysDown[n];
io.BackendUsingLegacyKeyArrays = 1;
}
}
#endif
// Update keys
for (int i = 0; i < IM_ARRAYSIZE(io.KeysData); i++)
{
ImGuiKeyData& key_data = io.KeysData[i];
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
key_data.Down = io.KeysDown[i];
#endif
key_data.DownDurationPrev = key_data.DownDuration;
key_data.DownDuration = key_data.Down ? (key_data.DownDuration < 0.0f ? 0.0f : key_data.DownDuration + io.DeltaTime) : -1.0f;
}
@ -7303,44 +7365,30 @@ bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool c
return true;
}
static int GetKeyDataIndexInternal(int imgui_key_or_user_key_index)
const ImGuiKeyData* ImGui::GetKeyData(ImGuiKey key)
{
if (imgui_key_or_user_key_index < 0)
return -1;
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
if (imgui_key_or_user_key_index >= ImGuiKey_LegacyNativeKey_BEGIN && imgui_key_or_user_key_index < ImGuiKey_LegacyNativeKey_END)
return imgui_key_or_user_key_index;
ImGuiContext& g = *GImGui;
if (imgui_key_or_user_key_index >= ImGuiKey_NamedKey_BEGIN && imgui_key_or_user_key_index < ImGuiKey_NamedKey_END)
return g.IO.KeyMap[imgui_key_or_user_key_index];
int index;
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
IM_ASSERT(key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_NamedKey_END);
if (IsLegacyKey(key))
index = (g.IO.KeyMap[key] != -1) ? g.IO.KeyMap[key] : key; // Remap native->imgui or imgui->native
else
index = key;
#else
if (imgui_key_or_user_key_index >= ImGuiKey_NamedKey_BEGIN && imgui_key_or_user_key_index < ImGuiKey_NamedKey_END)
return imgui_key_or_user_key_index - ImGuiKey_NamedKey_BEGIN;
IM_ASSERT(IsNamedKey(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code.");
index = key - ImGuiKey_NamedKey_BEGIN;
#endif
return -1;
return &g.IO.KeysData[index];
}
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
static ImGuiKey GetImGuiKeyFromLegacyKey(int user_key_index)
{
ImGuiContext& g = *GImGui;
IM_ASSERT(user_key_index >= 0 && user_key_index < ImGuiKey_LegacyNativeKey_END);
for (int imgui_key = ImGuiKey_NamedKey_BEGIN; imgui_key < ImGuiKey_NamedKey_END; ++imgui_key)
if (g.IO.KeyMap[imgui_key] == user_key_index)
return imgui_key;
return ImGuiKey_None;
}
#endif
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
int ImGui::GetKeyIndex(ImGuiKey key)
{
IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END);
return GetKeyDataIndexInternal(key);
ImGuiContext& g = *GImGui;
IM_ASSERT(IsNamedKey(key));
const ImGuiKeyData* key_data = GetKeyData(key);
return (int)(key_data - g.IO.KeysData);
}
#endif
@ -7364,11 +7412,16 @@ IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(GKeyNames));
const char* ImGui::GetKeyName(ImGuiKey key)
{
#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO
IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code.");
IM_ASSERT(IsNamedKey(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code.");
#else
if (key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_LegacyNativeKey_END)
if ((key = GetImGuiKeyFromLegacyKey(key)) == ImGuiKey_None)
if (IsLegacyKey(key))
{
ImGuiIO& io = GetIO();
if (io.KeyMap[key] == -1)
return "N/A";
IM_ASSERT(IsNamedKey((ImGuiKey)io.KeyMap[key]));
key = (ImGuiKey)io.KeyMap[key];
}
#endif
if (key == ImGuiKey_None)
return "None";
@ -7376,20 +7429,12 @@ const char* ImGui::GetKeyName(ImGuiKey key)
return GKeyNames[key - ImGuiKey_NamedKey_BEGIN];
}
// Note that dear imgui doesn't know the semantic of each entry of io.KeysDown[]!
// Use your own indices/enums according to how your backend/engine stored them into io.KeysDown[]!
// Note that Dear ImGui doesn't know the meaning/semantic of ImGuiKey from 0..511: they are legacy native keycodes.
// Consider transitioning from 'IsKeyDown(MY_ENGINE_KEY_A)' (<1.87) to IsKeyDown(ImGuiKey_A) (>= 1.87)
bool ImGui::IsKeyDown(ImGuiKey key)
{
#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO
IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code.");
#endif
int key_index = GetKeyDataIndexInternal(key);
if (key_index < 0)
return false;
ImGuiContext& g = *GImGui;
IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysData));
return g.IO.KeysData[key_index].Down;
const ImGuiKeyData* key_data = GetKeyData(key);
return key_data->Down;
}
// t0 = previous time (e.g.: g.Time - g.IO.DeltaTime)
@ -7410,33 +7455,19 @@ int ImGui::CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, flo
return count;
}
int ImGui::GetKeyPressedAmount(int key, float repeat_delay, float repeat_rate)
int ImGui::GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float repeat_rate)
{
#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO
IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code.");
#endif
int key_index = GetKeyDataIndexInternal(key);
if (key_index < 0)
return 0;
ImGuiContext& g = *GImGui;
IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysData));
const float t = g.IO.KeysData[key_index].DownDuration;
const ImGuiKeyData* key_data = GetKeyData(key);
const float t = key_data->DownDuration;
return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, repeat_delay, repeat_rate);
}
bool ImGui::IsKeyPressed(int key, bool repeat)
bool ImGui::IsKeyPressed(ImGuiKey key, bool repeat)
{
#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO
IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code.");
#endif
int key_index = GetKeyDataIndexInternal(key);
if (key_index < 0)
return false;
ImGuiContext& g = *GImGui;
IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysData));
const float t = g.IO.KeysData[key_index].DownDuration;
const ImGuiKeyData* key_data = GetKeyData(key);
const float t = key_data->DownDuration;
if (t == 0.0f)
return true;
if (repeat && t > g.IO.KeyRepeatDelay)
@ -7444,18 +7475,10 @@ bool ImGui::IsKeyPressed(int key, bool repeat)
return false;
}
bool ImGui::IsKeyReleased(int key)
bool ImGui::IsKeyReleased(ImGuiKey key)
{
#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO
IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code.");
#endif
int key_index = GetKeyDataIndexInternal(key);
if (key_index < 0)
return false;
ImGuiContext& g = *GImGui;
IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysData));
return g.IO.KeysData[key_index].DownDurationPrev >= 0.0f && !g.IO.KeysData[key_index].Down;
const ImGuiKeyData* key_data = GetKeyData(key);
return key_data->DownDurationPrev >= 0.0f && !key_data->Down;
}
bool ImGui::IsMouseDown(ImGuiMouseButton button)
@ -7655,11 +7678,11 @@ static void ImGui::ErrorCheckNewFrameSanityChecks()
IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting.");
IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right);
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
for (int n = 0; n < ImGuiKey_COUNT; n++)
IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < IM_ARRAYSIZE(g.IO.KeysDown) && "io.KeyMap[] contains an out of bound value (need to be 0..512, or -1 for unmapped key)");
for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_COUNT; n++)
IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < IM_ARRAYSIZE(g.IO.KeysDown) && "io.KeyMap[] contains an out of bound value (need to be 0..511, or -1 for unmapped key)");
// Check: required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was only added in 1.60 WIP)
if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard)
if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && g.IO.BackendUsingLegacyKeyArrays == 1)
IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation.");
#endif