Particles: Better integration for ResourceProvider

This untangles some of the dirty state tracking and dynamic rebuilding
support (that's only needed for the GUI editor), so the core code is
more streamlined. It also paves the way for feeding the RP to bindings.

Change-Id: I208ec59622154fdb2845c3ae8f7efb070d1abfc7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/257476
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/modules/canvaskit/particles_bindings.cpp b/modules/canvaskit/particles_bindings.cpp
index 2aa679b..abc51ee 100644
--- a/modules/canvaskit/particles_bindings.cpp
+++ b/modules/canvaskit/particles_bindings.cpp
@@ -114,11 +114,9 @@
         skjson::DOM dom(json.c_str(), json.length());
         SkFromJsonVisitor fromJson(dom.root());
         params->visitFields(&fromJson);
-        return sk_sp<SkParticleEffect>(new SkParticleEffect(
-                std::move(params),
-                skresources::DataURIResourceProviderProxy::Make(
-                    ParticleAssetProvider::Make(std::move(assets))),
-                r));
+        params->prepare(skresources::DataURIResourceProviderProxy::Make(
+                            ParticleAssetProvider::Make(std::move(assets))).get());
+        return sk_sp<SkParticleEffect>(new SkParticleEffect(std::move(params), r));
     }));
     constant("particles", true);
 
diff --git a/modules/particles/include/SkParticleBinding.h b/modules/particles/include/SkParticleBinding.h
index 39665f6..156ef53 100644
--- a/modules/particles/include/SkParticleBinding.h
+++ b/modules/particles/include/SkParticleBinding.h
@@ -18,6 +18,10 @@
 class SkParticleEffectParams;
 class SkRandom;
 
+namespace skresources {
+    class ResourceProvider;
+}
+
 namespace SkSL {
     class Compiler;
 }
@@ -47,7 +51,9 @@
     REFLECTED_ABSTRACT(SkParticleBinding, SkReflected)
 
     void visitFields(SkFieldVisitor* v) override;
+
     virtual std::unique_ptr<SkParticleExternalValue> toValue(SkSL::Compiler&) = 0;
+    virtual void prepare(const skresources::ResourceProvider*) = 0;
 
     static void RegisterBindingTypes();
 
diff --git a/modules/particles/include/SkParticleDrawable.h b/modules/particles/include/SkParticleDrawable.h
index fda784f..91c5922 100644
--- a/modules/particles/include/SkParticleDrawable.h
+++ b/modules/particles/include/SkParticleDrawable.h
@@ -21,8 +21,9 @@
 public:
     REFLECTED_ABSTRACT(SkParticleDrawable, SkReflected)
 
-    virtual void draw(const skresources::ResourceProvider* resourceProvider, SkCanvas* canvas,
-                      const SkParticles& particles, int count, const SkPaint& paint) = 0;
+    virtual void draw(SkCanvas* canvas, const SkParticles& particles, int count,
+                      const SkPaint& paint) = 0;
+    virtual void prepare(const skresources::ResourceProvider* resourceProvider) = 0;
 
     static void RegisterDrawableTypes();
 
diff --git a/modules/particles/include/SkParticleEffect.h b/modules/particles/include/SkParticleEffect.h
index 242dc1c..b19ce2f 100644
--- a/modules/particles/include/SkParticleEffect.h
+++ b/modules/particles/include/SkParticleEffect.h
@@ -115,6 +115,9 @@
 
     void visitFields(SkFieldVisitor* v);
 
+    // Load/compute cached resources
+    void prepare(const skresources::ResourceProvider*);
+
 private:
     friend class SkParticleEffect;
 
@@ -126,14 +129,11 @@
 
     Program fEffectProgram;
     Program fParticleProgram;
