Instantly share code, notes, and snippets.
My-Key/ToggleButtonsAttribute.cs Secret
Last active
August 20, 2024 17:20
-
Star
(7)
7
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 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; | |
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] | |
public class ToggleButtonsAttribute : Attribute | |
{ | |
public string m_trueText; | |
public string m_falseText; | |
public string m_trueTooltip; | |
public string m_falseTooltip; | |
public string m_trueIcon; | |
public string m_falseIcon; | |
public string m_trueColor; | |
public string m_falseColor; | |
public float m_sizeCompensationCompensation; | |
public bool m_singleButton; | |
/// <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">Icon for true button</param> | |
/// <param name="falseIcon">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 = "", string trueIcon = "", string falseIcon = "") | |
{ | |
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 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.Editor; | |
using Sirenix.OdinInspector.Editor.ValueResolvers; | |
using Sirenix.Utilities; | |
using Sirenix.Utilities.Editor; | |
using UnityEditor; | |
using UnityEngine; | |
public class ToggleButtonsAttributeDrawer : OdinAttributeDrawer<ToggleButtonsAttribute> | |
{ | |
private static readonly bool DO_MANUAL_COLORING = UnityVersion.IsVersionOrGreater(2019, 3); | |
private static readonly Color ACTIVE_COLOR = EditorGUIUtility.isProSkin ? Color.white : new Color(0.802f, 0.802f, 0.802f, 1f); | |
private static readonly Color INACTIVE_COLOR = EditorGUIUtility.isProSkin ? new Color(0.75f, 0.75f, 0.75f, 1f) : Color.white; | |
private Color?[] m_selectionColors; | |
private Color? m_color; | |
private ValueResolver<string>[] m_nameGetters; | |
private ValueResolver<string>[] m_tooltipGetters; | |
private ValueResolver<Texture>[] m_iconGetters; | |
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; | |
public override bool CanDrawTypeFilter(Type type) | |
{ | |
return type == typeof(bool); | |
} | |
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_iconGetters = new[] | |
{ | |
ValueResolver.Get(Property, Attribute.m_trueIcon, (Texture)null), | |
ValueResolver.Get(Property, Attribute.m_falseIcon, (Texture)null) | |
}; | |
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_iconGetters[i].GetValue(), | |
m_tooltipGetters[i].GetValue()); | |
m_nameSizes = m_buttonContents.Select(x => SirenixGUIStyles.MiniButtonMid.CalcSize(x).x).ToArray(); | |
m_rows = 1; | |
GUIHelper.RequestRepaint(); | |
if (!DO_MANUAL_COLORING) | |
return; | |
m_selectionColors = new Color?[2]; | |
m_color = new Color?(); | |
} | |
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(); | |
var newIcon = m_iconGetters[index].GetValue(); | |
m_buttonContents[index].tooltip = m_tooltipGetters[index].GetValue(); | |
var needUpdate = m_buttonContents[index].text != newText | m_buttonContents[index].image != newIcon; | |
m_needUpdate |= needUpdate; | |
m_buttonContents[index].text = newText; | |
m_buttonContents[index].image = newIcon; | |
m_nameSizes[index] = SirenixGUIStyles.MiniButton.CalcSize(m_buttonContents[index]).x; | |
} | |
protected override void DrawPropertyLayout(GUIContent label) | |
{ | |
foreach (var valueResolver in m_nameGetters) | |
valueResolver.DrawError(); | |
foreach (var valueResolver in m_iconGetters) | |
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); | |
} | |
else | |
{ | |
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, rowIndex, xMax); | |
++buttonIndex; | |
} | |
valueRect.y += valueRect.height; | |
} | |
} | |
if (Event.current.type != EventType.Repaint || m_previousControlRectWidth == rect.width && !m_needUpdate || | |
Attribute.m_singleButton) | |
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) | |
{ | |
if (DO_MANUAL_COLORING) | |
m_color = UpdateColor(m_color, currentValue ? ACTIVE_COLOR : INACTIVE_COLOR); | |
GUIStyle style = currentValue ? SirenixGUIStyles.MiniButtonSelected : SirenixGUIStyles.MiniButton; | |
GUI.backgroundColor = m_colorGetters[currentValue ? 0 : 1].GetValue(); | |
if (DO_MANUAL_COLORING) | |
GUIHelper.PushColor(m_color.Value * GUI.color); | |
valueRect.x--; | |
valueRect.xMax += 2; | |
if (GUI.Button(valueRect, m_buttonContents[currentValue ? 0 : 1], style)) | |
Property.ValueEntry.WeakSmartValue = !currentValue; | |
if (DO_MANUAL_COLORING) | |
GUIHelper.PopColor(); | |
GUI.backgroundColor = Color.white; | |
} | |
private Rect DrawButton(int buttonIndex, bool currentValue, Rect valueRect, int columnIndex, int rowIndex, | |
float xMax) | |
{ | |
var selectionColor = new Color?(); | |
var buttonValue = buttonIndex == 0; | |
if (DO_MANUAL_COLORING) | |
{ | |
var color = currentValue == buttonValue ? ACTIVE_COLOR : INACTIVE_COLOR; | |
selectionColor = m_selectionColors[buttonValue ? 0 : 1]; | |
selectionColor = UpdateColor(selectionColor, color); | |
m_selectionColors[buttonValue ? 0 : 1] = selectionColor; | |
} | |
var position = valueRect; | |
GUIStyle style; | |
if (columnIndex == 0 && columnIndex == (m_rows == 2 ? 1 : 2) - 1) | |
{ | |
style = currentValue ? SirenixGUIStyles.MiniButtonSelected : SirenixGUIStyles.MiniButton; | |
--position.x; | |
position.xMax = xMax + 1f; | |
} | |
else if (buttonIndex == 0) | |
style = currentValue ? SirenixGUIStyles.MiniButtonLeftSelected : SirenixGUIStyles.MiniButtonLeft; | |
else | |
{ | |
style = currentValue ? SirenixGUIStyles.MiniButtonRightSelected : SirenixGUIStyles.MiniButtonRight; | |
position.xMax = xMax; | |
} | |
GUI.backgroundColor = m_colorGetters[buttonIndex].GetValue(); | |
if (DO_MANUAL_COLORING) | |
GUIHelper.PushColor(selectionColor.Value * GUI.color); | |
if (GUI.Button(position, m_buttonContents[buttonIndex], style)) | |
Property.ValueEntry.WeakSmartValue = buttonValue; | |
GUI.backgroundColor = Color.white; | |
if (DO_MANUAL_COLORING) | |
GUIHelper.PopColor(); | |
valueRect.x += valueRect.width; | |
return valueRect; | |
} | |
private static Color? UpdateColor(Color? nullable, Color color) | |
{ | |
if (!nullable.HasValue) | |
nullable = color; | |
else if (nullable.Value != color && Event.current.type == EventType.Layout) | |
{ | |
nullable = color.MoveTowards(color, EditorTimeHelper.Time.DeltaTime * 4f); | |
GUIHelper.RequestRepaint(); | |
} | |
return nullable; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment