Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

non-interactable UI element (e.g. button) not skipped by navigation

Discussion in 'UGUI & TextMesh Pro' started by garv3, Dec 13, 2014.

  1. garv3

    garv3

    Joined:
    Dec 30, 2012
    Posts:
    32
    If I set a UI element (e.g. a button) as not interactable, I would expect it to be skipped by the navigation events (up, down, left, right), but it can still be selected. It won't change the Transition graphic, but it is not skipped and even its OnSelect is triggered.
    Is this intended or a bug?
     
  2. hoesterey

    hoesterey

    Joined:
    Mar 19, 2010
    Posts:
    659
    Seams intended. I could see wanting the user see info on a "Locked' button. E.g. A level that is not selectable yet. Though an option would be nice.
     
  3. garv3

    garv3

    Joined:
    Dec 30, 2012
    Posts:
    32
    Then maybe there should at least be an animation option (e.g. color tint, image...) to set for a selected non-interactive element. Right now the user can not see, if the element is selected.
    If I have three elements for example:
    left: interactableElement - center: NONinteractableElement - right: NONinteractableElement
    then there is a problem. If the left element is selected by default and the user navigates to the right, he can only see that the element is not selected any more, but he is unable to see which element is currently selected. If he now presses the "right arrow key" again, nothing changes visually but actually the right element is selected. What he would at least expect now is, that if he presses the "left arrow key", the left element should be selected again. But still nothing happens. Confusion is the result. This can not be the way it is meant to be...
     
  4. TechCor

    TechCor

    Joined:
    Apr 3, 2015
    Posts:
    56
    This problem is really this old? Blah.

    There is no way around this problem if you can't use automatic mode. It should really pass through like automatic does.

    It's almost like no one uses controllers on menu screens with non-interactable buttons with Unity.

    Hey, I'm offline. Let me just gray out this "social" button between these other menu options.
    Wait, why is my highlight disappearing? Oh, it selected this button that the user obviously doesn't want to go to.

    I could maybe see being able to select it to bring up some kind of help popup, but one could just as easily fake the gray out by replacing the sprite. On the other hand, you practically have to replace the navigation system to get around this problem. I can't tell the non-interactable button to just pass through to the next, so I'd either have to write my own nav system on top of Unity's or have conditionals for every non-interactable button that handles each direction that I might want to pass through.

    This isn't even the only problem with the nav system. I've already had to write a special helper script to take care of a bunch of other poor design problems.
     
  5. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    7,850
  6. TechCor

    TechCor

    Joined:
    Apr 3, 2015
    Posts:
    56
    Yes, I saw that feedback before posting and gave it a couple votes, but I'm surprised that no one else has mentioned it. Maybe they just work around it. Speaking of which, here is a workaround for anyone else having this issue. I still think this should be default behavior though (consistent with automatic mode).

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.UI;
    4. using UnityEngine.EventSystems;
    5.  
    6. public class SkipNonInteractable : MonoBehaviour, ISelectHandler
    7. {
    8.    private Selectable m_Selectable;
    9.  
    10.    // Use this for initialization
    11.    void Awake()
    12.    {
    13.      m_Selectable = GetComponent<Selectable>();
    14.    }
    15.    
    16.    public void OnSelect(BaseEventData evData)
    17.    {
    18.      // Don't apply skipping unless we are not interactable.
    19.      if (m_Selectable.interactable) return;
    20.  
    21.      // Check if the user navigated to this selectable.
    22.      if (Input.GetAxis("Horizontal") < 0)
    23.      {
    24.        Selectable select = m_Selectable.FindSelectableOnLeft();
    25.        if (select == null || !select.gameObject.activeInHierarchy)
    26.          select = m_Selectable.FindSelectableOnRight();
    27.        StartCoroutine(DelaySelect(select));
    28.      }
    29.      else if (Input.GetAxis("Horizontal") > 0)
    30.      {
    31.        Selectable select = m_Selectable.FindSelectableOnRight();
    32.        if (select == null || !select.gameObject.activeInHierarchy)
    33.          select = m_Selectable.FindSelectableOnLeft();
    34.        StartCoroutine(DelaySelect(select));
    35.      }
    36.      else if (Input.GetAxis("Vertical") < 0)
    37.      {
    38.        Selectable select = m_Selectable.FindSelectableOnDown();
    39.        if (select == null || !select.gameObject.activeInHierarchy)
    40.          select = m_Selectable.FindSelectableOnUp();
    41.        StartCoroutine(DelaySelect(select));
    42.      }
    43.      else if (Input.GetAxis("Vertical") > 0)
    44.      {
    45.        Selectable select = m_Selectable.FindSelectableOnUp();
    46.        if (select == null || !select.gameObject.activeInHierarchy)
    47.          select = m_Selectable.FindSelectableOnDown();
    48.        StartCoroutine(DelaySelect(select));
    49.      }
    50.    }
    51.  
    52.    // Delay the select until the end of the frame.
    53.    // If we do not, the current object will be selected instead.
    54.    private IEnumerator DelaySelect(Selectable select)
    55.    {
    56.      yield return new WaitForEndOfFrame();
    57.  
    58.      if (select != null || !select.gameObject.activeInHierarchy)
    59.        select.Select();
    60.      else
    61.        Debug.LogWarning("Please make sure your explicit navigation is configured correctly.");
    62.    }
    63. }
    Add this script to any buttons that need to be skipped when not interactable.

    Note: This does not stop event propagation. Any other scripts using OnSelect() need to check for interactable.

    EDIT:
    Code now handles noninteractable buttons that can't pass through (sends it back the other direction). Includes handling inactive objects.
     
    Last edited: Feb 23, 2016
  7. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,716
    That bug is so God damn dumb. How is that even a thing? (Almost 2 years later)
     
    tonycoculuzzi likes this.
  8. YVanhoutte

    YVanhoutte

    Joined:
    Mar 5, 2018
    Posts:
    8
    Good to see I'm not the only one stumbling on this.
    I worked around it myself since I have dynamic content for my UI (in which case thanks to indentation the automatic navigation was giving very inconsistent results), but to me this once again highlights the importance of having a separation of highlighting and selecting objects in the eventsystem.
     
  9. Max_Bol

    Max_Bol

    Joined:
    May 12, 2014
    Posts:
    168
    The main issue I seem to see in this topic is how some interpret the "Interactable" option which mean the issues is from the user, not the system.

    The "Interactable" is just an option that allow you to set quickly if the button returns its OnClick() function upon being activated as well as setting the related transition image or color automatically. You could call it "Lock the button" and it wouldn't be wrong. If you want something like having the button skipped all-together because it's not interactable, then you got to script it yourself.

    You might find it absurd or might seem like a bug to you, but that's simply the logical behavior which is also the easiest (simplest) to work with if you want custom behaviors in your UI.

    Here are examples of where the "Interactable" can be used as is:

    If you have an input menu followed by a button that bring the player to the next phase. You want the player to only be able to move on to the next phase if the text typed in the input menu fits within a set of rules. (The input Content Type is limited and you might want to add some additional rules like making it impossible to type specific words.) If the player type too few letters or have something that goes against the "rules", the button "Next" or "Register" (or whatever) is grayed out as it's "Interectable" is turned off and only turn on if the rules are followed.

    If you have a menu where the player gain some kind of advancement (like skill points), but each skills have requirement. When the requirement is not met, the "Interactable" of the skill's button is turned off. You might want the player to be able to have some kind of pop-up that mention what's missing to get this skill when selected even if the "Interactable" is turned off. (That's why the "OnSelect()" is activated when an non-interactable button is selected.)

    If you have a server list, you might want the player to be able to select the servers that are full even if they are unable to join those.

    If you want to use an Explicit navigation where non-interactable buttons are skipped, you got to do update the navigation through scripts which is relatively easy to do in a few additional lines of codes. It all depends on how you set up your menus' buttons. The golden rule, when it comes in making and scripting your UI is to keep track of everything in your menu through something like lists and arrays. Make it quite easy to find back through references afterwards.

    Is it a pain in the neck? Yeah sure! But that's the best and most optimal way of allowing everyone to handle the buttons' interaction the way each wants.
     
    jeremyfryc likes this.
  10. YVanhoutte

    YVanhoutte

    Joined:
    Mar 5, 2018
    Posts:
    8
    Well, another way they could handle this is instead of rolling navigation and selectable behaviours into one component is actually split that into two components with a dependancy (selectable and all its derived classes rely on navigation or a derived class) and let us override JUST the navigation.
    Right now if you want different navigation rules (for example categorise your labels in a dropdown tree and restrict navigation on one axis following these categories) you EITHER have to write a custom selectable and inherit it for EVERY kind of selectable there is (buttons, toggles, input fields, ... ) or do as I did and write a separate component that overrides the navigation of the standard selectable...
    Neither solution is elegant nor idiot-proof.
    Standard automatic navigation is fine for (dynamic) strict grids, but any dynamic content that needs to be navigatable in a non-grid structure is a nightmare.
     
  11. daville

    daville

    Joined:
    Aug 5, 2012
    Posts:
    303
    I just found this issue also.

    Perhaps an exta state "Non Interactable Selected" could solve the problem or a Checker to "Pass" the selection to the next one on the list.
     
  12. rhys_vdw

    rhys_vdw

    Joined:
    Mar 9, 2012
    Posts:
    101
    Okay. I've just hit this one as well. Very surprising behaviour here.

    I cbf but it looks like the solution is to duplicate the package com.unity.ugui into your local packages (or a private git package), I'm not sure if you're allowed to make a public fork.

    Anyway, once you're there you wanna modify these functions so that they do a search:

    https://github.com/Unity-Technologi...nityEngine.UI/UI/Core/Selectable.cs#L924-L972

    For explicit up, for example, you need to keep calling `FindSelectableOnUp` until you find an object that is interactable or hit `null`.

    Should be a pretty trivial fix.
     
  13. amarillosebas

    amarillosebas

    Joined:
    Feb 28, 2013
    Posts:
    44
    My dude, you are a hero. You might have saved me a day of work.

    Unity, get your stuff together. I know working on the "cooler" new features might be more fun, but please, please, fix your bugs before adding more half-baked content. Thank you.
     
  14. Jakub_Machowski

    Jakub_Machowski

    Joined:
    Mar 19, 2013
    Posts:
    638
    Is there any progress about that problem, its quite sad we have to avoid such as simple problems with lines of additional code
     
  15. centaurianmudpig

    centaurianmudpig

    Joined:
    Dec 16, 2011
    Posts:
    92
    This is great and I found a way to shorten the code.
    Code (CSharp):
    1.         public void OnSelect(BaseEventData _data) {
    2.             //Manually handle
    3.             if (!m_selectable.interactable && _data is AxisEventData axisEvent) {
    4.                 var goBackSelect = m_selectable.FindSelectable(axisEvent.moveVector *= -1);
    5.                 StartCoroutine(DelaySelect(goBackSelect));
    6.                return;
    7.             }
    8.             //do other stuff...
    9.         }
    Check for AxisEventData and then just reverse the moveVector to find your way back. You can modify to recursively find the next interactable selectable in the direction the user wanted to go.
     
    ThousandHandsAsura likes this.
  16. ZoloftConversion

    ZoloftConversion

    Joined:
    Apr 15, 2020
    Posts:
    2
    This works perfectly thanks. The "bug" is still around in 2022 as far as I can tell :D
     
  17. EmiSalvado

    EmiSalvado

    Joined:
    Aug 11, 2022
    Posts:
    1
    Didn't know this problem was so old
    I've just began using Unity in March this year, so I'm kinda new and didn't want to write a code that I don't understand (though I don't mind if someone does), so I came up with a more simple solution for my case.

    [Just an explanation of my particular case, maybe not so important] I'm making an RPG when the player moves using buttons selectable by both keyboard and mouse, so there are buttons that shouldn't be selectable if the player can't move in that direction.

    Easy simple solution focusing on visual feedback

    I just duplicated all buttons, disabling all their interactable option so they had the disabled color, and put them "under" the true buttons. Instead of using "interactable = false" just used "gameObject.SetActive(false)". If a button shouldn't be selectable, it dissappears and there's a disabled double instead.
    The problem then was that it wasn't skipping one button to select the next, i couldn't go to the next interactable button if there was a disabled one in the middle ( cause there was no button there to be used as a bridge), so i add the "vertical" option along with the "explicit" option in the navigation of each button (these buttons are in a column), and problem solved, skipping disabled buttons effect achieved.
     
  18. Jakub_Machowski

    Jakub_Machowski

    Joined:
    Mar 19, 2013
    Posts:
    638
    How is that possible that this is still not fixed :)
     
    ExNull-Tyelor likes this.