using Nova; using System; using UnityEngine; namespace NovaSamples.UIControls { /// /// Handles focus/unfocus logic for a using Nova's /// system. Also handles drag-to-select within the . /// /// /// This is meant to serve as an example or a stub which can be extended depending /// on how your app/game handles focus state. /// [RequireComponent(typeof(Interactable))] public class TextFieldSelector : MonoBehaviour { private static TextFieldSelector currentlySelected = null; /// /// Fires when the associated gains focus. /// public event Action OnFocused = null; /// /// Fires when the associated loses focus. /// public event Action OnFocusLost = null; [SerializeField] [Tooltip("The TextField for which to provide focus/unfocus logic.")] private TextField inputField = null; [SerializeField] [Tooltip("If true, whenever the TextField gains focus, all of the text will be highlighted.")] public bool SelectAllOnFocus = true; [SerializeField] [Tooltip("If two clicks occur on the TextField within this duration, the word at the clicked location will be highlighted")] private float wordSelectDoubleClickDelay = .25f; /// /// The on this game object, /// which is how we'll subscribe to gesture events. /// private UIBlock uiBlock = null; /// /// Is this the actively focused ? /// private bool isFocused = false; /// /// The last time this was clicked. /// private float lastClickTime = 0f; private void OnEnable() { if (uiBlock == null) { // Interactable is guaranteed to be there // because it's a required component uiBlock = GetComponent().UIBlock; } // Subscribe to events to handle different selection/focus changes uiBlock.AddGestureHandler(HandleClick); uiBlock.AddGestureHandler(HandlePress); uiBlock.AddGestureHandler(HandleDrag); uiBlock.AddGestureHandler(HandleSelect); uiBlock.AddGestureHandler(HandleDeselect); InputManager.OnPostClick += HandlePostClick; } private void OnDisable() { uiBlock.RemoveGestureHandler(HandleClick); uiBlock.RemoveGestureHandler(HandlePress); uiBlock.RemoveGestureHandler(HandleDrag); uiBlock.RemoveGestureHandler(HandleSelect); uiBlock.RemoveGestureHandler(HandleDeselect); InputManager.OnPostClick -= HandlePostClick; } /// /// Handles the drag event, moving the cursor to the new position. /// private void HandleDrag(Gesture.OnDrag evt) { if (!isFocused) { return; } if (TextPosition.TryCreate(inputField.TextBlock, evt.Interaction.Ray, out TextPosition pos)) { // Move the cursor with highlight inputField.MoveCursor(pos, true); } } /// /// We handle press events (as well as clicks) because we want the cursor to move /// when the mouse is pressed down, not on release. /// private void HandlePress(Gesture.OnPress evt) { if (!isFocused) { return; } if (TextPosition.TryCreate(inputField.TextBlock, evt.Interaction.Ray, out TextPosition pos)) { // Move the cursor to the position, highlighting along the way if shift is held down #if ENABLE_LEGACY_INPUT_MANAGER bool shiftHeldDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift); #else bool shiftHeldDown = UnityEngine.InputSystem.Keyboard.current != null && UnityEngine.InputSystem.Keyboard.current.shiftKey.isPressed; #endif inputField.MoveCursor(pos, shiftHeldDown); } } /// /// Remap the select event to a click event and handle as if it were a click /// private void HandleSelect(Navigate.OnSelect evt) { Gesture.OnClick click = new Gesture.OnClick() { Target = evt.Target, Receiver = evt.Receiver, Interaction = evt.Interaction, }; HandleClick(click); } /// /// Remove focus on deselect /// private void HandleDeselect(Navigate.OnDeselect evt) { RemoveFocus(); } private void HandleClick(Gesture.OnClick evt) { if (!isFocused) { // Not focused yet, so focus the input field isFocused = true; if (SelectAllOnFocus) { // Highlight all text inputField.SetCursorPosition(inputField.CursorPosition.MoveToEnd(), inputField.CursorPosition.MoveToStart()); } else if (TextPosition.TryCreate(inputField.TextBlock, evt.Interaction.Ray, out TextPosition pos)) { // Move the cursor to the clicked location inputField.MoveCursor(pos, false); } else { // Move the cursor to the end of the text inputField.MoveCursor(inputField.CursorPosition.MoveToEnd(), false); } OnFocused?.Invoke(); if (currentlySelected != null) { currentlySelected.RemoveFocus(); } currentlySelected = this; } else { // Already focused, check if the user double clicked a word and highlight it if so. if ((Time.unscaledTime - lastClickTime) < wordSelectDoubleClickDelay) { inputField.SetCursorPosition(inputField.CursorPosition.MoveToEndOfWord(), inputField.CursorPosition.MoveToStartOfWord()); } } lastClickTime = Time.unscaledTime; } /// /// Unfocus the . /// public void RemoveFocus() { if (!isFocused) { return; } currentlySelected = null; isFocused = false; inputField.HideAllVisuals(); OnFocusLost?.Invoke(); } /// /// Handles unfocusing the if the user clicks somewhere else. /// private void HandlePostClick(UIBlock clickedBlock) { if (!isFocused) { return; } if (clickedBlock != uiBlock) { // Clicked somewhere else, so remove focus. RemoveFocus(); } } } }