Instantly share code, notes, and snippets.
My-Key/ToggleButtonsAttribute.cs Secret
Last active
December 28, 2025 18:39
-
Star
8
(8)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save My-Key/783fd25d077dd5bc6cfa9091fb8bd7ca to your computer and use it in GitHub Desktop.
Odin attribute to draw bool as toggle buttons
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using Sirenix.OdinInspector; | |
| using UnityEngine; | |
| #if UNITY_EDITOR | |
| using Sirenix.OdinInspector.Editor; | |
| [assembly: OdinVisualDesignerAttributeItem("Custom", typeof(ToggleButtonsAttribute))] | |
| #endif | |
| [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] | |
| public class ToggleButtonsAttribute : Attribute | |
| { | |
| [Title("True values")] | |
| public string m_trueText; | |
| public string m_trueTooltip; | |
| public SdfIconType m_trueIcon; | |
| public string m_trueColor; | |
| [Title("False values")] | |
| public string m_falseText; | |
| public string m_falseTooltip; | |
| public SdfIconType m_falseIcon; | |
| public string m_falseColor; | |
| public bool m_singleButton; | |
| [HideIf(nameof(m_singleButton))] | |
| [Tooltip("Amount by which smaller button size is lerped to match bigger button.\n" + | |
| "\t0 - original size of smaller button (takes the least space).\n" + | |
| "\t1 - matches size of bigger button.")] | |
| [PropertyRange(0,1)] | |
| public float m_sizeCompensationCompensation; | |
| /// <summary> | |
| /// Attribute to draw boolean as buttons | |
| /// </summary> | |
| /// <param name="trueText">Text for true button. Can be resolved string</param> | |
| /// <param name="falseText">Text for false button. Can be resolved string</param> | |
| /// <param name="singleButton">If set to true, only one button matching bool value will be shown</param> | |
| /// <param name="sizeCompensation">Amount by which smaller button size is lerped to match bigger button. | |
| /// 0 - original size of smaller button (takes the least space). | |
| /// 1 - matches size of bigger button.</param> | |
| /// <param name="trueTooltip">Tooltip for true button. Can be resolved string</param> | |
| /// <param name="falseTooltip">Tooltip for false button. Can be resolved string</param> | |
| /// <param name="trueColor">Color of true button</param> | |
| /// <param name="falseColor">Color of false button</param> | |
| /// <param name="trueIcon">SDF icon for true button</param> | |
| /// <param name="falseIcon">SDF icon for false button</param> | |
| public ToggleButtonsAttribute(string trueText = "Yes", string falseText = "No", bool singleButton = false, | |
| float sizeCompensation = 1f, string trueTooltip = "", string falseTooltip = "", | |
| string trueColor = "", string falseColor = "", SdfIconType trueIcon = SdfIconType.None, | |
| SdfIconType falseIcon = SdfIconType.None) | |
| { | |
| m_trueText = trueText; | |
| m_falseText = falseText; | |
| m_singleButton = singleButton; | |
| m_sizeCompensationCompensation = sizeCompensation; | |
| m_trueTooltip = trueTooltip; | |
| m_falseTooltip = falseTooltip; | |
| m_trueIcon = trueIcon; | |
| m_falseIcon = falseIcon; | |
| m_trueColor = trueColor; | |
| m_falseColor = falseColor; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Linq; | |
| using Sirenix.OdinInspector; | |
| using Sirenix.OdinInspector.Editor; | |
| using Sirenix.OdinInspector.Editor.ValueResolvers; | |
| using Sirenix.Utilities; | |
| using Sirenix.Utilities.Editor; | |
| using UnityEditor; | |
| using UnityEngine; | |
| public class ToggleButtonsAttributeDrawer : OdinAttributeDrawer<ToggleButtonsAttribute, bool> | |
| { | |
| private ValueResolver<string>[] m_nameGetters; | |
| private ValueResolver<string>[] m_tooltipGetters; | |
| private ValueResolver<Color>[] m_colorGetters; | |
| private GUIContent[] m_buttonContents; | |
| private float[] m_nameSizes; | |
| private int m_rows = 1; | |
| private float m_previousControlRectWidth; | |
| private bool m_needUpdate = false; | |
| private float m_totalNamesSize = 0f; | |
| protected override void Initialize() | |
| { | |
| base.Initialize(); | |
| m_nameGetters = new[] | |
| { | |
| ValueResolver.GetForString(Property, Attribute.m_trueText), | |
| ValueResolver.GetForString(Property, Attribute.m_falseText) | |
| }; | |
| m_tooltipGetters = new[] | |
| { | |
| ValueResolver.GetForString(Property, Attribute.m_trueTooltip), | |
| ValueResolver.GetForString(Property, Attribute.m_falseTooltip) | |
| }; | |
| m_colorGetters = new[] | |
| { | |
| ValueResolver.Get(Property, Attribute.m_trueColor, Color.white), | |
| ValueResolver.Get(Property, Attribute.m_falseColor, Color.white) | |
| }; | |
| m_buttonContents = new GUIContent[2]; | |
| for (int i = 0; i < 2; i++) | |
| m_buttonContents[i] = new GUIContent(m_nameGetters[i].GetValue(), m_tooltipGetters[i].GetValue()); | |
| m_nameSizes = m_buttonContents.Select(x => SirenixGUIStyles.MiniButtonMid.CalcSize(x).x).ToArray(); | |
| m_rows = 1; | |
| GUIHelper.RequestRepaint(); | |
| } | |
| private void UpdateNames() | |
| { | |
| UpdateName(0); | |
| UpdateName(1); | |
| // Add extra padding to smaller button | |
| if (m_nameSizes[0] > m_nameSizes[1]) | |
| m_nameSizes[1] = Mathf.Lerp(m_nameSizes[1], m_nameSizes[0], Attribute.m_sizeCompensationCompensation); | |
| else | |
| m_nameSizes[0] = Mathf.Lerp(m_nameSizes[0], m_nameSizes[1], Attribute.m_sizeCompensationCompensation); | |
| m_totalNamesSize = m_nameSizes[0] + m_nameSizes[1]; | |
| } | |
| private void UpdateName(int index) | |
| { | |
| var newText = m_nameGetters[index].GetValue(); | |
| if (string.IsNullOrWhiteSpace(newText)) | |
| newText = index == 0 ? "True" : "False"; | |
| m_buttonContents[index].tooltip = m_tooltipGetters[index].GetValue(); | |
| m_needUpdate |= m_buttonContents[index].text != newText; | |
| m_buttonContents[index].text = newText; | |
| SirenixEditorGUI.CalculateMinimumSDFIconButtonWidth(newText, SirenixGUIStyles.MiniButton, | |
| (index == 0 ? Attribute.m_trueIcon : Attribute.m_falseIcon) != SdfIconType.None, | |
| EditorGUIUtility.singleLineHeight, out _, out _, out _, out var size); | |
| m_nameSizes[index] = size; | |
| } | |
| protected override void DrawPropertyLayout(GUIContent label) | |
| { | |
| foreach (var valueResolver in m_nameGetters) | |
| valueResolver.DrawError(); | |
| foreach (var valueResolver in m_tooltipGetters) | |
| valueResolver.DrawError(); | |
| foreach (var valueResolver in m_colorGetters) | |
| valueResolver.DrawError(); | |
| if (Event.current.type == EventType.Layout) | |
| UpdateNames(); | |
| var currentValue = (bool)Property.ValueEntry.WeakSmartValue; | |
| var buttonIndex = 0; | |
| var rect = new Rect(); | |
| SirenixEditorGUI.GetFeatureRichControlRect(label, | |
| Mathf.CeilToInt(EditorGUIUtility.singleLineHeight * (Attribute.m_singleButton ? 1 : m_rows)), | |
| out int _, out bool _, out var valueRect); | |
| if (Attribute.m_singleButton) | |
| { | |
| DrawSingleButton(currentValue, valueRect); | |
| return; | |
| } | |
| valueRect.height = EditorGUIUtility.singleLineHeight; | |
| rect = valueRect; | |
| for (int rowIndex = 0; rowIndex < m_rows; ++rowIndex) | |
| { | |
| valueRect.xMin = rect.xMin; | |
| valueRect.xMax = rect.xMax; | |
| var xMax = valueRect.xMax; | |
| for (int columnIndex = 0; columnIndex < (m_rows == 2 ? 1 : 2); ++columnIndex) | |
| { | |
| valueRect.width = (int)rect.width * m_nameSizes[buttonIndex] / m_totalNamesSize; | |
| valueRect = DrawButton(buttonIndex, currentValue, valueRect, columnIndex, xMax); | |
| ++buttonIndex; | |
| } | |
| valueRect.y += valueRect.height; | |
| } | |
| if (Event.current.type != EventType.Repaint || m_previousControlRectWidth == rect.width && !m_needUpdate) | |
| return; | |
| m_previousControlRectWidth = rect.width; | |
| m_rows = rect.width < m_nameSizes[0] + m_nameSizes[1] + 6f ? 2 : 1; | |
| m_needUpdate = false; | |
| } | |
| private void DrawSingleButton(bool currentValue, Rect valueRect) | |
| { | |
| GUI.backgroundColor = m_colorGetters[currentValue ? 0 : 1].GetValue(); | |
| valueRect.x--; | |
| valueRect.xMax += 2; | |
| if (SirenixEditorGUI.SDFIconButton(valueRect, m_buttonContents[currentValue ? 0 : 1], | |
| currentValue ? Attribute.m_trueIcon : Attribute.m_falseIcon, IconAlignment.LeftOfText, | |
| SirenixGUIStyles.MiniButton, currentValue)) | |
| { | |
| ValueEntry.SmartValue = !currentValue; | |
| } | |
| GUI.backgroundColor = Color.white; | |
| } | |
| private Rect DrawButton(int buttonIndex, bool currentValue, Rect valueRect, int columnIndex, float xMax) | |
| { | |
| var buttonValue = buttonIndex == 0; | |
| var position = valueRect; | |
| GUIStyle style; | |
| if (columnIndex == 0 && columnIndex == (m_rows == 2 ? 1 : 2) - 1) | |
| { | |
| style = SirenixGUIStyles.MiniButton; | |
| --position.x; | |
| position.xMax = xMax + 1f; | |
| } | |
| else if (buttonIndex == 0) | |
| style = SirenixGUIStyles.MiniButtonLeft; | |
| else | |
| { | |
| style = SirenixGUIStyles.MiniButtonRight; | |
| position.xMax = xMax; | |
| } | |
| GUI.backgroundColor = m_colorGetters[buttonIndex].GetValue(); | |
| if (SirenixEditorGUI.SDFIconButton(position, m_buttonContents[buttonIndex], | |
| buttonIndex == 0 ? Attribute.m_trueIcon : Attribute.m_falseIcon, IconAlignment.LeftOfText, style, | |
| currentValue == buttonValue)) | |
| { | |
| ValueEntry.SmartValue = buttonValue; | |
| } | |
| GUI.backgroundColor = Color.white; | |
| valueRect.x += valueRect.width; | |
| return valueRect; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment