Instantly share code, notes, and snippets.
-
Star
(62)
62
You must be signed in to star a gist -
Fork
(14)
14
You must be signed in to fork a gist
-
Save alexanderameye/1eef648d7d1699a9548bc2de3dde2a45 to your computer and use it in GitHub Desktop.
/* | |
MIT License | |
Copyright (c) [year] [fullname] | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |
*/ | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using Ameye.EditorUtilities.CircularMenu; | |
using UnityEditor; | |
using UnityEngine; | |
using UnityEngine.UIElements; | |
namespace Ameye.EditorUtilities.Editor.CircularMenu | |
{ | |
[InitializeOnLoad] | |
public class CircularMenu | |
{ | |
// Scene view. | |
private static SceneView _activeSceneView; | |
private static int _activeSceneViewInstanceID; | |
// VisualElements. | |
private static VisualElement _sceneViewRoot; | |
private static VisualElement _radialMenuRoot; | |
// Radial menu. | |
private const int Radius = 100; | |
private static readonly Vector2 RadialMenuSize = new(100, 100); | |
private const KeyCode ActivationShortcutKey = KeyCode.A; | |
private static int _currentlyHoveredSection = -1; | |
private static readonly CircularMenuView RootCircularMenuView = new("root", "", () => { }, null); | |
private static CircularMenuView _activeCircularMenuView; | |
private static double _timeWhenRadialMenuOpened; | |
private static bool RadialMenuIsVisible => _radialMenuRoot?.style.display == DisplayStyle.Flex; | |
// Mouse info. | |
private static Vector2 _mousePositionWhenRadialMenuOpened; | |
private static Vector2 _currentMousePosition; | |
private static float _currentMouseAngle; | |
// Colors | |
private static readonly Color AnnulusColor = new Color(0.02f, 0.02f, 0.02f, 0.8f); | |
private static readonly Color MouseAngleIndicatorBackgroundColor = new Color(0.01f, 0.01f, 0.01f, 1.0f); | |
private static readonly Color MouseAngleIndicatorForegroundColor = Color.white; | |
public class CircularMenuView | |
{ | |
public CircularMenuView Parent; | |
public readonly string Path; | |
public readonly string Icon; | |
public readonly List<CircularMenuView> Children = new(); | |
public readonly Action OnRadialMenuItemSelected; | |
public CircularMenuView(string path, string icon, Action onRadialMenuItemSelected, CircularMenuView parent) | |
{ | |
Path = path; | |
Icon = icon; | |
OnRadialMenuItemSelected = onRadialMenuItemSelected; | |
Parent = parent; | |
} | |
} | |
static CircularMenu() | |
{ | |
EditorApplication.update -= OnEditorApplicationUpdate; | |
EditorApplication.update += OnEditorApplicationUpdate; | |
SceneView.duringSceneGui -= OnDuringSceneGUI; | |
SceneView.duringSceneGui += OnDuringSceneGUI; | |
} | |
private static void OnEditorApplicationUpdate() | |
{ | |
// Get the currently active scene view. | |
_activeSceneView = SceneView.currentDrawingSceneView ? SceneView.currentDrawingSceneView : SceneView.lastActiveSceneView; | |
// Check if the scene view changed. | |
if (_activeSceneView && _activeSceneView.GetInstanceID() != _activeSceneViewInstanceID) | |
{ | |
_activeSceneViewInstanceID = _activeSceneView.GetInstanceID(); | |
RemovePreviousRadialMenu(); | |
} | |
if (_radialMenuRoot is not null || _sceneViewRoot is not null || _activeSceneView is null) return; | |
_sceneViewRoot = _activeSceneView.rootVisualElement; | |
if (_sceneViewRoot is { }) Initialize(); | |
else Debug.LogError("_activeSceneView.rootVisualElement was null"); | |
} | |
private static void Initialize() | |
{ | |
if (_radialMenuRoot is { }) RemovePreviousRadialMenu(); | |
var methods = TypeCache.GetMethodsWithAttribute<CircularMenuAttribute>(); | |
// Create the root VisualElement that holds the radial menu. | |
_radialMenuRoot = new VisualElement | |
{ | |
style = | |
{ | |
position = Position.Absolute, | |
width = RadialMenuSize.x, | |
height = RadialMenuSize.y, | |
display = DisplayStyle.None, // initially hidden | |
marginBottom = 0.0f, | |
marginTop = 0.0f, | |
marginRight = 0.0f, | |
marginLeft = 0.0f, | |
paddingBottom = 0.0f, | |
paddingTop = 0.0f, | |
paddingRight = 0.0f, | |
paddingLeft = 0.0f, | |
alignItems = Align.Center, | |
alignContent = Align.Center, | |
justifyContent = Justify.Center, | |
} | |
}; | |
// Draw the center mouse angle indicator. | |
_radialMenuRoot.generateVisualContent -= DrawMouseAngleIndicator; | |
_radialMenuRoot.generateVisualContent += DrawMouseAngleIndicator; | |
// Create the radial menu for each method. | |
RootCircularMenuView.Children.Clear(); | |
foreach (var method in methods) | |
{ | |
var attribute = (CircularMenuAttribute) method.GetCustomAttributes(typeof(CircularMenuAttribute), false).First(); | |
CreateRadialMenu(attribute.Path, attribute, method); | |
} | |
_activeCircularMenuView = RootCircularMenuView; | |
// Add the radial menu root to the scene view root. | |
_sceneViewRoot.Add(_radialMenuRoot); | |
} | |
private static void ShowRadialMenu(Vector2 position) | |
{ | |
if (_radialMenuRoot is null) return; | |
_radialMenuRoot.style.display = _radialMenuRoot.style.display == DisplayStyle.None ? DisplayStyle.Flex : DisplayStyle.None; | |
_radialMenuRoot.transform.position = position - new Vector2(RadialMenuSize.x * 0.5f, RadialMenuSize.y * 0.5f); | |
RebuildRadialMenu(); | |
_activeSceneView.Repaint(); | |
} | |
private static void RebuildRadialMenu() | |
{ | |
// Remove all child elements. | |
_radialMenuRoot.Clear(); | |
// If the radial menu item has a parent. | |
if (_activeCircularMenuView.Parent is { }) | |
{ | |
// Add a label showing the current folder. | |
_radialMenuRoot.Add(new Label(_activeCircularMenuView.Path) | |
{ | |
style = | |
{ | |
marginBottom = RadialMenuSize.x * 0.5f + 5.0f, | |
fontSize = 10, | |
unityTextAlign = TextAnchor.MiddleCenter, | |
color = Color.white, | |
textShadow = new TextShadow | |
{ | |
offset = new Vector2(0.2f, 0.2f), | |
blurRadius = 0, | |
color = Color.black | |
} | |
} | |
}); | |
// Add back button. | |
_radialMenuRoot.Add(new CircularMenuButton("Back", "", -1, () => SelectRadialMenuItem(_activeCircularMenuView.Parent))); | |
} | |
// If the menu item does not have a parent, | |
else _radialMenuRoot.Add(new Label("")); // HACKY | |
// Add a button for each child of the radial menu item. | |
var section = 1; | |
foreach (var item in _activeCircularMenuView.Children) | |
{ | |
_radialMenuRoot.Add(new CircularMenuButton( | |
item.Children.Count > 0 ? item.Path + "" : item.Path, | |
item.Icon, | |
_activeCircularMenuView.Children.Count - section, | |
item.OnRadialMenuItemSelected)); | |
section++; | |
} | |
// Move all buttons outwards using an animation. | |
var i = 0; | |
foreach (var item in _radialMenuRoot.Children().Where(c => c is CircularMenuButton)) | |
{ | |
item.transform.position = Vector3.zero; | |
var targetPosition = Vector2.zero + GetCircleOffset(Radius, i, _radialMenuRoot.childCount - 1); | |
item.experimental.animation.Position(targetPosition, 100); | |
i++; | |
} | |
} | |
private static void OnDuringSceneGUI(SceneView view) | |
{ | |
var currentEvent = Event.current; | |
switch (currentEvent.type) | |
{ | |
case EventType.Repaint: | |
_currentMousePosition = currentEvent.mousePosition; | |
break; | |
// Allow clicking the left mouse button to move the location of the radial menu. | |
case EventType.MouseDown when (currentEvent.button == 0 && RadialMenuIsVisible): | |
ShowRadialMenu(_currentMousePosition); | |
break; | |
// Show the radial menu when the activation shortcut is pressed and store the initial mouse position. | |
case EventType.KeyDown when (Event.current.keyCode == ActivationShortcutKey && !RadialMenuIsVisible): | |
_timeWhenRadialMenuOpened = EditorApplication.timeSinceStartup; | |
_mousePositionWhenRadialMenuOpened = _currentMousePosition; | |
ShowRadialMenu(_currentMousePosition); | |
break; | |
// Update the radial menu when moving the mouse. | |
case EventType.MouseMove when RadialMenuIsVisible: | |
{ | |
// Calculate the offset angle. | |
var referenceVector = new Vector2(0.0f, -1.0f); | |
var mouseVector = new Vector2( | |
_currentMousePosition.x - _mousePositionWhenRadialMenuOpened.x, | |
-_currentMousePosition.y + _mousePositionWhenRadialMenuOpened.y).normalized; | |
var angle = (float) (Math.Atan2(referenceVector.y, referenceVector.x) - Math.Atan2(mouseVector.y, mouseVector.x)) * (float) (180 / Math.PI); | |
if (angle < 0) angle += 360.0f; | |
_currentMouseAngle = angle; | |
// Calculate which section is being hovered over. | |
var sectionCount = _activeCircularMenuView.Children.Count; | |
if (_activeCircularMenuView.Parent is { }) sectionCount++; // back button | |
var sectionPartAngle = 360.0f / sectionCount; // the part of a single section | |
int hoveredSection; | |
// HACKY: This code is kind of hacky and was a little bit of trial and error. | |
if (_activeCircularMenuView.Parent is { }) hoveredSection = (Mathf.RoundToInt(angle / sectionPartAngle)) % sectionCount - 1; | |
else hoveredSection = (Mathf.RoundToInt(angle / sectionPartAngle) + sectionCount - 1) % sectionCount; | |
// If we moved the mouse to a new section. | |
if (hoveredSection != _currentlyHoveredSection) | |
{ | |
// Go through all the buttons | |
var buttons = _radialMenuRoot.Children().Where(child => child is CircularMenuButton).ToList().Select(e => e as CircularMenuButton); | |
foreach (var button in buttons) button?.Hover(button.Section == hoveredSection); | |
_currentlyHoveredSection = hoveredSection; | |
} | |
_radialMenuRoot.MarkDirtyRepaint(); | |
break; | |
} | |
// Select a radial menu item when the activation shortcut is released while the radial menu is visible. | |
case EventType.KeyUp when Event.current.keyCode == ActivationShortcutKey && RadialMenuIsVisible: | |
{ | |
if (EditorApplication.timeSinceStartup - _timeWhenRadialMenuOpened > 0.2) | |
{ | |
// Calculate the distance that the mouse has moved. | |
var mouseMoveDistance = Vector3.Distance( | |
EditorGUIUtility.PixelsToPoints(_mousePositionWhenRadialMenuOpened), | |
EditorGUIUtility.PixelsToPoints(_currentMousePosition)); | |
// Require a minimum mouse move distance before a selection is triggered. | |
if (mouseMoveDistance > 15) | |
{ | |
if (_currentlyHoveredSection == -1 && _activeCircularMenuView.Parent != null) SelectRadialMenuItem(_activeCircularMenuView.Parent); // back button | |
else _activeCircularMenuView.Children[_activeCircularMenuView.Children.Count - _currentlyHoveredSection - 1].OnRadialMenuItemSelected(); | |
} | |
else ShowRadialMenu(_currentMousePosition); | |
} | |
break; | |
} | |
} | |
} | |
private static void RemovePreviousRadialMenu() | |
{ | |
if (_radialMenuRoot is null) return; | |
_radialMenuRoot.RemoveFromHierarchy(); | |
_radialMenuRoot = null; | |
} | |
private static Vector2 GetCircleOffset(float radius, float index, float numberOfSections) | |
{ | |
var angle = index / numberOfSections * 360.0f; | |
var offset = new Vector2 | |
{ | |
x = radius * Mathf.Sin(angle * Mathf.Deg2Rad), | |
y = radius * Mathf.Cos(angle * Mathf.Deg2Rad), | |
}; | |
return offset; | |
} | |
private static void DrawMouseAngleIndicator(MeshGenerationContext context) | |
{ | |
var position = new Vector2(RadialMenuSize.x * 0.5f, RadialMenuSize.y * 0.5f); | |
var radius = RadialMenuSize.x * 0.1f; | |
const float indicatorSizeDegrees = 70.0f; | |
var painter = context.painter2D; | |
painter.lineCap = LineCap.Butt; | |
// Draw the annulus. | |
painter.lineWidth = 8.0f; | |
painter.strokeColor = AnnulusColor; | |
painter.BeginPath(); | |
painter.Arc(new Vector2(position.x, position.y), radius, 0.0f, 360.0f); | |
painter.Stroke(); | |
// Draw the mouse angle indicator background. | |
painter.lineWidth = 8.0f; | |
painter.strokeColor = MouseAngleIndicatorBackgroundColor; | |
painter.BeginPath(); | |
painter.Arc(new Vector2(position.x, position.y), radius, _currentMouseAngle + 90.0f - indicatorSizeDegrees * 0.5f, | |
_currentMouseAngle + 90.0f + indicatorSizeDegrees * 0.5f); | |
painter.Stroke(); | |
// Draw the mouse angle indicator. | |
painter.lineWidth = 4.0f; | |
painter.strokeColor = MouseAngleIndicatorForegroundColor; | |
painter.BeginPath(); | |
painter.Arc(new Vector2(position.x, position.y), radius, _currentMouseAngle + 90.0f - indicatorSizeDegrees * 0.5f, | |
_currentMouseAngle + 90.0f + indicatorSizeDegrees * 0.5f); | |
painter.Stroke(); | |
} | |
private static void CreateRadialMenu(string path, CircularMenuAttribute attribute, MethodInfo method) | |
{ | |
var pathSegments = path.Split('/'); | |
// Create the root radial menu view. | |
var rootRadialMenuView = RootCircularMenuView; | |
// Create the branch radial menus views. | |
if (pathSegments.Length > 1) | |
{ | |
for (var i = 0; i < pathSegments.Length - 1; i++) | |
{ | |
var pathSegment = pathSegments[i]; | |
// Look for an existing radial menu view with the same path. | |
var branchRadialMenuView = rootRadialMenuView.Children.Find(x => x.Path == pathSegment); | |
// Create a new one if it does not exist yet. | |
if (branchRadialMenuView is null) | |
{ | |
branchRadialMenuView = new CircularMenuView(pathSegment, "d_Folder Icon", () => SelectRadialMenuItem(branchRadialMenuView), rootRadialMenuView); | |
RootCircularMenuView.Children.Add(branchRadialMenuView); | |
} | |
rootRadialMenuView = branchRadialMenuView; | |
} | |
} | |
// Create the leaf radial menu view. | |
var leafRadialMenuView = new CircularMenuView(pathSegments.Last(), attribute.Icon, () => | |
{ | |
ShowRadialMenu(_mousePositionWhenRadialMenuOpened); | |
// Invoke the method that is linked to this leaf radial menu view. | |
method.Invoke(null, null); | |
}, null); | |
rootRadialMenuView.Children.Add(leafRadialMenuView); | |
} | |
private static void SelectRadialMenuItem(CircularMenuView circularMenuView) | |
{ | |
_activeCircularMenuView = circularMenuView; | |
RebuildRadialMenu(); | |
} | |
} | |
} |
/* | |
MIT License | |
Copyright (c) [year] [fullname] | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |
*/ | |
using System; | |
namespace Ameye.EditorUtilities.CircularMenu | |
{ | |
[AttributeUsage(AttributeTargets.Method)] | |
public class CircularMenuAttribute : Attribute | |
{ | |
public string Path; | |
public string Icon; | |
public CircularMenuAttribute() { } | |
public CircularMenuAttribute(string path, string icon) | |
{ | |
Path = path; | |
Icon = icon; | |
} | |
} | |
} |
/* | |
MIT License | |
Copyright (c) [year] [fullname] | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |
*/ | |
using System; | |
using UnityEditor; | |
using UnityEngine; | |
using UnityEngine.UIElements; | |
namespace Ameye.EditorUtilities.Editor.CircularMenu | |
{ | |
public sealed class CircularMenuButton : VisualElement | |
{ | |
public new class UxmlFactory : UxmlFactory<CircularMenuButton, UxmlTraits> | |
{ | |
} | |
public new class UxmlTraits : BindableElement.UxmlTraits | |
{ | |
} | |
public CircularMenuButton() | |
{ | |
} | |
private readonly Button button; | |
public readonly int Section; | |
public CircularMenuButton(string text, string icon, int section, Action clickEvent) | |
{ | |
Section = section; | |
style.position = Position.Absolute; | |
style.alignItems = Align.Center; | |
button = new Button(clickEvent) | |
{ | |
style = | |
{ | |
paddingLeft = 8, | |
paddingRight = 8, | |
paddingTop = 4, | |
paddingBottom = 4, | |
flexDirection = FlexDirection.Row, | |
borderTopLeftRadius = 4.0f, | |
borderBottomLeftRadius = 4.0f, | |
borderBottomRightRadius = 4.0f, | |
borderTopRightRadius = 4.0f, | |
flexGrow = 1, | |
backgroundColor = new Color(0.02f, 0.02f, 0.02f, 0.8f) | |
}, | |
text = "" | |
}; | |
var label = new Label | |
{ | |
style = | |
{ | |
paddingBottom = 0.0f, | |
paddingLeft = 0.0f, | |
paddingRight = 0.0f, | |
paddingTop = 0.0f, | |
marginLeft = 5.0f, | |
marginRight = 5.0f, | |
flexGrow = 1, | |
}, | |
text = text | |
}; | |
if (icon != "") | |
{ | |
var image = new Image | |
{ | |
image = EditorGUIUtility.IconContent(icon).image, | |
style = | |
{ | |
width = 16.0f, | |
height = 16.0f, | |
flexShrink = 0 | |
} | |
}; | |
button.Add(image); | |
} | |
button.Add(label); | |
if (section != -1) | |
{ | |
var index = new Label | |
{ | |
text = section.ToString(), | |
style = | |
{ | |
color = new Color(0.7f, 0.7f, 0.7f, 1.0f), | |
unityFontStyleAndWeight = FontStyle.Italic | |
} | |
}; | |
button.Add(index); | |
} | |
Add(button); | |
} | |
public void Hover(bool active) | |
{ | |
button.style.backgroundColor = active ? new Color(0.2745098f, 0.3764706f, 0.4862745f, 1.0f) : new Color(0.02f, 0.02f, 0.02f, 0.8f); | |
} | |
} | |
} |
Awesome tool.
after writing some tools with it, I encountered this error:
NullReferenceException: Object reference not set to an instance of an object Ameye.EditorUtilities.Editor.CircularMenu.CircularMenu.OnDuringSceneGUI (UnityEditor.SceneView view) (at Assets/Editor/CircularMenu/CircularMenu.cs:229)
maybe I'll dig in and try to fix it.
Awesome tool. after writing some tools with it, I encountered this error:
NullReferenceException: Object reference not set to an instance of an object Ameye.EditorUtilities.Editor.CircularMenu.CircularMenu.OnDuringSceneGUI (UnityEditor.SceneView view) (at Assets/Editor/CircularMenu/CircularMenu.cs:229)
maybe I'll dig in and try to fix it.
I think I've had that issue once as well. Maybe you could add a null check for '_activeCircularMenuView' or '_activeCircularMenuView.Children' but that seems like a bandaid instead of knowing the root of the problem. Will investigate as well 😄
Could you explicitly specify the license, please?
Awesome tool. after writing some tools with it, I encountered this error:
NullReferenceException: Object reference not set to an instance of an object Ameye.EditorUtilities.Editor.CircularMenu.CircularMenu.OnDuringSceneGUI (UnityEditor.SceneView view) (at Assets/Editor/CircularMenu/CircularMenu.cs:229)
maybe I'll dig in and try to fix it.I think I've had that issue once as well. Maybe you could add a null check for '_activeCircularMenuView' or '_activeCircularMenuView.Children' but that seems like a bandaid instead of knowing the root of the problem. Will investigate as well 😄
The error is on line 262 in CircularMenu.cs file. Add a null check for _activeCircularMenuView.Parent
Could you explicitly specify the license, please?
☺️
Added MIT license! Apologies for the delayed response.
Awesome tool. after writing some tools with it, I encountered this error:
NullReferenceException: Object reference not set to an instance of an object Ameye.EditorUtilities.Editor.CircularMenu.CircularMenu.OnDuringSceneGUI (UnityEditor.SceneView view) (at Assets/Editor/CircularMenu/CircularMenu.cs:229)
maybe I'll dig in and try to fix it.I think I've had that issue once as well. Maybe you could add a null check for '_activeCircularMenuView' or '_activeCircularMenuView.Children' but that seems like a bandaid instead of knowing the root of the problem. Will investigate as well 😄
The error is on line 262 in CircularMenu.cs file. Add a null check for _activeCircularMenuView.Parent
Changed! thanks
Gorgeous! Gonna be playing around with this a bunch🥰