-
-    void rebuild();
 };
 
 class SkParticleEffect : public SkRefCnt {
 public:
-    SkParticleEffect(sk_sp<SkParticleEffectParams> params,
-                     sk_sp<skresources::ResourceProvider> rp, const SkRandom& random);
+    SkParticleEffect(sk_sp<SkParticleEffectParams> params, const SkRandom& random);
 
     // Start playing this effect, specifying initial values for the emitter's properties
     void start(double now, bool looping, SkPoint position, SkVector heading, float scale,
@@ -204,7 +204,6 @@
     void runParticleScript(double now, const char* entry, int start, int count);
 
     sk_sp<SkParticleEffectParams>        fParams;
-    sk_sp<skresources::ResourceProvider> fResourceProvider;
 
     SkRandom fRandom;
 
diff --git a/modules/particles/include/SkParticleSerialization.h b/modules/particles/include/SkParticleSerialization.h
index 8cbc1c2..609650a 100644
--- a/modules/particles/include/SkParticleSerialization.h
+++ b/modules/particles/include/SkParticleSerialization.h
@@ -42,27 +42,8 @@
             fWriter.appendString(name, s.c_str());
         }
     }
-    void visit(const char* name, int& i, const EnumStringMapping* map, int count) override {
-        fWriter.appendString(name, EnumToString(i, map, count));
-    }
 
     // Compound types
-    void visit(const char* name, SkPoint& p) override {
-        fWriter.beginObject(name, false);
-        fWriter.appendFloat("x", p.fX);
-        fWriter.appendFloat("y", p.fY);
-        fWriter.endObject();
-    }
-
-    void visit(const char* name, SkColor4f& c) override {
-        fWriter.beginArray(name, false);
-        fWriter.appendFloat(c.fR);
-        fWriter.appendFloat(c.fG);
-        fWriter.appendFloat(c.fB);
-        fWriter.appendFloat(c.fA);
-        fWriter.endArray();
-    }
-
     void visit(sk_sp<SkReflected>& e, const SkReflected::Type* baseType) override {
         fWriter.appendString("Type", e ? e->getType()->fName : "Null");
     }
@@ -115,29 +96,6 @@
             TryParse(get(name), s);
         }
     }
-    void visit(const char* name, int& i, const EnumStringMapping* map, int count) override {
-        SkString str;
-        if (TryParse(get(name), str)) {
-            i = StringToEnum(str.c_str(), map, count);
-        }
-    }
-
-    void visit(const char* name, SkPoint& p) override {
-        if (const skjson::ObjectValue* obj = get(name)) {
-            TryParse((*obj)["x"], p.fX);
-            TryParse((*obj)["y"], p.fY);
-        }
-    }
-
-    void visit(const char* name, SkColor4f& c) override {
-        const skjson::ArrayValue* arr = get(name);
-        if (arr && arr->size() == 4) {
-            TryParse((*arr)[0], c.fR);
-            TryParse((*arr)[1], c.fG);
-            TryParse((*arr)[2], c.fB);
-            TryParse((*arr)[3], c.fA);
-        }
-    }
 
     void visit(sk_sp<SkReflected>& e, const SkReflected::Type* baseType) override {
         const skjson::StringValue* typeString = get("Type");
diff --git a/modules/particles/include/SkReflected.h b/modules/particles/include/SkReflected.h
index beb7291..104b482 100644
--- a/modules/particles/include/SkReflected.h
+++ b/modules/particles/include/SkReflected.h
@@ -128,8 +128,8 @@
  * SkReflected types, and of types that implement the visitFields() function.
  *
  * Classes implementing the interface must supply implementations of virtual functions that visit
- * basic types (float, int, bool, SkString, etc...), as well as helper methods for entering the
- * scope of an object or array.
+ * basic types (float, int, bool, SkString), as well as helper methods for entering the scope of
+ * an object or array.
  *
  * All visit functions supply a field name, and a non-constant reference to an actual field.
  * This allows visitors to serialize or deserialize collections of objects, or perform edits on
@@ -152,28 +152,9 @@
     virtual void visit(const char*, bool&) = 0;
     virtual void visit(const char*, SkString&) = 0;
 
-    virtual void visit(const char*, SkPoint&) = 0;
-    virtual void visit(const char*, SkColor4f&) = 0;
-
-    // Accommodation for enums, where caller can supply a value <-> string map
-    struct EnumStringMapping {
-        int         fValue;
-        const char* fName;
-    };
-    virtual void visit(const char*, int&, const EnumStringMapping*, int count) = 0;
-
-    // Default visit function for structs with no special behavior. It is assumed that any such
-    // struct implements visitFields(SkFieldVisitor*) to recursively visit each of its fields.
-    template <typename T>
-    void visit(const char* name, T& value) {
-        this->enterObject(name);
-        value.visitFields(this);
-        this->exitObject();
-    }
-
     // Specialization for SkTArrays. In conjunction with the enterArray/exitArray virtuals, this
     // allows visitors to resize an array (for deserialization), and apply a single edit operation
-    // (remove or move a single element). Each element of the array is visited as normal.
+    // (remove a single element). Each element of the array is visited as normal.
     template <typename T, bool MEM_MOVE>
     void visit(const char* name, SkTArray<T, MEM_MOVE>& arr) {
         arr.resize_back(this->enterArray(name, arr.count()));
@@ -214,7 +195,6 @@
         enum class Verb {
             kNone,
             kRemove,
-            kMoveForward,
         };
 
         Verb fVerb = Verb::kNone;
@@ -231,32 +211,11 @@
                 }
                 arr.pop_back();
                 break;
-            case Verb::kMoveForward:
-                if (fIndex > 0 && fIndex < arr.count()) {
-                    std::swap(arr[fIndex - 1], arr[fIndex]);
-                }
-                break;
             }
         }
     };
 
