Repository: AlicanHasirci/TextTween
Pull request #15: Fix Dragging + Dropping in Editor
Author: login: wallstop
Created: 2025-03-28T22:03:11Z | Merged: 2025-03-29T12:46:55Z

# Overview
With the current Editor script, I am unable to drag + drop modifiers or text into the Texts/Modifiers lists. The only way I am able to add items is by clicking "+" and then dragging / selecting items from there. The main way I populate these items is by dragging + dropping in editor, this breaks my workflow.

# The Fix
Instead of doing fancy serialized property checks / wizardy / custom application, I've changed the Editor code to just draw and do the normal serialization things. All of the "is this different?" code has been moved to explicit state management, which solves this particular bug. 

# Steps to Reproduce
1. Create a Tween Manager
2. Create a TMP Text instance somewhere
3. Create a Modifier somewhere
4. Try dragging either the Text or Modifier into the Texts/Modifiers fields

Expected results: Dragged Text/Modifiers are added

Actual results: Nothing happens

# Extra Info
OS: Windows 11
Unity: 2022.3.51f1


Diff:
diff --git a/Editor/TextTween.Editor.asmdef b/Editor/TextTween.Editor.asmdef
index 2f77893..238c14d 100644
--- a/Editor/TextTween.Editor.asmdef
+++ b/Editor/TextTween.Editor.asmdef
@@ -2,7 +2,8 @@
     "name": "TextTween.Editor",
     "rootNamespace": "TextTween",
     "references": [
-        "GUID:999262d7d7e04288bc6aebef915dc2b8"
+        "GUID:999262d7d7e04288bc6aebef915dc2b8",
+        "GUID:6055be8ebefd69e48b49212b09b47b2f"
     ],
     "includePlatforms": [
         "Editor"
diff --git a/Editor/TextTweenEditor.cs b/Editor/TextTweenEditor.cs
index 629638d..37e8b23 100644
--- a/Editor/TextTweenEditor.cs
+++ b/Editor/TextTweenEditor.cs
@@ -1,58 +1,74 @@
-using UnityEditor;
-
 namespace TextTween.Editor {
+#if UNITY_EDITOR
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Reflection;
+    using TMPro;
+    using UnityEditor;
+
     [CustomEditor(typeof(TweenManager))]
-    public class TextTweenEditor : UnityEditor.Editor {
-        private TweenManager _tweenManager;
-        
-        private SerializedProperty _progressProperty;
-        private SerializedProperty _offsetProperty;
-        private SerializedProperty _textsProperty;
-        private SerializedProperty _modifiersProperty;
-        
+    public class TextTweenEditor : Editor {
+        private float _progress;
+        private float _offset;
+        private readonly List<TMP_Text> _texts = new();
+        private readonly List<CharModifier> _modifiers = new();
+
+        private static readonly Lazy<FieldInfo> TextField = new(() => typeof(TweenManager).GetField("_texts", 
+            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
+
+        private static readonly Lazy<FieldInfo> ModifiersField = new(() => typeof(TweenManager).GetField("_modifiers", 
+            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
+
         private void OnEnable() {
-            _tweenManager = (TweenManager) target;
-            
-            _progressProperty = serializedObject.FindProperty("Progress");
-            _offsetProperty = serializedObject.FindProperty("Offset");
-            _textsProperty = serializedObject.FindProperty("_texts");
-            _modifiersProperty = serializedObject.FindProperty("_modifiers");
+            CheckForChanges(target as TweenManager, out _, out _, out _);
         }
 
         public override void OnInspectorGUI() {
-            var createArrays = false;
-            var applyChanges = SliderChanged(_progressProperty, "Progress");
+            base.OnInspectorGUI();
+            TweenManager tweenManager = target as TweenManager;
+            CheckForChanges(tweenManager, out bool createArrays, out bool applyChanges,  out IReadOnlyList<TMP_Text> oldTexts);
 
-            if (SliderChanged(_offsetProperty, "Offset")) {
-                createArrays = true;
-                applyChanges = true;
+            if (createArrays) {
+                tweenManager.Dispose(oldTexts); 
+                tweenManager.CreateNativeArrays();
             }
+            if (applyChanges) tweenManager.ForceUpdate();
+        }
 
-            if (PropertyChanged(_textsProperty)) {
+        private void CheckForChanges(TweenManager manager, out bool createArrays, out bool applyChanges, out IReadOnlyList<TMP_Text> oldTexts) {
+            createArrays = false;
+            applyChanges = false;
+            oldTexts = _texts;
+            
+            if (manager.Offset != _offset) {
                 createArrays = true;
                 applyChanges = true;
+                _offset = manager.Offset;
             }
-
-            if (PropertyChanged(_modifiersProperty)) {
+            if (manager.Progress != _progress) {
                 applyChanges = true;
+                _progress = manager.Progress;
+            } 
+            
+            IReadOnlyList<TMP_Text> texts = TextField.Value.GetValue(manager) as IReadOnlyList<TMP_Text> ?? 
+                                          Array.Empty<TMP_Text>();
+            if (!_texts.SequenceEqual(texts)) {
+                createArrays = true;
+                applyChanges = true;
+                oldTexts = _texts.ToArray();
+                _texts.Clear();
+                _texts.AddRange(texts);
             }
             
-            if (createArrays) _tweenManager.Dispose();
-            if (applyChanges) serializedObject.ApplyModifiedProperties();
-            if (createArrays) _tweenManager.CreateNativeArrays();
-            if (applyChanges) _tweenManager.ForceUpdate();
-        }
-
-        private bool SliderChanged(SerializedProperty property, string text) {
-            EditorGUI.BeginChangeCheck();
-            EditorGUILayout.Slider(property, 0, 1, text);
-            return EditorGUI.EndChangeCheck();
-        }
-
-        private bool PropertyChanged(SerializedProperty property) {
-            EditorGUI.BeginChangeCheck();
-            EditorGUILayout.PropertyField(property);
-            return EditorGUI.EndChangeCheck();
+            IReadOnlyList<CharModifier> modifiers = ModifiersField.Value.GetValue(manager) as IReadOnlyList<CharModifier> ?? 
+                                                    Array.Empty<CharModifier>();
+            if (!_modifiers.SequenceEqual(modifiers)) {
+                applyChanges = true;
+                _modifiers.Clear();
+                _modifiers.AddRange(modifiers);
+            }
         }
     }
+#endif
 }
\ No newline at end of file
diff --git a/Runtime/TweenManager.cs b/Runtime/TweenManager.cs
index 5132ae2..de21dde 100644
--- a/Runtime/TweenManager.cs
+++ b/Runtime/TweenManager.cs
@@ -30,7 +30,7 @@ private void OnEnable() {
                 _texts[i].ForceMeshUpdate(true);
             }
             
-            DisposeArrays();
+            DisposeArrays(_texts);
             CreateNativeArrays();
             ApplyModifiers(Progress);
             TMPro_EventManager.TEXT_CHANGED_EVENT.Add(OnTextChanged);
@@ -61,7 +61,7 @@ private void OnTextChanged(Object obj) {
 
             if (!found) return;
             
-            DisposeArrays();
+            DisposeArrays(_texts);
             CreateNativeArrays();
             ApplyModifiers(Progress);
         }
@@ -142,17 +142,17 @@ private void ApplyModifiers(float progress) {
             
             _jobHandle.Complete();
             
-            UpdateMeshes(vertices, colors);
+            UpdateMeshes(_texts, vertices, colors);
 
             _current = Progress;
             vertices.Dispose();
             colors.Dispose();
         }
 
-        private void UpdateMeshes(NativeArray<float3> vertices, NativeArray<float4> colors) {
+        private void UpdateMeshes(IReadOnlyList<TMP_Text> texts, NativeArray<float3> vertices, NativeArray<float4> colors) {
             var offset = 0;
-            for (var i = 0; i < _texts.Length; i++) {
-                var text = _texts[i];
+            for (var i = 0; i < texts.Count; i++) {
+                var text = texts[i];
                 if (text.mesh == null) continue;
                 var count = text.mesh.vertexCount;
                 text.mesh.SetVertices(vertices, offset, count);
@@ -170,16 +170,21 @@ private void UpdateMeshes(NativeArray<float3> vertices, NativeArray<float4> colo
         }
 
         public void Dispose() {
-            DisposeArrays();
+            Dispose(_texts);
+        }
+        
+        
+        public void Dispose(IReadOnlyList<TMP_Text> texts) {
+            DisposeArrays(texts);
         }
 
-        private void DisposeArrays() {
+        private void DisposeArrays(IReadOnlyList<TMP_Text> texts) {
             _jobHandle.Complete();
             if (_charData.IsCreated) {
                 _charData.Dispose();
             }
             if (_vertices.IsCreated && _colors.IsCreated) {
-                UpdateMeshes(_vertices, _colors);
+                UpdateMeshes(texts, _vertices, _colors);
                 _vertices.Dispose();
                 _colors.Dispose();
             }


Reviews:
user: login: wallstop
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-28T22:03:26Z
user: login: wallstop
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-28T22:04:56Z
user: login: wallstop
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-28T22:06:09Z
user: login: AlicanHasirci
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-28T22:48:17Z
user: login: wallstop
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-28T23:08:15Z

Review comments:
diff_hunk: @@ -2,7 +2,8 @@
     "name": "TextTween.Editor",
     "rootNamespace": "TextTween",
     "references": [
-        "GUID:999262d7d7e04288bc6aebef915dc2b8"
+        "GUID:999262d7d7e04288bc6aebef915dc2b8",
+        "GUID:6055be8ebefd69e48b49212b09b47b2f"
path: Editor/TextTween.Editor.asmdef
user: login: wallstop
body: Add TextMeshPro reference
created_at: 2025-03-28T22:03:25Z
author_association: COLLABORATOR
diff_hunk: @@ -1,58 +1,70 @@
-using UnityEditor;
path: Editor/TextTweenEditor.cs
user: login: wallstop
body: Usings should go inside of the namespace. This prevents accidental editor resolution in other files. See [1](https://stackoverflow.com/questions/125319/should-using-directives-be-inside-or-outside-the-namespace-in-c), 
created_at: 2025-03-28T22:04:56Z
author_association: COLLABORATOR
diff_hunk: @@ -1,58 +1,72 @@
-using UnityEditor;
-
 namespace TextTween.Editor {
+#if UNITY_EDITOR
path: Editor/TextTweenEditor.cs
user: login: wallstop
body: Some people might not be using assembly definitions in their project. If they don't, not having this can break them.
created_at: 2025-03-28T22:06:09Z
author_association: COLLABORATOR
diff_hunk: @@ -1,58 +1,72 @@
-using UnityEditor;
-
 namespace TextTween.Editor {
+#if UNITY_EDITOR
path: Editor/TextTweenEditor.cs
user: login: AlicanHasirci
body: I've never seen someone not using assembly definitions, but better safe than sorry I guess 😄 
created_at: 2025-03-28T22:48:16Z
author_association: OWNER
diff_hunk: @@ -1,58 +1,72 @@
-using UnityEditor;
-
 namespace TextTween.Editor {
+#if UNITY_EDITOR
path: Editor/TextTweenEditor.cs
user: login: wallstop
body: I learned this last year when someone used one of my libraries for a gamejam and they weren't familiar with assembly definitions - my code broke them. Since then I try to make sure I apply the `#if UNITY_EDITOR` around all Editor-only code *just* to make sure it can be used by all.
created_at: 2025-03-28T23:08:15Z
author_association: COLLABORATOR

PR comments:
user: login: AlicanHasirci
created_at: 2025-03-28T22:55:28Z
body: I'm opening the PR for #9  soon both for texts and modifiers which should ease the pain a bit, but you are right, drag+drop should be working for arrays.
 The only problem here is we should be calling 'tweenManager.Dispose()' before the new values are applied to object otherwise adding and removing texts become an issue (i.e. removing a text from the list leaves the text modified permanently until it is regenerated)
author_association: OWNER
user: login: wallstop
created_at: 2025-03-28T23:04:53Z
body: > I'm opening the PR for #9 soon both for texts and modifiers which should ease the pain a bit, but you are right, drag+drop should be working for arrays. The only problem here is we should be calling 'tweenManager.Dispose()' before the new values are applied to object otherwise adding and removing texts become an issue (i.e. removing a text from the list leaves the text modified permanently until it is regenerated)

Great catch, I've updated with a fix + tested manually in-editor.
author_association: COLLABORATOR
user: login: AlicanHasirci
created_at: 2025-03-29T12:46:21Z
body: LGTM
author_association: OWNER