Search Unity

Unity UI [SOLVED] Clicking anywhere outside a Toggle deselects it

Discussion in 'UGUI & TextMesh Pro' started by jazzbach, May 28, 2017.

  1. jazzbach

    jazzbach

    Joined:
    May 11, 2016
    Posts:
    41
    Hi !

    I have some Toggle UI elements that have a common ToggleGroup. When I click on any of them, the Toggle highlights correctly and stays highlighted as intended, but as soon as I click anywhere else on the screen, that toggle unhighlights.

    I'd like that toggle to remain highlighted even if I click somewhere else on the screen. (and of course, keeping the original Toggle logic intact, that is, a newly clicked Toggle will highlight itself and unhighlight the previous one)

    Thank you very much in advance for your help !
     
  2. XCodeWeaverX

    XCodeWeaverX

    Joined:
    Jun 3, 2018
    Posts:
    1
    Agreed. I just ran into this. It is baffling to me that a Toggle would behave in any other way. Clicking anywhere other than in the toggle, or amongst a set of toggles in a toggle group, should have no impact on the state whatsoever. It can probably be worked around, but I can't imagine what the use case would be of the existing behaviour. Unnecessary fettling required.
     
  3. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,277
  4. Mordizer

    Mordizer

    Joined:
    Oct 16, 2016
    Posts:
    1
    Has anyone found a solution for this? I tried fiddling with the navigation modes of the buttons within the toggle group but it seemed to have no effect.
     
    HuldaGnodima likes this.
  5. dylanfries

    dylanfries

    Joined:
    Jul 11, 2012
    Posts:
    16
    I think karl_jones is talking about disabling all button access. I've been looking into this as well and there seems to be no good solutions so far. Some posts I found helpful had a couple of different approaches:

    // Using the global EventSystem to update to the previously selected Selectable
    https://forum.unity.com/threads/keyboard-navigation-doesnt-work-if-you-click-off-the-ui.421977/

    // Using a button hack to manually set the button state
    https://answers.unity.com/questions/1313950/unity-ui-mouse-keyboard-navigate-un-highlight-butt.html

    I haven't tried these yet but it seems to be an ongoing issue.

    There is also a good blog post here: https://www.dylanwolf.com/2018/11/24/stupid-unity-ui-navigation-tricks/
    He outlines a similar solution with a global listener.
     
    kemijo, PrimalCoder and chrismarch like this.
  6. jazzbach

    jazzbach

    Joined:
    May 11, 2016
    Posts:
    41
    Thank you very much everyone for the replies (and the solutions) and I'm sorry for answering this late.

    I think is sad that, after all this time, this unnatural UI behaviour still exists in Unity right out of the box. Not sure if it's a bug or an intended feature but i think it's something that can be improved.
     
  7. shawnballay1

    shawnballay1

    Joined:
    Apr 21, 2019
    Posts:
    3
    Wait what was the solution? Why is this marked as solved?
     
    Kitamat likes this.
  8. jazzbach

    jazzbach

    Joined:
    May 11, 2016
    Posts:
    41
    Hi shawnballay1

    I'm sorry. You're totally right. The "solution" (which is not a complete solution but a workaround) is what dylanfries wrote:

    What worked for me was the second link (Using a button hack to manually set the button state). But the solution is still ugly unfortunately (adding a script on each button)
     
    Kitamat likes this.
  9. geck1942

    geck1942

    Joined:
    Jan 11, 2014
    Posts:
    2
    I had the same issue for a while and just discovered where my mistake was.
    My toggles are prefabs that are loaded dynamically during gameplay.
    The prefab had the checkbox Graphic deactivated. It turns out the "checkbox" should always be turned on, and will show only if the toggle isOn at runtime
     
    Weendie-Games and Raseru like this.
  10. Emiles

    Emiles

    Joined:
    Jan 22, 2014
    Posts:
    62
    I didn't have time to fully think this through, and it was for a prototype, but this script might help others in a pinch.
    it only works for buttons using the "Colour Tint" transition type. Stick it on a standard btn and call toggleSelected on click. Honestly its a bit dirty but it helped me out.


    Code (CSharp):
    1. public class StickyBtnState : MonoBehaviour
    2. {
    3.     bool selected = false;
    4.     public Button btn;
    5.     public Color normalColor = Color.white;
    6.     public Color selectedColor = Color.grey;
    7.  
    8.     private ColorBlock colors;
    9.  
    10.     public void Awake()
    11.     {
    12.         btn = gameObject.GetComponent<Button>();
    13.         colors = btn.colors;
    14.     }
    15.  
    16.     public void toggleSelected()
    17.     {
    18.         selected = !selected;
    19.  
    20.         if (selected)
    21.         {
    22.             //var colors = btn.colors;
    23.             colors.normalColor = selectedColor;
    24.             colors.selectedColor = selectedColor;
    25.             btn.colors = colors;
    26.         }
    27.         else
    28.         {
    29.             //var colors = btn.colors;
    30.             colors.normalColor = normalColor;
    31.             colors.selectedColor = normalColor;
    32.             btn.colors = colors;
    33.         }
    34.     }
    35. }
     
    ric_moore likes this.
  11. Sebastiran

    Sebastiran

    Joined:
    Feb 23, 2017
    Posts:
    13
    For anyone that's struggling with this problem: I decided to make a custom Toggle and ToggleGroup to suit my needs. Got the code here. You might want to add some things or customize it to your liking, but it should give you an idea. I used an animator, but you could use Color tints as well of course.

    The Toggle class
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3. using UnityEngine.EventSystems;
    4.  
    5. public class Toggle : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
    6.     {
    7.         [SerializeField] private Animator animator;
    8.         [SerializeField] private UnityEvent onSelect;
    9.  
    10.         [HideInInspector] public ToggleGroup toggleGroup;
    11.  
    12.         private bool isSelected = false;
    13.         public bool IsSelected => isSelected;
    14.  
    15.         private bool isDisabled = false;
    16.         public bool IsDisabled => isDisabled;
    17.  
    18.         public void OnPointerClick(PointerEventData eventData)
    19.         {
    20.             if (isDisabled)
    21.                 return;
    22.  
    23.             if (isSelected)
    24.             {
    25.                 OnDeselect();
    26.             }
    27.             else
    28.             {
    29.                 onSelect?.Invoke();
    30.                 SetSelected(true);
    31.             }
    32.         }
    33.  
    34.         public void OnDeselect()
    35.         {
    36.             if (isDisabled)
    37.                 return;
    38.  
    39.             SetSelected(false);
    40.         }
    41.  
    42.         public void OnPointerEnter(PointerEventData eventData)
    43.         {
    44.             if (isDisabled)
    45.                 return;
    46.  
    47.             if (!isSelected)
    48.             {
    49.                 animator.SetTrigger("Highlighted");
    50.             }
    51.         }
    52.  
    53.         public void OnPointerExit(PointerEventData eventData)
    54.         {
    55.             if (isDisabled)
    56.                 return;
    57.  
    58.             if (!isSelected)
    59.             {
    60.                 animator.SetTrigger("Normal");
    61.             }
    62.         }
    63.  
    64.         public void SetSelected(bool value)
    65.         {
    66.             if (isDisabled)
    67.                 return;
    68.  
    69.             isSelected = value;
    70.             if (isSelected == true)
    71.             {
    72.                 if (toggleGroup)
    73.                 {
    74.                     toggleGroup.SetCurrentlySelected(this);
    75.                 }
    76.                 animator.SetTrigger("Selected");
    77.             }
    78.             else
    79.             {
    80.                 animator.SetTrigger("Normal");
    81.             }
    82.         }
    83.  
    84.         public void SetDisabled(bool value)
    85.         {
    86.             isDisabled = value;
    87.             if (isDisabled == true)
    88.             {
    89.                 isSelected = false;
    90.                 animator.SetTrigger("Disabled");
    91.             }
    92.             else
    93.             {
    94.                 animator.SetTrigger("Normal");
    95.             }
    96.         }
    97.     }
    And the ToggleGroup:
    Code (CSharp):
    1. [RequireComponent(typeof(ClickerBehaviour))]
    2.     public class ToggleGroup : MonoBehaviour
    3.     {
    4.         private Toggle lastSelectedToggle;
    5.         private Toggle currentSelectedToggle;
    6.  
    7.         private Toggle[] toggles;
    8.  
    9.         private void Start()
    10.         {
    11.             toggles = GetComponentsInChildren<Toggle>();
    12.             for (int i = 0; i < toggles.Length; i++)
    13.             {
    14.                 toggles[i].toggleGroup = this;
    15.             }
    16.         }
    17.  
    18.         public void SetCurrentlySelected(Toggle value)
    19.         {
    20.             if (currentSelectedToggle != null)
    21.             {
    22.                 if (currentSelectedToggle != value)
    23.                 {
    24.                     currentSelectedToggle.SetSelected(false);
    25.                     lastSelectedToggle = currentSelectedToggle;
    26.                 }
    27.             }
    28.             currentSelectedToggle = value;
    29.         }
    30.  
    31.         public void DeselectEverything()
    32.         {
    33.             if (currentSelectedToggle != null)
    34.             {
    35.                 lastSelectedToggle = currentSelectedToggle;
    36.                 currentSelectedToggle = null;
    37.             }
    38.  
    39.             for (int i = 0; i < toggles.Length; i++)
    40.             {
    41.                 toggles[i].SetSelected(false);
    42.             }
    43.         }
    44.     }
    The ClickerBehaviour class is just a class that checks if there was a click anywhere on a certain layer mask and then calls the DeselectEverything() function. I wanted all Toggles to clear when I clicked with my right mouse button.
    Also, the lastSelectedToggle variable isn't really used, but I imagine you might find some use for it.
     
  12. Necrohunter

    Necrohunter

    Joined:
    May 18, 2017
    Posts:
    9
    still exists.
    im using animation behavier on my toggles, and if i click anywhere else, the animation state goes to "Normal".
     
  13. holyfot

    holyfot

    Joined:
    Dec 16, 2016
    Posts:
    42
    Thanks Sebastiran. I too found Toggle's to behave unintentionally like this (click outside of the group and all the toggles go off), even in 2019.4.3 LTS. Gonna test this out.
     
    Sebastiran likes this.
  14. holyfot

    holyfot

    Joined:
    Dec 16, 2016
    Posts:
    42
  15. gamedevgarage

    gamedevgarage

    Joined:
    Apr 21, 2018
    Posts:
    6
  16. Firewalker

    Firewalker

    Joined:
    Mar 30, 2012
    Posts:
    39
    You have an error in the code.
    yield return null; should be in the while loop(s).

    Besides that - great work! Thanks.
     
  17. Magixxar

    Magixxar

    Joined:
    May 15, 2016
    Posts:
    2
    I must say I have found the fastest and the shortest solution to this problem; Here it is:

    Code (CSharp):
    1.  
    2.  
    3. List<Button> ribbonButtons; // Add all the [buttons] in the same group to this list
    4.  
    5.     public void SelectRibbonButton(Button but) // Call this functin with the button and pass the button itself as the parameter
    6.     {
    7.         foreach (Button b in ribbonButtons)
    8.         {
    9.             if (b == but)
    10.             {
    11.                 b.interactable = false;
    12.             }
    13.             else
    14.             {
    15.                 b.interactable = true;
    16.             }
    17.         }
    18.     }
    And this is the state of the button:
    Untitled.png

    As you can see, you must place your Normal color on the Disabled Color section instead, because the selected button will become disabled (Uninteractable) and the unselected buttons will become enabled (Interactable).

    This is the outcome:
    Untitled2.png

    Clicking anywhere on the screen won't change the state of the buttons; Only if you click one of the greyed out buttons, their state will change

    Note: The change in color tint you see here is due to the glowing effect I have put on the selected button
     
    Last edited: Jul 2, 2021
    Vexacoe likes this.
  18. felipenunesfob

    felipenunesfob

    Joined:
    Feb 28, 2019
    Posts:
    3
    Uncheck this box on event system worked for me
    upload_2021-7-7_21-12-1.png
     
    kloot, MarkHelsinki and GuirieSanchez like this.
  19. unaton

    unaton

    Joined:
    Aug 31, 2019
    Posts:
    1
    Where did you find this checkbox? I can't see it in my event system game object upload_2021-7-16_19-6-17.png
     
    mikirosario likes this.
  20. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
    I found this on the Input System UI Input Module component ... which happened to be on the same object as the Event System one.

    Using that checkbox stops clicks on empty space deselecting toggle buttons BUT it doesn't stop clicks on other buttons from changing the Toggle image.
     
    GuirieSanchez likes this.
  21. Aurigan

    Aurigan

    Joined:
    Jun 30, 2013
    Posts:
    291
  22. tuncturelFlavour

    tuncturelFlavour

    Joined:
    Oct 27, 2021
    Posts:
    3
    Could we get a fix for this please? Still not working.
     
  23. HuldaGnodima

    HuldaGnodima

    Joined:
    Oct 10, 2016
    Posts:
    110
    I just made a formal feature-request for this to be an option in the standard UI Toggle here.

    I also linked to this thread. Crossing my fingers (for all of us and people who look for this feature in the future)! :)

    Edit:
    User Lurking-Ninja found a quick solution for Toggles to behave like people in this thread request that is easy to set up and requires no custom code! You just replace the Toggle checkmark with an UIbox sprite of a "selected" color. You can find the post here:
    https://forum.unity.com/threads/add...on-in-that-toggle-group.1197334/#post-7653283

    I'm using that setup now.
    I'm so grateful that the Unity community contains so many people willing to so kindly help each other!
     
    Last edited: Nov 13, 2021
  24. peachvirgo90

    peachvirgo90

    Joined:
    Aug 31, 2021
    Posts:
    1
    This thread no longer works :(

    I'm very desperate to solve this issue. I've spent all day trying to get my head around it.
     
  25. HuldaGnodima

    HuldaGnodima

    Joined:
    Oct 10, 2016
    Posts:
    110
    My friend, I'm so grateful to @Lurking-Ninja that they found a solution, so I'll try to describe the steps so you also get the solution:

    THE EASY SOLUTION (no code required):

    1. Add Toggle Group

    Create a Gameobject under a Canvas. Add the "ToggleGroup"-script o it, like so:



    2. Create toggles
    Create a couple of UI Toggles (via the UI-menu for example). They can be children to the ToggleGroup-Gameobject like so:



    3. Set Toggles' ToggleGroup
    Before you move to the next step make sure each Toggle has it's Toggle-Group set to the GameObject you created that s the ToggleGroup-script attached to it. See here:



    4. Find Checkmark gameobject in each Toggle
    Now onto how you will be able to solve the problem (of button being deselected if other buttons pressed/non-UI elements pressed). Go into all of the Toggles and find the gameobject called "Checkmark", you see it here:



    The Checkmark has an Image-attribute that looks like this:


    5. Change Checkmark according to your desires
    Press the source-image and choose the sprite you want to pop up when the buttons is chosen. If you're using the default toggle, change it to "UISprute". Also change the "color" to the color you want the button to be when it's selected. It will look like this more or less.



    6. Change checkmark for all toggles
    Make sure you do this checkmark-change on ALL toggles in your scene. Change the check-mark to the UI-Image/color to what you want when it's selected.

    7. Double-check that it works for your single toggle group
    At this stage you're actually done. If you press play, it will work like this now:

    If you duplicate this process you will see that toggles work independantly/aren't deselected on clicks outside! :)

    8. Drouple-check that your setup works for multiple ToggleGroups.
    Now to see that this will work, make som duplicates of this setup. Copy the Togglegroup and its toggles and move them to the side a bit. You can change the group's "on-colors" to see it more clearly. If you have copied them, and press play you will see something like this:


    You can see that they work independently of each other.

    9. TADAAA!!!! You're done :) and can start customizing. Now, ALL your toggles work independently and won't be deselected when pressing outside of UI.
    Feel free to customize your buttons/remove/add text/change sprites/add grid layouts etc. however you want. I made a quick example (excuse the hideous colors haha, just to show you you can customize).

    "UI with multiple toggles"-example using method above:


    That's it, hope this helps someone! I wrote this in detail for anyone who might find this thread in the future. Thanks again Lurking-Ninja for spending so much time finding this solution!

    (the post I linked before was removed by 'mistake'.
    Edit: The original solution-post is now reinstated now with the help of a nice mod :) )
     
    Last edited: Nov 22, 2021
  26. FYI: If you want to see how we ended up with this solution the other thread has been reopened.
     
    Selek-of-Vulcan and HuldaGnodima like this.
  27. Selek-of-Vulcan

    Selek-of-Vulcan

    Joined:
    Sep 23, 2021
    Posts:
    78
    This looks like the answer to my prayers! In my case, I want the user to be able to choose none, some or all buttons; and I want the user to be able to change their mind, selecting and deselecting as they wish. (In my game, the user is choosing which ships to put in a fleet.) So suppose I have 4 toggle buttons. Do I need 4 separate toggle groups? Or can I do it with no toggle group at all?

    Edit: I got this to work by using a separate Toggle Group for each and every Toggle. Crucially, one most check "allow toggle off" or a lonesome toggle will always stay on. It works great now, though it's odd that I may have up to 12 toggle groups so that I can have 12 independent toggles. At this point, I'll take it!

    Thanks for a wonderful thread. This was driving me crazy. :)
     
    Last edited: Aug 7, 2022
    HuldaGnodima likes this.
  28. pepeInLudus

    pepeInLudus

    Joined:
    Jan 18, 2022
    Posts:
    5
    Here is another solution if you need to use the Animation Transition system. My approach consists of blocking transitions to the Normal and Highlighted states when Toggle.isOn is true. You can manually change controller parameters and conditions get this working, but the FixController method does this automatically. It assumes you have a standard AnimatorController with typical transition States (Normal, Highlighted, Selected, etc.)

    Assign this script where you have the Toggle and the Animator component. At runtime it will transition to the Selected state if isOn is true to set the correct animation state. At gameobject OnEnable it will subscribe to Toggle changes to set the bool "ToggleSelected" parameter.

    Code (CSharp):
    1. using System.Linq;
    2. using UnityEditor.Animations;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6. public class ToggleSelectionFixer : MonoBehaviour
    7. {
    8.     private Animator _animator;
    9.     private Toggle _toggle;
    10.  
    11.     [SerializeField] private string parameterName = "ToggleSelected";
    12.     private string normalStateName = "Normal"; // Add Your Normal State Name
    13.     private string highlightedStateName = "Highlighted"; // Add Your Highlighted State Name
    14.     private string selectedParameterName = "Selected"; //
    15.  
    16.     void Awake()
    17.     {
    18.         _animator = GetComponent<Animator>();
    19.         _toggle = GetComponent<Toggle>();
    20.     }
    21.  
    22.     private void OnEnable()
    23.     {
    24.         _toggle.onValueChanged.AddListener(OnToggleValueChanged);
    25.  
    26.         _animator.SetBool(parameterName, _toggle.isOn);
    27.         if (_toggle.isOn)
    28.         {
    29.             _animator.SetTrigger(selectedParameterName);
    30.         }
    31.     }
    32.  
    33.     private void OnDisable()
    34.     {
    35.         _toggle.onValueChanged.RemoveListener(OnToggleValueChanged);
    36.     }
    37.  
    38.     private void OnToggleValueChanged(bool toState)
    39.     {
    40.         _animator.SetBool(parameterName, toState);
    41.     }
    42.  
    43.     //[Button] Call this method to prepare controller
    44.     public void FixController()
    45.     {
    46.         _animator = GetComponent<Animator>();
    47.         var _controller = _animator.runtimeAnimatorController as AnimatorController;
    48.        
    49.         // Add a new parameter to the animator
    50.         AddParameter(_controller);
    51.  
    52.         // Find transitions and add conditions
    53.         FixTransition(_controller, normalStateName);
    54.         FixTransition(_controller, highlightedStateName);
    55.     }
    56.  
    57.     private void AddParameter(AnimatorController controller)
    58.     {
    59.         var existingParameter = controller.parameters.FirstOrDefault(p => p.name == parameterName);
    60.         if (existingParameter is not null) return;
    61.  
    62.         var parameter = new AnimatorControllerParameter
    63.         {
    64.             name = parameterName,
    65.             type = AnimatorControllerParameterType.Bool
    66.         };
    67.         controller.AddParameter(parameter);
    68.     }
    69.  
    70.     void FixTransition(AnimatorController controller, string stateName)
    71.     {
    72.         var targetState = controller.layers[0].stateMachine.states
    73.             .FirstOrDefault(state => state.state.name == stateName);
    74.         if (targetState.state == null) return;
    75.  
    76.         var anyStateTransitions = controller.layers[0].stateMachine.anyStateTransitions;
    77.         var existingTransition =
    78.             anyStateTransitions.FirstOrDefault(transition => transition.destinationState == targetState.state);
    79.  
    80.         if (existingTransition == null) return;
    81.        
    82.         // Add condition if one doesn't exist
    83.         var existsCondition = existingTransition.conditions.Any(condition => condition.parameter == parameterName);
    84.         if (!existsCondition)
    85.         {
    86.             existingTransition.AddCondition(AnimatorConditionMode.IfNot, 0, parameterName);
    87.         }
    88.     }
    89. }