From 819e2bc16d687f5b45a880baf0e6ee4aca59dea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Tue, 14 May 2024 10:21:35 +0900 Subject: [PATCH 01/19] =?UTF-8?q?[fix]=20regster=20notrickedown=20?= =?UTF-8?q?=E2=9E=A1=20trickledown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs index 3040191..80fce04 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs @@ -111,15 +111,15 @@ public void Back() private void OnAttach(AttachToPanelEvent evt) { generateVisualContent += OnGenerateVisualContent; - _target.RegisterCallback(UpdateMousePosition); - _target.RegisterCallback(OnClick); + _target.RegisterCallback(UpdateMousePosition, TrickleDown.TrickleDown); + _target.RegisterCallback(OnClick, TrickleDown.TrickleDown); } private void OnDetach(DetachFromPanelEvent evt) { generateVisualContent -= OnGenerateVisualContent; - _target.UnregisterCallback(UpdateMousePosition); - _target.UnregisterCallback(OnClick); + _target.UnregisterCallback(UpdateMousePosition, TrickleDown.TrickleDown); + _target.UnregisterCallback(OnClick, TrickleDown.TrickleDown); } private void UpdateMousePosition(MouseMoveEvent evt) From 4669417016ab60fab50c8ee3bb9a1d304c2a66e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Tue, 14 May 2024 10:22:39 +0900 Subject: [PATCH 02/19] [add] button factory --- .../Core/ClircleButtons/ButtonFactory.cs | 14 +++ .../Core/ClircleButtons/ButtonFactory.cs.meta | 3 + .../Core/ClircleButtons/CircleButton.cs | 64 +++++++++++++ .../Core/ClircleButtons/CircleButton.cs.meta | 3 + .../Core/ClircleButtons/IButtonFactory.cs | 11 +++ .../ClircleButtons/IButtonFactory.cs.meta | 3 + .../Core/ClircleButtons/SimpleCircleButton.cs | 89 +++++++++++++++++++ .../ClircleButtons/SimpleCircleButton.cs.meta | 3 + 8 files changed, 190 insertions(+) create mode 100644 Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs create mode 100644 Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs.meta create mode 100644 Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs create mode 100644 Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs.meta create mode 100644 Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs create mode 100644 Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs.meta create mode 100644 Assets/ContextCircleMenu/Editor/Core/ClircleButtons/SimpleCircleButton.cs create mode 100644 Assets/ContextCircleMenu/Editor/Core/ClircleButtons/SimpleCircleButton.cs.meta diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs new file mode 100644 index 0000000..739c073 --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs @@ -0,0 +1,14 @@ +using System; +using UnityEngine; + +namespace ContextCircleMenu.Editor +{ + public class ButtonFactory : IButtonFactory + { + public CircleButton Create(string path, GUIContent icon, Action onSelected, int section, + bool shouldCloseMenuAfterSelection) + { + return new SimpleCircleButton(path, icon, section, onSelected, shouldCloseMenuAfterSelection); + } + } +} \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs.meta b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs.meta new file mode 100644 index 0000000..35a9782 --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 925ee182e9d94ae792d656e54ed4231f +timeCreated: 1715646161 \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs new file mode 100644 index 0000000..1a24eee --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs @@ -0,0 +1,64 @@ +using System; +using UnityEngine; +using UnityEngine.UIElements; + +namespace ContextCircleMenu.Editor +{ + public abstract class CircleButton : VisualElement + { + private readonly Action _onSelect; + private readonly bool _shouldCloseMenuAfterSelect; + private Button _button; + public bool IsEntered; + + protected CircleButton(string text, GUIContent icon, int section, Action onSelect, + bool shouldCloseMenuAfterSelect) + { + _onSelect = onSelect; + _shouldCloseMenuAfterSelect = shouldCloseMenuAfterSelect; + + Initialize(text, icon, section); + } + + private void Initialize(string text, GUIContent icon, int section) + { + style.position = Position.Absolute; + style.alignItems = Align.Center; + + _button = new Button(_onSelect); + ModifierButton(_button, text, icon, section); + Add(_button); + + RegisterCallback(InternalOnMouseEnter); + RegisterCallback(InternalOnMouseLeave); + } + + protected abstract void ModifierButton(Button button, string text, GUIContent icon, int section); + + internal bool TryForceSelect() + { + _onSelect?.Invoke(); + return _shouldCloseMenuAfterSelect; + } + + private void InternalOnMouseEnter(MouseEnterEvent evt) + { + IsEntered = true; + OnMouseEnter(_button, evt); + } + + protected virtual void OnMouseEnter(Button button, MouseEnterEvent evt) + { + } + + private void InternalOnMouseLeave(MouseLeaveEvent evt) + { + IsEntered = false; + OnMouseLeave(_button, evt); + } + + protected virtual void OnMouseLeave(Button button, MouseLeaveEvent evt) + { + } + } +} \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs.meta b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs.meta new file mode 100644 index 0000000..9c2a442 --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1a4acd60cf754149b9b197a9d2c9b490 +timeCreated: 1713720243 \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs new file mode 100644 index 0000000..ae03711 --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs @@ -0,0 +1,11 @@ +using System; +using UnityEngine; + +namespace ContextCircleMenu.Editor +{ + public interface IButtonFactory + { + public CircleButton Create(string path, GUIContent icon, Action onSelected, int section, + bool shouldCloseMenuAfterSelection); + } +} \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs.meta b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs.meta new file mode 100644 index 0000000..99db266 --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bd92d51777ad4b77957c94541043b5f0 +timeCreated: 1715649254 \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/SimpleCircleButton.cs b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/SimpleCircleButton.cs new file mode 100644 index 0000000..2e2533b --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/SimpleCircleButton.cs @@ -0,0 +1,89 @@ +using System; +using UnityEngine; +using UnityEngine.UIElements; + +namespace ContextCircleMenu.Editor +{ + public class SimpleCircleButton : CircleButton + { + private readonly Color _hoverColor = new(0.2745098f, 0.3764706f, 0.4862745f, 1.0f); + private readonly Color _normalColor = new(0.02f, 0.02f, 0.02f, 0.8f); + + public SimpleCircleButton(string text, GUIContent icon, int section, Action onSelect, + bool shouldCloseMenuAfterSelect = true) : base(text, icon, section, onSelect, shouldCloseMenuAfterSelect) + { + } + + protected override void ModifierButton(Button button, string text, GUIContent icon, int section) + { + button.style.paddingLeft = 8f; + button.style.paddingRight = 8f; + button.style.paddingTop = 4f; + button.style.paddingBottom = 4f; + button.style.flexDirection = FlexDirection.Row; + button.style.borderTopLeftRadius = 4f; + button.style.borderBottomLeftRadius = 4f; + button.style.borderBottomRightRadius = 4f; + button.style.borderTopRightRadius = 4f; + button.style.flexGrow = 1; + button.style.backgroundColor = _normalColor; + button.text = ""; + + var label = new Label + { + style = + { + paddingBottom = 0f, + paddingLeft = 0f, + paddingRight = 0f, + paddingTop = 0f, + marginLeft = 5f, + marginRight = 5f, + flexGrow = 1 + }, + text = text + }; + + if (icon != null) + { + var image = new Image + { + image = icon.image, + style = + { + width = 16f, + height = 16f, + 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); + } + } + + protected override void OnMouseEnter(Button button, MouseEnterEvent evt) + { + button.style.backgroundColor = _hoverColor; + } + + protected override void OnMouseLeave(Button button, MouseLeaveEvent evt) + { + button.style.backgroundColor = _normalColor; + } + } +} \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/SimpleCircleButton.cs.meta b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/SimpleCircleButton.cs.meta new file mode 100644 index 0000000..bafc407 --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/SimpleCircleButton.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 59172d3eb1194cdd992880de8aa1bdb2 +timeCreated: 1715644710 \ No newline at end of file From 7042f034dfeca9c7038e5b73f5279bde790c0cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Tue, 14 May 2024 10:23:11 +0900 Subject: [PATCH 03/19] [add] support button factory to circle menu --- .../CircleMenu.cs | 14 ++-- .../CircleMenu.cs.meta | 0 .../Core/CircleMenus/CircleMenuFactory.cs | 72 +++++++++++++++++++ .../CircleMenus/CircleMenuFactory.cs.meta | 3 + .../FolderCircleMenu.cs | 11 +-- .../FolderCircleMenu.cs.meta | 0 .../Core/CircleMenus/ICircleMenuFactory.cs | 15 ++++ .../CircleMenus/ICircleMenuFactory.cs.meta | 3 + .../LeafCircleMenu.cs | 7 +- .../LeafCircleMenu.cs.meta | 0 .../RootCircleMenu.cs | 8 +-- .../RootCircleMenu.cs.meta | 0 12 files changed, 116 insertions(+), 17 deletions(-) rename Assets/ContextCircleMenu/Editor/Core/{CircularMenus => CircleMenus}/CircleMenu.cs (88%) rename Assets/ContextCircleMenu/Editor/Core/{CircularMenus => CircleMenus}/CircleMenu.cs.meta (100%) create mode 100644 Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs create mode 100644 Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs.meta rename Assets/ContextCircleMenu/Editor/Core/{CircularMenus => CircleMenus}/FolderCircleMenu.cs (85%) rename Assets/ContextCircleMenu/Editor/Core/{CircularMenus => CircleMenus}/FolderCircleMenu.cs.meta (100%) create mode 100644 Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs create mode 100644 Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs.meta rename Assets/ContextCircleMenu/Editor/Core/{CircularMenus => CircleMenus}/LeafCircleMenu.cs (69%) rename Assets/ContextCircleMenu/Editor/Core/{CircularMenus => CircleMenus}/LeafCircleMenu.cs.meta (100%) rename Assets/ContextCircleMenu/Editor/Core/{CircularMenus => CircleMenus}/RootCircleMenu.cs (80%) rename Assets/ContextCircleMenu/Editor/Core/{CircularMenus => CircleMenus}/RootCircleMenu.cs.meta (100%) diff --git a/Assets/ContextCircleMenu/Editor/Core/CircularMenus/CircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs similarity index 88% rename from Assets/ContextCircleMenu/Editor/Core/CircularMenus/CircleMenu.cs rename to Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs index d537b03..fea4540 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircularMenus/CircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs @@ -12,30 +12,34 @@ namespace ContextCircleMenu.Editor public abstract class CircleMenu { private readonly int _radius; - protected internal readonly List Children = new(); + protected internal readonly List Children = new(8); protected internal readonly GUIContent Icon; protected internal readonly CircleMenu Parent; protected internal readonly string Path; protected internal readonly bool ShouldCloseMenuAfterSelection; - private VisualElement[] _buttonElements; + + private IButtonFactory _buttonFactory; private VisualElement[] _utilityElements; protected internal Action OnSelected; - public CircleMenu(string path, GUIContent icon, Action onSelected, CircleMenu parent, int radius = 100, + public CircleMenu(string path, GUIContent icon, Action onSelected, CircleMenu parent, IButtonFactory factory, + int radius = 100, bool shouldCloseMenuAfterSelection = true) { Path = path; Icon = icon; OnSelected = onSelected; Parent = parent; + _buttonFactory = factory; _radius = radius; ShouldCloseMenuAfterSelection = shouldCloseMenuAfterSelection; } internal ReadOnlySpan CreateElements() { - _buttonElements ??= CreateButtons(); + _buttonFactory ??= new ButtonFactory(); + _buttonElements ??= CreateButtons(_buttonFactory); _utilityElements ??= CreateUtilityElements(); for (var i = 0; i < _buttonElements.Length; i++) @@ -59,7 +63,7 @@ internal ReadOnlySpan CreateElements() /// Creates the buttons for the menu. /// /// - protected abstract VisualElement[] CreateButtons(); + protected abstract VisualElement[] CreateButtons(IButtonFactory factory); /// /// Creates the utility elements for the menu. diff --git a/Assets/ContextCircleMenu/Editor/Core/CircularMenus/CircleMenu.cs.meta b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs.meta similarity index 100% rename from Assets/ContextCircleMenu/Editor/Core/CircularMenus/CircleMenu.cs.meta rename to Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs.meta diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs new file mode 100644 index 0000000..128587a --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace ContextCircleMenu.Editor +{ + public class AttributeCircleMenuFactory : ICircleMenuFactory + { + private readonly GUIContent _content; + private readonly MethodInfo _method; + + public AttributeCircleMenuFactory(ContextCircleMenuAttribute attribute, MethodInfo method) + { + PathSegments = attribute.Path.Split("/"); + _method = method; + + _content = !string.IsNullOrEmpty(attribute.IconPath) + ? EditorGUIUtility.IconContent(attribute.IconPath) + : default; + } + + public IEnumerable PathSegments { get; } + + public CircleMenu Create(IButtonFactory factory) + { + return new LeafCircleMenu(PathSegments.Last(), _content, () => _method.Invoke(null, null), factory); + } + } + + public class CircleMenuFactory : ICircleMenuFactory + { + private readonly Action _action; + private readonly GUIContent _content; + + public CircleMenuFactory(string path, GUIContent content, Action action) + { + PathSegments = path.Split("/"); + _content = content; + _action = action; + } + + public IEnumerable PathSegments { get; } + + public CircleMenu Create(IButtonFactory factory) + { + return new LeafCircleMenu(PathSegments.Last(), _content, _action, factory); + } + } + + public class RootMenuFactory : ICircleMenuFactory + { + public IEnumerable PathSegments => null; + + public CircleMenu Create(IButtonFactory factory) + { + return new RootCircleMenu(factory); + } + } + + public class FolderMenuFactory : IFolderCircleMenuFactory + { + public int Radius { get; set; } = 100; + + public CircleMenu Create(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory) + { + return new FolderCircleMenu(path, menu.Open, menu.Back, parent, factory, Radius); + } + } +} \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs.meta b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs.meta new file mode 100644 index 0000000..80ac925 --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 04bc51fbfc96404c84f552527002746f +timeCreated: 1713957951 \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircularMenus/FolderCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs similarity index 85% rename from Assets/ContextCircleMenu/Editor/Core/CircularMenus/FolderCircleMenu.cs rename to Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs index 2afb2ed..f14f761 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircularMenus/FolderCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs @@ -11,28 +11,29 @@ public class FolderCircleMenu : CircleMenu private readonly Action _onBack; public FolderCircleMenu(string path, Action onOpen, Action onBack, CircleMenu parent, + IButtonFactory factory, int radius = 100) : base(path, EditorGUIUtility.IconContent(EditorIcons.FolderIcon), - null, parent, radius, false) + null, parent, factory, radius, false) { OnSelected = () => onOpen(this); _onBack = onBack; } /// - protected override VisualElement[] CreateButtons() + protected override VisualElement[] CreateButtons(IButtonFactory factory) { var buttons = new VisualElement[Children.Count + 1]; - buttons[0] = CircularButton.CreateBackButton(_onBack); + buttons[0] = factory.Create("Back", EditorGUIUtility.IconContent(EditorIcons.Back2x), _onBack, -1, false); for (var index = 1; index < buttons.Length; index++) { var item = Children[index - 1]; buttons[index] = - new CircularButton( + factory.Create( item.Children.Count > 0 ? item.Path + "" : item.Path, item.Icon, - Children.Count - index + 1, item.OnSelected, + Children.Count - index + 1, item.ShouldCloseMenuAfterSelection); } diff --git a/Assets/ContextCircleMenu/Editor/Core/CircularMenus/FolderCircleMenu.cs.meta b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs.meta similarity index 100% rename from Assets/ContextCircleMenu/Editor/Core/CircularMenus/FolderCircleMenu.cs.meta rename to Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs.meta diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs new file mode 100644 index 0000000..d134a2d --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace ContextCircleMenu.Editor +{ + public interface ICircleMenuFactory + { + public IEnumerable PathSegments { get; } + public CircleMenu Create(IButtonFactory factory); + } + + public interface IFolderCircleMenuFactory + { + public CircleMenu Create(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory); + } +} \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs.meta b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs.meta new file mode 100644 index 0000000..556ac35 --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6ffd726355244507a3abc8368ba95681 +timeCreated: 1713957829 \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircularMenus/LeafCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs similarity index 69% rename from Assets/ContextCircleMenu/Editor/Core/CircularMenus/LeafCircleMenu.cs rename to Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs index 8dc1c33..fc6c7a5 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircularMenus/LeafCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs @@ -7,13 +7,14 @@ namespace ContextCircleMenu.Editor /// public class LeafCircleMenu : CircleMenu { - public LeafCircleMenu(string path, GUIContent icon, Action onSelected, CircleMenu parent = null, - int radius = 100) : base(path, icon, onSelected, parent, radius) + public LeafCircleMenu(string path, GUIContent icon, Action onSelected, IButtonFactory factory, + CircleMenu parent = null, + int radius = 100) : base(path, icon, onSelected, parent, factory, radius) { } /// - protected override VisualElement[] CreateButtons() + protected override VisualElement[] CreateButtons(IButtonFactory factory) { return null; } diff --git a/Assets/ContextCircleMenu/Editor/Core/CircularMenus/LeafCircleMenu.cs.meta b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs.meta similarity index 100% rename from Assets/ContextCircleMenu/Editor/Core/CircularMenus/LeafCircleMenu.cs.meta rename to Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs.meta diff --git a/Assets/ContextCircleMenu/Editor/Core/CircularMenus/RootCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs similarity index 80% rename from Assets/ContextCircleMenu/Editor/Core/CircularMenus/RootCircleMenu.cs rename to Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs index ce6bc7e..4765ee4 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircularMenus/RootCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs @@ -5,23 +5,23 @@ namespace ContextCircleMenu.Editor /// public class RootCircleMenu : CircleMenu { - public RootCircleMenu() : base("root", default, null, null) + public RootCircleMenu(IButtonFactory factory) : base("root", default, null, null, factory) { } /// - protected override VisualElement[] CreateButtons() + protected override VisualElement[] CreateButtons(IButtonFactory factory) { var buttons = new VisualElement[Children.Count]; for (var index = 0; index < Children.Count; index++) { var item = Children[index]; buttons[index] = - new CircularButton( + factory.Create( item.Children.Count > 0 ? item.Path + "" : item.Path, item.Icon, - Children.Count - index, item.OnSelected, + Children.Count - index, item.ShouldCloseMenuAfterSelection); } diff --git a/Assets/ContextCircleMenu/Editor/Core/CircularMenus/RootCircleMenu.cs.meta b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs.meta similarity index 100% rename from Assets/ContextCircleMenu/Editor/Core/CircularMenus/RootCircleMenu.cs.meta rename to Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs.meta From efe6ab9d613b04c5eaa13d5a9a3f6cc95159f047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Tue, 14 May 2024 10:23:49 +0900 Subject: [PATCH 04/19] [add] use button factory in circle menu builder --- .../Editor/Core/CircleMenuBuilder.cs | 17 ++- .../Editor/Core/CircleMenuFactory.cs | 72 ---------- .../Editor/Core/CircleMenuFactory.cs.meta | 3 - .../{CircularMenus.meta => CircleMenus.meta} | 0 .../Editor/Core/CircularButton.cs | 123 ------------------ .../Editor/Core/CircularButton.cs.meta | 3 - .../Editor/Core/ClircleButtons.meta | 3 + .../Editor/Core/ContextCircleMenu.cs | 2 +- .../Editor/Core/ICircleMenuFactory.cs | 15 --- .../Editor/Core/ICircleMenuFactory.cs.meta | 3 - 10 files changed, 18 insertions(+), 223 deletions(-) delete mode 100644 Assets/ContextCircleMenu/Editor/Core/CircleMenuFactory.cs delete mode 100644 Assets/ContextCircleMenu/Editor/Core/CircleMenuFactory.cs.meta rename Assets/ContextCircleMenu/Editor/Core/{CircularMenus.meta => CircleMenus.meta} (100%) delete mode 100644 Assets/ContextCircleMenu/Editor/Core/CircularButton.cs delete mode 100644 Assets/ContextCircleMenu/Editor/Core/CircularButton.cs.meta create mode 100644 Assets/ContextCircleMenu/Editor/Core/ClircleButtons.meta delete mode 100644 Assets/ContextCircleMenu/Editor/Core/ICircleMenuFactory.cs delete mode 100644 Assets/ContextCircleMenu/Editor/Core/ICircleMenuFactory.cs.meta diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs index aff38a2..eaba066 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs @@ -9,6 +9,7 @@ namespace ContextCircleMenu.Editor public sealed class CircleMenuBuilder { private readonly List _factories = new(); + private IButtonFactory _buttonFactory; private IFolderCircleMenuFactory _folderFactory; private ICircleMenuFactory _rootFactory; @@ -16,8 +17,9 @@ internal CircleMenu Build(IMenuControllable menu) { _rootFactory ??= new RootMenuFactory(); _folderFactory ??= new FolderMenuFactory(); + _buttonFactory ??= new ButtonFactory(); - var root = _rootFactory.Create(); + var root = _rootFactory.Create(_buttonFactory); foreach (var factory in _factories) { var pathSegments = factory.PathSegments.SkipLast(1); @@ -27,14 +29,14 @@ internal CircleMenu Build(IMenuControllable menu) var child = currentMenu.Children.Find(m => m.Path == pathSegment); if (child == null) { - child = _folderFactory.Create(pathSegment, menu, currentMenu); + child = _folderFactory.Create(pathSegment, menu, currentMenu, _buttonFactory); currentMenu.Children.Add(child); } currentMenu = child; } - currentMenu.Children.Add(factory.Create()); + currentMenu.Children.Add(factory.Create(_buttonFactory)); } return root; @@ -78,5 +80,14 @@ public void ConfigureFolder(IFolderCircleMenuFactory factory) { _folderFactory = factory; } + + /// + /// Sets a custom factory for creating menu buttons, allowing for further customization of menu buttons. + /// + /// The factory to use for creating menu buttons. + public void ConfigureButton(IButtonFactory factory) + { + _buttonFactory = factory; + } } } \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenuFactory.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenuFactory.cs deleted file mode 100644 index ba167c0..0000000 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenuFactory.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using UnityEditor; -using UnityEngine; - -namespace ContextCircleMenu.Editor -{ - public class AttributeCircleMenuFactory : ICircleMenuFactory - { - private readonly GUIContent _content; - private readonly MethodInfo _method; - - public AttributeCircleMenuFactory(ContextCircleMenuAttribute attribute, MethodInfo method) - { - PathSegments = attribute.Path.Split("/"); - _method = method; - - _content = !string.IsNullOrEmpty(attribute.IconPath) - ? EditorGUIUtility.IconContent(attribute.IconPath) - : default; - } - - public IEnumerable PathSegments { get; } - - public CircleMenu Create() - { - return new LeafCircleMenu(PathSegments.Last(), _content, () => _method.Invoke(null, null)); - } - } - - public class CircleMenuFactory : ICircleMenuFactory - { - private readonly Action _action; - private readonly GUIContent _content; - - public CircleMenuFactory(string path, GUIContent content, Action action) - { - PathSegments = path.Split("/"); - _content = content; - _action = action; - } - - public IEnumerable PathSegments { get; } - - public CircleMenu Create() - { - return new LeafCircleMenu(PathSegments.Last(), _content, _action); - } - } - - public class RootMenuFactory : ICircleMenuFactory - { - public IEnumerable PathSegments => null; - - public CircleMenu Create() - { - return new RootCircleMenu(); - } - } - - public class FolderMenuFactory : IFolderCircleMenuFactory - { - public int Radius { get; set; } = 100; - - public CircleMenu Create(string path, IMenuControllable menu, CircleMenu parent) - { - return new FolderCircleMenu(path, menu.Open, menu.Back, parent, Radius); - } - } -} \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenuFactory.cs.meta b/Assets/ContextCircleMenu/Editor/Core/CircleMenuFactory.cs.meta deleted file mode 100644 index 80ac925..0000000 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenuFactory.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 04bc51fbfc96404c84f552527002746f -timeCreated: 1713957951 \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircularMenus.meta b/Assets/ContextCircleMenu/Editor/Core/CircleMenus.meta similarity index 100% rename from Assets/ContextCircleMenu/Editor/Core/CircularMenus.meta rename to Assets/ContextCircleMenu/Editor/Core/CircleMenus.meta diff --git a/Assets/ContextCircleMenu/Editor/Core/CircularButton.cs b/Assets/ContextCircleMenu/Editor/Core/CircularButton.cs deleted file mode 100644 index b2bc68d..0000000 --- a/Assets/ContextCircleMenu/Editor/Core/CircularButton.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using UnityEditor; -using UnityEngine; -using UnityEngine.UIElements; - -namespace ContextCircleMenu.Editor -{ - public sealed class CircularButton : VisualElement - { - private readonly Button _button; - private readonly Color _hoverColor = new(0.2745098f, 0.3764706f, 0.4862745f, 1.0f); - private readonly Color _normalColor = new(0.02f, 0.02f, 0.02f, 0.8f); - private readonly Action _onSelect; - private readonly bool _shouldCloseMenuAfterSelect; - public bool IsEntered; - - - public CircularButton(string text, GUIContent icon, int section, Action onSelect, - bool shouldCloseMenuAfterSelect = true) - { - _onSelect = onSelect; - _shouldCloseMenuAfterSelect = shouldCloseMenuAfterSelect; - - style.position = Position.Absolute; - style.alignItems = Align.Center; - - _button = new Button(onSelect) - { - 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 = _normalColor - }, - 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 != null) - { - var image = new Image - { - image = 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); - - RegisterCallback(OnMouseEnter); - RegisterCallback(OnMouseLeave); - } - - internal bool TryForceSelect() - { - _onSelect?.Invoke(); - return _shouldCloseMenuAfterSelect; - } - - private void OnMouseEnter(MouseEnterEvent evt) - { - IsEntered = true; - _button.style.backgroundColor = _hoverColor; - } - - private void OnMouseLeave(MouseLeaveEvent evt) - { - IsEntered = false; - _button.style.backgroundColor = _normalColor; - } - - - public static CircularButton CreateBackButton(Action back) - { - return new CircularButton("Back", - EditorGUIUtility.IconContent(EditorIcons.Back2x), -1, back, false); - } - } -} \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircularButton.cs.meta b/Assets/ContextCircleMenu/Editor/Core/CircularButton.cs.meta deleted file mode 100644 index 9c2a442..0000000 --- a/Assets/ContextCircleMenu/Editor/Core/CircularButton.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 1a4acd60cf754149b9b197a9d2c9b490 -timeCreated: 1713720243 \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons.meta b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons.meta new file mode 100644 index 0000000..b29e930 --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a1bc21fb923548c2b66d294f4c889c63 +timeCreated: 1715644602 \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs index 3040191..5a1bb8d 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs @@ -147,7 +147,7 @@ private void OnClick(ClickEvent evt) /// public bool TryForceSelect() { - var button = Children().OfType().FirstOrDefault(b => b.IsEntered); + var button = Children().OfType().FirstOrDefault(b => b.IsEntered); return button != null && button.TryForceSelect(); } diff --git a/Assets/ContextCircleMenu/Editor/Core/ICircleMenuFactory.cs b/Assets/ContextCircleMenu/Editor/Core/ICircleMenuFactory.cs deleted file mode 100644 index c1001c5..0000000 --- a/Assets/ContextCircleMenu/Editor/Core/ICircleMenuFactory.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; - -namespace ContextCircleMenu.Editor -{ - public interface ICircleMenuFactory - { - public IEnumerable PathSegments { get; } - public CircleMenu Create(); - } - - public interface IFolderCircleMenuFactory - { - public CircleMenu Create(string path, IMenuControllable menu, CircleMenu parent); - } -} \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ICircleMenuFactory.cs.meta b/Assets/ContextCircleMenu/Editor/Core/ICircleMenuFactory.cs.meta deleted file mode 100644 index 556ac35..0000000 --- a/Assets/ContextCircleMenu/Editor/Core/ICircleMenuFactory.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 6ffd726355244507a3abc8368ba95681 -timeCreated: 1713957829 \ No newline at end of file From 1d19f29560b530f2723f251c1fe895c5a1dfa254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Tue, 14 May 2024 10:24:17 +0900 Subject: [PATCH 05/19] [add] sample support to custom button factory --- .../Samples~/Custom/Editor/CustomMenu.cs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Assets/ContextCircleMenu/Samples~/Custom/Editor/CustomMenu.cs b/Assets/ContextCircleMenu/Samples~/Custom/Editor/CustomMenu.cs index 28c2779..dfff90a 100644 --- a/Assets/ContextCircleMenu/Samples~/Custom/Editor/CustomMenu.cs +++ b/Assets/ContextCircleMenu/Samples~/Custom/Editor/CustomMenu.cs @@ -1,6 +1,8 @@ -using ContextCircleMenu.Editor; +using System; +using ContextCircleMenu.Editor; using UnityEditor; using UnityEngine; +using UnityEngine.UIElements; namespace ContextCircleMenu.Custom { @@ -13,7 +15,40 @@ public static void Initialize() { builder.AddMenu("Custom/Debug Test", new GUIContent(), () => Debug.Log("custom/test")); builder.AddMenu("Debug Test", new GUIContent(), () => Debug.Log("test")); + builder.ConfigureButton(new CustomButtonFactory()); }; } } + + public class CustomButtonFactory : IButtonFactory + { + public CircleButton Create(string path, GUIContent icon, Action onSelected, int section, + bool shouldCloseMenuAfterSelection) + { + return new OnlyImageCircleButton(path, icon, section, onSelected, shouldCloseMenuAfterSelection); + } + } + + public class OnlyImageCircleButton : CircleButton + { + public OnlyImageCircleButton(string text, GUIContent icon, int section, Action onSelect, + bool shouldCloseMenuAfterSelect = true) : base(text, icon, section, onSelect, shouldCloseMenuAfterSelect) + { + } + + protected override void ModifierButton(Button button, string text, GUIContent icon, int section) + { + var image = new Image + { + image = icon.image, + style = + { + width = 16f, + height = 16f, + flexShrink = 0 + } + }; + button.Add(image); + } + } } \ No newline at end of file From 56ba6b4a292407ced9fd32b6f6d5267fdd093c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Tue, 14 May 2024 10:25:43 +0900 Subject: [PATCH 06/19] [fix] version up to 1.0.0 --- Assets/ContextCircleMenu/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/ContextCircleMenu/package.json b/Assets/ContextCircleMenu/package.json index 539e70f..75ee5f1 100644 --- a/Assets/ContextCircleMenu/package.json +++ b/Assets/ContextCircleMenu/package.json @@ -1,7 +1,7 @@ { "name": "com.garume.context-circle-menu", "displayName": "Context Circle Menu", - "version": "0.3.0", + "version": "1.0.0", "unity": "2022.3", "license": "MIT", "category": "UI", From 096c3e593029c92d8a2171690f18e100219c6146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Fri, 17 May 2024 02:37:01 +0900 Subject: [PATCH 07/19] [add] context circle menu option --- .../Editor/ContextCircleMenuLoader.cs | 4 ++-- .../Editor/Core/ContextCircleMenu.cs | 17 ++++++++--------- .../Editor/Core/ContextCircleMenuOption.cs | 16 ++++++++++++++++ .../Editor/Core/ContextCircleMenuOption.cs.meta | 3 +++ 4 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 Assets/ContextCircleMenu/Editor/Core/ContextCircleMenuOption.cs create mode 100644 Assets/ContextCircleMenu/Editor/Core/ContextCircleMenuOption.cs.meta diff --git a/Assets/ContextCircleMenu/Editor/ContextCircleMenuLoader.cs b/Assets/ContextCircleMenu/Editor/ContextCircleMenuLoader.cs index 3d0b7fe..596f98b 100644 --- a/Assets/ContextCircleMenu/Editor/ContextCircleMenuLoader.cs +++ b/Assets/ContextCircleMenu/Editor/ContextCircleMenuLoader.cs @@ -52,7 +52,7 @@ private static void Initialize() { if (_contextCircleMenu != null) RemovePreviousRadialMenu(); _contextCircleMenu = - new ContextCircleMenu(RadialMenuSize.x, RadialMenuSize.y, _activeSceneView.rootVisualElement); + new ContextCircleMenu(RadialMenuSize.x, RadialMenuSize.y, 100f, _activeSceneView.rootVisualElement); if (_onBuild == null) _contextCircleMenu.CreateMenu(builder => @@ -69,7 +69,7 @@ private static void Initialize() _activeSceneView.rootVisualElement.Add(_contextCircleMenu); } - + /// /// Event that allows customization of the Context Circle Menu construction. diff --git a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs index c9f09fd..4bde89a 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs @@ -14,12 +14,11 @@ public class ContextCircleMenu : VisualElement, IMenuControllable private static readonly Color AnnulusColor = new(0.02f, 0.02f, 0.02f, 0.8f); private static readonly Color MouseAngleIndicatorBackgroundColor = new(0.01f, 0.01f, 0.01f, 1.0f); private static readonly Color MouseAngleIndicatorForegroundColor = Color.white; - private readonly float _height; private readonly VisualElement _target; - private readonly float _width; private float _currentMouseAngle; private Vector2 _mousePosition; + private ContextCircleMenuOption _option; private Vector2 _position; private CircleMenu _selectedMenu; @@ -29,11 +28,11 @@ public class ContextCircleMenu : VisualElement, IMenuControllable /// /// Width of the menu. /// Height of the menu. + /// Radius of the menu. /// The UI element in which the menu will appear. - public ContextCircleMenu(float width, float height, VisualElement target) + public ContextCircleMenu(float width, float height, float radius, VisualElement target) { - _width = width; - _height = height; + _option = new ContextCircleMenuOption(radius, height, width); _target = target; style.position = Position.Absolute; @@ -73,7 +72,7 @@ public void Show() style.display = DisplayStyle.Flex; _position = _mousePosition; - transform.position = _position - new Vector2(_width * 0.5f, _height * 0.5f); + transform.position = _position - new Vector2(_option.Width * 0.5f, _option.Height * 0.5f); Rebuild(); } @@ -154,14 +153,14 @@ public bool TryForceSelect() private void Rebuild() { Clear(); - var elements = _selectedMenu.CreateElements(); + var elements = _selectedMenu.CreateElements(ref _option); for (var i = 0; i < elements.Length; i++) Add(elements[i]); } private void OnGenerateVisualContent(MeshGenerationContext context) { - var position = new Vector2(_width * 0.5f, _height * 0.5f); - var radius = _width * 0.1f; + var position = new Vector2(_option.Width * 0.5f, _option.Height * 0.5f); + var radius = _option.Width * 0.1f; var startAngle = _currentMouseAngle + 90.0f - IndicatorSizeDegrees * 0.5f; var endAngle = _currentMouseAngle + 90.0f + IndicatorSizeDegrees * 0.5f; diff --git a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenuOption.cs b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenuOption.cs new file mode 100644 index 0000000..7f53b4a --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenuOption.cs @@ -0,0 +1,16 @@ +namespace ContextCircleMenu.Editor +{ + public struct ContextCircleMenuOption + { + public ContextCircleMenuOption(float radius, float height, float width) + { + Radius = radius; + Height = height; + Width = width; + } + + public readonly float Radius; + public readonly float Height; + public readonly float Width; + } +} \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenuOption.cs.meta b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenuOption.cs.meta new file mode 100644 index 0000000..6179b9f --- /dev/null +++ b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenuOption.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 79090acad5c84c3abc55b874b127d8ce +timeCreated: 1715833322 \ No newline at end of file From b4756027e11df2df029a812b7b5a2ca022586e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Fri, 17 May 2024 02:38:05 +0900 Subject: [PATCH 08/19] refactoring --- .../Editor/Core/CircleMenuBuilder.cs | 6 +- .../Editor/Core/CircleMenus/CircleMenu.cs | 71 ++++++++++++++----- .../Core/CircleMenus/CircleMenuFactory.cs | 16 +---- .../Core/CircleMenus/FolderCircleMenu.cs | 43 +++++------ .../Core/CircleMenus/ICircleMenuFactory.cs | 2 +- .../Editor/Core/CircleMenus/LeafCircleMenu.cs | 4 +- .../Core/ClircleButtons/ButtonFactory.cs | 7 ++ .../Core/ClircleButtons/IButtonFactory.cs | 2 + 8 files changed, 93 insertions(+), 58 deletions(-) diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs index eaba066..2ad0058 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs @@ -11,15 +11,13 @@ public sealed class CircleMenuBuilder private readonly List _factories = new(); private IButtonFactory _buttonFactory; private IFolderCircleMenuFactory _folderFactory; - private ICircleMenuFactory _rootFactory; internal CircleMenu Build(IMenuControllable menu) { - _rootFactory ??= new RootMenuFactory(); _folderFactory ??= new FolderMenuFactory(); _buttonFactory ??= new ButtonFactory(); - var root = _rootFactory.Create(_buttonFactory); + CircleMenu root = _folderFactory.Create(string.Empty, menu, null, _buttonFactory); foreach (var factory in _factories) { var pathSegments = factory.PathSegments.SkipLast(1); @@ -30,6 +28,8 @@ internal CircleMenu Build(IMenuControllable menu) if (child == null) { child = _folderFactory.Create(pathSegment, menu, currentMenu, _buttonFactory); + var backButton = _buttonFactory.CreateBackButton(menu.Back); + child.PrepareButton(backButton); currentMenu.Children.Add(child); } diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs index fea4540..da9e836 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs @@ -1,6 +1,7 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Linq; using UnityEngine; using UnityEngine.UIElements; @@ -12,18 +13,17 @@ namespace ContextCircleMenu.Editor public abstract class CircleMenu { private readonly int _radius; - protected internal readonly List Children = new(8); - protected internal readonly GUIContent Icon; protected internal readonly CircleMenu Parent; - protected internal readonly string Path; - protected internal readonly bool ShouldCloseMenuAfterSelection; - private VisualElement[] _buttonElements; + private bool _alreadyInitialized; + + private VisualElement[] _buttonElements; private IButtonFactory _buttonFactory; + private Vector3[] _buttonPositions; + private VisualElement _preparedElement; private VisualElement[] _utilityElements; - protected internal Action OnSelected; - public CircleMenu(string path, GUIContent icon, Action onSelected, CircleMenu parent, IButtonFactory factory, + protected CircleMenu(string path, GUIContent icon, Action onSelected, CircleMenu parent, IButtonFactory factory, int radius = 100, bool shouldCloseMenuAfterSelection = true) { @@ -36,17 +36,32 @@ public CircleMenu(string path, GUIContent icon, Action onSelected, CircleMenu pa ShouldCloseMenuAfterSelection = shouldCloseMenuAfterSelection; } - internal ReadOnlySpan CreateElements() + public List Children { get; } = new(8); + public GUIContent Icon { get; } + public string Path { get; } + public bool ShouldCloseMenuAfterSelection { get; } + public Action OnSelected { get; protected set; } + + internal ReadOnlySpan CreateElements(ref ContextCircleMenuOption menuOption) { - _buttonFactory ??= new ButtonFactory(); - _buttonElements ??= CreateButtons(_buttonFactory); - _utilityElements ??= CreateUtilityElements(); + if (!_alreadyInitialized) + { + _buttonFactory ??= new ButtonFactory(); + var buttons = CreateButtons(_buttonFactory, ref menuOption); + _buttonElements = _buttonElements == null ? buttons : _buttonElements.Concat(buttons).ToArray(); + _utilityElements ??= CreateUtilityElements(ref menuOption); + + _buttonPositions = new Vector3[_buttonElements.Length]; + for (var i = 0; i < _buttonElements.Length; i++) + _buttonPositions[i] = GetPositionForIndex(i, _buttonElements.Length); + _alreadyInitialized = true; + } for (var i = 0; i < _buttonElements.Length; i++) { var button = _buttonElements[i]; button.transform.position = Vector3.zero; - var to = Vector2.zero + GetPositionForIndex(i, _buttonElements.Length); + var to = _buttonPositions[i]; button.experimental.animation.Position(to, 100); } @@ -59,23 +74,45 @@ internal ReadOnlySpan CreateElements() return combinedSpan; } + internal void PrepareButton(CircleButton button) + { + if (_buttonElements == null) + { + _buttonElements = new VisualElement[] { button }; + } + else + { + Array.Resize(ref _buttonElements, _buttonElements.Length + 1); + _buttonElements[^1] = button; + } + } + + /// + /// Gets the number of buttons in the menu. + /// + /// + public int GetButtonCount() + { + return _buttonElements.Length; + } + /// /// Creates the buttons for the menu. /// /// - protected abstract VisualElement[] CreateButtons(IButtonFactory factory); + protected abstract VisualElement[] + CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption); /// /// Creates the utility elements for the menu. /// /// - protected virtual VisualElement[] CreateUtilityElements() + protected virtual VisualElement[] CreateUtilityElements(ref ContextCircleMenuOption menuOption) { - return null; + return Array.Empty(); } - - private Vector2 GetPositionForIndex(float index, float totalCount) + private Vector3 GetPositionForIndex(float index, float totalCount) { var angle = index / totalCount * 360f; return new Vector2( diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs index 128587a..b363c66 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenuFactory.cs @@ -50,23 +50,11 @@ public CircleMenu Create(IButtonFactory factory) } } - public class RootMenuFactory : ICircleMenuFactory - { - public IEnumerable PathSegments => null; - - public CircleMenu Create(IButtonFactory factory) - { - return new RootCircleMenu(factory); - } - } - public class FolderMenuFactory : IFolderCircleMenuFactory { - public int Radius { get; set; } = 100; - - public CircleMenu Create(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory) + public FolderCircleMenu Create(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory) { - return new FolderCircleMenu(path, menu.Open, menu.Back, parent, factory, Radius); + return new FolderCircleMenu(path, menu, parent, factory); } } } \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs index f14f761..7fc3b2c 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs @@ -1,4 +1,3 @@ -using System; using UnityEditor; using UnityEngine; using UnityEngine.UIElements; @@ -8,46 +7,48 @@ namespace ContextCircleMenu.Editor /// public class FolderCircleMenu : CircleMenu { - private readonly Action _onBack; - - public FolderCircleMenu(string path, Action onOpen, Action onBack, CircleMenu parent, + public FolderCircleMenu(string path, IMenuControllable menu, + GUIContent icon, + CircleMenu parent, IButtonFactory factory, int radius = 100) : - base(path, EditorGUIUtility.IconContent(EditorIcons.FolderIcon), + base(path, icon, null, parent, factory, radius, false) { - OnSelected = () => onOpen(this); - _onBack = onBack; + OnSelected = () => menu.Open(this); + } + + internal FolderCircleMenu(string path, IMenuControllable menu, + CircleMenu parent, + IButtonFactory factory, + int radius = 100) : + this(path, menu, EditorGUIUtility.IconContent(EditorIcons.FolderIcon), parent, factory, radius) + { } /// - protected override VisualElement[] CreateButtons(IButtonFactory factory) + protected override VisualElement[] CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption) { - var buttons = new VisualElement[Children.Count + 1]; - buttons[0] = factory.Create("Back", EditorGUIUtility.IconContent(EditorIcons.Back2x), _onBack, -1, false); - for (var index = 1; index < buttons.Length; index++) + var buttons = new VisualElement[Children.Count]; + for (var index = 0; index < buttons.Length; index++) { - var item = Children[index - 1]; - buttons[index] = - factory.Create( - item.Children.Count > 0 ? item.Path + "" : item.Path, - item.Icon, - item.OnSelected, - Children.Count - index + 1, - item.ShouldCloseMenuAfterSelection); + var item = Children[index]; + buttons[index] = factory.Create(item.Path, item.Icon, item.OnSelected, + Children.Count - index, + item.ShouldCloseMenuAfterSelection); } return buttons; } /// - protected override VisualElement[] CreateUtilityElements() + protected override VisualElement[] CreateUtilityElements(ref ContextCircleMenuOption menuOption) { var label = new Label(Path) { style = { - marginBottom = 100f * 0.5f + 5.0f, + marginBottom = menuOption.Height * 0.5f + 5.0f, fontSize = 10, unityTextAlign = TextAnchor.MiddleCenter, color = Color.white, diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs index d134a2d..81d0b7a 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/ICircleMenuFactory.cs @@ -10,6 +10,6 @@ public interface ICircleMenuFactory public interface IFolderCircleMenuFactory { - public CircleMenu Create(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory); + public FolderCircleMenu Create(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory); } } \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs index fc6c7a5..e1536c6 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs @@ -5,7 +5,7 @@ namespace ContextCircleMenu.Editor { /// - public class LeafCircleMenu : CircleMenu + public sealed class LeafCircleMenu : CircleMenu { public LeafCircleMenu(string path, GUIContent icon, Action onSelected, IButtonFactory factory, CircleMenu parent = null, @@ -14,7 +14,7 @@ public LeafCircleMenu(string path, GUIContent icon, Action onSelected, IButtonFa } /// - protected override VisualElement[] CreateButtons(IButtonFactory factory) + protected override VisualElement[] CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption) { return null; } diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs index 739c073..7a11a02 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs @@ -1,4 +1,5 @@ using System; +using UnityEditor; using UnityEngine; namespace ContextCircleMenu.Editor @@ -10,5 +11,11 @@ public CircleButton Create(string path, GUIContent icon, Action onSelected, int { return new SimpleCircleButton(path, icon, section, onSelected, shouldCloseMenuAfterSelection); } + + public CircleButton CreateBackButton(Action onBack) + { + return new SimpleCircleButton("Back", EditorGUIUtility.IconContent(EditorIcons.Back2x), + -1, onBack, false); + } } } \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs index ae03711..8d039fa 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs @@ -7,5 +7,7 @@ public interface IButtonFactory { public CircleButton Create(string path, GUIContent icon, Action onSelected, int section, bool shouldCloseMenuAfterSelection); + + public CircleButton CreateBackButton(Action onBack); } } \ No newline at end of file From 92945da960cd14c38a5b6c2a0ac0aed6a6aa6917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Fri, 17 May 2024 02:38:28 +0900 Subject: [PATCH 09/19] [remove] unuse file RootCircleMenu --- .../Editor/Core/CircleMenus/RootCircleMenu.cs | 37 ------------------- .../Core/CircleMenus/RootCircleMenu.cs.meta | 3 -- 2 files changed, 40 deletions(-) delete mode 100644 Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs delete mode 100644 Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs.meta diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs deleted file mode 100644 index 4765ee4..0000000 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs +++ /dev/null @@ -1,37 +0,0 @@ -using UnityEngine.UIElements; - -namespace ContextCircleMenu.Editor -{ - /// - public class RootCircleMenu : CircleMenu - { - public RootCircleMenu(IButtonFactory factory) : base("root", default, null, null, factory) - { - } - - /// - protected override VisualElement[] CreateButtons(IButtonFactory factory) - { - var buttons = new VisualElement[Children.Count]; - for (var index = 0; index < Children.Count; index++) - { - var item = Children[index]; - buttons[index] = - factory.Create( - item.Children.Count > 0 ? item.Path + "" : item.Path, - item.Icon, - item.OnSelected, - Children.Count - index, - item.ShouldCloseMenuAfterSelection); - } - - return buttons; - } - - /// - protected override VisualElement[] CreateUtilityElements() - { - return new VisualElement[] { new Label("") }; - } - } -} \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs.meta b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs.meta deleted file mode 100644 index 0ae4429..0000000 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/RootCircleMenu.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 624f0d0075734e05aca6da80ad0555c8 -timeCreated: 1713976308 \ No newline at end of file From c73358c7313e121db2f2d8b69d0b05687a45c1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Fri, 17 May 2024 02:39:19 +0900 Subject: [PATCH 10/19] [add] custom menu --- .../Samples~/Custom/Editor/CustomMenu.cs | 106 +++++++++++++++++- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/Assets/ContextCircleMenu/Samples~/Custom/Editor/CustomMenu.cs b/Assets/ContextCircleMenu/Samples~/Custom/Editor/CustomMenu.cs index dfff90a..10177fb 100644 --- a/Assets/ContextCircleMenu/Samples~/Custom/Editor/CustomMenu.cs +++ b/Assets/ContextCircleMenu/Samples~/Custom/Editor/CustomMenu.cs @@ -14,9 +14,101 @@ public static void Initialize() ContextCircleMenuLoader.OnBuild += builder => { builder.AddMenu("Custom/Debug Test", new GUIContent(), () => Debug.Log("custom/test")); + builder.AddMenu("Custom/Debug Test 2", EditorGUIUtility.IconContent(EditorIcons.ConsoleInfoIcon2x), + () => Debug.Log("custom/test2")); + builder.AddMenu("Custom/Debug Test 3", EditorGUIUtility.IconContent(EditorIcons.ConsoleInfoIcon2x), + () => Debug.Log("custom/test3")); + + for (var i = 0; i < 5; i++) + builder.AddMenu($"Custom/a/Debug Test {i}", + EditorGUIUtility.IconContent(EditorIcons.ConsoleInfoIcon2x), + () => Debug.Log($"custom/test{i}")); + + for (var i = 0; i < 6; i++) + builder.AddMenu($"Custom/b/Debug Test {i}", + EditorGUIUtility.IconContent(EditorIcons.ConsoleInfoIcon2x), + () => Debug.Log($"custom/test{i}")); + builder.AddMenu("Debug Test", new GUIContent(), () => Debug.Log("test")); + builder.AddMenu("Debug Test 2", EditorGUIUtility.IconContent(EditorIcons.ConsoleInfoIcon2x), + () => Debug.Log("test2")); builder.ConfigureButton(new CustomButtonFactory()); + builder.ConfigureFolder(new CustomFolderMenuFactory()); + }; + } + } + + public class CustomFolderMenuFactory : IFolderCircleMenuFactory + { + public FolderCircleMenu Create(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory) + { + return new CustomFolderCircleMenu(path, menu, parent, factory); + } + } + + public class CustomFolderCircleMenu : FolderCircleMenu + { + public CustomFolderCircleMenu(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory, + int radius = 100) : base(path, menu, EditorGUIUtility.IconContent(EditorIcons.FolderIcon), parent, factory, + radius) + { + } + + protected override VisualElement[] CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption) + { + var buttons = new VisualElement[Children.Count]; + for (var index = 0; index < buttons.Length; index++) + { + var item = Children[index]; + buttons[index] = + factory.Create( + item.Path, + item.Icon, + item.OnSelected, + Children.Count - index, + item.ShouldCloseMenuAfterSelection); + } + + return buttons; + } + + protected override VisualElement[] CreateUtilityElements(ref ContextCircleMenuOption menuOption) + { + var element = new VisualElement(); + var option = menuOption; + element.generateVisualContent += context => + { + var painter = context.painter2D; + + for (var i = 0; i < GetButtonCount(); i++) + { + var angle = (float)i / GetButtonCount() * 360f; + if (GetButtonCount() % 2 == 1) + angle += 180f; + else + angle += 180f - 360f / GetButtonCount() / 2; + var vector = new Vector2( + Mathf.Sin(Mathf.Deg2Rad * angle), + Mathf.Cos(Mathf.Deg2Rad * angle)).normalized; + + var from = vector * 12f; + var to = vector * option.Radius * 1.5f; + painter.strokeColor = Color.black; + painter.lineWidth = 2f; + painter.BeginPath(); + painter.MoveTo(from); + painter.LineTo(to); + painter.Stroke(); + } + + painter.BeginPath(); + painter.Arc(Vector2.zero, option.Radius * 1.5f, 0, 360f); + painter.fillColor = new Color(0f, 0f, 0f, 0.2f); + painter.Fill(); + + painter.DrawCircle(Vector2.zero, option.Radius * 1.5f, 0, 360f, 5f, Color.gray); }; + return new[] { element }; } } @@ -27,6 +119,12 @@ public CircleButton Create(string path, GUIContent icon, Action onSelected, int { return new OnlyImageCircleButton(path, icon, section, onSelected, shouldCloseMenuAfterSelection); } + + public CircleButton CreateBackButton(Action onBack) + { + return new OnlyImageCircleButton("Back", EditorGUIUtility.IconContent(EditorIcons.Back2x), + -1, onBack, false); + } } public class OnlyImageCircleButton : CircleButton @@ -43,11 +141,13 @@ protected override void ModifierButton(Button button, string text, GUIContent ic image = icon.image, style = { - width = 16f, - height = 16f, + width = 32f, + height = 32f, flexShrink = 0 - } + }, + tooltip = text }; + button.Add(image); } } From 6291b2008d535b469f9ff6a01b9fbc0fd7140fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Thu, 16 May 2024 02:03:05 +0900 Subject: [PATCH 11/19] [add] try force enter --- .../Editor/Core/ContextCircleMenu.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs index c9f09fd..11d76d7 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs @@ -151,6 +151,32 @@ public bool TryForceSelect() return button != null && button.TryForceSelect(); } + public bool TryForceEnterByMousePosition() + { + var anglePerRegion = 360f / (Children().Count() - 1); + + var currentAngle = _currentMouseAngle % 360; + if (currentAngle < 0) currentAngle += 360; + + var region = (int)(currentAngle / anglePerRegion); + if (region <= 0) region = Children().Count() - 1; + + var otherButtons = Children().OfType().Where(x => x.Section != region); + foreach (var otherButton in otherButtons) + { + var mouseLeaveEvent = MouseLeaveEvent.GetPooled(); + mouseLeaveEvent.target = otherButton; + otherButton.SendEvent(mouseLeaveEvent); + } + + var button = Children().OfType().FirstOrDefault(x => x.Section == region); + if (button == null) return true; + var mouseEnterEvent = MouseEnterEvent.GetPooled(); + mouseEnterEvent.target = button; + button.SendEvent(mouseEnterEvent); + return false; + } + private void Rebuild() { Clear(); From a5f9d8663e848028e9a6cd4310c66ee267534f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Fri, 17 May 2024 02:48:43 +0900 Subject: [PATCH 12/19] [fix] conflict rename to circle button --- .../Editor/Core/ClircleButtons/CircleButton.cs | 5 ++++- Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs | 6 +++--- Packages/packages-lock.json | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs index 1a24eee..83ae783 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs @@ -9,7 +9,6 @@ public abstract class CircleButton : VisualElement private readonly Action _onSelect; private readonly bool _shouldCloseMenuAfterSelect; private Button _button; - public bool IsEntered; protected CircleButton(string text, GUIContent icon, int section, Action onSelect, bool shouldCloseMenuAfterSelect) @@ -17,9 +16,13 @@ protected CircleButton(string text, GUIContent icon, int section, Action onSelec _onSelect = onSelect; _shouldCloseMenuAfterSelect = shouldCloseMenuAfterSelect; + Section = section; + Initialize(text, icon, section); } + public bool IsEntered { get; private set; } + public int Section { get; private set; } private void Initialize(string text, GUIContent icon, int section) { style.position = Position.Absolute; diff --git a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs index 11d76d7..d8854cf 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs @@ -157,11 +157,11 @@ public bool TryForceEnterByMousePosition() var currentAngle = _currentMouseAngle % 360; if (currentAngle < 0) currentAngle += 360; - + var region = (int)(currentAngle / anglePerRegion); if (region <= 0) region = Children().Count() - 1; - var otherButtons = Children().OfType().Where(x => x.Section != region); + var otherButtons = Children().OfType().Where(x => x.Section != region); foreach (var otherButton in otherButtons) { var mouseLeaveEvent = MouseLeaveEvent.GetPooled(); @@ -169,7 +169,7 @@ public bool TryForceEnterByMousePosition() otherButton.SendEvent(mouseLeaveEvent); } - var button = Children().OfType().FirstOrDefault(x => x.Section == region); + var button = Children().OfType().FirstOrDefault(x => x.Section == region); if (button == null) return true; var mouseEnterEvent = MouseEnterEvent.GetPooled(); mouseEnterEvent.target = button; diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 1c2845a..6cdad5f 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -27,7 +27,7 @@ "source": "builtin", "dependencies": { "com.unity.ide.visualstudio": "2.0.22", - "com.unity.ide.rider": "3.0.27", + "com.unity.ide.rider": "3.0.28", "com.unity.ide.vscode": "1.2.5", "com.unity.editorcoroutines": "1.0.0", "com.unity.performance.profile-analyzer": "1.2.2", @@ -36,7 +36,7 @@ } }, "com.unity.ide.rider": { - "version": "3.0.27", + "version": "3.0.28", "depth": 1, "source": "registry", "dependencies": { From 9ce3bee96920faa270560ec9a1c23ccfecf70bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Sat, 18 May 2024 02:28:22 +0900 Subject: [PATCH 13/19] [add] menu lifecycle --- .../Editor/Core/CircleMenuBuilder.cs | 1 + .../Editor/Core/CircleMenus/CircleMenu.cs | 69 +++++++------------ .../Core/CircleMenus/FolderCircleMenu.cs | 55 +++++++++++---- .../Editor/Core/CircleMenus/LeafCircleMenu.cs | 6 +- .../Core/ClircleButtons/ButtonFactory.cs | 8 +-- .../Core/ClircleButtons/CircleButton.cs | 9 ++- .../Core/ClircleButtons/IButtonFactory.cs | 3 +- .../Core/ClircleButtons/SimpleCircleButton.cs | 4 +- .../Editor/Core/ContextCircleMenu.cs | 4 +- 9 files changed, 81 insertions(+), 78 deletions(-) diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs index 2ad0058..2f28b36 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenuBuilder.cs @@ -29,6 +29,7 @@ internal CircleMenu Build(IMenuControllable menu) { child = _folderFactory.Create(pathSegment, menu, currentMenu, _buttonFactory); var backButton = _buttonFactory.CreateBackButton(menu.Back); + backButton.ShouldCloseMenuAfterSelection = false; child.PrepareButton(backButton); currentMenu.Children.Add(child); } diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs index da9e836..ffa3477 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs @@ -12,28 +12,24 @@ namespace ContextCircleMenu.Editor /// public abstract class CircleMenu { - private readonly int _radius; protected internal readonly CircleMenu Parent; private bool _alreadyInitialized; - - private VisualElement[] _buttonElements; private IButtonFactory _buttonFactory; - private Vector3[] _buttonPositions; private VisualElement _preparedElement; - private VisualElement[] _utilityElements; + + protected CircleButton[] ButtonElements; + protected VisualElement[] UtilityElements; protected CircleMenu(string path, GUIContent icon, Action onSelected, CircleMenu parent, IButtonFactory factory, - int radius = 100, bool shouldCloseMenuAfterSelection = true) { Path = path; Icon = icon; OnSelected = onSelected; Parent = parent; - _buttonFactory = factory; - _radius = radius; ShouldCloseMenuAfterSelection = shouldCloseMenuAfterSelection; + _buttonFactory = factory; } public List Children { get; } = new(8); @@ -42,65 +38,49 @@ protected CircleMenu(string path, GUIContent icon, Action onSelected, CircleMenu public bool ShouldCloseMenuAfterSelection { get; } public Action OnSelected { get; protected set; } - internal ReadOnlySpan CreateElements(ref ContextCircleMenuOption menuOption) + internal ReadOnlySpan BuildElements(ref ContextCircleMenuOption menuOption) { if (!_alreadyInitialized) { _buttonFactory ??= new ButtonFactory(); var buttons = CreateButtons(_buttonFactory, ref menuOption); - _buttonElements = _buttonElements == null ? buttons : _buttonElements.Concat(buttons).ToArray(); - _utilityElements ??= CreateUtilityElements(ref menuOption); + ButtonElements = ButtonElements == null ? buttons : ButtonElements.Concat(buttons).ToArray(); + UtilityElements = CreateUtilityElements(ref menuOption); - _buttonPositions = new Vector3[_buttonElements.Length]; - for (var i = 0; i < _buttonElements.Length; i++) - _buttonPositions[i] = GetPositionForIndex(i, _buttonElements.Length); + OnInitialized(ref menuOption); _alreadyInitialized = true; } - for (var i = 0; i < _buttonElements.Length; i++) - { - var button = _buttonElements[i]; - button.transform.position = Vector3.zero; - var to = _buttonPositions[i]; - button.experimental.animation.Position(to, 100); - } + OnBuild(); var pool = ArrayPool.Shared; - var buffer = pool.Rent(_buttonElements.Length + _utilityElements.Length); - _buttonElements.CopyTo(buffer, 0); - _utilityElements.CopyTo(buffer, _buttonElements.Length); - var combinedSpan = new Span(buffer, 0, _buttonElements.Length + _utilityElements.Length); + var buffer = pool.Rent(ButtonElements.Length + UtilityElements.Length); + ButtonElements.CopyTo(buffer, 0); + UtilityElements.CopyTo(buffer, ButtonElements.Length); + var combinedSpan = new Span(buffer, 0, ButtonElements.Length + UtilityElements.Length); pool.Return(buffer); return combinedSpan; } + internal void PrepareButton(CircleButton button) { - if (_buttonElements == null) + if (ButtonElements == null) { - _buttonElements = new VisualElement[] { button }; + ButtonElements = new[] { button }; } else { - Array.Resize(ref _buttonElements, _buttonElements.Length + 1); - _buttonElements[^1] = button; + Array.Resize(ref ButtonElements, ButtonElements.Length + 1); + ButtonElements[^1] = button; } } - /// - /// Gets the number of buttons in the menu. - /// - /// - public int GetButtonCount() - { - return _buttonElements.Length; - } - /// /// Creates the buttons for the menu. /// /// - protected abstract VisualElement[] + protected abstract CircleButton[] CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption); /// @@ -112,13 +92,12 @@ protected virtual VisualElement[] CreateUtilityElements(ref ContextCircleMenuOpt return Array.Empty(); } - private Vector3 GetPositionForIndex(float index, float totalCount) + protected virtual void OnInitialized(ref ContextCircleMenuOption menuOption) + { + } + + protected virtual void OnBuild() { - var angle = index / totalCount * 360f; - return new Vector2( - Mathf.Sin(angle * Mathf.Deg2Rad) * _radius, - Mathf.Cos(angle * Mathf.Deg2Rad) * _radius - ); } } } \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs index 7fc3b2c..3287196 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs @@ -7,35 +7,38 @@ namespace ContextCircleMenu.Editor /// public class FolderCircleMenu : CircleMenu { + private Vector3[] _buttonPositions; + public FolderCircleMenu(string path, IMenuControllable menu, GUIContent icon, CircleMenu parent, - IButtonFactory factory, - int radius = 100) : - base(path, icon, - null, parent, factory, radius, false) + IButtonFactory factory) : + base(path, icon, null, parent, factory, false) { OnSelected = () => menu.Open(this); } internal FolderCircleMenu(string path, IMenuControllable menu, CircleMenu parent, - IButtonFactory factory, - int radius = 100) : - this(path, menu, EditorGUIUtility.IconContent(EditorIcons.FolderIcon), parent, factory, radius) + IButtonFactory factory) : + this(path, menu, EditorGUIUtility.IconContent(EditorIcons.FolderIcon), parent, factory) { } /// - protected override VisualElement[] CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption) + protected override CircleButton[] CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption) { - var buttons = new VisualElement[Children.Count]; + var buttons = new CircleButton[Children.Count]; for (var index = 0; index < buttons.Length; index++) { var item = Children[index]; - buttons[index] = factory.Create(item.Path, item.Icon, item.OnSelected, - Children.Count - index, - item.ShouldCloseMenuAfterSelection); + var button = factory.Create( + item.Path, + item.Icon, + item.OnSelected, + Children.Count - index); + button.ShouldCloseMenuAfterSelection = item.ShouldCloseMenuAfterSelection; + buttons[index] = button; } return buttons; @@ -62,5 +65,33 @@ protected override VisualElement[] CreateUtilityElements(ref ContextCircleMenuOp }; return new VisualElement[] { label }; } + + protected override void OnInitialized(ref ContextCircleMenuOption menuOption) + { + var buttonCount = ButtonElements.Length; + _buttonPositions = new Vector3[buttonCount]; + for (var i = 0; i < buttonCount; i++) + _buttonPositions[i] = GetPositionForIndex(i, buttonCount, menuOption.Radius); + } + + protected override void OnBuild() + { + for (var i = 0; i < ButtonElements.Length; i++) + { + var button = ButtonElements[i]; + button.transform.position = Vector3.zero; + var to = _buttonPositions[i]; + button.experimental.animation.Position(to, 100); + } + } + + private Vector3 GetPositionForIndex(float index, float totalCount, float radius) + { + var angle = index / totalCount * 360f; + return new Vector2( + Mathf.Sin(angle * Mathf.Deg2Rad) * radius, + Mathf.Cos(angle * Mathf.Deg2Rad) * radius + ); + } } } \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs index e1536c6..4401217 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/LeafCircleMenu.cs @@ -1,6 +1,5 @@ using System; using UnityEngine; -using UnityEngine.UIElements; namespace ContextCircleMenu.Editor { @@ -8,13 +7,12 @@ namespace ContextCircleMenu.Editor public sealed class LeafCircleMenu : CircleMenu { public LeafCircleMenu(string path, GUIContent icon, Action onSelected, IButtonFactory factory, - CircleMenu parent = null, - int radius = 100) : base(path, icon, onSelected, parent, factory, radius) + CircleMenu parent = null) : base(path, icon, onSelected, parent, factory) { } /// - protected override VisualElement[] CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption) + protected override CircleButton[] CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption) { return null; } diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs index 7a11a02..44b3b61 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/ButtonFactory.cs @@ -6,16 +6,14 @@ namespace ContextCircleMenu.Editor { public class ButtonFactory : IButtonFactory { - public CircleButton Create(string path, GUIContent icon, Action onSelected, int section, - bool shouldCloseMenuAfterSelection) + public CircleButton Create(string path, GUIContent icon, Action onSelected, int section) { - return new SimpleCircleButton(path, icon, section, onSelected, shouldCloseMenuAfterSelection); + return new SimpleCircleButton(path, icon, section, onSelected); } public CircleButton CreateBackButton(Action onBack) { - return new SimpleCircleButton("Back", EditorGUIUtility.IconContent(EditorIcons.Back2x), - -1, onBack, false); + return new SimpleCircleButton("Back", EditorGUIUtility.IconContent(EditorIcons.Back2x), -1, onBack); } } } \ No newline at end of file diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs index 1a24eee..178f874 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/CircleButton.cs @@ -7,19 +7,18 @@ namespace ContextCircleMenu.Editor public abstract class CircleButton : VisualElement { private readonly Action _onSelect; - private readonly bool _shouldCloseMenuAfterSelect; private Button _button; public bool IsEntered; - protected CircleButton(string text, GUIContent icon, int section, Action onSelect, - bool shouldCloseMenuAfterSelect) + protected CircleButton(string text, GUIContent icon, int section, Action onSelect) { _onSelect = onSelect; - _shouldCloseMenuAfterSelect = shouldCloseMenuAfterSelect; Initialize(text, icon, section); } + internal bool ShouldCloseMenuAfterSelection { get; set; } = true; + private void Initialize(string text, GUIContent icon, int section) { style.position = Position.Absolute; @@ -38,7 +37,7 @@ private void Initialize(string text, GUIContent icon, int section) internal bool TryForceSelect() { _onSelect?.Invoke(); - return _shouldCloseMenuAfterSelect; + return ShouldCloseMenuAfterSelection; } private void InternalOnMouseEnter(MouseEnterEvent evt) diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs index 8d039fa..46a996b 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/IButtonFactory.cs @@ -5,8 +5,7 @@ namespace ContextCircleMenu.Editor { public interface IButtonFactory { - public CircleButton Create(string path, GUIContent icon, Action onSelected, int section, - bool shouldCloseMenuAfterSelection); + public CircleButton Create(string path, GUIContent icon, Action onSelected, int section); public CircleButton CreateBackButton(Action onBack); } diff --git a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/SimpleCircleButton.cs b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/SimpleCircleButton.cs index 2e2533b..03a0a83 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/SimpleCircleButton.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ClircleButtons/SimpleCircleButton.cs @@ -9,8 +9,8 @@ public class SimpleCircleButton : CircleButton private readonly Color _hoverColor = new(0.2745098f, 0.3764706f, 0.4862745f, 1.0f); private readonly Color _normalColor = new(0.02f, 0.02f, 0.02f, 0.8f); - public SimpleCircleButton(string text, GUIContent icon, int section, Action onSelect, - bool shouldCloseMenuAfterSelect = true) : base(text, icon, section, onSelect, shouldCloseMenuAfterSelect) + public SimpleCircleButton(string text, GUIContent icon, int section, Action onSelect) + : base(text, icon, section, onSelect) { } diff --git a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs index 4bde89a..c9e66f8 100644 --- a/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/ContextCircleMenu.cs @@ -93,7 +93,6 @@ public void Hide() public void Open(CircleMenu menu) { if (!IsVisible) return; - _selectedMenu = menu; Rebuild(); } @@ -106,7 +105,6 @@ public void Back() if (_selectedMenu.Parent != null) Open(_selectedMenu.Parent); } - private void OnAttach(AttachToPanelEvent evt) { generateVisualContent += OnGenerateVisualContent; @@ -153,7 +151,7 @@ public bool TryForceSelect() private void Rebuild() { Clear(); - var elements = _selectedMenu.CreateElements(ref _option); + var elements = _selectedMenu.BuildElements(ref _option); for (var i = 0; i < elements.Length; i++) Add(elements[i]); } From a2695aa851c19cffcb6c196a4fbb072754e5d1f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Sat, 18 May 2024 02:28:51 +0900 Subject: [PATCH 14/19] [fix] support to new architecture --- .../Samples~/Custom/Editor/CustomMenu.cs | 54 +++++++------------ 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/Assets/ContextCircleMenu/Samples~/Custom/Editor/CustomMenu.cs b/Assets/ContextCircleMenu/Samples~/Custom/Editor/CustomMenu.cs index 10177fb..eb38ca4 100644 --- a/Assets/ContextCircleMenu/Samples~/Custom/Editor/CustomMenu.cs +++ b/Assets/ContextCircleMenu/Samples~/Custom/Editor/CustomMenu.cs @@ -20,14 +20,20 @@ public static void Initialize() () => Debug.Log("custom/test3")); for (var i = 0; i < 5; i++) + { + var i1 = i; builder.AddMenu($"Custom/a/Debug Test {i}", EditorGUIUtility.IconContent(EditorIcons.ConsoleInfoIcon2x), - () => Debug.Log($"custom/test{i}")); + () => Debug.Log($"custom/test{i1}")); + } for (var i = 0; i < 6; i++) + { + var i1 = i; builder.AddMenu($"Custom/b/Debug Test {i}", EditorGUIUtility.IconContent(EditorIcons.ConsoleInfoIcon2x), - () => Debug.Log($"custom/test{i}")); + () => Debug.Log($"custom/test{i1}")); + } builder.AddMenu("Debug Test", new GUIContent(), () => Debug.Log("test")); builder.AddMenu("Debug Test 2", EditorGUIUtility.IconContent(EditorIcons.ConsoleInfoIcon2x), @@ -48,28 +54,9 @@ public FolderCircleMenu Create(string path, IMenuControllable menu, CircleMenu p public class CustomFolderCircleMenu : FolderCircleMenu { - public CustomFolderCircleMenu(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory, - int radius = 100) : base(path, menu, EditorGUIUtility.IconContent(EditorIcons.FolderIcon), parent, factory, - radius) - { - } - - protected override VisualElement[] CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption) + public CustomFolderCircleMenu(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory) : + base(path, menu, EditorGUIUtility.IconContent(EditorIcons.FolderIcon), parent, factory) { - var buttons = new VisualElement[Children.Count]; - for (var index = 0; index < buttons.Length; index++) - { - var item = Children[index]; - buttons[index] = - factory.Create( - item.Path, - item.Icon, - item.OnSelected, - Children.Count - index, - item.ShouldCloseMenuAfterSelection); - } - - return buttons; } protected override VisualElement[] CreateUtilityElements(ref ContextCircleMenuOption menuOption) @@ -79,14 +66,14 @@ protected override VisualElement[] CreateUtilityElements(ref ContextCircleMenuOp element.generateVisualContent += context => { var painter = context.painter2D; - - for (var i = 0; i < GetButtonCount(); i++) + var buttonCount = ButtonElements.Length; + for (var i = 0; i < buttonCount; i++) { - var angle = (float)i / GetButtonCount() * 360f; - if (GetButtonCount() % 2 == 1) + var angle = (float)i / buttonCount * 360f; + if (buttonCount % 2 == 1) angle += 180f; else - angle += 180f - 360f / GetButtonCount() / 2; + angle += 180f - 360f / buttonCount / 2; var vector = new Vector2( Mathf.Sin(Mathf.Deg2Rad * angle), Mathf.Cos(Mathf.Deg2Rad * angle)).normalized; @@ -114,23 +101,22 @@ protected override VisualElement[] CreateUtilityElements(ref ContextCircleMenuOp public class CustomButtonFactory : IButtonFactory { - public CircleButton Create(string path, GUIContent icon, Action onSelected, int section, - bool shouldCloseMenuAfterSelection) + public CircleButton Create(string path, GUIContent icon, Action onSelected, int section) { - return new OnlyImageCircleButton(path, icon, section, onSelected, shouldCloseMenuAfterSelection); + return new OnlyImageCircleButton(path, icon, section, onSelected); } public CircleButton CreateBackButton(Action onBack) { return new OnlyImageCircleButton("Back", EditorGUIUtility.IconContent(EditorIcons.Back2x), - -1, onBack, false); + -1, onBack); } } public class OnlyImageCircleButton : CircleButton { - public OnlyImageCircleButton(string text, GUIContent icon, int section, Action onSelect, - bool shouldCloseMenuAfterSelect = true) : base(text, icon, section, onSelect, shouldCloseMenuAfterSelect) + public OnlyImageCircleButton(string text, GUIContent icon, int section, Action onSelect) : base(text, icon, + section, onSelect) { } From f69fac62313e0e2da039daf20e8e597b95e0c251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Sat, 18 May 2024 02:39:33 +0900 Subject: [PATCH 15/19] [add] lifecycle document comment --- .../Editor/Core/CircleMenus/CircleMenu.cs | 7 +++++++ .../Editor/Core/CircleMenus/FolderCircleMenu.cs | 2 ++ 2 files changed, 9 insertions(+) diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs index ffa3477..5b81942 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs @@ -92,10 +92,17 @@ protected virtual VisualElement[] CreateUtilityElements(ref ContextCircleMenuOpt return Array.Empty(); } + /// + /// Called when the menu is initialized. + /// + /// protected virtual void OnInitialized(ref ContextCircleMenuOption menuOption) { } + /// + /// Called when the menu is built. + /// protected virtual void OnBuild() { } diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs index 3287196..d8256e5 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/FolderCircleMenu.cs @@ -66,6 +66,7 @@ protected override VisualElement[] CreateUtilityElements(ref ContextCircleMenuOp return new VisualElement[] { label }; } + /// protected override void OnInitialized(ref ContextCircleMenuOption menuOption) { var buttonCount = ButtonElements.Length; @@ -74,6 +75,7 @@ protected override void OnInitialized(ref ContextCircleMenuOption menuOption) _buttonPositions[i] = GetPositionForIndex(i, buttonCount, menuOption.Radius); } + /// protected override void OnBuild() { for (var i = 0; i < ButtonElements.Length; i++) From 9bf263777767f23d845382ee6770233ad71708de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Sat, 18 May 2024 13:41:05 +0900 Subject: [PATCH 16/19] [remove] unnecessary member --- Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs index 5b81942..ef08767 100644 --- a/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs +++ b/Assets/ContextCircleMenu/Editor/Core/CircleMenus/CircleMenu.cs @@ -16,7 +16,6 @@ public abstract class CircleMenu private bool _alreadyInitialized; private IButtonFactory _buttonFactory; - private VisualElement _preparedElement; protected CircleButton[] ButtonElements; protected VisualElement[] UtilityElements; From d44b5e40b3581e0b06f186612a02cc2c13ccd855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Sat, 18 May 2024 13:41:50 +0900 Subject: [PATCH 17/19] [fix] edit markdown --- README.md | 284 ++++++++++++++++++++++++++++++++++++++++------- README_JA.md | 281 +++++++++++++++++++++++++++++++++++++++------- docs/image-5.png | Bin 0 -> 47752 bytes docs/image-6.png | Bin 0 -> 10392 bytes 4 files changed, 485 insertions(+), 80 deletions(-) create mode 100644 docs/image-5.png create mode 100644 docs/image-6.png diff --git a/README.md b/README.md index 24590a2..5ec5ba5 100644 --- a/README.md +++ b/README.md @@ -20,40 +20,31 @@ Context Circle Menu is a simple open-source tool for Unity. It lets users open a ### Feature - Create Context Circle Menu - -![alt text](docs/image.png) - - Easy to use Editor Icon - - Customized Menu - - Add from Attribute - - ![alt text](docs/image-1.png) - - Add Manual - - ![alt text](docs/image-2.png) - + - Button design + - Folder design - Open in Scene View - Customized Shortcut Key -![alt text](docs/image-3.png) - ## Table of Contents - [Context Circle Menu](#context-circle-menu) - - [Overview](#overview) - - [Feature](#feature) - - [Table of Contents](#table-of-contents) - - [Setup](#setup) - - [Requirements](#requirements) - - [Installation](#installation) - - [Demonstration](#demonstration) - - [Editor Icons](#editor-icons) - - [Customized Menu](#customized-menu) - - [Manual Add Method](#manual-add-method) - - [Customized Button](#customized-button) - - [Customized Shortcut Key](#customized-shortcut-key) + - [Overview](#Overview) + - [Features](#Features) + - [Table of Contents](#Table-of-Contents) + - [Setup](#Setup) + - [Requirements](#Requirements) + - [Installation](#Installation) + - [Demonstration](#Demonstration) + - [Editor Icons](#editor-icons) + - [Add Manual Method](#Add-Manual-Method) + - [Customize](#Customize) + - [Customize Buttons](#Customize-Buttons) + - [Customize Folder](#Customize-Folder) + - [Customize Shortcut](#Customized-Shortcut-Key) + - [API Documentation](#API-Documentation) - [LISENCE](#lisence) - [AUTHOR](#author) @@ -121,7 +112,7 @@ Then you will see below menu. [![Image from Gyazo](https://i.gyazo.com/39b665e8fdd473bb408102e1b5d5bf09.gif)](https://gyazo.com/39b665e8fdd473bb408102e1b5d5bf09) -## Editor Icons +### Editor Icons Icons can be attached to menu buttons. @@ -136,9 +127,6 @@ public static void TestMethod() Debug.Log("TestMethod"); } ``` - -## Customized Menu - ### Manual Add Method If you do not want to use the `Context Circle Menu` attribute, you can register the method manually. @@ -161,25 +149,94 @@ public class Menu } ``` +## Customization + ### Customized Button -If you don't like the button UI, you can replace it with your own. +If you don't like the UI of the button, you can replace it with your own button. -Use `builder.ConfigureFolder();` +Use `builder.ConfigureButton` in `ContextCircleMenuLoader.OnBuild`. -> [!CAUTION] -> It is an incomplete feature. -> -> Destructive changes may be made. +```cs +ContextCircleMenuLoader.OnBuild += (builder => +{ + ... + builder.ConfigureButton(FolderMenuFactory); +}); +``` + +To create your own button, you must create a class that extends CircleButton and a corresponding Factory class. -First, you need to create a FolderMenu that extends `CircleMenu`. -You can create buttons freely in `CreateButtons`. -Please refer to `FolderCircleMenu.cs` for detailed code. +Here is an example code similar to the one provided in Sample's Custom. -Next, create a FolderMenuFactory that implements `IFolderCircleMenuFactory`. -Please refer to `CircleMenuFactory` for detailed code. +Samples can be imported from Package Manager > ContextCircleMenu > Samples. -Finally, you can replace the UI by doing the below. +In this example, the button is replaced with a button that displays only an icon. + +Create the following class + +```cs +public class CustomButtonFactory : IButtonFactory +{ + public CircleButton Create(string path, GUIContent icon, Action onSelected, int section) + { + return new OnlyImageCircleButton(path, icon, section, onSelected); + } + + // Back button is needed when creating a folder structure menu. + // section should be -1 unless ConfigureFolder is used. + public CircleButton CreateBackButton(Action onBack) + { + return new OnlyImageCircleButton("Back", EditorGUIUtility.IconContent(EditorIcons.Back2x), + -1, onBack); + } +} + +public class OnlyImageCircleButton : CircleButton +{ + public OnlyImageCircleButton(string text, GUIContent icon, int section, Action onSelect) : base(text, icon, section, onSelect) + { + } + + // You can edit the generated buttons. + // Feel free to customize the buttons here. + protected override void ModifierButton(Button button, string text, GUIContent icon, int section) + { + var image = new Image + { + image = icon.image, + style = + { + width = 32f, + height = 32f, + flexShrink = 0 + }, + tooltip = text + }; + + button.Add(image); + } +} + +``` + +Set the created Factory class. + +```cs +ContextCircleMenuLoader.OnBuild += (builder => +{ + ... + builder.ConfigureButton(new CustomButtonFactory()); +}); +``` + +It will then be replaced by a button that displays only the icon as shown below. + +![alt text](docs/image-6.png) + +### Customize Folder + +If you don't like the folder UI, you can replace it with your own folder. ```cs ContextCircleMenuLoader.OnBuild += (builder => @@ -189,7 +246,93 @@ ContextCircleMenuLoader.OnBuild += (builder => }); ``` -## Customized Shortcut Key +> [!CAUTION] +> Destructive changes were made in v1.0.0. + +To create your own folder, you need to create a class that extends FolderCircleMenu and a corresponding Factory class. + + +Here is an example code similar to the one provided in Sample's Custom. + +Samples can be imported from Package Manager > ContextCircleMenu > Samples. + +In this example, we are replacing an existing UI with vector graphics. + +Create the following classes + +```cs +public class CustomFolderMenuFactory : IFolderCircleMenuFactory +{ + public FolderCircleMenu Create(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory) + { + return new CustomFolderCircleMenu(path, menu, parent, factory); + } +} + +public class CustomFolderCircleMenu : FolderCircleMenu +{ + public CustomFolderCircleMenu(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory) : + base(path, menu, EditorGUIUtility.IconContent(EditorIcons.FolderIcon), parent, factory) + { + } + + protected override VisualElement[] CreateUtilityElements(ref ContextCircleMenuOption menuOption) + { + var element = new VisualElement(); + var option = menuOption; + element.generateVisualContent += context => + { + var painter = context.painter2D; + var buttonCount = ButtonElements.Length; + for (var i = 0; i < buttonCount; i++) + { + var angle = (float)i / buttonCount * 360f; + if (buttonCount % 2 == 1) + angle += 180f; + else + angle += 180f - 360f / buttonCount / 2; + var vector = new Vector2( + Mathf.Sin(Mathf.Deg2Rad * angle), + Mathf.Cos(Mathf.Deg2Rad * angle)).normalized; + + var from = vector * 12f; + var to = vector * option.Radius * 1.5f; + painter.strokeColor = Color.black; + painter.lineWidth = 2f; + painter.BeginPath(); + painter.MoveTo(from); + painter.LineTo(to); + painter.Stroke(); + } + + painter.BeginPath(); + painter.Arc(Vector2.zero, option.Radius * 1.5f, 0, 360f); + painter.fillColor = new Color(0f, 0f, 0f, 0.2f); + painter.Fill(); + + painter.DrawCircle(Vector2.zero, option.Radius * 1.5f, 0, 360f, 5f, Color.gray); + }; + return new[] { element }; + } +} +``` + +Set the created Factory class. + +```cs +ContextCircleMenuLoader.OnBuild += (builder => +{ + ... + builder.ConfigureButton(new CustomButtonFactory()); + builder.ConfigureFolder(new CustomFolderMenuFactory()); +}); +``` + +Then it will be replaced by the following UI. + +! [alt text](docs/image-5.png) + +### Customized Shortcut Key The default menu open/close button is set to the `A` key, but can be changed freely. @@ -201,6 +344,63 @@ Search for `Context Circle Menu` and you will find the settings as shown in the Set the keys as you like. +## API Documentation + +This section describes the major APIs and can be used as a reference when customizing the UI. + +### ContextCircleMenu + +#### property +| property name | description | +| ---- | ---- | +| BlockMouseEvents | Disables mouse operations such as clicking if true. | + +#### method +| method name | description | +| ---- | ---- | +| Show() | Show menu. | +| Hide() | Hide menu. | +| Open(CircleMenu menu) | Opens the menu specified in the argument. | +| Back() | Open the previous menu. | +| TryForceSelect() | If there is a button in focus, it is forced to select it. | +| TryForceEnterByMousePosition() | Forces the button corresponding to the mouse position to focus. | +| CreateMenu(Action\ configureMenu) | Create the menu content using CircleMenuBuilder. | + +### CircleMenuBuilder +#### method +| method name | description | +| ---- | ---- | +| AddMenu(ICircleMenuFactory factory) | Add your custom menu. | +| AddMenu(string path, GUIContent content, Action action) | Add menus manually. | +| AddMenu(ContextCircleMenuAttribute attribute, MethodInfo method) | Add a menu from the attributes. | +| ConfigureFolder(IFolderCircleMenuFactory factory) | Replace with your custom folder menu. | +| ConfigureButton(IButtonFactory factory) | Replace with your custom button. | + +### CircleMenu +#### abstract method +| method name | description | +| ---- | ---- | +| CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption) | Create a button to be displayed on the menu. The IButtonFactory passed here will be the one set in CircleMenuBuilder.ConfigureButton(). | + +#### virtual method +| method name | description | +| ---- | ---- | +| CreateUtilityElements(ref ContextCircleMenuOption menuOption) | Create elements other than buttons. | +| OnInitialized(ref ContextCircleMenuOption menuOption) | Called at initialization. | +| OnBuild() | Called when an element is created. Mainly when Show() or Open() is called. | + +### CircleButton +#### abstract method +| method name | description | +| ---- | ---- | +| ModifierButton(Button button, string text, GUIContent icon, int section) | Called when creating a button. Use it to modify the elements of the button. | + +#### virtual method +| method name | description | +| ---- | ---- | +| OnMouseEnter(Button button, MouseEnterEvent evt) | Called when the mouse enters an element.| +| OnMouseLeave(Button button, MouseLeaveEvent evt) | Called when the mouse leaves an element. | + ## LISENCE MIT diff --git a/README_JA.md b/README_JA.md index 11f3ea2..180004c 100644 --- a/README_JA.md +++ b/README_JA.md @@ -9,7 +9,7 @@ Context Circle Menu は、Sceneビュー上で円形のメニューを開き、 ## 概要 -Context Circle Menu はUnity用のシンプルなツールです。円形のメニューを開くことができるVisualElementを提供します。また、Sceneビュー上でメニューを開くローダー機能を搭載しています。このメニューにより、任意のメソッドを素早く使用することができます。円形にボタンが広がるため少ないマウス操作と直観的な理解で開発効率を極限まで高めることが可能です。 +Context Circle Menu はUnity用のシンプルなツールです。円形のメニューを開くことができるVisualElementを提供します。また、Sceneビュー上でメニューを開くローダー機能を搭載しています。このメニューは円形にボタンが広がるため少ないマウス操作と直観的な理解で任意のメソッドを実行させることができます。開発効率を極限まで高まることが期待できます。 [![Image from Gyazo](https://i.gyazo.com/8124142a3643fb0d735f7dd66b068142.gif)](https://gyazo.com/8124142a3643fb0d735f7dd66b068142) @@ -18,26 +18,15 @@ Context Circle Menu はUnity用のシンプルなツールです。円形のメ ### 特徴 - 円形メニューの作成 - -![alt text](docs/image.png) - - 使いやすいエディターアイコン - - メニューのカスタマイズ - - アトリビュートからメソッドを追加 - - ![alt text](docs/image-1.png) - - 手動でメソッドを追加 - - ![alt text](docs/image-2.png) - + - ボタンのデザイン + - フォルダーのデザイン - Sceneビュー上でメニューを開く - ショートカットキーのカスタマイズ -![alt text](docs/image-3.png) - ## 目次 - [Context Circle Menu](#context-circle-menu) - [概要](#概要) @@ -47,11 +36,13 @@ Context Circle Menu はUnity用のシンプルなツールです。円形のメ - [要求](#要求) - [インストール](#インストール) - [デモ](#デモ) - - [Editor Icons](#editor-icons) - - [メニューのカスタマイズ](#メニューのカスタマイズ) + - [Editor Icons](#editor-icons) - [手動メソッド追加](#手動メソッド追加) + - [カスタマイズ](#カスタマイズ) - [ボタンのカスタマイズ](#ボタンのカスタマイズ) - - [ショートカットキーのカスタマイズ](#ショートカットキーのカスタマイズ) + - [フォルダのカスタマイズ](#フォルダのカスタマイズ) + - [ショートカットキーのカスタマイズ](#ショートカットキーのカスタマイズ) + - [API ドキュメント](#API ドキュメント) - [LISENCE](#lisence) - [AUTHOR](#author) @@ -83,7 +74,7 @@ https://github.com/Garume/ContextCircleMenu.git?path=/Assets/ContextCircleMenu ## デモ -追加したい静的メソッドに `Context Circle Menu` 属性を適用するだけで追加できます。 +静的メソッドに `ContextCircleMenu` 属性を適用するだけで追加できます。 ```cs public class Menu @@ -96,9 +87,9 @@ public class Menu } ``` -すると下のようにメニューが作成されます。 +下図のようにメニューが作成されます。 -Sceneビュー上で`A`キーを押すことでメニューを開けます。 +メニューはSceneビュー上で`A`キーを押すことで開くことができます。 [![Image from Gyazo](https://i.gyazo.com/1ec027f73700f52c6b3cd9691647a8a1.gif)](https://gyazo.com/1ec027f73700f52c6b3cd9691647a8a1) @@ -115,11 +106,11 @@ public class Menu } ``` -すると下のようにメニューが作成されます。 +すると下図のようにフォルダ階層付きのメニューが作成されます。 [![Image from Gyazo](https://i.gyazo.com/39b665e8fdd473bb408102e1b5d5bf09.gif)](https://gyazo.com/39b665e8fdd473bb408102e1b5d5bf09) -## Editor Icons +### Editor Icons メニューボタンにアイコンを付けることができます。 @@ -135,13 +126,11 @@ public static void TestMethod() } ``` -## メニューのカスタマイズ - ### 手動メソッド追加 -もし `Context Circle Menu` 属性を使いたくない場合は、手動でメソッドを登録することができます。 +`Context Circle Menu` 属性を使用する以外に、手動で追加することもできます。 -内部的にメソッドを登録するプロセスを`ContextCircleMenuLoader.OnBuild`でフックすることができます。 +内部的にメソッドを登録するプロセスを`ContextCircleMenuLoader.OnBuild`でフックすることができます。これを利用して以下のように記述します。 ```cs public class Menu @@ -159,25 +148,98 @@ public class Menu } ``` +## カスタマイズ + + ### ボタンのカスタマイズ ボタンのUIが気に入らなければ、独自のボタンに置き換えることができます。 -`builder.ConfigureFolder();`を使ってください。 +`ContextCircleMenuLoader.OnBuild`内で`builder.ConfigureButton`を使用してください。 -> [!CAUTION] -> これは未完成の機能です。 -> -> 破壊的な変更が加えられる可能性があります。 +```cs +ContextCircleMenuLoader.OnBuild += (builder => +{ + ... + builder.ConfigureButton(FolderMenuFactory); +}); +``` + +独自のボタンを作成するためには、CircleButtonを継承したクラスとそれに対応したFactoryクラスを作成する必要があります。 + +ここでは、SampleのCustomで提供しているものと同様のコードを例として紹介します。 + +SampleはPackage Manager > ContextCircleMenu > Samplesからインポートすることができます。 -まず、`CircleMenu`を継承したFolderMenuを作成します。 -独自のボタンは `CreateButtons` を通して自由に作成してください。 -詳しいコードは `FolderCircleMenu.cs` を参照してください。 +この例では、アイコンのみ表示するボタンに置き換えています。 -次に `IFolderCircleMenuFactory` を実装した FolderMenuFactory を作成します。 -詳しいコードは `CircleMenuFactory` を参照してください。 +以下のクラスを作成してください。 -最後に、以下のようにすることでUIの置き換えが完了します。 +```cs +public class CustomButtonFactory : IButtonFactory +{ + public CircleButton Create(string path, GUIContent icon, Action onSelected, int section) + { + return new OnlyImageCircleButton(path, icon, section, onSelected); + } + + // フォルダ構造のメニューを作成する際に戻るボタンが必要です。 + // ConfigureFolderを利用しない限りsectionは-1にしてください。 + public CircleButton CreateBackButton(Action onBack) + { + return new OnlyImageCircleButton("Back", EditorGUIUtility.IconContent(EditorIcons.Back2x), + -1, onBack); + } +} + +public class OnlyImageCircleButton : CircleButton +{ + public OnlyImageCircleButton(string text, GUIContent icon, int section, Action onSelect) : base(text, icon, section, onSelect) + { + } + + // 生成したボタンを編集することができます。 + // ここで自由にボタンをカスタマイズしてください。 + protected override void ModifierButton(Button button, string text, GUIContent icon, int section) + { + var image = new Image + { + image = icon.image, + style = + { + width = 32f, + height = 32f, + flexShrink = 0 + }, + tooltip = text + }; + + button.Add(image); + } +} + +``` + +作成したFactoryクラスを設定します。 + +```cs +ContextCircleMenuLoader.OnBuild += (builder => +{ + ... + builder.ConfigureButton(new CustomButtonFactory()); +}); +``` + +すると以下のようにアイコンのみ表示するボタンに置き換わります。 + +![alt text](docs/image-6.png) + + +### フォルダのカスタマイズ + +フォルダのUIが気に入らなければ、独自のフォルダに置き換えることができます。 + +`ContextCircleMenuLoader.OnBuild`内で`builder.ConfigureFolder`を使用してください。 ```cs ContextCircleMenuLoader.OnBuild += (builder => @@ -187,7 +249,93 @@ ContextCircleMenuLoader.OnBuild += (builder => }); ``` -## ショートカットキーのカスタマイズ +> [!CAUTION] +> v1.0.0で破壊的な変更が加えられました。 + +独自のフォルダを作成するためには、FolderCircleMenuを継承したクラスとそれに対応したFactoryクラスを作成する必要があります。 + + +ここでは、SampleのCustomで提供しているものと同様のコードを例として紹介します。 + +SampleはPackage Manager > ContextCircleMenu > Samplesからインポートすることができます。 + +この例では、既存のUIにベクターグラフィックスを加えたものに置き換えています。 + +以下のクラスを作成してください。 + +```cs +public class CustomFolderMenuFactory : IFolderCircleMenuFactory +{ + public FolderCircleMenu Create(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory) + { + return new CustomFolderCircleMenu(path, menu, parent, factory); + } +} + +public class CustomFolderCircleMenu : FolderCircleMenu +{ + public CustomFolderCircleMenu(string path, IMenuControllable menu, CircleMenu parent, IButtonFactory factory) : + base(path, menu, EditorGUIUtility.IconContent(EditorIcons.FolderIcon), parent, factory) + { + } + + protected override VisualElement[] CreateUtilityElements(ref ContextCircleMenuOption menuOption) + { + var element = new VisualElement(); + var option = menuOption; + element.generateVisualContent += context => + { + var painter = context.painter2D; + var buttonCount = ButtonElements.Length; + for (var i = 0; i < buttonCount; i++) + { + var angle = (float)i / buttonCount * 360f; + if (buttonCount % 2 == 1) + angle += 180f; + else + angle += 180f - 360f / buttonCount / 2; + var vector = new Vector2( + Mathf.Sin(Mathf.Deg2Rad * angle), + Mathf.Cos(Mathf.Deg2Rad * angle)).normalized; + + var from = vector * 12f; + var to = vector * option.Radius * 1.5f; + painter.strokeColor = Color.black; + painter.lineWidth = 2f; + painter.BeginPath(); + painter.MoveTo(from); + painter.LineTo(to); + painter.Stroke(); + } + + painter.BeginPath(); + painter.Arc(Vector2.zero, option.Radius * 1.5f, 0, 360f); + painter.fillColor = new Color(0f, 0f, 0f, 0.2f); + painter.Fill(); + + painter.DrawCircle(Vector2.zero, option.Radius * 1.5f, 0, 360f, 5f, Color.gray); + }; + return new[] { element }; + } +} +``` + +作成したFactoryクラスを設定します。 + +```cs +ContextCircleMenuLoader.OnBuild += (builder => +{ + ... + builder.ConfigureButton(new CustomButtonFactory()); + builder.ConfigureFolder(new CustomFolderMenuFactory()); +}); +``` + +すると以下のようなUIに置き換わります。 + +![alt text](docs/image-5.png) + +### ショートカットキーのカスタマイズ デフォルトのメニュー開閉ボタンは`A`キーに設定されていますが、自由に変更することができます。 @@ -198,6 +346,63 @@ ContextCircleMenuLoader.OnBuild += (builder => 好きなキーに設定してください。 +## API ドキュメント + +ここでは主要なAPIを説明します。UIをカスタマイズする際の参考にしてください。 + +### ContextCircleMenu + +#### プロパティ +| プロパティ名 | 説明 | +| ---- | ---- | +| BlockMouseEvents | trueの場合にクリック等のマウス操作を無効化します | + +#### メソッド +| メソッド名 | 説明 | +| ---- | ---- | +| Show() | メニューを表示します。 | +| Hide() | メニューを非表示にします。 | +| Open(CircleMenu menu) | 引数に指定したメニューを開きます。 | +| Back() | 前回のメニューを開きます。 | +| TryForceSelect() | フォーカスされているボタンがあれば強制的に選択します。 | +| TryForceEnterByMousePosition() | マウスの位置に対応するボタンを強制的にフォーカスします。 | +| CreateMenu(Action\ configureMenu) | メニュー内容をCircleMenuBuilderを用いて作成します。 | + +### CircleMenuBuilder +#### メソッド +| メソッド名 | 説明 | +| ---- | ---- | +| AddMenu(ICircleMenuFactory factory) | 独自のメニューを追加します。 | +| AddMenu(string path, GUIContent content, Action action) | 手動でメニューを追加します。 | +| AddMenu(ContextCircleMenuAttribute attribute, MethodInfo method) | 属性からメニューを追加します。 | +| ConfigureFolder(IFolderCircleMenuFactory factory) | 独自のフォルダーメニューに置き換えます。 | +| ConfigureButton(IButtonFactory factory) | 独自のボタンに置き換えます。 | + +### CircleMenu +#### 抽象メソッド +| メソッド名 | 説明 | +| ---- | ---- | +| CreateButtons(IButtonFactory factory, ref ContextCircleMenuOption menuOption) | メニューに表示するボタンを作成します。ここで渡されるIButtonFactoryはCircleMenuBuilder.ConfigureButton()で設定したものになります。 | + +#### 仮想メソッド +| メソッド名 | 説明 | +| ---- | ---- | +| CreateUtilityElements(ref ContextCircleMenuOption menuOption) | ボタン以外の要素を作成します。 | +| OnInitialized(ref ContextCircleMenuOption menuOption) | 初期化時に呼ばれます。 | +| OnBuild() | 要素作成時に呼ばれます。主にShow()やOpen()が呼ばれた時です。 | + +### CircleButton +#### 抽象メソッド +| メソッド名 | 説明 | +| ---- | ---- | +| ModifierButton(Button button, string text, GUIContent icon, int section) | ボタン作成時に呼ばれます。ボタンの要素を編集するために用いてください。 | + +#### 仮想メソッド +| メソッド名 | 説明 | +| ---- | ---- | +| OnMouseEnter(Button button, MouseEnterEvent evt) | マウスが要素内に入った際に呼ばれます。| +| OnMouseLeave(Button button, MouseLeaveEvent evt) | マウスが要素内から出た際に呼ばれます。 | + ## LISENCE MIT diff --git a/docs/image-5.png b/docs/image-5.png new file mode 100644 index 0000000000000000000000000000000000000000..b69830e9f4edb4f57ccb7c258ebbf841b481e515 GIT binary patch literal 47752 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 literal 0 HcmV?d00001 diff --git a/docs/image-6.png b/docs/image-6.png new file mode 100644 index 0000000000000000000000000000000000000000..977da2ee6dd037d08bdb1e877d30a17c9c574817 GIT binary patch literal 10392 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 literal 0 HcmV?d00001 From 916e1a1009c7bc7deccce9cbbb1d64325679d395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Sat, 18 May 2024 13:44:52 +0900 Subject: [PATCH 18/19] [fix] readme-ja api document link --- README_JA.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_JA.md b/README_JA.md index 180004c..e1db9dc 100644 --- a/README_JA.md +++ b/README_JA.md @@ -42,7 +42,7 @@ Context Circle Menu はUnity用のシンプルなツールです。円形のメ - [ボタンのカスタマイズ](#ボタンのカスタマイズ) - [フォルダのカスタマイズ](#フォルダのカスタマイズ) - [ショートカットキーのカスタマイズ](#ショートカットキーのカスタマイズ) - - [API ドキュメント](#API ドキュメント) + - [API ドキュメント](#API-ドキュメント) - [LISENCE](#lisence) - [AUTHOR](#author) From ede14d1fabfae667336c680c9733e21f9a31978f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=AC=E3=83=AB=E3=83=A0?= Date: Sat, 18 May 2024 13:48:29 +0900 Subject: [PATCH 19/19] [fix] reame --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5ec5ba5..2affaa4 100644 --- a/README.md +++ b/README.md @@ -370,8 +370,8 @@ This section describes the major APIs and can be used as a reference when custom #### method | method name | description | | ---- | ---- | -| AddMenu(ICircleMenuFactory factory) | Add your custom menu. | -| AddMenu(string path, GUIContent content, Action action) | Add menus manually. | +| AddMenu(ICircleMenuFactory factory) | Add custom menu. | +| AddMenu(string path, GUIContent content, Action action) | Add a menu manually. | | AddMenu(ContextCircleMenuAttribute attribute, MethodInfo method) | Add a menu from the attributes. | | ConfigureFolder(IFolderCircleMenuFactory factory) | Replace with your custom folder menu. | | ConfigureButton(IButtonFactory factory) | Replace with your custom button. |