A simple package that allows you to easily implement Tab key navigation functionality between Selectables. This package is very easy to install and does not require any configuration to work, just add it to the project
By default, the package is automatically embedded in PlayerLoop Unity. It is enough to install the package in any convenient way and Tab key navigation should work
Method 1. Downlaod and import unitypackage
| ๐ | Just download EasyTab.unitypackage on the release page and import it into your project |
|---|
Method 2. Via Git URL
This installation method requires support for query parameters in the url to specify the path to the package.
Requires a unity version of at least 2019.3.4 (If you are using a later version of unity, just use a different installation method)
- Go to menu
Window โ PackageManager - Click
+button - Paste url
https://github.com/dav-sea/EasyTab.git?path=Assets/EasyTab - Click
Addbutton
[!TIP]
You can also specify the target version of the package by adding
#1.0.0at the end of the URL, for example:https://github.com/dav-sea/EasyTab.git?path=Assets/Package#1.0.0
Method 4. AssetStore
| ๐ | Download asset from AssetStore page |
|---|
Everything is ready! If the installation of the package was successful, then now you can go to Play Mode and check how the Tab key navigation works in your project
โ
Support for all components based on Selectable
๐ Zero configuration. Performing useful functionality immediately after installation
๐ Automatic detection of the navigation sequence by traversing the Transform tree
โก๏ธ Support for sequential navigation of the Selectable when pressing Tab key
โฉ Fast navigation by Tab key holding
๐ As well as navigation in reverse order when using the Shift+Tab key combination
โฉ๏ธ Processing of the Return key presses when the focus is on the text field (or custom component)
๐ค Additional support for input fields, including TextMeshPro (multiline case and Tab non-processing when typing)
โฐ Dropdown support. Items inside Dropdown are automatically navigated cyclically (Roll)
โฎ๏ธ Navigate to the last selected object when the focus is not set
๐ก Automatic checking of objects for accessibility (alive, active, intractable, canvas-group)
๐จ Canvas support of all types (world-space, screen space overlay, screen space camera)
๐ซ Automatic navigation between multiple canvases and multiple scenes
โจ๏ธ Support for all input modes (InputSystem V1 (old), InputSystem V2 (new), Both mode)
๐ No allocations during navigation operation
๐ค Minimal and simple code base
๐งฉ Easy navigation configuration using the EasyTab component for GameObject via inspector window
๐ The ability to explicitly set the navigation sequence using jumps
โ๏ธ The ability to exclude Selectable or children for navigation
๐ The ability to override navigation options
๐ง The ability to determine the behavior when reaching the boundaries (๐ Roll / ๐ Clamp /
๐ The package code is defined in the EasyTab.Runtime assembly (don't forget to include it in your assembly)
๐ง The ability to change all fields of the EasyTab component through the code
โ๏ธ The ability to customize global behavior using EasyTabIntegration.Globally
๐ฑ๏ธ The ability to create local instances of EasyTabIntegration and EasyTabSolver
๐น๏ธ The ability to independently control navigation through the code
โจ The ability to define additional blocking conditions for the availability of objects (by decorate default Driver)
The package provides several configuration levels: EasyTab (component), EasyTabIntegration, EasyTabSolver
Component configuration is a universal way to easily and quickly configure navigation, because for this you just need to add the component to the GameObject and set the necessary parameters using the inspector or code.
It is great in cases where you need to define behavior in a local area of your project (for example, on a specific prefab or window)
You can ignore Selectable on the same GameObject.
You can determine what the solver should do in cases when the extreme children (first or last) are reached when traversing the Transform tree
Escape (default):
The solver will go beyond the boundaries of this Transform and search in neighboring Transform`s
Roll: The solver will NOT go beyond the boundaries of this Transform, but simply start navigation from the first available child element (๐ the best solution for pop-ups and windows)
Clamp: The solver will NOT go beyond the boundaries of this Transform, but simply stop at the last selected
You can exclude all the children of this Transform from the tree
Allows you to set the behavior of jumping to other elements during navigation.
Tip
The specified jumps will be used only when the definition of the next Selectable starts with this GameObject (that is, when it is specified as EventSystem.current.currentSelectedGameObject). When traversing the Transform tree, jumps will be ignored
DontUseJumps: Disables jumping
UseOnlyJumps: When navigating from this GameObject, the specified jumps will be used to determine the next Selectable. But if the specified jumps are unavailable (for example, not interactable), then the transition to the next Selectable will not occur.
UseJumpsOrHierarchy: When navigating from this GameObject, the specified jumps will be used to determine the next Selectable. But if the specified jumps are unavailable (for example, not interactable), then the next Selectable will be searched by traversing the Transform tree relative to this GameObject.
UseJumpsOrTheirNext: When navigating from this GameObject, the specified jumps will be used to determine the next Selectable. But if the specified jumps are unavailable (for example, not interactable), then the process of finding for the next Selectable will be started again, but relative GameObject specified as a jump (if it is not destroyed).
Jumps to the next and previous GameObject to be used when navigating from this GameObject. (Jumps are used only when JumpingPolicy is not DontUseJumps)
You can block navigation when this GameObject is current. This can be useful when you don't need navigation on this GameObject. In Auto mode, navigation is blocked automatically when this GameObject contains an InputField in the multiline and focused state
You can determine whether navigation on this GameObject should occur when you press the Enter key. In auto mode, when you press Enter, navigation will occur if this GameObject contains an InputField in the single-line state
EasyTabIntegration is a class that uses InputSystem and EventSystem to define the input and the currently selected object. As well as processing auxiliary functions, such as remembering the last selected object.
For ease of integration, one instance of class EasyTabIntegration is located in EasyTabIntegration.Globally. The update logic of this instance is embedded in PlayerLoop, and is always processed.
If this does not suit you, you can disable the processing of the global object from PlayerLoop via EasyTabIntegration.GloballyEnabled = false
You can call the update logic yourself at the time you need via EasyTabIntegration.Globally.UpdateAll().
You can change the time to start fast navigation through the SecondsForStartFastNavigation field as well as the speed of fast navigation through the SecondsBetweenSelectablesInFastNavigation field
Or you can make your own instance of EasyTabIntegration and work with it, ignoring EasyTabIntegration.Globally
You can also configure EasyTabIntegration.Solver (see the following section)
EasyTabSolver is a class whose task is to decide which Selectable will be next. To configure it globally, use EasyTabIntegration.Globally.Solver
The GameObject to be used as the initial one. Combined with the FallbackNavigationPolicy specified in the WhenCurrentIsNotSet and WhenCurrentIsNotSelectable fields
The last selected object. If this solver is specified in EasyTabIntegration, then this field is updated by it. Combined with the FallbackNavigationPolicy specified in the WhenCurrentIsNotSet and WhenCurrentIsNotSelectable fields
You can set NavigationPolicy in different cases: WhenCurrentIsNotSet, WhenCurrentIsNotSelectable
AllowNavigateToEntryPoint: Allow navigation on EntryPoint as a fallback option
AllowNavigateToClosest: Allow to find the nearest Selectable (works only if the current GameObject is not null) as a fallback option
AllowNavigateToLastSelected: Allow navigation to the last selected object (especially useful when focus is lost when clicking into the void) as a fallback option
The idea of EasyTab is that it works "out of the box" and does not require any configuration. However, if you use third-party tools and unusual approaches to hide user interface elements and windows, then navigation may not work as expected.
EasyTab needs to understand which objects are really active and which are hidden. To do this, EasyTab offers a simple API based on decorators.
In simple cases, you just need to describe in which cases the element is not Selectable, for example:
var solver = EasyTabIntergation.Globally.Solver;
// we decorate the default driver with a handler that defines elements with a Graphic component and a transparent color as not selectable
solver.Driver = solver.Driver
.WithSelectableBlocking(t => t.TryGetComponent<Graphic>(out var graphic) && graphic.color.a <= 0);A common practice when developing a user interface in Unity is to hide elements by changing transparency, moving outside the screen space, zooming out, etc.
However, EasyTab does not know about your specific implementation of hiding windows/popups, so you can use the API too, but to exclude the hierarchy of objects from navigation, for example:
var solver = EasyTabIntergation.Globally.Solver;
// we decorate the default driver with a handler that excludes from navigation all children of elements that have a disabled window.
solver.Driver = solver.Driver
.WithTraversingChildrenBlocking(t => t.TryGetComponent(out Window window) && !window.IsActive);You can decorate any IEasyTabDriver method.
For example, you can decorate GetBorderMode and change the BorderMode for all windows through the decorator, so as not to override the BorderMode through the EasyTab component
var solver = EasyTabIntergation.Globally.Solver;
solver.Driver = solver.Driver.DecorateBorderMode((@base, target) =>
{
// We want to navigate all the elements inside the window cyclically
if (target.IsTransform(out var targetTransform) && targetTransform.TryGetComponent<Window>(out _))
return BorderMode.Roll;
// If the target is not a window, then use the base decorator
return @base.GetBorderMode(target);
});