-    static const char* EnumToString(int value, const EnumStringMapping* map, int count) {
-        for (int i = 0; i < count; ++i) {
-            if (map[i].fValue == value) {
-                return map[i].fName;
-            }
-        }
-        return nullptr;
-    }
-    static int StringToEnum(const char* str, const EnumStringMapping* map, int count) {
-        for (int i = 0; i < count; ++i) {
-            if (0 == strcmp(str, map[i].fName)) {
-                return map[i].fValue;
-            }
-        }
-        return -1;
-    }
-
+private:
     virtual void enterObject(const char* name) = 0;
     virtual void exitObject() = 0;
 
diff --git a/modules/particles/src/SkParticleBinding.cpp b/modules/particles/src/SkParticleBinding.cpp
index 1f41d5c..e0d3328 100644
--- a/modules/particles/src/SkParticleBinding.cpp
+++ b/modules/particles/src/SkParticleBinding.cpp
@@ -14,6 +14,7 @@
 #include "include/utils/SkTextUtils.h"
 #include "modules/particles/include/SkParticleEffect.h"
 #include "modules/particles/include/SkReflected.h"
+#include "modules/skresources/include/SkResources.h"
 #include "src/sksl/SkSLCompiler.h"
 
 void SkParticleBinding::visitFields(SkFieldVisitor* v) {
@@ -64,6 +65,10 @@
             new SkEffectExternalValue(fName.c_str(), compiler, fParams));
     }
 
+    void prepare(const skresources::ResourceProvider* resourceProvider) override {
+        fParams->prepare(resourceProvider);
+    }
+
 private:
     sk_sp<SkParticleEffectParams> fParams;
 };
@@ -121,21 +126,13 @@
 public:
     SkPathBinding(const char* name = "", const char* path = "")
             : SkParticleBinding(name)
-            , fPath(path) {
-        this->rebuild();
-    }
+            , fPath(path) {}
 
     REFLECTED(SkPathBinding, SkParticleBinding)
 
     void visitFields(SkFieldVisitor* v) override {
-        SkString oldPath = fPath;
-
         SkParticleBinding::visitFields(v);
         v->visit("Path", fPath);
-
-        if (fPath != oldPath) {
-            this->rebuild();
-        }
     }
 
     std::unique_ptr<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
@@ -143,16 +140,16 @@
             new SkPathExternalValue(fName.c_str(), compiler, &fContours));
     }
 
-private:
-    SkString fPath;
-
-    void rebuild() {
+    void prepare(const skresources::ResourceProvider*) override {
         SkPath path;
         if (SkParsePath::FromSVGString(fPath.c_str(), &path)) {
             fContours.rebuild(path);
         }
     }
 
+private:
+    SkString fPath;
+
     // Cached
     SkPathContours fContours;
 };
