IFocusable
Description
Focus and focus states are a core concept to any user interface. They play a crucial role in the communication of interactions and feedback to the user.
The IFocusable interface aims to provide a handy system which allows for easy focus-based transitions.
To achieve such interactions IFocusable's core is based on a simply hashmap which tracks which member is the currently focused one for a given string key. This key is
also referred to as the 'FocusGroup'. All components which share the same FocusGroup will therefore be part of the same logical group for focus-based interactions.
The idea of FocusGroup is that at any point in time there can only be exactly one member which is the 'focused' one. Whenever another members wants to be focused the previously focused element will first be unfocused and then the new element will be focused. Behind the scenes the reference to the focused element in the map for the respective FocusGroup will also be adjusted to properly reflect the new state.
Requirements
To conform to this interface members have to implement the following members:
string FocusGroup- The group the element is part of.UnityEvent OnFocusEvent- Reference to an optional event which is fired on focus.UnityEvent OnUnfocusEvent- Reference to an optional event which is fired when loosing focus.void HandleFocus()- Handler invoked when element is focused.void HandleUnfocus()- Handler invoked when element looses its focused.
You dont't have to initialize the FocusGroup string to specify which group they want
to belong to.
UCGUI will fall back to an internal global group "ucgui-focus-group-default".
It is set via Defaults.Focus.DefaultGroup.
You can also change it at runtime by setting the property (only recommended for unqiue or single-use components, otherwise initialize the property inside the class directly).
The static FocusableHelper class offers CreateFocusEvent() and CreateUnfocusEvent() functions which can be used to externally and safely initialize your optional events.
FocusState<T>
where
Textendsstruct,Enum
The FocusState class aims to provide a closed and context based focus group wrapper with some additional functionalities.
To join a FocusState, any member implementing IFocusable can call
.Focusable(FocusState<T>, T). This uniquely links this member to that specific enum value.
The idea behind a FocusSate is that it created a bidirectional relationship between the enum and the members in the FocusState.
Every FocusState has a T Value property which points to the "current" or "active" enum
value. This allows for easy control the internal FocusGroup's active member: simply set
'Value' to the new desired enum member and the corresponding IFocusable is focused.
This also works vice versa.
This value can be directly assigned when creating a FocusState. It will default to null if
no value is passed into the constructor.
FocusState<MyEnum> simple = new FocusState<MyEnum>(MyEnum.Value1);
When joining a FocusState a member's FocusGroup is automatically assigned based on a unique identifier. There is no need to unify the members into a separate group manually!
Cycling Through States
Additionally FocusStates have the Next() and Previous() functions which all you to
easily cycle through the bound members. The order is defined by the order you add the
elements to the FocusState.
Additionally, an optional TransitionModes can be specified to adjust the
forward and backward cycling behaviour:
Simple- Cycles through every element in the state until either the left or right border has been hit.Loop- Cycles through every element in the state, looping around to the opposite border after it has been hit.LoopWithNull- Cycles through every element in the state. After a border has been hit the state will first set its value tonullbefore looping back to the opposite border.
FocusStates are by no means a new concept. If you are familiar with SwiftUI's @FocusState property wrapper expect this to work in a very similar way.
Examples
class MyFocusableElement : BaseComponent, IFocusable {
/* your element code */
// mandatory ifocsuable members:
public string FocusGroup { get; set; } // = "my-custom-focus-group"
public UnityEvent OnFocusEvent { get; set; } // = new UnityEvent();
public UnityEvent OnUnfocusEvent { get; set; } // = new UnityEvent();
public void HandleFocus() {
// invoked every time this element calls '.Focus()'
}
public void HandleUnfocus() {
// invoked every time this element calls '.Unfocus()'
}
}
enum SelectedTab { Shop, Home, Profile }
FocusState<SelectedTab> focusState = new (/* optional default init value*/);
// create and configure your elements ...
var tab1 = UI.N<MyFocusableElement>().Focusable(focusState, Shop);
var tab2 = UI.N<MyFocusableElement>().Focusable(focusState, Home);
var tab3 = UI.N<MyFocusableElement>().Focusable(focusState, Profile);
// ... and later focus them
focus.Value = Home; // invokes 'HandleFocus' in 'tab2'
// ... or cycle through them
focus.Next(); // invokes 'HandleFocus' in 'tab3', sets Value to 'Profile'
focus.Next(TransitionMode.Loop); // invokes 'HandleFocus' in 'tab1', sets Value to 'Shop'
References
This interface is implemented by: ButtonComponent, InputComponent.