Skip to content

Commit a993a30

Browse files
authored
Merge pull request #7 from Garume/v1.0.0
Version up to 1.0.0
2 parents c1a8f95 + eebec3e commit a993a30

38 files changed

+1121
-458
lines changed

Assets/ContextCircleMenu/Editor/ContextCircleMenuLoader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ private static void Initialize()
5252
{
5353
if (_contextCircleMenu != null) RemovePreviousRadialMenu();
5454
_contextCircleMenu =
55-
new ContextCircleMenu(RadialMenuSize.x, RadialMenuSize.y, _activeSceneView.rootVisualElement);
55+
new ContextCircleMenu(RadialMenuSize.x, RadialMenuSize.y, 100f, _activeSceneView.rootVisualElement);
5656

5757
if (_onBuild == null)
5858
_contextCircleMenu.CreateMenu(builder =>
@@ -69,7 +69,7 @@ private static void Initialize()
6969

7070
_activeSceneView.rootVisualElement.Add(_contextCircleMenu);
7171
}
72-
72+
7373

7474
/// <summary>
7575
/// Event that allows customization of the Context Circle Menu construction.

Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ namespace ContextCircleMenu.Editor
99
public sealed class CircleMenuBuilder
1010
{
1111
private readonly List<ICircleMenuFactory> _factories = new();
12+
private IButtonFactory _buttonFactory;
1213
private IFolderCircleMenuFactory _folderFactory;
13-
private ICircleMenuFactory _rootFactory;
1414

1515
internal CircleMenu Build(IMenuControllable menu)
1616
{
17-
_rootFactory ??= new RootMenuFactory();
1817
_folderFactory ??= new FolderMenuFactory();
18+
_buttonFactory ??= new ButtonFactory();
1919

20-
var root = _rootFactory.Create();
20+
CircleMenu root = _folderFactory.Create(string.Empty, menu, null, _buttonFactory);
2121
foreach (var factory in _factories)
2222
{
2323
var pathSegments = factory.PathSegments.SkipLast(1);
@@ -27,14 +27,17 @@ internal CircleMenu Build(IMenuControllable menu)
2727
var child = currentMenu.Children.Find(m => m.Path == pathSegment);
2828
if (child == null)
2929
{
30-
child = _folderFactory.Create(pathSegment, menu, currentMenu);
30+
child = _folderFactory.Create(pathSegment, menu, currentMenu, _buttonFactory);
31+
var backButton = _buttonFactory.CreateBackButton(menu.Back);
32+
backButton.ShouldCloseMenuAfterSelection = false;
33+
child.PrepareButton(backButton);
3134
currentMenu.Children.Add(child);
3235
}
3336

3437
currentMenu = child;
3538
}
3639

37-
currentMenu.Children.Add(factory.Create());
40+
currentMenu.Children.Add(factory.Create(_buttonFactory));
3841
}
3942

4043
return root;
@@ -78,5 +81,14 @@ public void ConfigureFolder(IFolderCircleMenuFactory factory)
7881
{
7982
_folderFactory = factory;
8083
}
84+
85+
/// <summary>
86+
/// Sets a custom factory for creating menu buttons, allowing for further customization of menu buttons.
87+
/// </summary>
88+
/// <param name="factory">The factory to use for creating menu buttons.</param>
89+
public void ConfigureButton(IButtonFactory factory)
90+
{
91+
_buttonFactory = factory;
92+
}
8193
}
8294
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using System;
2+
using System.Buffers;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using UnityEngine;
6+
using UnityEngine.UIElements;
7+
8+
namespace ContextCircleMenu.Editor
9+
{
10+
/// <summary>
11+
/// Represents a menu item in the circle menu.
12+
/// </summary>
13+
public abstract class CircleMenu
14+
{
15+
protected internal readonly CircleMenu Parent;
16+
17+
private bool _alreadyInitialized;
18+
private IButtonFactory _buttonFactory;
19+
20+
protected CircleButton[] ButtonElements;
21+
protected VisualElement[] UtilityElements;
22+
23+
protected CircleMenu(string path, GUIContent icon, Action onSelected, CircleMenu parent, IButtonFactory factory,
24+
bool shouldCloseMenuAfterSelection = true)
25+
{
26+
Path = path;
27+
Icon = icon;
28+
OnSelected = onSelected;
29+
Parent = parent;
30+
ShouldCloseMenuAfterSelection = shouldCloseMenuAfterSelection;
31+
_buttonFactory = factory;
32+
}
33+
34+
public List<CircleMenu> Children { get; } = new(8);
35+
public GUIContent Icon { get; }
36+
public string Path { get; }
37+
public bool ShouldCloseMenuAfterSelection { get; }
38+
public Action OnSelected { get; protected set; }
39+
40+
internal ReadOnlySpan<VisualElement> BuildElements(ref ContextCircleMenuOption menuOption)
41+
{
42+
if (!_alreadyInitialized)
43+
{
44+
_buttonFactory ??= new ButtonFactory();
45+
var buttons = CreateButtons(_buttonFactory, ref menuOption);
46+
ButtonElements = ButtonElements == null ? buttons : ButtonElements.Concat(buttons).ToArray();
47+
UtilityElements = CreateUtilityElements(ref menuOption);
48+
49+
OnInitialized(ref menuOption);
50+
_alreadyInitialized = true;
51+
}
52+
53+
OnBuild();
54+
55+
var pool = ArrayPool<VisualElement>.Shared;
56+
var buffer = pool.Rent(ButtonElements.Length + UtilityElements.Length);
57+
ButtonElements.CopyTo(buffer, 0);
58+
UtilityElements.CopyTo(buffer, ButtonElements.Length);
59+
var combinedSpan = new Span<VisualElement>(buffer, 0, ButtonElements.Length + UtilityElements.Length);
60+
pool.Return(buffer);
61+
return combinedSpan;
62+
}
63+
64+
65+
internal void PrepareButton(CircleButton button)
66+
{
67+
if (ButtonElements == null)
68+
{
69+
ButtonElements = new[] { button };
70+
}
71+
else
72+
{
73+
Array.Resize(ref ButtonElements, ButtonElements.Length + 1);
74+
ButtonElements[^1] = button;
75+
}
76+
}
77+
78+
/// <summary>
79+
/// Creates the buttons for the menu.
80+
/// </summary>
81+
/// <returns></returns>
82+
protected abstract CircleButton[]
83+
CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption);
84+
85+
/// <summary>
86+
/// Creates the utility elements for the menu.
87+
/// </summary>
88+
/// <returns></returns>
89+
protected virtual VisualElement[] CreateUtilityElements(ref ContextCircleMenuOption menuOption)
90+
{
91+
return Array.Empty<VisualElement>();
92+
}
93+
94+
/// <summary>
95+
/// Called when the menu is initialized.
96+
/// </summary>
97+
/// <param name="menuOption"></param>
98+
protected virtual void OnInitialized(ref ContextCircleMenuOption menuOption)
99+
{
100+
}
101+
102+
/// <summary>
103+
/// Called when the menu is built.
104+
/// </summary>
105+
protected virtual void OnBuild()
106+
{
107+
}
108+
}
109+
}