@@ -162,23 +159,14 @@
     SkTextBinding(const char* name = "", const char* text = "", SkScalar fontSize = 96)
             : SkParticleBinding(name)
             , fText(text)
-            , fFontSize(fontSize) {
-        this->rebuild();
-    }
+            , fFontSize(fontSize) {}
 
     REFLECTED(SkTextBinding, SkParticleBinding)
 
     void visitFields(SkFieldVisitor* v) override {
-        SkString oldText = fText;
-        SkScalar oldSize = fFontSize;
-
         SkParticleBinding::visitFields(v);
         v->visit("Text", fText);
         v->visit("FontSize", fFontSize);
-
-        if (fText != oldText || fFontSize != oldSize) {
-            this->rebuild();
-        }
     }
 
     std::unique_ptr<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
@@ -186,11 +174,7 @@
             new SkPathExternalValue(fName.c_str(), compiler, &fContours));
     }
 
-private:
-    SkString fText;
-    SkScalar fFontSize;
-
-    void rebuild() {
+    void prepare(const skresources::ResourceProvider*) override {
         if (fText.isEmpty()) {
             return;
         }
@@ -201,6 +185,10 @@
         fContours.rebuild(path);
     }
 
+private:
+    SkString fText;
+    SkScalar fFontSize;
+
     // Cached
     SkPathContours fContours;
 };
