Move ImGui support code to ImGuiLayer

Viewer still has plenty of code that uses ImGui to create application
specific UI, but the structural code that forwards input to ImGui, and
converts per-frame ImGui rendering data to Skia draw commands is now in
a single component that can be reused in any sk_app-based application.

Bug: skia:
Change-Id: Ic14ece659d4af8ee13b69c638bdaf7df6c24f5c0
Reviewed-on: https://skia-review.googlesource.com/82627
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/tools/viewer/ImGuiLayer.cpp b/tools/viewer/ImGuiLayer.cpp
new file mode 100644
index 0000000..e55ac9c
--- /dev/null
+++ b/tools/viewer/ImGuiLayer.cpp
@@ -0,0 +1,188 @@
+/*
+* 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 "ImGuiLayer.h"
+
+#include "SkCanvas.h"
+#include "SkImage.h"
+#include "SkPixmap.h"
+#include "SkSwizzle.h"
+#include "SkVertices.h"
+
+#include "imgui.h"
+
+#include <stdlib.h>
+#include <map>
+
+using namespace sk_app;
+
+ImGuiLayer::ImGuiLayer() {
+    // ImGui initialization:
+    ImGuiIO& io = ImGui::GetIO();
+
+    // Keymap...
+    io.KeyMap[ImGuiKey_Tab] = (int)Window::Key::kTab;
+    io.KeyMap[ImGuiKey_LeftArrow] = (int)Window::Key::kLeft;
+    io.KeyMap[ImGuiKey_RightArrow] = (int)Window::Key::kRight;
+    io.KeyMap[ImGuiKey_UpArrow] = (int)Window::Key::kUp;
+    io.KeyMap[ImGuiKey_DownArrow] = (int)Window::Key::kDown;
+    io.KeyMap[ImGuiKey_PageUp] = (int)Window::Key::kPageUp;
+    io.KeyMap[ImGuiKey_PageDown] = (int)Window::Key::kPageDown;
+    io.KeyMap[ImGuiKey_Home] = (int)Window::Key::kHome;
+    io.KeyMap[ImGuiKey_End] = (int)Window::Key::kEnd;
+    io.KeyMap[ImGuiKey_Delete] = (int)Window::Key::kDelete;
+    io.KeyMap[ImGuiKey_Backspace] = (int)Window::Key::kBack;
+    io.KeyMap[ImGuiKey_Enter] = (int)Window::Key::kOK;
+    io.KeyMap[ImGuiKey_Escape] = (int)Window::Key::kEscape;
+    io.KeyMap[ImGuiKey_A] = (int)Window::Key::kA;
+    io.KeyMap[ImGuiKey_C] = (int)Window::Key::kC;
+    io.KeyMap[ImGuiKey_V] = (int)Window::Key::kV;
+    io.KeyMap[ImGuiKey_X] = (int)Window::Key::kX;
+    io.KeyMap[ImGuiKey_Y] = (int)Window::Key::kY;
+    io.KeyMap[ImGuiKey_Z] = (int)Window::Key::kZ;
+
+    int w, h;
+    unsigned char* pixels;
+    io.Fonts->GetTexDataAsAlpha8(&pixels, &w, &h);
+    SkImageInfo info = SkImageInfo::MakeA8(w, h);
+    SkPixmap pmap(info, pixels, info.minRowBytes());
+    SkMatrix localMatrix = SkMatrix::MakeScale(1.0f / w, 1.0f / h);
+    auto fontImage = SkImage::MakeFromRaster(pmap, nullptr, nullptr);
+    auto fontShader = fontImage->makeShader(&localMatrix);
+    fFontPaint.setShader(fontShader);
+    fFontPaint.setColor(SK_ColorWHITE);
+    fFontPaint.setFilterQuality(kLow_SkFilterQuality);
+    io.Fonts->TexID = &fFontPaint;
+}
+
+void ImGuiLayer::onAttach(Window* window) {
+    fWindow = window;
+}
+
+bool ImGuiLayer::onMouse(int x, int y, Window::InputState state, uint32_t modifiers) {
+    ImGuiIO& io = ImGui::GetIO();
+    io.MousePos.x = static_cast<float>(x);
+    io.MousePos.y = static_cast<float>(y);
+    if (Window::kDown_InputState == state) {
+        io.MouseDown[0] = true;
+    } else if (Window::kUp_InputState == state) {
+        io.MouseDown[0] = false;
+    }
+    return io.WantCaptureMouse;
+}
+
+bool ImGuiLayer::onMouseWheel(float delta, uint32_t modifiers) {
+    ImGuiIO& io = ImGui::GetIO();
+    io.MouseWheel += delta;
+    return true;
+}
+
+void ImGuiLayer::skiaWidget(const ImVec2& size, SkiaWidgetFunc func) {
+    intptr_t funcIndex = fSkiaWidgetFuncs.count();
+    fSkiaWidgetFuncs.push_back(func);
+    ImGui::Image((ImTextureID)funcIndex, size);
+}
+
+void ImGuiLayer::onPrePaint() {
+    // Update ImGui input
+    ImGuiIO& io = ImGui::GetIO();
+    io.DeltaTime = 1.0f / 60.0f;
+    io.DisplaySize.x = static_cast<float>(fWindow->width());
+    io.DisplaySize.y = static_cast<float>(fWindow->height());
+
+    io.KeyAlt = io.KeysDown[static_cast<int>(Window::Key::kOption)];
+    io.KeyCtrl = io.KeysDown[static_cast<int>(Window::Key::kCtrl)];
+    io.KeyShift = io.KeysDown[static_cast<int>(Window::Key::kShift)];
+
+    ImGui::NewFrame();
+}
+
+void ImGuiLayer::onPaint(SkCanvas* canvas) {
+    // This causes ImGui to rebuild vertex/index data based on all immediate-mode commands
+    // (widgets, etc...) that have been issued
+    ImGui::Render();
+
+    // Then we fetch the most recent data, and convert it so we can render with Skia
+    const ImDrawData* drawData = ImGui::GetDrawData();
+    SkTDArray<SkPoint> pos;
+    SkTDArray<SkPoint> uv;
+    SkTDArray<SkColor> color;
+
+    for (int i = 0; i < drawData->CmdListsCount; ++i) {
+        const ImDrawList* drawList = drawData->CmdLists[i];
+
+        // De-interleave all vertex data (sigh), convert to Skia types
+        pos.rewind(); uv.rewind(); color.rewind();
+        for (int i = 0; i < drawList->VtxBuffer.size(); ++i) {
+            const ImDrawVert& vert = drawList->VtxBuffer[i];
+            pos.push(SkPoint::Make(vert.pos.x, vert.pos.y));
+            uv.push(SkPoint::Make(vert.uv.x, vert.uv.y));
+            color.push(vert.col);
+        }
+        // ImGui colors are RGBA
+        SkSwapRB(color.begin(), color.begin(), color.count());
+
+        int indexOffset = 0;
+
+        // Draw everything with canvas.drawVertices...
+        for (int j = 0; j < drawList->CmdBuffer.size(); ++j) {
+            const ImDrawCmd* drawCmd = &drawList->CmdBuffer[j];
+
+            SkAutoCanvasRestore acr(canvas, true);
+
+            // TODO: Find min/max index for each draw, so we know how many vertices (sigh)
+            if (drawCmd->UserCallback) {
+                drawCmd->UserCallback(drawList, drawCmd);
+            } else {
+                intptr_t idIndex = (intptr_t)drawCmd->TextureId;
+                if (idIndex < fSkiaWidgetFuncs.count()) {
+                    // Small image IDs are actually indices into a list of callbacks. We directly
+                    // examing the vertex data to deduce the image rectangle, then reconfigure the
+                    // canvas to be clipped and translated so that the callback code gets to use
+                    // Skia to render a widget in the middle of an ImGui panel.
+                    ImDrawIdx rectIndex = drawList->IdxBuffer[indexOffset];
+                    SkPoint tl = pos[rectIndex], br = pos[rectIndex + 2];
+                    canvas->clipRect(SkRect::MakeLTRB(tl.fX, tl.fY, br.fX, br.fY));
+                    canvas->translate(tl.fX, tl.fY);
+                    fSkiaWidgetFuncs[idIndex](canvas);
+                } else {
+                    SkPaint* paint = static_cast<SkPaint*>(drawCmd->TextureId);
+                    SkASSERT(paint);
+
+                    canvas->clipRect(SkRect::MakeLTRB(drawCmd->ClipRect.x, drawCmd->ClipRect.y,
+                                                      drawCmd->ClipRect.z, drawCmd->ClipRect.w));
+                    auto vertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode,
+                                                         drawList->VtxBuffer.size(),
+                                                         pos.begin(), uv.begin(), color.begin(),
+                                                         drawCmd->ElemCount,
+                                                         drawList->IdxBuffer.begin() + indexOffset);
+                    canvas->drawVertices(vertices, SkBlendMode::kModulate, *paint);
+                    indexOffset += drawCmd->ElemCount;
+                }
+            }
+        }
+    }
+
+    fSkiaWidgetFuncs.reset();
+}
+
+bool ImGuiLayer::onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers) {
+    ImGuiIO& io = ImGui::GetIO();
+    io.KeysDown[static_cast<int>(key)] = (Window::kDown_InputState == state);
+    return io.WantCaptureKeyboard;
+}
+
+bool ImGuiLayer::onChar(SkUnichar c, uint32_t modifiers) {
+    ImGuiIO& io = ImGui::GetIO();
+    if (io.WantTextInput) {
+        if (c > 0 && c < 0x10000) {
+            io.AddInputCharacter(c);
+        }
+        return true;
+    }
+    return false;
+}