Particles/SkReflected: Generalize array reflection

This supports arrays of any type, and removes all of the special case
code for arrays of SkReflected objects. (This is extracted from my
rewrite of SkCurve, which needed something like this to work).

Bug: skia:
Change-Id: I55ab942f7922335dca0685d28b3b122bc4d53daa
Reviewed-on: https://skia-review.googlesource.com/c/192620
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/modules/particles/include/SkParticleSerialization.h b/modules/particles/include/SkParticleSerialization.h
index b4f51d7..946dfd6 100644
--- a/modules/particles/include/SkParticleSerialization.h
+++ b/modules/particles/include/SkParticleSerialization.h
@@ -56,13 +56,13 @@
     void enterObject(const char* name) override { fWriter.beginObject(name); }
     void exitObject()                  override { fWriter.endObject(); }
 
-    void visit(const char* name, SkTArray<sk_sp<SkReflected>>& arr,
-               const SkReflected::Type* baseType) override {
+    int enterArray(const char* name, int oldCount) override {
         fWriter.beginArray(name);
-        for (auto ptr : arr) {
-            SkFieldVisitor::visit(nullptr, ptr);
-        }
+        return oldCount;
+    }
+    ArrayEdit exitArray() override {
         fWriter.endArray();
+        return ArrayEdit();
     }
 
 private:
@@ -124,34 +124,25 @@
         fStack.pop_back();
     }
 