diff --git a/modules/particles/src/SkParticleDrawable.cpp b/modules/particles/src/SkParticleDrawable.cpp
index 0d5766b..addbe5e 100644
--- a/modules/particles/src/SkParticleDrawable.cpp
+++ b/modules/particles/src/SkParticleDrawable.cpp
@@ -72,16 +72,14 @@
 
 class SkCircleDrawable : public SkParticleDrawable {
 public:
-    SkCircleDrawable(int radius = 1)
-            : fRadius(radius) {
-        this->rebuild();
-    }
+    SkCircleDrawable(int radius = 1) : fRadius(radius) {}
 
     REFLECTED(SkCircleDrawable, SkParticleDrawable)
 
-    void draw(const skresources::ResourceProvider* /* unused */, SkCanvas* canvas,
-              const SkParticles& particles, int count, const SkPaint& paint) override {
-        SkPoint center = { SkIntToScalar(fRadius), SkIntToScalar(fRadius) };
+    void draw(SkCanvas* canvas, const SkParticles& particles, int count,
+              const SkPaint& paint) override {
+        int r = SkTMax(fRadius, 1);
+        SkPoint center = { SkIntToScalar(r), SkIntToScalar(r) };
         DrawAtlasArrays arrays(particles, count, center);
         for (int i = 0; i < count; ++i) {
             arrays.fRects[i].setIWH(fImage->width(), fImage->height());
@@ -90,21 +88,20 @@
                           count, SkBlendMode::kModulate, nullptr, &paint);
     }
 
+    void prepare(const skresources::ResourceProvider*) override {
+        int r = SkTMax(fRadius, 1);
+        if (!fImage || fImage->width() != 2 * r) {
+            fImage = make_circle_image(r);
+        }
+    }
+
     void visitFields(SkFieldVisitor* v) override {
         v->visit("Radius", fRadius);
-        this->rebuild();
     }
 
 private:
     int fRadius;
 
-    void rebuild() {
-        fRadius = SkTMax(fRadius, 1);
-        if (!fImage || fImage->width() != 2 * fRadius) {
-            fImage = make_circle_image(fRadius);
-        }
-    }
-
     // Cached
     sk_sp<SkImage> fImage;
 };
@@ -116,60 +113,33 @@
             : fPath(path)
             , fName(name)
             , fCols(cols)
-            , fRows(rows)
-            , fDirty(true) {}
+            , fRows(rows) {}
 
     REFLECTED(SkImageDrawable, SkParticleDrawable)
 
-    void draw(const skresources::ResourceProvider* resourceProvider, SkCanvas* canvas,
-              const SkParticles& particles, int count, const SkPaint& paint) override {
-        this->rebuildIfDirty(resourceProvider);
-
-        SkRect baseRect = SkRect::MakeWH(static_cast<float>(fImage->width()) / fCols,
-                                         static_cast<float>(fImage->height()) / fRows);
+    void draw(SkCanvas* canvas, const SkParticles& particles, int count,
+              const SkPaint& paint) override {
+        int cols = SkTMax(fCols, 1),
+            rows = SkTMax(fRows, 1);
+        SkRect baseRect = SkRect::MakeWH(static_cast<float>(fImage->width()) / cols,
+                                         static_cast<float>(fImage->height()) / rows);
         SkPoint center = { baseRect.width() * 0.5f, baseRect.height() * 0.5f };
         DrawAtlasArrays arrays(particles, count, center);
 
-        int frameCount = fCols * fRows;
+        int frameCount = cols * rows;
         float* spriteFrames = particles.fData[SkParticles::kSpriteFrame].get();
         for (int i = 0; i < count; ++i) {
             int frame = static_cast<int>(spriteFrames[i] * frameCount + 0.5f);
             frame = SkTPin(frame, 0, frameCount - 1);
-            int row = frame / fCols;
-            int col = frame % fCols;
+            int row = frame / cols;
+            int col = frame % cols;
             arrays.fRects[i] = baseRect.makeOffset(col * baseRect.width(), row * baseRect.height());
         }
         canvas->drawAtlas(fImage, arrays.fXforms.get(), arrays.fRects.get(), arrays.fColors.get(),
                           count, SkBlendMode::kModulate, nullptr, &paint);
     }
 
-    void visitFields(SkFieldVisitor* v) override {
-        SkString oldPath = fPath,
-                 oldName = fName;
-
-        v->visit("Path", fPath);
-        v->visit("Name", fName);
-        v->visit("Columns", fCols);
-        v->visit("Rows", fRows);
-
-        fCols = SkTMax(fCols, 1);
-        fRows = SkTMax(fRows, 1);
-        if (oldPath != fPath || oldName != fName) {
-            fDirty = true;
-        }
-    }
-
-private:
-    SkString fPath;
-    SkString fName;
-    int      fCols;
-    int      fRows;
-
-    void rebuildIfDirty(const skresources::ResourceProvider* resourceProvider) {
-        if (!fDirty) {
-            return;
-        }
-
+    void prepare(const skresources::ResourceProvider* resourceProvider) override {
         fImage.reset();
         if (auto asset = resourceProvider->loadImageAsset(fPath.c_str(), fName.c_str(), nullptr)) {
             fImage = asset->getFrame(0);
@@ -178,12 +148,23 @@
             SkDebugf("Could not load image \"%s:%s\"\n", fPath.c_str(), fName.c_str());
             fImage = make_circle_image(1);
         }
-        fDirty = false;
     }
 
+    void visitFields(SkFieldVisitor* v) override {
+        v->visit("Path", fPath);
+        v->visit("Name", fName);
+        v->visit("Columns", fCols);
+        v->visit("Rows", fRows);
+    }
+
+private:
+    SkString fPath;
+    SkString fName;
+    int      fCols;
+    int      fRows;
+
     // Cached
     sk_sp<SkImage> fImage;
-    bool           fDirty;
 };
 
 void SkParticleDrawable::RegisterDrawableTypes() {
diff --git a/modules/particles/src/SkParticleEffect.cpp b/modules/particles/src/SkParticleEffect.cpp
index 105dcda..fe00f5c 100644
--- a/modules/particles/src/SkParticleEffect.cpp
+++ b/modules/particles/src/SkParticleEffect.cpp
@@ -100,30 +100,26 @@
         : fMaxCount(128)
         , fDrawable(nullptr)
         , fEffectCode(kDefaultEffectCode)
-        , fParticleCode(kDefaultParticleCode) {
-    this->rebuild();
-}
+        , fParticleCode(kDefaultParticleCode) {}
 
 void SkParticleEffectParams::visitFields(SkFieldVisitor* v) {
-    SkString oldEffectCode = fEffectCode;
-    SkString oldParticleCode = fParticleCode;
-
     v->visit("MaxCount", fMaxCount);
-
     v->visit("Drawable", fDrawable);
-
     v->visit("EffectCode", fEffectCode);
     v->visit("Code", fParticleCode);
-
     v->visit("Bindings", fBindings);
-
-    // TODO: Or, if any change to binding metadata?
-    if (fParticleCode != oldParticleCode || fEffectCode != oldEffectCode) {
-        this->rebuild();
-    }
 }
 
-void SkParticleEffectParams::rebuild() {
+void SkParticleEffectParams::prepare(const skresources::ResourceProvider* resourceProvider) {
+    for (auto& binding : fBindings) {
+        if (binding) {
+            binding->prepare(resourceProvider);
+        }
+    }
+    if (fDrawable) {
+        fDrawable->prepare(resourceProvider);
+    }
+
     auto buildProgram = [this](const SkSL::String& code, Program* p) {
         SkSL::Compiler compiler;
         SkSL::Program::Settings settings;
@@ -169,11 +165,8 @@
     buildProgram(particleCode, &fParticleProgram);
 }
 
-SkParticleEffect::SkParticleEffect(sk_sp<SkParticleEffectParams> params,
-                                   sk_sp<skresources::ResourceProvider> rp,
-                                   const SkRandom& random)
+SkParticleEffect::SkParticleEffect(sk_sp<SkParticleEffectParams> params, const SkRandom& random)
         : fParams(std::move(params))
-        , fResourceProvider(std::move(rp))
         , fRandom(random)
         , fLooping(false)
         , fCount(0)
@@ -218,7 +211,7 @@
 void SkParticleEffect::processEffectSpawnRequests(double now) {
     for (const auto& spawnReq : fSpawnRequests) {
         sk_sp<SkParticleEffect> newEffect(new SkParticleEffect(std::move(spawnReq.fParams),
-                                                               fResourceProvider, fRandom));
+                                                               fRandom));
         fRandom.nextU();
 
         newEffect->start(now, spawnReq.fLoop, fState.fPosition, fState.fHeading, fState.fScale,
@@ -249,7 +242,6 @@
     for (const auto& spawnReq : fSpawnRequests) {
         int idx = start + spawnReq.fIndex;
         sk_sp<SkParticleEffect> newEffect(new SkParticleEffect(std::move(spawnReq.fParams),
-                                                               fResourceProvider,
                                                                fParticles.fRandom[idx]));
         newEffect->start(now, spawnReq.fLoop,
                          { data[SkParticles::kPositionX      ][idx],
@@ -472,7 +464,7 @@
     if (this->isAlive(false) && fParams->fDrawable) {
         SkPaint paint;
         paint.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
-        fParams->fDrawable->draw(fResourceProvider.get(), canvas, fParticles, fCount, paint);
+        fParams->fDrawable->draw(canvas, fParticles, fCount, paint);
     }
 
     for (const auto& subEffect : fSubEffects) {
diff --git a/tools/viewer/ParticlesSlide.cpp b/tools/viewer/ParticlesSlide.cpp
index 71a4c5b..0809352b 100644
--- a/tools/viewer/ParticlesSlide.cpp
+++ b/tools/viewer/ParticlesSlide.cpp
@@ -58,17 +58,16 @@
         fTreeStack.push_back(true);
     }
 
-#define IF_OPEN(WIDGET) if (fTreeStack.back()) { WIDGET; }
-
     void visit(const char* name, float& f) override {
-        IF_OPEN(ImGui::DragFloat(item(name), &f))
+        fDirty = (fTreeStack.back() && ImGui::DragFloat(item(name), &f)) || fDirty;
     }
     void visit(const char* name, int& i) override {
-        IF_OPEN(ImGui::DragInt(item(name), &i))
+        fDirty = (fTreeStack.back() && ImGui::DragInt(item(name), &i)) || fDirty;
     }
     void visit(const char* name, bool& b) override {
-        IF_OPEN(ImGui::Checkbox(item(name), &b))
+        fDirty = (fTreeStack.back() && ImGui::Checkbox(item(name), &b)) || fDirty;
     }
+
     void visit(const char* name, SkString& s) override {
         if (fTreeStack.back()) {
             int lines = count_lines(s);
@@ -76,48 +75,26 @@
             if (lines > 1) {
                 ImGui::LabelText("##Label", "%s", name);
                 ImVec2 boxSize(-1.0f, ImGui::GetTextLineHeight() * (lines + 1));
-                ImGui::InputTextMultiline(item(name), s.writable_str(), s.size() + 1, boxSize,
-                                          flags, InputTextCallback, &s);
+                fDirty = ImGui::InputTextMultiline(item(name), s.writable_str(), s.size() + 1,
+                                                   boxSize, flags, InputTextCallback, &s)
+                      || fDirty;
             } else {
-                ImGui::InputText(item(name), s.writable_str(), s.size() + 1, flags,
-                                 InputTextCallback, &s);
+                fDirty = ImGui::InputText(item(name), s.writable_str(), s.size() + 1, flags,
+                                          InputTextCallback, &s)
+                      || fDirty;
             }
         }
     }
-    void visit(const char* name, int& i, const EnumStringMapping* map, int count) override {
-        if (fTreeStack.back()) {
-            const char* curStr = EnumToString(i, map, count);
-            if (ImGui::BeginCombo(item(name), curStr ? curStr : "Unknown")) {
-                for (int j = 0; j < count; ++j) {
-                    if (ImGui::Selectable(map[j].fName, i == map[j].fValue)) {
-                        i = map[j].fValue;
-                    }
-                }
-                ImGui::EndCombo();
-            }
-        }
-    }
-
-    void visit(const char* name, SkPoint& p) override {
-        if (fTreeStack.back()) {
-            ImGui::DragFloat2(item(name), &p.fX);
-            gDragPoints.push_back(&p);
-        }
-    }
-    void visit(const char* name, SkColor4f& c) override {
-        IF_OPEN(ImGui::ColorEdit4(item(name), c.vec()))
-    }
-
-#undef IF_OPEN
 
     void visit(sk_sp<SkReflected>& e, const SkReflected::Type* baseType) override {
         if (fTreeStack.back()) {
             const SkReflected::Type* curType = e ? e->getType() : nullptr;
             if (ImGui::BeginCombo("Type", curType ? curType->fName : "Null")) {
-                auto visitType = [baseType, curType, &e](const SkReflected::Type* t) {
+                auto visitType = [baseType, curType, &e, this](const SkReflected::Type* t) {
                     if (t->fFactory && (t == baseType || t->isDerivedFrom(baseType)) &&
                         ImGui::Selectable(t->fName, curType == t)) {
                         e = t->fFactory();
+                        fDirty = true;
                     }
                 };
                 SkReflected::VisitTypes(visitType);
@@ -151,6 +128,7 @@
             ImGui::SameLine();
             if (ImGui::Button("+")) {
                 ++count;
+                fDirty = true;
             }
         }
         return count;
@@ -163,6 +141,8 @@
         return edit;
     }
 
+    bool fDirty = false;
+
 private:
     const char* item(const char* name) {
         if (name) {
@@ -179,16 +159,7 @@
         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;
+            fDirty = true;
         }
         ImGui::SameLine();
 
@@ -223,6 +194,7 @@
             skjson::DOM dom(static_cast<const char*>(fileData->data()), fileData->size());
             SkFromJsonVisitor fromJson(dom.root());
             effect.fParams->visitFields(&fromJson);
+            effect.fParams->prepare(fResourceProvider.get());
             fLoaded.push_back(effect);
         }
     }
@@ -283,7 +255,7 @@
             ImGui::PushID(i);
             if (fAnimated && ImGui::Button("Play")) {
                 sk_sp<SkParticleEffect> effect(new SkParticleEffect(fLoaded[i].fParams,
-                                                                    fResourceProvider, fRandom));
+                                                                    fRandom));
                 effect->start(fAnimationTime, looped);
                 fRunning.push_back({ fPlayPosition, fLoaded[i].fName, effect, false });
                 fRandom.nextU();
@@ -296,6 +268,10 @@
             if (ImGui::TreeNode("##Details")) {
                 fLoaded[i].fParams->visitFields(&gui);
                 ImGui::TreePop();
+                if (gui.fDirty) {
+                    fLoaded[i].fParams->prepare(fResourceProvider.get());
+                    gui.fDirty = false;
+                }
             }
             ImGui::PopID();
         }