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)
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.
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.
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
// Using a button hack to manually set the button state
I haven’t tried these yet but it seems to be an ongoing issue.
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.
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)
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
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.
public class StickyBtnState : MonoBehaviour
{
bool selected = false;
public Button btn;
public Color normalColor = Color.white;
public Color selectedColor = Color.grey;
private ColorBlock colors;
public void Awake()
{
btn = gameObject.GetComponent<Button>();
colors = btn.colors;
}
public void toggleSelected()
{
selected = !selected;
if (selected)
{
//var colors = btn.colors;
colors.normalColor = selectedColor;
colors.selectedColor = selectedColor;
btn.colors = colors;
}
else
{
//var colors = btn.colors;
colors.normalColor = normalColor;
colors.selectedColor = normalColor;
btn.colors = colors;
}
}
}
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
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
public class Toggle : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
{
[SerializeField] private Animator animator;
[SerializeField] private UnityEvent onSelect;
[HideInInspector] public ToggleGroup toggleGroup;
private bool isSelected = false;
public bool IsSelected => isSelected;
private bool isDisabled = false;
public bool IsDisabled => isDisabled;
public void OnPointerClick(PointerEventData eventData)
{
if (isDisabled)
return;
if (isSelected)
{
OnDeselect();
}
else
{
onSelect?.Invoke();
SetSelected(true);
}
}
public void OnDeselect()
{
if (isDisabled)
return;
SetSelected(false);
}
public void OnPointerEnter(PointerEventData eventData)
{
if (isDisabled)
return;
if (!isSelected)
{
animator.SetTrigger("Highlighted");
}
}
public void OnPointerExit(PointerEventData eventData)
{
if (isDisabled)
return;
if (!isSelected)
{
animator.SetTrigger("Normal");
}
}
public void SetSelected(bool value)
{
if (isDisabled)
return;
isSelected = value;
if (isSelected == true)
{
if (toggleGroup)
{
toggleGroup.SetCurrentlySelected(this);
}
animator.SetTrigger("Selected");
}
else
{
animator.SetTrigger("Normal");
}
}
public void SetDisabled(bool value)
{
isDisabled = value;
if (isDisabled == true)
{
isSelected = false;
animator.SetTrigger("Disabled");
}
else
{
animator.SetTrigger("Normal");
}
}
}
And the ToggleGroup:
[RequireComponent(typeof(ClickerBehaviour))]
public class ToggleGroup : MonoBehaviour
{
private Toggle lastSelectedToggle;
private Toggle currentSelectedToggle;
private Toggle[] toggles;
private void Start()
{
toggles = GetComponentsInChildren<Toggle>();
for (int i = 0; i < toggles.Length; i++)
{
toggles[i].toggleGroup = this;
}
}
public void SetCurrentlySelected(Toggle value)
{
if (currentSelectedToggle != null)
{
if (currentSelectedToggle != value)
{
currentSelectedToggle.SetSelected(false);
lastSelectedToggle = currentSelectedToggle;
}
}
currentSelectedToggle = value;
}
public void DeselectEverything()
{
if (currentSelectedToggle != null)
{
lastSelectedToggle = currentSelectedToggle;
currentSelectedToggle = null;
}
for (int i = 0; i < toggles.Length; i++)
{
toggles[i].SetSelected(false);
}
}
}
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.
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.
I ended up making some better ones kinda based on Sebastrian’s code but with Sprites and color switches, custom fade animation time, pressed/down sprite/color, no deselect bug:
I must say I have found the fastest and the shortest solution to this problem; Here it is:
List<Button> ribbonButtons; // Add all the [buttons] in the same group to this list
public void SelectRibbonButton(Button but) // Call this functin with the button and pass the button itself as the parameter
{
foreach (Button b in ribbonButtons)
{
if (b == but)
{
b.interactable = false;
}
else
{
b.interactable = true;
}
}
}
And this is the state of the button:
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).