Solidify Model/View split

Change-Id: Iecf034feaa009002b5f09c47052c915d22aec0e4
Reviewed-on: https://skia-review.googlesource.com/43040
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 8d9d056..7837aa6 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1710,6 +1710,7 @@
         "tools/debugger/SkJsonWriteBuffer.cpp",
         "tools/debugger/SkObjectParser.cpp",
         "tools/mdbviz/MainWindow.cpp",
+        "tools/mdbviz/Model.cpp",
         "tools/mdbviz/main.cpp",
         "tools/picture_utils.cpp",
 
diff --git a/tools/debugger/SkDebugCanvas.h b/tools/debugger/SkDebugCanvas.h
index 9b944f7..72d65d7 100644
--- a/tools/debugger/SkDebugCanvas.h
+++ b/tools/debugger/SkDebugCanvas.h
@@ -184,6 +184,10 @@
         return SkIRect::MakeWH(this->imageInfo().width(), this->imageInfo().height());
     }
 
+    void detachCommands(SkTDArray<SkDrawCommand*>* dst) {
+        fCommandVector.swap(*dst);
+    }
+
 protected:
     void willSave() override;
 
@@ -266,7 +270,7 @@
     SkTDArray<SkDrawCommand*> fActiveLayers;
 
     /**
-        Adds the command to the classes vector of commands.
+        Adds the command to the class' vector of commands.
         @param command  The draw command for execution
      */
     void addDrawCommand(SkDrawCommand* command);
diff --git a/tools/mdbviz/Model.cpp b/tools/mdbviz/Model.cpp
new file mode 100644
index 0000000..a3f8e1a
--- /dev/null
+++ b/tools/mdbviz/Model.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <memory>
+
+#include "Model.h"
+
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkDebugCanvas.h"
+#include "SkPicture.h"
+#include "SkStream.h"
+
+Model::Model() : fCurOp(0) {
+    SkImageInfo ii = SkImageInfo::MakeN32Premul(1024, 1024);
+    fBM.allocPixels(ii, 0);
+}
+
+Model::~Model() {
+    this->resetOpList();
+}
+
+Model::ErrorCode Model::load(const char* filename) {
+    std::unique_ptr<SkStream> stream = SkStream::MakeFromFile(filename);
+    if (!stream) {
+        return ErrorCode::kCouldntOpenFile;
+    }
+    sk_sp<SkPicture> pic(SkPicture::MakeFromStream(stream.get()));
+    if (!pic) {
+        return ErrorCode::kCouldntDecodeSKP;
+    }
+
+    {
+        std::unique_ptr<SkDebugCanvas> temp(new SkDebugCanvas(
+                                                    SkScalarCeilToInt(pic->cullRect().width()),
+                                                    SkScalarCeilToInt(pic->cullRect().height())));
+
+        temp->setPicture(pic.get());
+        pic->playback(temp.get());
+        temp->setPicture(nullptr);
+        this->resetOpList();
+        temp->detachCommands(&fOps);
+    }
+
+    this->setCurOp(fOps.count()-1);
+
+    return ErrorCode::kOK;
+}
+
+const char* Model::ErrorString(ErrorCode err) {
+    static const char* kStrings[] = {
+        "OK",
+        "Couldn't read file",
+        "Couldn't decode picture"
+    };
+
+    return kStrings[(int)err];
+}
+
+const char* Model::getOpName(int index) {
+    return SkDrawCommand::GetCommandString(fOps[index]->getType());
+}
+
+void Model::setCurOp(int curOp) {
+    SkASSERT(curOp < fOps.count());
+
+    if (curOp == fCurOp) {
+        return; // the render state is already up to date
+    }
+
+    fCurOp = curOp;
+    this->drawTo(fCurOp);
+}
+
+void Model::drawTo(int index) {
+    SkASSERT(index < fOps.count());
+
+    SkCanvas canvas(fBM);
+
+    int saveCount = canvas.save();
+
+    for (int i = 0; i <= index; ++i) {
+        if (fOps[i]->isVisible()) {
+            fOps[i]->execute(&canvas);
+        }
+    }
+
+    canvas.restoreToCount(saveCount);
+}
+
+void Model::resetOpList() {
+    for (int i = 0; i < fOps.count(); ++i) {
+        delete fOps[i];
+    }
+    fCurOp = 0;
+}
diff --git a/tools/mdbviz/Model.h b/tools/mdbviz/Model.h
new file mode 100644
index 0000000..773c148
--- /dev/null
+++ b/tools/mdbviz/Model.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkTDArray.h"
+
+class SkDrawCommand;
+
+// This class encapsulates the both the in-memory representation of the draw ops
+// and the state of Skia/Ganesh's rendering. It should never have any Qt intrusions.
+class Model {
+public:
+    enum class ErrorCode {
+        kOK,
+        kCouldntOpenFile,
+        kCouldntDecodeSKP
+    };
+
+    Model();
+    ~Model();
+
+    static const char* ErrorString(ErrorCode);
+
+    // Replace the list of draw ops by reading the provided skp filename and
+    // reset the Skia draw state. It is up to the view portion to update itself
+    // after this call (i.e., rebuild the opList view).
+    ErrorCode load(const char* filename);
+
+    // Update the rendering state to the provided op
+    void setCurOp(int curOp);
+    int curOp() const { return fCurOp; }
+
+    int numOps() const { return fOps.count(); }
+    const char* getOpName(int index);
+
+    // Get the bits visually representing the current rendering state
+    void* getPixels() const { return fBM.getPixels(); }
+    int width() const { return fBM.width(); }
+    int height() const { return fBM.height(); }
+
+protected:
+    // draw the ops up to (and including) the index-th op
+    void drawTo(int index);
+    void resetOpList();
+
+private:
+    SkTDArray<SkDrawCommand*> fOps;
+    int                       fCurOp;  // The current op the rendering state is at
+    SkBitmap                  fBM;
+};
diff --git a/tools/mdbviz/mainwindow.cpp b/tools/mdbviz/mainwindow.cpp
index 5fb1e7e..c09ad09 100644
--- a/tools/mdbviz/mainwindow.cpp
+++ b/tools/mdbviz/mainwindow.cpp
@@ -9,11 +9,6 @@
 
 #include "MainWindow.h"
 
