├── Preview.gif ├── LICENSE ├── README.md └── SwitchAttribute.cs /Preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schwapo/Switch/HEAD/Preview.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Antonio Rafael Antunes Miranda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Switch 2 | 3 | **Requires [Odin Inspector](https://odininspector.com/)**. 4 | 5 | **Draws a configurable switch instead of Unity's default toggle.** 6 | 7 | ![Preview](Preview.gif) 8 | 9 | ### Installation 10 | 11 | Simply put the downloaded `SwitchAttribute.cs` file in your project and start using the attribute as shown in the examples. **Do not put the attribute in an Editor folder or the attribute will be removed during build, causing errors**. If you need/want to put it in an Editor folder you'll have to put the attribute definition in a separate file. 12 | 13 | 14 | ### Examples 15 | 16 | ```csharp 17 | public class SomeMonoBehaviour : MonoBehaviour 18 | { 19 | // Simply add the Switch attribute. 20 | 21 | [Switch] 22 | public bool SomeBool; 23 | 24 | 25 | // You can provide custom colors for the switch using Odin's ValueResolvers. 26 | // The switch's color will be determined by the background color if you don't set a specific one. 27 | // By default the background will be set to a dark grey while off and to a light blue while on. 28 | 29 | [Switch("bgColorOn", "bgColorOff", "switchColorOn", "switchColorOff")] 30 | public bool CustomColors; 31 | 32 | public Color bgColorOn = new Color(0.6f, 1f, 0.6f); 33 | public Color bgColorOff = new Color(0.165f, 0.165f, 0.165f); 34 | public Color switchColorOn = new Color(0.165f, 0.165f, 0.165f); 35 | public Color switchColorOff = new Color(0.6f, 1f, 0.6f); 36 | 37 | // You can change the aligment of the switch to left, right, or center 38 | // and make it a square switch by setting rounded to false. 39 | 40 | [Switch(SwitchAlignment.Center, rounded: false)] 41 | public bool SquareSwitch; 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /SwitchAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using Sirenix.Utilities; 4 | 5 | #if UNITY_EDITOR 6 | using Sirenix.OdinInspector; 7 | using UnityEditor; 8 | using Sirenix.Utilities.Editor; 9 | using Sirenix.OdinInspector.Editor; 10 | using Sirenix.OdinInspector.Editor.ValueResolvers; 11 | 12 | public class SwitchAttributeDrawer : OdinAttributeDrawer 13 | { 14 | private const float AnimationSpeedMultiplier = 6f; 15 | private const float SwitchWidth = 28f; 16 | private static readonly int ControlHint = "SwitchControlHint".GetHashCode(); 17 | 18 | private ValueResolver backgroundColorOnResolver; 19 | private ValueResolver backgroundColorOffResolver; 20 | private ValueResolver switchColorOnResolver; 21 | private ValueResolver switchColorOffResolver; 22 | private Color backgroundColor; 23 | private Color switchColor; 24 | private float switchPosition; 25 | private Texture whiteTexture; 26 | private bool animating; 27 | private bool hasToggleLeftAttribute; 28 | 29 | protected override void Initialize() 30 | { 31 | backgroundColorOnResolver = ValueResolver.Get(Property, Attribute.BackgroundColorOn); 32 | backgroundColorOffResolver = ValueResolver.Get(Property, Attribute.BackgroundColorOff); 33 | switchColorOnResolver = ValueResolver.Get(Property, Attribute.SwitchColorOn); 34 | switchColorOffResolver = ValueResolver.Get(Property, Attribute.SwitchColorOff); 35 | 36 | var isOn = ValueEntry.SmartValue; 37 | backgroundColor = isOn ? backgroundColorOnResolver.GetValue() : backgroundColorOffResolver.GetValue(); 38 | switchColor = isOn ? switchColorOnResolver.GetValue() : switchColorOffResolver.GetValue(); 39 | switchPosition = isOn ? SwitchWidth * 0.5f : 0f; 40 | 41 | whiteTexture = Texture2D.whiteTexture; 42 | hasToggleLeftAttribute = Property.Attributes.HasAttribute(); 43 | } 44 | 45 | protected override void DrawPropertyLayout(GUIContent label) 46 | { 47 | ValueResolver.DrawErrors( 48 | backgroundColorOnResolver, 49 | backgroundColorOffResolver, 50 | switchColorOnResolver, 51 | switchColorOffResolver); 52 | 53 | var backgroundColorOn = backgroundColorOnResolver.GetValue(); 54 | var backgroundColorOff = backgroundColorOffResolver.GetValue(); 55 | var switchColorOn = switchColorOnResolver.GetValue(); 56 | var switchColorOff = switchColorOffResolver.GetValue(); 57 | 58 | var totalRect = EditorGUILayout.GetControlRect(label != null, EditorGUIUtility.singleLineHeight); 59 | 60 | if (label != null && !hasToggleLeftAttribute) 61 | { 62 | totalRect = EditorGUI.PrefixLabel(totalRect, label); 63 | } 64 | 65 | var switchBackgroundRect = Attribute.Alignment switch 66 | { 67 | SwitchAlignment.Left => totalRect.AlignLeft(SwitchWidth).AlignCenterY(SwitchWidth * 0.5f), 68 | SwitchAlignment.Right => totalRect.AlignRight(SwitchWidth).AlignCenterY(SwitchWidth * 0.5f), 69 | SwitchAlignment.Center => totalRect.AlignCenterX(SwitchWidth).AlignCenterY(SwitchWidth * 0.5f), 70 | _ => throw new ArgumentException("Invalid enum argument possible values are:\n- SwitchAlignment.Left\n- SwitchAlignment.Right\n- SwitchAlignment.Center") 71 | }; 72 | 73 | var evt = Event.current; 74 | var isOn = ValueEntry.SmartValue; 75 | var controlID = GUIUtility.GetControlID(ControlHint, FocusType.Keyboard, switchBackgroundRect); 76 | var hasKeyboardFocus = GUIUtility.keyboardControl == controlID; 77 | var targetBackgroundColor = isOn ? backgroundColorOn : backgroundColorOff; 78 | var targetSwitchColor = isOn ? switchColorOn : switchColorOff; 79 | 80 | if (ColorHasChanged(targetBackgroundColor, targetSwitchColor)) 81 | { 82 | animating = true; 83 | } 84 | 85 | if (evt.type == EventType.Layout && animating) 86 | { 87 | backgroundColor = backgroundColor.MoveTowards( 88 | targetBackgroundColor, 89 | GUITimeHelper.LayoutDeltaTime * AnimationSpeedMultiplier); 90 | 91 | switchColor = switchColor.MoveTowards( 92 | targetSwitchColor, 93 | GUITimeHelper.LayoutDeltaTime * AnimationSpeedMultiplier); 94 | 95 | var targetSwitchPosition = isOn ? SwitchWidth * 0.5f : 0f; 96 | 97 | switchPosition = Mathf.MoveTowards( 98 | switchPosition, 99 | targetSwitchPosition, 100 | GUITimeHelper.LayoutDeltaTime * AnimationSpeedMultiplier * SwitchWidth * 0.5f); 101 | 102 | if (backgroundColor == targetBackgroundColor 103 | && switchColor == targetSwitchColor 104 | && switchPosition == targetSwitchPosition) 105 | { 106 | animating = false; 107 | } 108 | 109 | GUIHelper.RequestRepaint(); 110 | } 111 | else if (evt.OnMouseDown(switchBackgroundRect, 0)) 112 | { 113 | GUIUtility.hotControl = controlID; 114 | GUIUtility.keyboardControl = controlID; 115 | } 116 | else if (evt.OnMouseUp(switchBackgroundRect, 0)) 117 | { 118 | GUIUtility.hotControl = 0; 119 | GUIUtility.keyboardControl = 0; 120 | ChangeValueTo(!isOn); 121 | } 122 | else if (hasKeyboardFocus && evt.type == EventType.KeyDown) 123 | { 124 | switch (evt.keyCode) 125 | { 126 | case KeyCode.Return: 127 | case KeyCode.Space: 128 | ChangeValueTo(!isOn); 129 | break; 130 | case KeyCode.LeftArrow: 131 | ChangeValueTo(false); 132 | break; 133 | case KeyCode.RightArrow: 134 | ChangeValueTo(true); 135 | break; 136 | } 137 | } 138 | 139 | var finalBackgroundColor = hasKeyboardFocus ? Darken(backgroundColor, 1.5f) : backgroundColor; 140 | var borderRadius = Attribute.Rounded ? 99f : 0f; 141 | GUI.DrawTexture(switchBackgroundRect, whiteTexture, ScaleMode.StretchToFill, true, 0f, finalBackgroundColor, 0f, borderRadius); 142 | 143 | var finalSwitchColor = hasKeyboardFocus ? Darken(switchColor, 1.5f) : switchColor; 144 | var switchRect = switchBackgroundRect.SetWidth(SwitchWidth * 0.5f).Padding(SwitchWidth * 0.07f).AddX(switchPosition); 145 | GUI.DrawTexture(switchRect, whiteTexture, ScaleMode.StretchToFill, true, 0f, finalSwitchColor, 0f, borderRadius); 146 | 147 | if (hasToggleLeftAttribute) 148 | { 149 | EditorGUI.LabelField(totalRect.AddX(SwitchWidth + 4f), label); 150 | } 151 | } 152 | 153 | private void ChangeValueTo(bool newValue) 154 | { 155 | ValueEntry.SmartValue = newValue; 156 | animating = true; 157 | } 158 | 159 | private bool ColorHasChanged(Color targetBackgroundColor, Color targetSwitchColor) 160 | { 161 | return backgroundColor != targetBackgroundColor || switchColor != targetSwitchColor; 162 | } 163 | 164 | private Color Darken(Color color, float factor) 165 | { 166 | return new Color(color.r / factor, color.g / factor, color.b / factor); 167 | } 168 | } 169 | #endif 170 | 171 | public class SwitchAttribute : Attribute 172 | { 173 | private static readonly string DefaultBackgroundColorOn = "@new Color(0.498f, 0.843f, 0.992f)"; 174 | private static readonly string DefaultBackgroundColorOff = "@new Color(0.165f, 0.165f, 0.165f)"; 175 | private static readonly string DefaultSwitchColorOn = DefaultBackgroundColorOff; 176 | private static readonly string DefaultSwitchColorOff = DefaultBackgroundColorOn; 177 | 178 | public string BackgroundColorOn = null; 179 | public string BackgroundColorOff = null; 180 | public string SwitchColorOn = null; 181 | public string SwitchColorOff = null; 182 | public bool Rounded; 183 | public SwitchAlignment Alignment; 184 | 185 | public SwitchAttribute( 186 | SwitchAlignment alignment, 187 | string backgroundColorOn = null, 188 | string backgroundColorOff = null, 189 | string switchColorOn = null, 190 | string switchColorOff = null, 191 | bool rounded = true) 192 | { 193 | Alignment = alignment; 194 | Rounded = rounded; 195 | SetColors(backgroundColorOn, backgroundColorOff, switchColorOn, switchColorOff); 196 | } 197 | 198 | public SwitchAttribute( 199 | string backgroundColorOn = null, 200 | string backgroundColorOff = null, 201 | string switchColorOn = null, 202 | string switchColorOff = null, 203 | bool rounded = true) 204 | { 205 | Alignment = SwitchAlignment.Left; 206 | Rounded = rounded; 207 | SetColors(backgroundColorOn, backgroundColorOff, switchColorOn, switchColorOff); 208 | } 209 | 210 | private void SetColors( 211 | string backgroundColorOn, 212 | string backgroundColorOff, 213 | string switchColorOn, 214 | string switchColorOff) 215 | { 216 | BackgroundColorOn = backgroundColorOn ?? DefaultBackgroundColorOn; 217 | BackgroundColorOff = backgroundColorOff ?? DefaultBackgroundColorOff; 218 | SwitchColorOn = switchColorOn ?? backgroundColorOff ?? DefaultSwitchColorOn; 219 | SwitchColorOff = switchColorOff ?? backgroundColorOn ?? DefaultSwitchColorOff; 220 | } 221 | } 222 | 223 | public enum SwitchAlignment 224 | { 225 | Left, 226 | Right, 227 | Center, 228 | } 229 | --------------------------------------------------------------------------------