Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.0.1 #18

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
63 changes: 63 additions & 0 deletions Assets/ContextCircleMenu/Editor/Core/CircleMenuAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using UnityEngine;

namespace ContextCircleMenu.Editor
{
public class CircleMenuAction
{
[Flags]
public enum Status
{
Normal,
Disabled
}

private readonly Func<CircleMenuEventInformation, Status> _actionStatusCallback;

private CircleMenuEventInformation _information;

public CircleMenuAction(string path, Action<CircleMenuEventInformation> action,
Func<CircleMenuEventInformation, Status> status = null, GUIContent icon = null)
{
Path = path;
ActionName = path.Split("/")[^1];
ActionCallback = action;
_actionStatusCallback = status;
Icon = icon;
}

public CircleMenuAction(string path, Action<CircleMenuEventInformation> action, GUIContent icon)
: this(path, action, AlwaysEnabled, icon)
{
}

public string Path { get; }
public string ActionName { get; set; }
public Action<CircleMenuEventInformation> ActionCallback { get; internal set; }
public GUIContent Icon { get; set; }
public Status CurrentStatus { get; private set; }

public void UpdateStatus(CircleMenuEventInformation information)
{
_information = information;
CurrentStatus = _actionStatusCallback?.Invoke(information) ?? Status.Disabled;
}

public void Execute()
{
if (CurrentStatus == Status.Disabled)
return;
ActionCallback?.Invoke(_information);
}

public static Status AlwaysEnabled(CircleMenuEventInformation _)
{
return Status.Normal;
}

public static Status AlwaysDisabled(CircleMenuEventInformation _)
{
return Status.Disabled;
}
}
}
3 changes: 3 additions & 0 deletions Assets/ContextCircleMenu/Editor/Core/CircleMenuAction.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 52 additions & 3 deletions Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,59 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;

