Inputs: added routing priorities. (#456, #2637, #3724)

- and ImGuiInputFlags_RouteUnlessBgFocused
- will be useful for blind menu handlers.
This commit is contained in:
ocornut 2022-10-19 18:53:53 +02:00
parent 7ff2d3353c
commit 4d6a9ef93f
3 changed files with 51 additions and 21 deletions

View File

@ -8052,11 +8052,24 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord)
bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags, ImGuiWindow* location) bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags, ImGuiWindow* location)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
if ((flags & ImGuiInputFlags_RouteMask_) == 0)
flags = ImGuiInputFlags_RouteGlobalHigh; // This is the default for SetShortcutRouting() but NOT Shortcut() which doesn't touch routing by default!
else
IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteMask_)); // Check that only 1 routing flag is used
IM_ASSERT(owner_id != ImGuiKeyOwner_None); IM_ASSERT(owner_id != ImGuiKeyOwner_None);
// Calculate our score if (flags & ImGuiInputFlags_RouteUnlessBgFocused)
if (g.NavWindow == NULL)
return false;
// Current score encoding (lower is highest priority):
// - 0: ImGuiInputFlags_RouteGlobalHigh
// - 1: ImGuiInputFlags_RouteFocused (if item active)
// - 2: ImGuiInputFlags_RouteGlobal
// - 3+: ImGuiInputFlags_RouteFocused (if window in focus-stack)
// - 254: ImGuiInputFlags_RouteGlobalLow
// - 255: none
int score = 255; int score = 255;
bool always_set_next_route = false;
if (flags & ImGuiInputFlags_RouteFocused) if (flags & ImGuiInputFlags_RouteFocused)
{ {
if (location == NULL) if (location == NULL)
@ -8065,44 +8078,48 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI
if (g.ActiveId != 0 && g.ActiveId == owner_id) if (g.ActiveId != 0 && g.ActiveId == owner_id)
{ {
// ActiveID gets top priority (0) // ActiveID gets top priority
// (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it) // (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it)
score = 0; score = 1;
} }
else if (focused != NULL && focused->RootWindow == location->RootWindow) // Early out else if (focused != NULL && focused->RootWindow == location->RootWindow) // Early out
{ {
// Score based on distance to focused window (lower is better) // Score based on distance to focused window (lower is better)
// Assuming both windows are submitting a routing request, // Assuming both windows are submitting a routing request,
// - When WindowA...... is focused -> WindowA scores 1 (best), WindowA/ChildB scores 255 (no match) // - When Window....... is focused -> Window scores 3 (best), Window/ChildB scores 255 (no match)
// - When Window/ChildB is focused -> WindowA scores 2, WindowA/ChildB scores 1 (best) // - When Window/ChildB is focused -> Window scores 4, Window/ChildB scores 3 (best)
// Assuming only WindowA is submitting a routing request, // Assuming only WindowA is submitting a routing request,
// - When Window/ChildB is focused -> WindowA scores 2 (best), WindowA/ChildB doesn't have a scoe. // - When Window/ChildB is focused -> Window scores 4 (best), Window/ChildB doesn't have a score.
for (int next_score = 1; focused != NULL; next_score++) for (int next_score = 3; focused != NULL; next_score++)
{ {
if (focused == location) if (focused == location)
{ {
IM_ASSERT(next_score < 255);
score = (ImU8)next_score; score = (ImU8)next_score;
break; break;
} }
focused = (focused->RootWindow != focused) ? focused->ParentWindow : NULL; // FIXME: This could be later abstracted as a focus path focused = (focused->RootWindow != focused) ? focused->ParentWindow : NULL; // FIXME: This could be later abstracted as a focus path
} }
} }
if (score == 255)
return false;
} }
else else
{ {
score = 0; if (flags & ImGuiInputFlags_RouteGlobal)
always_set_next_route = true; score = 2;
else if (flags & ImGuiInputFlags_RouteGlobalLow)
score = 254;
else // ImGuiInputFlags_RouteGlobalHigh is default, so call to SetShorcutRouting() without no flags are not conditional
score = 0;
} }
if (score == 255)
return false;
// Submit routing for NEXT frame (assuming score is sufficient) // 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 <). // FIXME: Could expose a way to use a "serve last" policy for same score resolution (using <= instead of <).
ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord);
const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); // FIXME: Location const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); // FIXME: Location
//const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score <= routing_data->RoutingNextScore) : (score < routing_data->RoutingNextScore); //const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score <= routing_data->RoutingNextScore) : (score < routing_data->RoutingNextScore);
if (score < routing_data->RoutingNextScore || always_set_next_route) if (score < routing_data->RoutingNextScore)
{ {
routing_data->RoutingNext = routing_id; routing_data->RoutingNext = routing_id;
routing_data->RoutingNextScore = (ImU8)score; routing_data->RoutingNextScore = (ImU8)score;
@ -8598,8 +8615,9 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
if (flags & ImGuiInputFlags_RouteFocused) // When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any.
if (!SetShortcutRouting(key_chord, owner_id, ImGuiInputFlags_RouteFocused, g.CurrentWindow)) if (flags & (ImGuiInputFlags_RouteMask_ | ImGuiInputFlags_RouteUnlessBgFocused))
if (!SetShortcutRouting(key_chord, owner_id, flags, g.CurrentWindow))
return false; return false;
ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);

View File

@ -1337,13 +1337,25 @@ enum ImGuiInputFlags_
ImGuiInputFlags_LockThisFrame = 1 << 6, // Access to key data will requires EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared at end of frame. This is useful to make input-owner-aware code steal keys from non-input-owner-aware code. ImGuiInputFlags_LockThisFrame = 1 << 6, // Access to key data will requires EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared at end of frame. This is useful to make input-owner-aware code steal keys from non-input-owner-aware code.
ImGuiInputFlags_LockUntilRelease = 1 << 7, // Access to key data will requires EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared when key is released or at end of frame is not down. This is useful to make input-owner-aware code steal keys from non-input-owner-aware code. ImGuiInputFlags_LockUntilRelease = 1 << 7, // Access to key data will requires EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared when key is released or at end of frame is not down. This is useful to make input-owner-aware code steal keys from non-input-owner-aware code.
// Flags for Shortcut(), SetShortcutRouting() // Routing policies for Shortcut(), SetShortcutRouting()
// When Focus Routing is enabled, function will call SetShortcutRouting(): Accept inputs if currently in focus stack. Deep-most focused window takes inputs. ActiveId takes inputs over deep-most focused window. // - When a policy is set, Shortcut() will register itself with SetShortcutRouting(),
ImGuiInputFlags_RouteFocused = 1 << 8, // Enable focus routing // allowing the system to decide where to route the input among other route-aware calls.
// The general idea is that several callers register a shortcut, and only one gets it.
// - Routing is NOT registered by default, meaning that a simple Shortcut() call
// will see all inputs, won't have any side-effect and won't interfere with other inputs.
// - Priorities (highest-to-lowest): GlobalHigh > Focused (when active item) > Global > Focused (when focused window) > GlobalLow.
// - Can select only 1 policy among all available.
ImGuiInputFlags_RouteNone = 0, // Do not register route (provided for completeness but technically zero-value)
ImGuiInputFlags_RouteFocused = 1 << 8, // Register route if focused: Accept inputs if window is in focus stack. Deep-most focused window takes inputs. ActiveId takes inputs over deep-most focused window.
ImGuiInputFlags_RouteGlobalLow = 1 << 9, // Register route globally (lowest priority: unless a focused window or active item registered the route) -> recommended Global priority.
ImGuiInputFlags_RouteGlobal = 1 << 10, // Register route globally (medium priority: unless an active item registered the route, e.g. CTRL+A registered by InputText).
ImGuiInputFlags_RouteGlobalHigh = 1 << 11, // Register route globally (highest priority: unlikely you need to use that: will interfere with every active items)
ImGuiInputFlags_RouteMask_ = ImGuiInputFlags_RouteFocused | ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteGlobalLow | ImGuiInputFlags_RouteGlobalHigh,
ImGuiInputFlags_RouteUnlessBgFocused= 1 << 12, // Global routes will not be applied if underlying background/void is focused (== no Dear ImGui windows are focused). Useful for overlay applications.
// [Internal] Mask of which function support which flags // [Internal] Mask of which function support which flags
ImGuiInputFlags_SupportedByIsKeyPressed = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_, ImGuiInputFlags_SupportedByIsKeyPressed = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_,
ImGuiInputFlags_SupportedByShortcut = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RouteFocused, ImGuiInputFlags_SupportedByShortcut = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RouteMask_ | ImGuiInputFlags_RouteUnlessBgFocused,
ImGuiInputFlags_SupportedBySetKeyOwner = ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease, ImGuiInputFlags_SupportedBySetKeyOwner = ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease,
ImGuiInputFlags_SupportedBySetItemKeyOwner = ImGuiInputFlags_SupportedBySetKeyOwner | ImGuiInputFlags_CondMask_, ImGuiInputFlags_SupportedBySetItemKeyOwner = ImGuiInputFlags_SupportedBySetKeyOwner | ImGuiInputFlags_CondMask_,
}; };

View File

@ -4290,7 +4290,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
// Using Shortcut() with ImGuiInputFlags_FocusRouting flag to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText) // Using Shortcut() with ImGuiInputFlags_RouteFocused flag to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText)
// Otherwise we could simply assume that we own the keys as we are active. // Otherwise we could simply assume that we own the keys as we are active.
const ImGuiInputFlags shortcut_flags = ImGuiInputFlags_RouteFocused | ImGuiInputFlags_Repeat; const ImGuiInputFlags shortcut_flags = ImGuiInputFlags_RouteFocused | ImGuiInputFlags_Repeat;
const bool is_cut = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_X, id, shortcut_flags) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, id, shortcut_flags)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection()); const bool is_cut = (Shortcut(ImGuiMod_Shortcut | ImGuiKey_X, id, shortcut_flags) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, id, shortcut_flags)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());