From b6d2a3683c3bf30736777719b1f66a3529cb4f4f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 6 Dec 2024 16:10:50 +0100 Subject: [PATCH] Add `ButtonAttribute` to display methods in editor properties panel #1917 --- .../Editor/Content/Items/VisualScriptItem.cs | 14 +++++++ Source/Editor/CustomEditors/CustomEditor.cs | 29 +++++++++++++ .../Editor/Scripting/ScriptType.Interfaces.cs | 8 ++++ Source/Editor/Scripting/ScriptType.cs | 17 ++++++++ Source/Editor/Surface/NodeFactory.cs | 1 + Source/Engine/Content/Assets/VisualScript.cpp | 8 ++++ Source/Engine/Content/Assets/VisualScript.h | 3 ++ .../Attributes/Editor/ButtonAttribute.cs | 42 +++++++++++++++++++ 8 files changed, 122 insertions(+) create mode 100644 Source/Engine/Scripting/Attributes/Editor/ButtonAttribute.cs diff --git a/Source/Editor/Content/Items/VisualScriptItem.cs b/Source/Editor/Content/Items/VisualScriptItem.cs index 54133e6e2..c3953513f 100644 --- a/Source/Editor/Content/Items/VisualScriptItem.cs +++ b/Source/Editor/Content/Items/VisualScriptItem.cs @@ -112,6 +112,12 @@ namespace FlaxEditor.Content throw new TargetException("Missing Visual Script asset."); _type.Asset.SetScriptInstanceParameterValue(_parameter.Name, (Object)obj, value); } + + /// + public object Invoke(object obj, object[] parameters) + { + throw new NotSupportedException(); + } } sealed class VisualScriptMethodInfo : IScriptMemberInfo @@ -240,6 +246,14 @@ namespace FlaxEditor.Content { throw new NotSupportedException(); } + + /// + public object Invoke(object obj, object[] parameters) + { + if (!_type.Asset) + throw new TargetException("Missing Visual Script asset."); + return _type.Asset.InvokeMethod(_index, obj, parameters); + } } /// diff --git a/Source/Editor/CustomEditors/CustomEditor.cs b/Source/Editor/CustomEditors/CustomEditor.cs index 0d2be75cc..d5f6c5667 100644 --- a/Source/Editor/CustomEditors/CustomEditor.cs +++ b/Source/Editor/CustomEditors/CustomEditor.cs @@ -127,12 +127,41 @@ namespace FlaxEditor.CustomEditors _isSetBlocked = true; Initialize(layout); + ShowButtons(); Refresh(); _isSetBlocked = false; CurrentCustomEditor = prev; } + private void ShowButtons() + { + var values = Values; + if (values == null || values.HasDifferentTypes) + return; + var type = TypeUtils.GetObjectType(values[0]); + var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + foreach (var method in methods) + { + if (!method.HasAttribute(typeof(ButtonAttribute)) || + method.ParametersCount != 0) + continue; + var attribute = method.GetAttribute(); + var text = string.IsNullOrEmpty(attribute.Text) ? Utilities.Utils.GetPropertyNameUI(method.Name) : attribute.Text; + var tooltip = string.IsNullOrEmpty(attribute.Tooltip) ? Editor.Instance.CodeDocs.GetTooltip(method) : attribute.Tooltip; + var button = _layout.Button(text, tooltip); + button.Button.Tag = method; + button.Button.ButtonClicked += OnButtonClicked; + } + } + + private void OnButtonClicked(Button button) + { + var method = (ScriptMemberInfo)button.Tag; + var obj = method.IsStatic ? null : Values[0]; + method.Invoke(obj); + } + internal static CustomEditor CurrentCustomEditor; internal void OnChildCreated(CustomEditor child) diff --git a/Source/Editor/Scripting/ScriptType.Interfaces.cs b/Source/Editor/Scripting/ScriptType.Interfaces.cs index 01a4755ec..34294fd60 100644 --- a/Source/Editor/Scripting/ScriptType.Interfaces.cs +++ b/Source/Editor/Scripting/ScriptType.Interfaces.cs @@ -293,6 +293,14 @@ namespace FlaxEditor.Scripting /// The object whose member value will be modified. /// The new member value. void SetValue(object obj, object value); + + /// + /// Invokes the method on a specific object (null if static) using the provided parameters. + /// + /// The instance of the object to invoke its method. Use null for static methods. + /// List of parameters to provide. + /// The value returned by the method. + object Invoke(object obj, object[] parameters); } /// diff --git a/Source/Editor/Scripting/ScriptType.cs b/Source/Editor/Scripting/ScriptType.cs index ec3775b95..66c7a9b06 100644 --- a/Source/Editor/Scripting/ScriptType.cs +++ b/Source/Editor/Scripting/ScriptType.cs @@ -691,6 +691,23 @@ namespace FlaxEditor.Scripting else _custom.SetValue(obj, value); } + + /// + /// Invokes the method on a specific object (null if static) using the provided parameters. + /// + /// The instance of the object to invoke its method. Use null for static methods. + /// List of parameters to provide. + /// The value returned by the method. + public object Invoke(object obj = null, object[] parameters = null) + { + if (parameters == null) + parameters = Array.Empty(); + if (_managed is MethodInfo methodInfo) + return methodInfo.Invoke(obj, parameters); + if (_managed != null) + throw new NotSupportedException(); + return _custom.Invoke(obj, parameters); + } } /// diff --git a/Source/Editor/Surface/NodeFactory.cs b/Source/Editor/Surface/NodeFactory.cs index ee4553f2f..484b5d89b 100644 --- a/Source/Editor/Surface/NodeFactory.cs +++ b/Source/Editor/Surface/NodeFactory.cs @@ -39,6 +39,7 @@ namespace FlaxEditor.Surface typeof(TooltipAttribute), typeof(HideInEditorAttribute), typeof(NoAnimateAttribute), + typeof(ButtonAttribute), }; /// diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index 4fd1718ab..b410d62f7 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -2263,6 +2263,14 @@ void VisualScript::GetMethodSignature(int32 index, String& name, byte& flags, St } } +Variant VisualScript::InvokeMethod(int32 index, const Variant& instance, Span parameters) const +{ + auto& method = _methods[index]; + Variant result; + VisualScriptingModule.InvokeMethod((void*)&method, instance, parameters, result); + return result; +} + Span VisualScript::GetMetaData(int32 typeID) { auto meta = Graph.Meta.GetEntry(typeID); diff --git a/Source/Engine/Content/Assets/VisualScript.h b/Source/Engine/Content/Assets/VisualScript.h index 1cd8e9749..c07e3883a 100644 --- a/Source/Engine/Content/Assets/VisualScript.h +++ b/Source/Engine/Content/Assets/VisualScript.h @@ -267,6 +267,9 @@ public: // Gets the signature data of the method. API_FUNCTION() void GetMethodSignature(int32 index, API_PARAM(Out) String& name, API_PARAM(Out) byte& flags, API_PARAM(Out) String& returnTypeName, API_PARAM(Out) Array& paramNames, API_PARAM(Out) Array& paramTypeNames, API_PARAM(Out) Array& paramOuts); + // Invokes the method. + API_FUNCTION() Variant InvokeMethod(int32 index, const Variant& instance, Span parameters) const; + // Gets the metadata of the script surface. API_FUNCTION() Span GetMetaData(int32 typeID); diff --git a/Source/Engine/Scripting/Attributes/Editor/ButtonAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/ButtonAttribute.cs new file mode 100644 index 000000000..75ee9f5f0 --- /dev/null +++ b/Source/Engine/Scripting/Attributes/Editor/ButtonAttribute.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine +{ + /// + /// Displays the method in the properties panel where user can click and invoke this method. + /// + /// Supported on both static and member methods that are parameterless. + [AttributeUsage(AttributeTargets.Method)] + public sealed class ButtonAttribute : Attribute + { + /// + /// The button text. Empty value will use method name (auto-formatted). + /// + public string Text; + + /// + /// The button tooltip text. Empty value will use method documentation. + /// + public string Tooltip; + + /// + /// Initializes a new instance of the class. + /// + public ButtonAttribute() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The button text. + /// The button tooltip. + public ButtonAttribute(string text, string tooltip = null) + { + Text = text; + Tooltip = tooltip; + } + } +}