Assets/ContextCircleMenu/Editor/Core/CircleMenuFactory.cs renamed to Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ public AttributeCircleMenuFactory(ContextCircleMenuAttribute attribute, MethodIn
2424

2525
public IEnumerable<string> PathSegments { get; }
2626

27-
public CircleMenu Create()
27+
public CircleMenu Create(IButtonFactory factory)
2828
{
29-
return new LeafCircleMenu(PathSegments.Last(), _content, () => _method.Invoke(null, null));
29+
return new LeafCircleMenu(PathSegments.Last(), _content, () => _method.Invoke(null, null), factory);
3030
}
3131
}
3232

@@ -44,29 +44,17 @@ public CircleMenuFactory(string path, GUIContent content, Action action)
4444

4545
public IEnumerable<string> PathSegments { get; }
4646

47-
public CircleMenu Create()
47+
public CircleMenu Create(IButtonFactory factory)
4848
{
49-
return new LeafCircleMenu(PathSegments.Last(), _content, _action);
50-
}
51-
}
52-
53-
public class RootMenuFactory : ICircleMenuFactory
54-
{
55-
public IEnumerable<string> PathSegments => null;
56-
57-
public CircleMenu Create()
58-
{
59-
return new RootCircleMenu();
49+
return new LeafCircleMenu(PathSegments.Last(), _content, _action, factory);
6050
}
6151
}
6252

