Skip to content

Instantly share code, notes, and snippets.

@My-Key
Last active December 28, 2025 18:39
Show Gist options
  • Select an option

  • Save My-Key/783fd25d077dd5bc6cfa9091fb8bd7ca to your computer and use it in GitHub Desktop.

Select an option

Save My-Key/783fd25d077dd5bc6cfa9091fb8bd7ca to your computer and use it in GitHub Desktop.
Odin attribute to draw bool as toggle buttons
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;
}
}
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