mirror of
https://github.com/Drezil/imgui.git
synced 2025-07-14 00:39:55 +02:00
Inputs: added wip/experiment routing system: Shortcut(), RoutingFocused policy, SetShortcutRouting(). (#456, #2637, #3724)
- InputText() uses Shortcut().
This commit is contained in:
201
imgui.cpp
201
imgui.cpp
@ -4116,6 +4116,45 @@ static void UpdateAliasKey(ImGuiKey key, bool v, float analog_value)
|
||||
key_data->AnalogValue = analog_value;
|
||||
}
|
||||
|
||||
// Rewrite routing data buffers to strip old entries + sort by key to make queries not touch scattered data.
|
||||
// Entries D,A,B,B,A,C,B --> A,A,B,B,B,C,D
|
||||
// Index A:1 B:2 C:5 D:0 --> A:0 B:2 C:5 D:6
|
||||
// See 'Metrics->Key Owners & Shortcut Routing' to visualize the result of that operation.
|
||||
static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
rt->EntriesNext.resize(0);
|
||||
for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
|
||||
{
|
||||
const int new_routing_start_idx = rt->EntriesNext.Size;
|
||||
ImGuiKeyRoutingData* routing_entry;
|
||||
for (int old_routing_idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; old_routing_idx != -1; old_routing_idx = routing_entry->NextEntryIndex)
|
||||
{
|
||||
routing_entry = &rt->Entries[old_routing_idx];
|
||||
routing_entry->RoutingCurr = routing_entry->RoutingNext; // Update entry
|
||||
routing_entry->RoutingNext = ImGuiKeyOwner_None;
|
||||
routing_entry->RoutingNextScore = 255;
|
||||
if (routing_entry->RoutingCurr == ImGuiKeyOwner_None)
|
||||
continue;
|
||||
rt->EntriesNext.push_back(*routing_entry); // Write alive ones into new buffer
|
||||
|
||||
// Apply routing to owner if there's no owner already (RoutingCurr == None at this point)
|
||||
if (routing_entry->Mods == g.IO.KeyMods)
|
||||
{
|
||||
ImGuiKeyOwnerData* owner_data = ImGui::GetKeyOwnerData(key);
|
||||
if (owner_data->OwnerCurr == ImGuiKeyOwner_None)
|
||||
owner_data->OwnerCurr = routing_entry->RoutingCurr;
|
||||
}
|
||||
}
|
||||
|
||||
// Rewrite linked-list
|
||||
rt->Index[key - ImGuiKey_NamedKey_BEGIN] = (ImGuiKeyRoutingIndex)(new_routing_start_idx < rt->EntriesNext.Size ? new_routing_start_idx : -1);
|
||||
for (int n = new_routing_start_idx; n < rt->EntriesNext.Size; n++)
|
||||
rt->EntriesNext[n].NextEntryIndex = (ImGuiKeyRoutingIndex)((n + 1 < rt->EntriesNext.Size) ? n + 1 : -1);
|
||||
}
|
||||
rt->Entries.swap(rt->EntriesNext); // Swap new and old indexes
|
||||
}
|
||||
|
||||
// [Internal] Do not use directly (should read io.KeyMods instead)
|
||||
static ImGuiKeyChord GetMergedModsFromBools()
|
||||
{
|
||||
@ -4234,6 +4273,8 @@ static void ImGui::UpdateKeyboardInputs()
|
||||
owner_data->OwnerNext = ImGuiKeyOwner_None;
|
||||
owner_data->LockThisFrame = owner_data->LockUntilRelease = owner_data->LockUntilRelease && key_data->Down; // Clear LockUntilRelease when key is not Down anymore
|
||||
}
|
||||
|
||||
UpdateKeyRoutingTable(&g.KeysRoutingTable);
|
||||
}
|
||||
|
||||
static void ImGui::UpdateMouseInputs()
|
||||
@ -4794,6 +4835,9 @@ void ImGui::Shutdown()
|
||||
g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL;
|
||||
g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL;
|
||||
g.MovingWindow = NULL;
|
||||
|
||||
g.KeysRoutingTable.Clear();
|
||||
|
||||
g.ColorStack.clear();
|
||||
g.StyleVarStack.clear();
|
||||
g.FontStack.clear();
|
||||
@ -7955,6 +7999,128 @@ ImVec2 ImGui::GetKeyVector2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key
|
||||
GetKeyData(key_down)->AnalogValue - GetKeyData(key_up)->AnalogValue);
|
||||
}
|
||||
|
||||
// owner_id may be None/Any, but routing_id needs to be always be set, so we default to GetID("") aka current ID stack.
|
||||
static inline ImGuiID GetRoutingIdFromOwnerId(ImGuiID owner_id)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
return (owner_id != ImGuiKeyOwner_None && owner_id != ImGuiKeyOwner_Any) ? owner_id : g.CurrentWindow->IDStack.back();
|
||||
}
|
||||
|
||||
ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord)
|
||||
{
|
||||
// Majority of shortcuts will be Key + any number of Mods
|
||||
// We accept _Single_ mod with ImGuiKey_None.
|
||||
// - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl); // Legal
|
||||
// - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl | ImGuiMod_Shift); // Legal
|
||||
// - Shortcut(ImGuiMod_Ctrl); // Legal
|
||||
// - Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift); // Not legal
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable;
|
||||
ImGuiKeyRoutingData* routing_data;
|
||||
ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
|
||||
ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
|
||||
if (key == ImGuiKey_None)
|
||||
key = ConvertSingleModFlagToKey(mods);
|
||||
IM_ASSERT(IsNamedKey(key));
|
||||
|
||||
// Get (in the majority of case, the linked list will have one element so this should be 2 reads.
|
||||
// Subsequent elements will be contiguous in memory as list is sorted/rebuilt in NewFrame).
|
||||
for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1; idx = routing_data->NextEntryIndex)
|
||||
{
|
||||
routing_data = &rt->Entries[idx];
|
||||
if (routing_data->Mods == mods)
|
||||
return routing_data;
|
||||
}
|
||||
|
||||
// Add
|
||||
ImGuiKeyRoutingIndex idx = (ImGuiKeyRoutingIndex)rt->Entries.Size;
|
||||
rt->Entries.push_back(ImGuiKeyRoutingData());
|
||||
routing_data = &rt->Entries[idx];
|
||||
routing_data->Mods = (ImU16)mods;
|
||||
routing_data->NextEntryIndex = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; // Setup linked list
|
||||
rt->Index[key - ImGuiKey_NamedKey_BEGIN] = idx;
|
||||
return routing_data;
|
||||
}
|
||||
|
||||
// Request a desired route for an input chord (key + mods).
|
||||
// Return true if the route is available this frame.
|
||||
// - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state.
|
||||
// (Conceptually this does a "Submit for next frame" + "Test for current frame".
|
||||
// As such, it could be called TrySetXXX or SubmitXXX, or the Submit and Test operations should be separate.)
|
||||
// - Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default)
|
||||
// - Using 'owner_id == ImGuiKeyOwner_None': allows disabling/locking a shortcut.
|
||||
bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags, ImGuiWindow* location)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
IM_ASSERT(owner_id != ImGuiKeyOwner_None);
|
||||
|
||||
// Calculate our score
|
||||
int score = 255;
|
||||
bool always_set_next_route = false;
|
||||
if (flags & ImGuiInputFlags_RouteFocused)
|
||||
{
|
||||
if (location == NULL)
|
||||
location = g.CurrentWindow;
|
||||
ImGuiWindow* focused = g.NavWindow;
|
||||
|
||||
if (g.ActiveId != 0 && g.ActiveId == owner_id)
|
||||
{
|
||||
// ActiveID gets top priority (0)
|
||||
// (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it)
|
||||
score = 0;
|
||||
}
|
||||
else if (focused != NULL && focused->RootWindow == location->RootWindow) // Early out
|
||||
{
|
||||
// Score based on distance to focused window (lower is better)
|
||||
// Assuming both windows are submitting a routing request,
|
||||
// - When WindowA...... is focused -> WindowA scores 1 (best), WindowA/ChildB scores 255 (no match)
|
||||
// - When Window/ChildB is focused -> WindowA scores 2, WindowA/ChildB scores 1 (best)
|
||||
// Assuming only WindowA is submitting a routing request,
|
||||
// - When Window/ChildB is focused -> WindowA scores 2 (best), WindowA/ChildB doesn't have a scoe.
|
||||
for (int next_score = 1; focused != NULL; next_score++)
|
||||
{
|
||||
if (focused == location)
|
||||
{
|
||||
score = (ImU8)next_score;
|
||||
break;
|
||||
}
|
||||
focused = (focused->RootWindow != focused) ? focused->ParentWindow : NULL; // FIXME: This could be later abstracted as a focus path
|
||||
}
|
||||
}
|
||||
|
||||
if (score == 255)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
score = 0;
|
||||
always_set_next_route = true;
|
||||
}
|
||||
|
||||
// Submit routing for NEXT frame (assuming score is sufficient)
|
||||
// FIXME: Could expose a way to use a "serve last" policy for same score resolution (using <= instead of <).
|
||||
ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord);
|
||||
const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); // FIXME: Location
|
||||
//const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score <= routing_data->RoutingNextScore) : (score < routing_data->RoutingNextScore);
|
||||
if (score < routing_data->RoutingNextScore || always_set_next_route)
|
||||
{
|
||||
routing_data->RoutingNext = routing_id;
|
||||
routing_data->RoutingNextScore = (ImU8)score;
|
||||
}
|
||||
|
||||
// Return routing state for CURRENT frame
|
||||
return routing_data->RoutingCurr == routing_id;
|
||||
}
|
||||
|
||||
// Currently unused by core (but used by tests)
|
||||
// Note: this cannot be turned into GetShortcutRouting() because we do the owner_id->routing_id translation, name would be more misleading.
|
||||
bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id)
|
||||
{
|
||||
const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id);
|
||||
ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord);
|
||||
return routing_data->RoutingCurr == routing_id;
|
||||
}
|
||||
|
||||
// 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)
|
||||
@ -7986,6 +8152,7 @@ bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags)
|
||||
const float t = key_data->DownDuration;
|
||||
if (t < 0.0f)
|
||||
return false;
|
||||
IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function!
|
||||
|
||||
bool pressed = (t == 0.0f);
|
||||
if (!pressed && ((flags & ImGuiInputFlags_Repeat) != 0))
|
||||
@ -8044,6 +8211,7 @@ bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInput
|
||||
const float t = g.IO.MouseDownDuration[button];
|
||||
if (t < 0.0f)
|
||||
return false;
|
||||
IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function!
|
||||
|
||||
const bool repeat = (flags & ImGuiInputFlags_Repeat) != 0;
|
||||
const bool pressed = (t == 0.0f) || (repeat && t > g.IO.KeyRepeatDelay && CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0);
|
||||
@ -8392,6 +8560,7 @@ bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id)
|
||||
void ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags)
|
||||
{
|
||||
IM_ASSERT(IsNamedKeyOrModKey(key) && (owner_id != ImGuiKeyOwner_Any || (flags & (ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease)))); // Can only use _Any with _LockXXX flags (to eat a key away without an ID to retrieve it)
|
||||
IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetKeyOwner) == 0); // Passing flags not supported by this function!
|
||||
|
||||
ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key);
|
||||
owner_data->OwnerCurr = owner_data->OwnerNext = owner_id;
|
||||
@ -8417,7 +8586,10 @@ void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags)
|
||||
if ((flags & ImGuiInputFlags_CondMask_) == 0)
|
||||
flags |= ImGuiInputFlags_CondDefault_;
|
||||
if ((g.HoveredId == id && (flags & ImGuiInputFlags_CondHovered)) || (g.ActiveId == id && (flags & ImGuiInputFlags_CondActive)))
|
||||
SetKeyOwner(key, id, flags);
|
||||
{
|
||||
IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetItemKeyOwner) == 0); // Passing flags not supported by this function!
|
||||
SetKeyOwner(key, id, flags & ~ImGuiInputFlags_CondMask_);
|
||||
}
|
||||
}
|
||||
|
||||
// - Need to decide how to handle shortcut translations for Non-Mac <> Mac
|
||||
@ -8425,6 +8597,11 @@ void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags)
|
||||
bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
|
||||
if (flags & ImGuiInputFlags_RouteFocused)
|
||||
if (!SetShortcutRouting(key_chord, owner_id, ImGuiInputFlags_RouteFocused, g.CurrentWindow))
|
||||
return false;
|
||||
|
||||
ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
|
||||
ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
|
||||
if (g.IO.KeyMods != mods)
|
||||
@ -8436,6 +8613,8 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags
|
||||
|
||||
if (!IsKeyPressed(key, owner_id, (flags & (ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_))))
|
||||
return false;
|
||||
IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByShortcut) == 0); // Passing flags not supported by this function!
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -13080,7 +13259,7 @@ void ImGui::ShowMetricsWindow(bool* p_open)
|
||||
TreePop();
|
||||
}
|
||||
|
||||
if (TreeNode("Key Owners"))
|
||||
if (TreeNode("Key Owners & Shortcut Routing"))
|
||||
{
|
||||
TextUnformatted("Key Owners:");
|
||||
if (BeginListBox("##owners", ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 8)))
|
||||
@ -13096,6 +13275,24 @@ void ImGui::ShowMetricsWindow(bool* p_open)
|
||||
}
|
||||
EndListBox();
|
||||
}
|
||||
TextUnformatted("Shortcut Routing:");
|
||||
if (BeginListBox("##routes", ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 8)))
|
||||
{
|
||||
for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
|
||||
{
|
||||
ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable;
|
||||
for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1; )
|
||||
{
|
||||
char key_chord_name[64];
|
||||
ImGuiKeyRoutingData* routing_data = &rt->Entries[idx];
|
||||
GetKeyChordName(key | routing_data->Mods, key_chord_name, IM_ARRAYSIZE(key_chord_name));
|
||||
Text("%s: 0x%08X", key_chord_name, routing_data->RoutingCurr);
|
||||
DebugLocateItemOnHover(routing_data->RoutingCurr);
|
||||
idx = routing_data->NextEntryIndex;
|
||||
}
|
||||
}
|
||||
EndListBox();
|
||||
}
|
||||
Text("(ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: 0x%X)", g.ActiveIdUsingAllKeyboardKeys, g.ActiveIdUsingNavDirMask);
|
||||
TreePop();
|
||||
}
|
||||
|
Reference in New Issue
Block a user