namespace ContextCircleMenu.Editor
{
public sealed class CircleMenuBuilder
{
private readonly List<ICircleMenuFactory> _factories = new();
private Func<string> _basePathCallback;
private IButtonFactory _buttonFactory;

private IFolderCircleMenuFactory _folderFactory;

internal CircleMenu Build(IMenuControllable menu)
{
_folderFactory ??= new FolderMenuFactory();
_buttonFactory ??= new ButtonFactory();

var basePath = _basePathCallback?.Invoke();

CircleMenu root = _folderFactory.Create(string.Empty, menu, null, _buttonFactory);
foreach (var factory in _factories)
{
if (factory is FilteredCircleMenuFactory filteredFactory)
{
if (!filteredFactory.Filter())
continue;
}

var pathSegments = factory.PathSegments.SkipLast(1);

if (!string.IsNullOrEmpty(basePath))
{
var basePathSegments = basePath.Split('/');
if (pathSegments.Take(basePathSegments.Length).SequenceEqual(basePathSegments))
{
pathSegments = pathSegments.Skip(basePathSegments.Length);
}else
{
continue;
}
}

var currentMenu = root;
foreach (var pathSegment in pathSegments)
{
var child = currentMenu.Children.Find(m => m.Path == pathSegment);
var child = currentMenu.Children.Find(m => m.MenuAction.Path == pathSegment);
if (child == null)
{
child = _folderFactory.Create(pathSegment, menu, currentMenu, _buttonFactory);
var backButton = _buttonFactory.CreateBackButton(menu.Back);
var backMenuAction = new CircleMenuAction(pathSegment, _ => menu.Back(),
_ => CircleMenuAction.Status.Normal, EditorGUIUtility.IconContent(EditorIcons.Back2x));
var backButton = _buttonFactory.CreateBackButton(backMenuAction, -1);
backButton.ShouldCloseMenuAfterSelection = false;
child.PrepareButton(backButton);
currentMenu.Children.Add(child);
Expand Down Expand Up @@ -61,7 +87,24 @@ public void AddMenu(ContextCircleMenuAttribute attribute, MethodInfo method)
/// <param name="action"></param>
public void AddMenu(string path, GUIContent content, Action action)
{
AddMenu(new CircleMenuFactory(path, content, action));
var circleMenuAction = new CircleMenuAction(path, _ => action(), content);
AddMenu(new CircleMenuFactory(circleMenuAction));
}

public void AddMenu(string path, Action<CircleMenuEventInformation> action,
Func<CircleMenuEventInformation, CircleMenuAction.Status> statusCallback = null,
GUIContent content = null)
{
var circleMenuAction = new CircleMenuAction(path, action, statusCallback, content);
AddMenu(new CircleMenuFactory(circleMenuAction));
}

public void AddMenuWithFilter(string path, Action<CircleMenuEventInformation> action, Func<bool> filter,
Func<CircleMenuEventInformation, CircleMenuAction.Status> statusCallback = null,
GUIContent content = null)
{
var circleMenuAction = new CircleMenuAction(path, action, statusCallback, content);
AddMenu(new FilteredCircleMenuFactory(circleMenuAction, filter));
}

/// <summary>
Expand Down Expand Up @@ -90,5 +133,11 @@ public void ConfigureButton(IButtonFactory factory)
{
_buttonFactory = factory;
}

public void ConfigureBasePath(Func<string> basePathCallback)
{
_basePathCallback = basePathCallback;
}

}
}
16 changes: 16 additions & 0 deletions Assets/ContextCircleMenu/Editor/Core/CircleMenuEventInformation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using UnityEngine;

namespace ContextCircleMenu.Editor
{
public struct CircleMenuEventInformation
{
public CircleMenuEventInformation(Vector2 mousePosition, Vector2 position)
{
MousePosition = mousePosition;
Position = position;
}

public Vector2 MousePosition { get; }
public Vector2 Position { get; }
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 10 additions & 11 deletions Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements;

namespace ContextCircleMenu.Editor
Expand All @@ -20,24 +19,21 @@ public abstract class CircleMenu
protected CircleButton[] ButtonElements;
protected VisualElement[] UtilityElements;

protected CircleMenu(string path, GUIContent icon, Action onSelected, CircleMenu parent, IButtonFactory factory,
bool shouldCloseMenuAfterSelection = true)
protected CircleMenu(CircleMenuAction menuAction, IButtonFactory factory,
CircleMenu parent = null, bool shouldCloseMenuAfterSelection = true)
{
Path = path;
Icon = icon;
OnSelected = onSelected;
MenuAction = menuAction;
_buttonFactory = factory;
Parent = parent;
ShouldCloseMenuAfterSelection = shouldCloseMenuAfterSelection;
_buttonFactory = factory;
}

internal CircleMenuAction MenuAction { get; private set; }
public List<CircleMenu> Children { get; } = new(8);
public GUIContent Icon { get; }
public string Path { get; }
public bool ShouldCloseMenuAfterSelection { get; }
public Action OnSelected { get; protected set; }

internal ReadOnlySpan<VisualElement> BuildElements(ref ContextCircleMenuOption menuOption)
internal ReadOnlySpan<VisualElement> BuildElements(CircleMenuEventInformation information,
ref ContextCircleMenuOption menuOption)
{
if (!_alreadyInitialized)
{
Expand All @@ -50,6 +46,9 @@ internal ReadOnlySpan<VisualElement> BuildElements(ref ContextCircleMenuOption m
_alreadyInitialized = true;
}

var buttonSpan = ButtonElements.AsSpan();
for (var i = 0; i < buttonSpan.Length; i++) buttonSpan[i].UpdateStatus(information);

OnBuild();

var pool = ArrayPool<VisualElement>.Shared;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,55 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;

namespace ContextCircleMenu.Editor
{
public class AttributeCircleMenuFactory : ICircleMenuFactory
public class CircleMenuFactory : ICircleMenuFactory
{
private readonly GUIContent _content;
private readonly MethodInfo _method;
private readonly CircleMenuAction _circleMenuAction;

public AttributeCircleMenuFactory(ContextCircleMenuAttribute attribute, MethodInfo method)
public CircleMenuFactory(CircleMenuAction circleMenuAction)
{
PathSegments = attribute.Path.Split("/");
_method = method;

_content = !string.IsNullOrEmpty(attribute.IconPath)
? EditorGUIUtility.IconContent(attribute.IconPath)
: default;
PathSegments = circleMenuAction.Path.Split("/");
_circleMenuAction = circleMenuAction;
}

public IEnumerable<string> PathSegments { get; }

public CircleMenu Create(IButtonFactory factory)
{
return new LeafCircleMenu(PathSegments.Last(), _content, () => _method.Invoke(null, null), factory);
return new LeafCircleMenu(_circleMenuAction, factory);
}
}

public class CircleMenuFactory : ICircleMenuFactory
public class AttributeCircleMenuFactory : ICircleMenuFactory
{
private readonly Action _action;
private readonly GUIContent _content;
private readonly CircleMenuAction _circleMenuAction;

public CircleMenuFactory(string path, GUIContent content, Action action)
public AttributeCircleMenuFactory(ContextCircleMenuAttribute attribute, MethodInfo method)
{
PathSegments = path.Split("/");
_content = content;
_action = action;
PathSegments = attribute.Path.Split("/");

var icon = default(GUIContent);

if (!string.IsNullOrEmpty(attribute.IconPath))
icon = EditorGUIUtility.IconContent(attribute.IconPath);

_circleMenuAction =
new CircleMenuAction(attribute.Path,
action =>
method.Invoke(null, method.GetParameters().Length == 0 ? null : new object[] { action }),
icon);
}

public IEnumerable<string> PathSegments { get; }

public CircleMenu Create(IButtonFactory factory)
{
return new LeafCircleMenu(PathSegments.Last(), _content, _action, factory);
return new LeafCircleMenu(_circleMenuAction, factory);
}
}

public class FilteredCircleMenuFactory : CircleMenuFactory
{
public FilteredCircleMenuFactory(CircleMenuAction circleMenuAction, Func<bool> filter) : base(
circleMenuAction)
{
Filter = filter;
}

public Func<bool> Filter { get; }
}

public class FolderMenuFactory : IFolderCircleMenuFactory
{
public FolderCircleMenu Create(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,11 @@ public class FolderCircleMenu : CircleMenu
{
private Vector3[] _buttonPositions;

public FolderCircleMenu(string path, IMenuControllable menu,
GUIContent icon,
CircleMenu parent,
IButtonFactory factory) :
base(path, icon, null, parent, factory, false)
{
OnSelected = () => menu.Open(this);
}

internal FolderCircleMenu(string path, IMenuControllable menu,
CircleMenu parent,
IButtonFactory factory) :
this(path, menu, EditorGUIUtility.IconContent(EditorIcons.FolderIcon), parent, factory)
public FolderCircleMenu(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory)
: base(new CircleMenuAction(path, null, EditorGUIUtility.IconContent(EditorIcons.FolderIcon))
, factory, parent, false)
{
MenuAction.ActionCallback = _ => menu.Open(this);
}

/// <inheritdoc />
Expand All @@ -32,11 +23,7 @@ protected override CircleButton[] CreateButtons(IButtonFactory factory, ref Cont
for (var index = 0; index < buttons.Length; index++)
{
var item = Children[index];
var button = factory.Create(
item.Path,
item.Icon,
item.OnSelected,
Children.Count - index);
var button = factory.Create(item.MenuAction, Children.Count - index);
button.ShouldCloseMenuAfterSelection = item.ShouldCloseMenuAfterSelection;
buttons[index] = button;
}
Expand All @@ -47,7 +34,7 @@ protected override CircleButton[] CreateButtons(IButtonFactory factory, ref Cont
/// <inheritdoc />
protected override VisualElement[] CreateUtilityElements(ref ContextCircleMenuOption menuOption)
{
var label = new Label(Path)
var label = new Label(MenuAction.ActionName)
{
style =
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
using System;
using UnityEngine;

namespace ContextCircleMenu.Editor
{
/// <inheritdoc />
public sealed class LeafCircleMenu : CircleMenu
{
public LeafCircleMenu(string path, GUIContent icon, Action onSelected, IButtonFactory factory,
CircleMenu parent = null) : base(path, icon, onSelected, parent, factory)
public LeafCircleMenu(CircleMenuAction action, IButtonFactory factory) : base(action, factory)
{
}

Expand Down
Loading