-    void visit(const char* name, SkTArray<sk_sp<SkReflected>>& arr,
-               const SkReflected::Type* baseType) override {
-        arr.reset();
-
-        if (const skjson::ArrayValue* arrVal = get(name)) {
-            arr.reserve(arrVal->size());
-
-            for (const skjson::ObjectValue* obj : *arrVal) {
-                sk_sp<SkReflected> ptr = nullptr;
-                if (obj) {
-                    fStack.push_back(obj);
-                    this->visit(ptr, baseType);
-                    if (ptr) {
-                        ptr->visitFields(this);
-                    }
-                    fStack.pop_back();
-                }
-                if (ptr && ptr->isOfType(baseType)) {
-                    arr.push_back(ptr);
-                }
-            }
-        }
+    int enterArray(const char* name, int oldCount) override {
+        const skjson::ArrayValue* arrVal = get(name);
+        fStack.push_back(arrVal);
+        fArrayIndexStack.push_back(0);
+        return arrVal ? arrVal->size() : 0;
+    }
+    ArrayEdit exitArray() override {
+        fStack.pop_back();
+        fArrayIndexStack.pop_back();
+        return ArrayEdit();
     }
 
 private:
-    const skjson::Value& get(const char* name) const {
+    const skjson::Value& get(const char* name) {
         if (const skjson::Value* cur = fStack.back()) {
-            if (!name) {
+            if (cur->is<skjson::ArrayValue>()) {
+                SkASSERT(!name);
+                return cur->as<skjson::ArrayValue>()[fArrayIndexStack.back()++];
+            } else if (!name) {
                 return *cur;
             } else if (cur->is<skjson::ObjectValue>()) {
                 return cur->as<skjson::ObjectValue>()[name];
@@ -203,6 +194,7 @@
 
     const skjson::Value& fRoot;
     SkSTArray<16, const skjson::Value*, true> fStack;
+    SkSTArray<16, size_t, true>               fArrayIndexStack;
 };
 
 #endif // SkParticleSerialization_DEFINED
diff --git a/modules/particles/include/SkReflected.h b/modules/particles/include/SkReflected.h
index 6b9d689..ed9d025 100644
--- a/modules/particles/include/SkReflected.h
+++ b/modules/particles/include/SkReflected.h
@@ -132,23 +132,13 @@
         this->exitObject();
     }
 
-    template <typename T>
-    void visit(const char* name, SkTArray<sk_sp<T>>& arr) {
-        SkTArray<sk_sp<SkReflected>> newArr;
-        for (auto ptr : arr) {
-            newArr.push_back(ptr);
+    template <typename T, bool MEM_MOVE>
+    void visit(const char* name, SkTArray<T, MEM_MOVE>& arr) {
+        arr.resize_back(this->enterArray(name, arr.count()));
+        for (int i = 0; i < arr.count(); ++i) {
+            this->visit(nullptr, arr[i]);
         }
-
-        this->visit(name, newArr, T::GetType());
-
-        arr.reset();
-        for (auto ptr : newArr) {
-            if (ptr && !ptr->isOfType(T::GetType())) {
-                ptr.reset();
-            }
-            sk_sp<T> newPtr(static_cast<T*>(ptr.release()));
-            arr.push_back(std::move(newPtr));
-        }
+        this->exitArray().apply(arr);
     }
 
     template <typename T>
@@ -172,11 +162,43 @@
     }
 
 protected:
+    struct ArrayEdit {
+        enum class Verb {
+            kNone,
+            kRemove,
+            kMoveForward,
+        };
+
+        Verb fVerb = Verb::kNone;
+        int fIndex = 0;
+
+        template <typename T, bool MEM_MOVE>
+        void apply(SkTArray<T, MEM_MOVE>& arr) const {
+            switch (fVerb) {
+            case Verb::kNone:
+                break;
+            case Verb::kRemove:
+                for (int i = fIndex; i < arr.count() - 1; ++i) {
+                    arr[i] = arr[i + 1];
+                }
+                arr.pop_back();
+                break;
+            case Verb::kMoveForward:
+                if (fIndex > 0 && fIndex < arr.count()) {
+                    std::swap(arr[fIndex - 1], arr[fIndex]);
+                }
+                break;
+            }
+        }
+    };
+
     virtual void enterObject(const char* name) = 0;
     virtual void exitObject() = 0;
+
+    virtual int enterArray(const char* name, int oldCount) = 0;
+    virtual ArrayEdit exitArray() = 0;
+
     virtual void visit(sk_sp<SkReflected>&, const SkReflected::Type* baseType) = 0;
-    virtual void visit(const char* name, SkTArray<sk_sp<SkReflected>>&,
-                       const SkReflected::Type* baseType) = 0;
 };
 
 #endif // SkReflected_DEFINED
diff --git a/tools/viewer/ParticlesSlide.cpp b/tools/viewer/ParticlesSlide.cpp
index c91e9bf..a948c7e 100644
--- a/tools/viewer/ParticlesSlide.cpp
+++ b/tools/viewer/ParticlesSlide.cpp
@@ -96,37 +96,40 @@
     void visit(const char* name, float& f, SkField field) override {
         if (fTreeStack.back()) {
             if (field.fFlags & SkField::kAngle_Field) {
-                ImGui::SliderAngle(name, &f, 0.0f);
+                ImGui::SliderAngle(item(name), &f, 0.0f);
             } else {
-                ImGui::DragFloat(name, &f);
+                ImGui::DragFloat(item(name), &f);
             }
         }
     }
     void visit(const char* name, int& i, SkField) override {
-        IF_OPEN(ImGui::DragInt(name, &i))
+        IF_OPEN(ImGui::DragInt(item(name), &i))
     }
     void visit(const char* name, bool& b, SkField) override {
-        IF_OPEN(ImGui::Checkbox(name, &b))
+        IF_OPEN(ImGui::Checkbox(item(name), &b))
     }
     void visit(const char* name, SkString& s, SkField) override {
         if (fTreeStack.back()) {
             ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackResize;
-            ImGui::InputText(name, s.writable_str(), s.size() + 1, flags, InputTextCallback, &s);
+            ImGui::InputText(item(name), s.writable_str(), s.size() + 1, flags, InputTextCallback,
+                             &s);
         }
     }
 
     void visit(const char* name, SkPoint& p, SkField) override {
         if (fTreeStack.back()) {
-            ImGui::DragFloat2(name, &p.fX);
+            ImGui::DragFloat2(item(name), &p.fX);
             gDragPoints.push_back(&p);
         }
     }
     void visit(const char* name, SkColor4f& c, SkField) override {
-        IF_OPEN(ImGui::ColorEdit4(name, c.vec()))
+        IF_OPEN(ImGui::ColorEdit4(item(name), c.vec()))
     }
 
+#undef IF_OPEN
+
     void visit(const char* name, SkCurve& c, SkField) override {
-        this->enterObject(name);
+        this->enterObject(item(name));
         if (fTreeStack.back()) {
             ImGui::Checkbox("Ranged", &c.fRanged);
             ImGui::DragFloat4("Min", c.fMin);
@@ -157,7 +160,8 @@
 
     void enterObject(const char* name) override {
         if (fTreeStack.back()) {
-            fTreeStack.push_back(ImGui::TreeNode(name));
+            fTreeStack.push_back(ImGui::TreeNodeEx(item(name),
+                                                   ImGuiTreeNodeFlags_AllowItemOverlap));
         } else {
             fTreeStack.push_back(false);
         }
@@ -169,54 +173,66 @@
         fTreeStack.pop_back();
     }
 
-#undef IF_OPEN
+    int enterArray(const char* name, int oldCount) override {
+        this->enterObject(item(name));
+        fArrayCounterStack.push_back(0);
+        fArrayEditStack.push_back();
 
-    void visit(const char* name, SkTArray<sk_sp<SkReflected>>& arr,
-               const SkReflected::Type* baseType) override {
-        this->enterObject(name);
+        int count = oldCount;
         if (fTreeStack.back()) {
-            for (int i = 0; i < arr.count(); ++i) {
-                ImGui::PushID(i);
-
-                if (ImGui::Button("X")) {
-                    for (int j = i; j < arr.count() - 1; ++j) {
-                        arr[j] = arr[j + 1];
-                    }
-                    arr.pop_back();
-                    ImGui::PopID();
-                    continue;
-                }
-
-                ImGui::SameLine();
-                if (ImGui::Button("^") && i > 0) {
-                    std::swap(arr[i], arr[i - 1]);
-                }
-                ImGui::SameLine();
-                if (ImGui::Button("v") && i < arr.count() - 1) {
-                    std::swap(arr[i], arr[i + 1]);
-                }
-
-                const char* typeName = arr[i] ? arr[i]->getType()->fName : "Null";
-                ImGui::SameLine(); this->enterObject(typeName);
-
-                this->visit(arr[i], baseType);
-                if (arr[i]) {
-                    arr[i]->visitFields(this);
-                }
-
-                this->exitObject();
-                ImGui::PopID();
-            }
-
+            ImGui::SameLine();
             if (ImGui::Button("+")) {
-                arr.push_back(nullptr);
+                ++count;
             }
         }
+        return count;
+    }
+    ArrayEdit exitArray() override {
+        fArrayCounterStack.pop_back();
+        auto edit = fArrayEditStack.back();
+        fArrayEditStack.pop_back();
         this->exitObject();
+        return edit;
     }
 
 private:
+    const char* item(const char* name) {
+        if (name) {
+            return name;
+        }
+
+        // We're in an array. Add extra controls and a dynamic label.
+        int index = fArrayCounterStack.back()++;
+        ArrayEdit& edit(fArrayEditStack.back());
+        fScratchLabel = SkStringPrintf("[%d]", index);
+
+        ImGui::PushID(index);
+
+        if (ImGui::Button("X")) {
+            edit.fVerb = ArrayEdit::Verb::kRemove;
+            edit.fIndex = index;
+        }
+        ImGui::SameLine();
+        if (ImGui::Button("^")) {
+            edit.fVerb = ArrayEdit::Verb::kMoveForward;
+            edit.fIndex = index;
+        }
+        ImGui::SameLine();
+        if (ImGui::Button("v")) {
+            edit.fVerb = ArrayEdit::Verb::kMoveForward;
+            edit.fIndex = index + 1;
+        }
+        ImGui::SameLine();
+
+        ImGui::PopID();
+
+        return fScratchLabel.c_str();
+    }
+
     SkSTArray<16, bool, true> fTreeStack;
+    SkSTArray<16, int, true>  fArrayCounterStack;
+    SkSTArray<16, ArrayEdit, true> fArrayEditStack;
+    SkString fScratchLabel;
 };
 
 static sk_sp<SkParticleEffectParams> LoadEffectParams(const char* filename) {