6353
public class FolderMenuFactory : IFolderCircleMenuFactory
6454
{
65-
public int Radius { get; set; } = 100;
66-
67-
public CircleMenu Create(string path, IMenuControllable menu, CircleMenu parent)
55+
public FolderCircleMenu Create(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory)
6856
{
69-
return new FolderCircleMenu(path, menu.Open, menu.Back, parent, Radius);
57+
return new FolderCircleMenu(path, menu, parent, factory);
7058
}
7159
}
7260
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using UnityEditor;
2+
using UnityEngine;
3+
using UnityEngine.UIElements;
4+
5+
namespace ContextCircleMenu.Editor
6+
{
7+
/// <inheritdoc />
8+
public class FolderCircleMenu : CircleMenu
9+
{
10+
private Vector3[] _buttonPositions;
11+
12+
public FolderCircleMenu(string path, IMenuControllable menu,
13+
GUIContent icon,
14+
CircleMenu parent,
15+
IButtonFactory factory) :
16+
base(path, icon, null, parent, factory, false)
17+
{
18+
OnSelected = () => menu.Open(this);
19+
}
20+
21+
internal FolderCircleMenu(string path, IMenuControllable menu,
22+
CircleMenu parent,
23+
IButtonFactory factory) :
24+
this(path, menu, EditorGUIUtility.IconContent(EditorIcons.FolderIcon), parent, factory)
25+
{
26+
}
27+
28+
/// <inheritdoc />
29+
protected override CircleButton[] CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption)
30+
{
31+
var buttons = new CircleButton[Children.Count];
32+
for (var index = 0; index < buttons.Length; index++)
33+
{
34+
var item = Children[index];
35+
var button = factory.Create(
36+
item.Path,
37+
item.Icon,
38+
item.OnSelected,
39+
Children.Count - index);
40+
button.ShouldCloseMenuAfterSelection = item.ShouldCloseMenuAfterSelection;
41+
buttons[index] = button;
42+
}
43+
44+
return buttons;
45+
}
46+
47+
/// <inheritdoc />
48+
protected override VisualElement[] CreateUtilityElements(ref ContextCircleMenuOption menuOption)
49+
{
50+
var label = new Label(Path)
51+
{
52+
style =
53+
{
54+
marginBottom = menuOption.Height * 0.5f + 5.0f,
55+
fontSize = 10,
56+
unityTextAlign = TextAnchor.MiddleCenter,
57+
color = Color.white,
58+
textShadow = new TextShadow
59+
{
60+
offset = new Vector2(0.2f, 0.2f),
61+
blurRadius = 0,
62+
color = Color.black
63+
}
64+
}
65+
};
66+
return new VisualElement[] { label };
67+
}
68+
69+
/// <inheritdoc />
70+
protected override void OnInitialized(ref ContextCircleMenuOption menuOption)
71+
{
72+
var buttonCount = ButtonElements.Length;
73+
_buttonPositions = new Vector3[buttonCount];
74+
for (var i = 0; i < buttonCount; i++)
75+
_buttonPositions[i] = GetPositionForIndex(i, buttonCount, menuOption.Radius);
76+
}
77+
78+
/// <inheritdoc />
79+
protected override void OnBuild()
80+
{
81+
for (var i = 0; i < ButtonElements.Length; i++)
82+
{
83+
var button = ButtonElements[i];
84+
button.transform.position = Vector3.zero;
85+
var to = _buttonPositions[i];
86+
button.experimental.animation.Position(to, 100);
87+
}
88+
}
89+
90+
private Vector3 GetPositionForIndex(float index, float totalCount, float radius)
91+
{
92+
var angle = index / totalCount * 360f;
93+
return new Vector2(
94+
Mathf.Sin(angle * Mathf.Deg2Rad) * radius,
95+
Mathf.Cos(angle * Mathf.Deg2Rad) * radius
96+
);
97+
}
98+
}
99+
}

Assets/ContextCircleMenu/Editor/Core/ICircleMenuFactory.cs renamed to Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ namespace ContextCircleMenu.Editor
55
public interface ICircleMenuFactory
66
{
77
public IEnumerable<string> PathSegments { get; }
8-
public CircleMenu Create();
8+
public CircleMenu Create(IButtonFactory factory);
99
}
1010

1111
public interface IFolderCircleMenuFactory
1212
{
13-
public CircleMenu Create(string path, IMenuControllable menu, CircleMenu parent);
13+
public FolderCircleMenu Create(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory);
1414
}
1515
}

0 commit comments

Comments
 (0)