Skip to content

Commit

Permalink
Merge branch 'develop/remove-internal-access' into next
Browse files Browse the repository at this point in the history
  • Loading branch information
fuqunaga committed Nov 29, 2024
2 parents 886e996 + c953170 commit a75d55e
Show file tree
Hide file tree
Showing 17 changed files with 316 additions and 156 deletions.
2 changes: 1 addition & 1 deletion .idea/.idea.RosettaUI/.idea/projectSettingsUpdater.xml

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

Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using RosettaUI.UIToolkit.Builder;
using RosettaUI.UIToolkit.UnityInternalAccess;
using UnityEngine;
using UnityEngine.UIElements;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using RosettaUI.UIToolkit.UnityInternalAccess;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEngine.UIElements;

namespace RosettaUI.UIToolkit
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,7 @@ private string GetValueToDisplay(T str)

public PopupFieldCustom()
{
CreateMenuCallbackFieldInfo.SetValue(this, (Func<GenericDropdownMenu>)CreateMenu);
return;

GenericDropdownMenu CreateMenu() => new GenericDropdownMenuIgnoreAnchored();
CreateMenuCallbackFieldInfo.SetValue(this, (Func<GenericDropdownMenu>)GenericDropdownMenuIgnoreAnchoredBuilder.CreateGenericDropdownMenuIgnoreAnchored);
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UIElements;

namespace RosettaUI.UIToolkit
{
public static class DropDownMenuGenerator
{
public static object Generate(IEnumerable<MenuItem> menuItems, Rect position, VisualElement targetElement = null, bool anchored = false)
{
if (menuItems == null) return null;

#if UNITY_2023_1_OR_NEWER
// refs: DropdownUtility, EditorDelegateRegistration, GenericOSMenu
var menu = new GenericDropdownMenu();
#else
// refs: BasePopupField
// https://github.com/Unity-Technologies/UnityCsReference/blob/c84064be69f20dcf21ebe4a7bbc176d48e2f289c/ModuleOverrides/com.unity.ui/Core/Controls/BasePopupField.cs#L206
var isPlayer = targetElement?.panel.contextType == ContextType.Player;

GenericDropdownMenuWrapper menu;
if (isPlayer)
{
var genericMenu = new GenericDropdownMenu();
menu = new GenericDropdownMenuWrapper(genericMenu);
}
// GenericDropdownMenu だとエディターでエラーになる場合があるのでDropdownUtility経由でOSのメニューを使用する
// RosettaUIEditorWindowExample > MiscExample > UI.Popup() でエラーになる
// at Unity2022.3
else
{
// DropdownUtility.CreateDropdown() は internal なのでリフレクションで取得
#if false
menu = DropdownUtility.CreateDropdown();
#else
_createDropdownFunc ??= (Func<object>)typeof(VisualElement).Assembly
.GetType("UnityEngine.UIElements.DropdownUtility")?
.GetMethod("CreateDropdown", BindingFlags.NonPublic | BindingFlags.Static)?
.CreateDelegate(typeof(Func<object>));

if (_createDropdownFunc == null)
{
return null;
}

menu = new GenericDropdownMenuWrapper(_createDropdownFunc());
#endif
}

#endif

foreach (var item in menuItems)
{
if ( item == MenuItem.Separator)
{
menu.AddSeparator("");
continue;
}

if (item.isEnable)
{
menu.AddItem(item.name, item.isChecked, item.action);
}
else
{
menu.AddDisabledItem(item.name, item.isChecked);
}
}

menu.DropDown(position, targetElement, anchored);
return menu;
}

#if !UNITY_2023_1_OR_NEWER
private static Func<object> _createDropdownFunc;

/// <summary>
/// UnityEngine.UIElements.IGenericMenu が internal なので代替インターフェース
/// </summary>
private class GenericDropdownMenuWrapper
{
private class MethodInfoSet
{
private static readonly Type[] AddItemTypes = {typeof(string), typeof(bool), typeof(Action)};
private static readonly Type[] AddDisabledItemTypes = {typeof(string), typeof(bool)};
private static readonly Type[] AddSeparatorTypes = {typeof(string)};
private static readonly Type[] DropDownTypes = {typeof(Rect), typeof(VisualElement), typeof(bool)};

public readonly MethodInfo addItem;
public readonly MethodInfo addDisabledItem;
public readonly MethodInfo addSeparator;
public readonly MethodInfo dropDown;

public MethodInfoSet(Type type)
{
addItem = type.GetMethod("AddItem", AddItemTypes);
addDisabledItem = type.GetMethod("AddDisabledItem", AddDisabledItemTypes);
addSeparator = type.GetMethod("AddSeparator", AddSeparatorTypes);
dropDown = type.GetMethod("DropDown", DropDownTypes);
}
}


private static readonly Dictionary<Type, MethodInfoSet> TypeToMethodInfoSet = new();

private static MethodInfoSet GetMethodInfoSet(Type type)
{
if (!TypeToMethodInfoSet.TryGetValue(type, out var methodInfoSet))
{
methodInfoSet = new MethodInfoSet(type);
TypeToMethodInfoSet[type] = methodInfoSet;
}

return methodInfoSet;
}


private readonly Action<string, bool, Action> _addItemAction;
private readonly Action<string, bool> _addDisabledItemAction;
private readonly Action<string> _addSeparatorAction;
private readonly Action<Rect, VisualElement, bool> _dropDownAction;

public GenericDropdownMenuWrapper(object baseObject)
{
var methodInfoSet = GetMethodInfoSet(baseObject.GetType());
_addItemAction = (Action<string, bool, Action>)Delegate.CreateDelegate(typeof(Action<string, bool, Action>), baseObject, methodInfoSet.addItem);
_addDisabledItemAction = (Action<string, bool>)Delegate.CreateDelegate(typeof(Action<string, bool>), baseObject, methodInfoSet.addDisabledItem);
_addSeparatorAction = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), baseObject, methodInfoSet.addSeparator);
_dropDownAction = (Action<Rect, VisualElement, bool>)Delegate.CreateDelegate(typeof(Action<Rect, VisualElement, bool>), baseObject, methodInfoSet.dropDown);
}

public void AddItem(string name, bool isChecked, Action action) => _addItemAction(name, isChecked, action);
public void AddDisabledItem(string name, bool isChecked) => _addDisabledItemAction(name, isChecked);
public void AddSeparator(string path) => _addSeparatorAction(path);
public void DropDown(Rect position, VisualElement targetElement, bool anchored) => _dropDownAction(position, targetElement, anchored);
}
#endif
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine;
using UnityEngine.UIElements;

namespace RosettaUI.UIToolkit
{
/// <summary>
/// Dropdown()の第3引数を無視するGenericDropdownMenu
/// BasePopupFieldがDropdownMenuでanchor == trueで呼び出しているが、強制的falseにする
///
/// IGenericMenuがinternalなので次のようなクラスを動的に生成する
///
/// public class GenericDropdownMenuIgnoreAnchored : GenericDropdownMenu, IGenericMenu
/// {
/// void IGenericMenu.DropDown(Rect position, VisualElement targetElement, bool anchored)
/// => base.DropDown(position, targetElement, false);
/// }
/// </summary>
public static class GenericDropdownMenuIgnoreAnchoredBuilder
{
private static readonly Type GenericDropdownMenuIgnoreAnchoredType;

public static GenericDropdownMenu CreateGenericDropdownMenuIgnoreAnchored()
{
return (GenericDropdownMenu)Activator.CreateInstance(GenericDropdownMenuIgnoreAnchoredType);
}

static GenericDropdownMenuIgnoreAnchoredBuilder()
{
const string dropdownMethodName = "DropDown";

var iGenericMenuType = typeof(GenericDropdownMenu).Assembly.GetType("UnityEngine.UIElements.IGenericMenu");
var moduleBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("RosettaUI.UIToolkit.DynamicAssembly"), AssemblyBuilderAccess.Run).DefineDynamicModule("RosettaUI.UIToolkit.DynamicModule");
var typeBuilder = moduleBuilder.DefineType("RosettaUI.UIToolkit.GenericDropdownMenuIgnoreAnchored", TypeAttributes.Public | TypeAttributes.Class, typeof(GenericDropdownMenu), new[]{iGenericMenuType});

// インターフェイスの実装
typeBuilder.AddInterfaceImplementation(iGenericMenuType);

var dropDownMethodArgumentTypes = new[] { typeof(Rect), typeof(VisualElement), typeof(bool) };
var methodBuilder = typeBuilder.DefineMethod(
dropdownMethodName,
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot,
null,
dropDownMethodArgumentTypes
);

// IL生成 (メソッドの実装)
var ilGenerator = methodBuilder.GetILGenerator();

// 静的メソッドの呼び出しを行うILコードを生成
ilGenerator.Emit(OpCodes.Ldarg_0); // this
ilGenerator.Emit(OpCodes.Ldarg_1); // Rect position
ilGenerator.Emit(OpCodes.Ldarg_2); // VisualElement targetElement
ilGenerator.Emit(OpCodes.Ldc_I4_0); // bool anchored (false)
ilGenerator.Emit(OpCodes.Call, typeof(GenericDropdownMenu).GetMethod(dropdownMethodName, dropDownMethodArgumentTypes));
ilGenerator.Emit(OpCodes.Ret);

// メソッドをインターフェイスのメソッドにマップする
typeBuilder.DefineMethodOverride(methodBuilder, iGenericMenuType.GetMethod(dropdownMethodName));

GenericDropdownMenuIgnoreAnchoredType = typeBuilder.CreateType();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using RosettaUI.UIToolkit.Builder;
using RosettaUI.UIToolkit.UnityInternalAccess;
using UnityEngine;
using UnityEngine.UIElements;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
using UnityEngine.Assertions;
using UnityEngine.UIElements;

namespace RosettaUI.UIToolkit.UnityInternalAccess
namespace RosettaUI.UIToolkit
{
public static class UIToolkitUtility
{
private static MethodInfo _focusControllerGetLeafFocusedElementMethodInfo;
#if UNITY_2023_1_OR_NEWER
private static PropertyInfo _baseBoolFieldAcceptClicksIfDisabledPropertyInfo;
#else
private static FieldInfo _baseBoolFieldClickableFieldInfo;
private static PropertyInfo _clickableAcceptClicksIfDisabledPropertyInfo;
#endif

public static bool WillUseKeyInput(IPanel panel)
Expand All @@ -30,7 +33,11 @@ public static void SetAcceptClicksIfDisabled(BaseBoolField baseBoolField, bool f
_baseBoolFieldAcceptClicksIfDisabledPropertyInfo ??= typeof(BaseBoolField).GetProperty("acceptClicksIfDisabled", BindingFlags.NonPublic | BindingFlags.Instance);
_baseBoolFieldAcceptClicksIfDisabledPropertyInfo?.SetValue(baseBoolField, flag);
#else
baseBoolField.m_Clickable.acceptClicksIfDisabled = flag;
_baseBoolFieldClickableFieldInfo ??= typeof(BaseBoolField).GetField("m_Clickable", BindingFlags.NonPublic | BindingFlags.Instance);
_clickableAcceptClicksIfDisabledPropertyInfo ??= typeof(Clickable).GetProperty("acceptClicksIfDisabled", BindingFlags.NonPublic | BindingFlags.Instance);

var baseBoolFieldClickable = _baseBoolFieldClickableFieldInfo?.GetValue(baseBoolField);
_clickableAcceptClicksIfDisabledPropertyInfo?.SetValue(baseBoolFieldClickable, flag);
#endif
}
}
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit a75d55e

Please sign in to comment.