-#include "SkBitmap.h"
-#include "SkCanvas.h"
-#include "SkPicture.h"
-#include "SkStream.h"
-
 MainWindow::MainWindow() {
     this->createActions();
     this->createStatusBar();
@@ -35,16 +30,21 @@
 void MainWindow::setupOpListWidget() {
     fOpListWidget->clear();
 
-    for (int i = 0; i < fDebugCanvas->getSize(); i++) {
+    for (int i = 0; i < fModel.numOps(); i++) {
         QListWidgetItem *item = new QListWidgetItem();
 
-        const SkDrawCommand* command = fDebugCanvas->getDrawCommandAt(i);
-
-        SkString commandString = command->toString();
-        item->setData(Qt::DisplayRole, commandString.c_str());
+        item->setData(Qt::DisplayRole, fModel.getOpName(i));
 
         fOpListWidget->addItem(item);
     }
+
+    fOpListWidget->setCurrentRow(fModel.numOps()-1);
+}
+
+void MainWindow::presentCurrentRenderState() {
+    fImage = QImage((uchar*)fModel.getPixels(), fModel.width(), fModel.height(),
+                    QImage::Format_RGBA8888);
+    fImageLabel->setPixmap(QPixmap::fromImage(fImage));
 }
 
 void MainWindow::loadFile(const QString &fileName) {
@@ -63,37 +63,14 @@
 
     std::string str = file.fileName().toLocal8Bit().constData();
 
-    std::unique_ptr<SkStream> stream = SkStream::MakeFromFile(str.c_str());
-    if (!stream) {
-        this->statusBar()->showMessage(tr("Couldn't read file"));
+    Model::ErrorCode err = fModel.load(str.c_str());
+    if (Model::ErrorCode::kOK != err) {
+        this->statusBar()->showMessage(Model::ErrorString(err));
         return;
     }
-    sk_sp<SkPicture> pic(SkPicture::MakeFromStream(stream.get()));
-    if (!pic) {
-        this->statusBar()->showMessage(tr("Couldn't decode picture"));
-        return;
-    }
-
-    fDebugCanvas.reset(new SkDebugCanvas(SkScalarCeilToInt(pic->cullRect().width()),
-                                         SkScalarCeilToInt(pic->cullRect().height())));
-
-    fDebugCanvas->setPicture(pic.get());
-    pic->playback(fDebugCanvas.get());
-    fDebugCanvas->setPicture(nullptr);
 
     this->setupOpListWidget();
-
-    SkBitmap bm;
-
-    SkImageInfo ii = SkImageInfo::MakeN32Premul(1024, 1024);
-    bm.allocPixels(ii, 0);
-
-    SkCanvas canvas(bm);
-
-    fDebugCanvas->draw(&canvas);
-
-    fImage = QImage((uchar*)bm.getPixels(), bm.width(), bm.height(), QImage::Format_RGBA8888);
-    fImageLabel->setPixmap(QPixmap::fromImage(fImage));
+    this->presentCurrentRenderState();
 
 #ifndef QT_NO_CURSOR
     QApplication::restoreOverrideCursor();
@@ -138,6 +115,11 @@
     aboutAct->setStatusTip(tr("Show the application's About box"));
 }
 
+void MainWindow::onCurrentRowChanged(int currentRow) {
+    fModel.setCurOp(currentRow);
+    this->presentCurrentRenderState();
+}
+
 void MainWindow::createStatusBar() {
     this->statusBar()->showMessage(tr("Ready"));
 }
@@ -155,6 +137,8 @@
         this->addDockWidget(Qt::LeftDockWidgetArea, opListDock);
 
         fViewMenu->addAction(opListDock->toggleViewAction());
+
+        connect(fOpListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(onCurrentRowChanged(int)));
     }
 
     // Main canvas Window
diff --git a/tools/mdbviz/mainwindow.h b/tools/mdbviz/mainwindow.h
index 80c86b0..59e7b98 100644
--- a/tools/mdbviz/mainwindow.h
+++ b/tools/mdbviz/mainwindow.h
@@ -11,7 +11,7 @@
 #include <memory>
 #include <QMainWindow>
 
-#include "SkDebugCanvas.h"
+#include "Model.h"
 
 class QLabel;
 class QListWidget;
@@ -27,10 +27,13 @@
 private slots:
     void openFile();
     void about();
+    void onCurrentRowChanged(int currentRow);
 
 private:
     void loadFile(const QString &fileName);
     void setupOpListWidget();
+    void presentCurrentRenderState();
+
 
     void createActions();
     void createStatusBar();
@@ -46,7 +49,7 @@
 
     QMenu* fViewMenu;
 
-    std::unique_ptr<SkDebugCanvas> fDebugCanvas;
+    Model fModel;
 };
 
 #endif