Brian Osman | d67e518 | 2017-12-08 16:46:09 -0500 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2017 Google Inc. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license that can be |
| 5 | * found in the LICENSE file. |
| 6 | */ |
| 7 | |
| 8 | #include "ImGuiLayer.h" |
| 9 | |
| 10 | #include "SkCanvas.h" |
| 11 | #include "SkImage.h" |
| 12 | #include "SkPixmap.h" |
| 13 | #include "SkSwizzle.h" |
| 14 | #include "SkVertices.h" |
| 15 | |
| 16 | #include "imgui.h" |
| 17 | |
| 18 | #include <stdlib.h> |
| 19 | #include <map> |
| 20 | |
| 21 | using namespace sk_app; |
| 22 | |
| 23 | ImGuiLayer::ImGuiLayer() { |
| 24 | // ImGui initialization: |
| 25 | ImGuiIO& io = ImGui::GetIO(); |
| 26 | |
| 27 | // Keymap... |
| 28 | io.KeyMap[ImGuiKey_Tab] = (int)Window::Key::kTab; |
| 29 | io.KeyMap[ImGuiKey_LeftArrow] = (int)Window::Key::kLeft; |
| 30 | io.KeyMap[ImGuiKey_RightArrow] = (int)Window::Key::kRight; |
| 31 | io.KeyMap[ImGuiKey_UpArrow] = (int)Window::Key::kUp; |
| 32 | io.KeyMap[ImGuiKey_DownArrow] = (int)Window::Key::kDown; |
| 33 | io.KeyMap[ImGuiKey_PageUp] = (int)Window::Key::kPageUp; |
| 34 | io.KeyMap[ImGuiKey_PageDown] = (int)Window::Key::kPageDown; |
| 35 | io.KeyMap[ImGuiKey_Home] = (int)Window::Key::kHome; |
| 36 | io.KeyMap[ImGuiKey_End] = (int)Window::Key::kEnd; |
| 37 | io.KeyMap[ImGuiKey_Delete] = (int)Window::Key::kDelete; |
| 38 | io.KeyMap[ImGuiKey_Backspace] = (int)Window::Key::kBack; |
| 39 | io.KeyMap[ImGuiKey_Enter] = (int)Window::Key::kOK; |
| 40 | io.KeyMap[ImGuiKey_Escape] = (int)Window::Key::kEscape; |
| 41 | io.KeyMap[ImGuiKey_A] = (int)Window::Key::kA; |
| 42 | io.KeyMap[ImGuiKey_C] = (int)Window::Key::kC; |
| 43 | io.KeyMap[ImGuiKey_V] = (int)Window::Key::kV; |
| 44 | io.KeyMap[ImGuiKey_X] = (int)Window::Key::kX; |
| 45 | io.KeyMap[ImGuiKey_Y] = (int)Window::Key::kY; |
| 46 | io.KeyMap[ImGuiKey_Z] = (int)Window::Key::kZ; |
| 47 | |
| 48 | int w, h; |
| 49 | unsigned char* pixels; |
| 50 | io.Fonts->GetTexDataAsAlpha8(&pixels, &w, &h); |
| 51 | SkImageInfo info = SkImageInfo::MakeA8(w, h); |
| 52 | SkPixmap pmap(info, pixels, info.minRowBytes()); |
| 53 | SkMatrix localMatrix = SkMatrix::MakeScale(1.0f / w, 1.0f / h); |
| 54 | auto fontImage = SkImage::MakeFromRaster(pmap, nullptr, nullptr); |
| 55 | auto fontShader = fontImage->makeShader(&localMatrix); |
| 56 | fFontPaint.setShader(fontShader); |
| 57 | fFontPaint.setColor(SK_ColorWHITE); |
| 58 | fFontPaint.setFilterQuality(kLow_SkFilterQuality); |
| 59 | io.Fonts->TexID = &fFontPaint; |
| 60 | } |
| 61 | |
| 62 | void ImGuiLayer::onAttach(Window* window) { |
| 63 | fWindow = window; |
| 64 | } |
| 65 | |
| 66 | bool ImGuiLayer::onMouse(int x, int y, Window::InputState state, uint32_t modifiers) { |
| 67 | ImGuiIO& io = ImGui::GetIO(); |
| 68 | io.MousePos.x = static_cast<float>(x); |
| 69 | io.MousePos.y = static_cast<float>(y); |
| 70 | if (Window::kDown_InputState == state) { |
| 71 | io.MouseDown[0] = true; |
| 72 | } else if (Window::kUp_InputState == state) { |
| 73 | io.MouseDown[0] = false; |
| 74 | } |
| 75 | return io.WantCaptureMouse; |
| 76 | } |
| 77 | |
| 78 | bool ImGuiLayer::onMouseWheel(float delta, uint32_t modifiers) { |
| 79 | ImGuiIO& io = ImGui::GetIO(); |
| 80 | io.MouseWheel += delta; |
| 81 | return true; |
| 82 | } |
| 83 | |
| 84 | void ImGuiLayer::skiaWidget(const ImVec2& size, SkiaWidgetFunc func) { |
| 85 | intptr_t funcIndex = fSkiaWidgetFuncs.count(); |
| 86 | fSkiaWidgetFuncs.push_back(func); |
| 87 | ImGui::Image((ImTextureID)funcIndex, size); |
| 88 | } |
| 89 | |
| 90 | void ImGuiLayer::onPrePaint() { |
| 91 | // Update ImGui input |
| 92 | ImGuiIO& io = ImGui::GetIO(); |
| 93 | io.DeltaTime = 1.0f / 60.0f; |
| 94 | io.DisplaySize.x = static_cast<float>(fWindow->width()); |
| 95 | io.DisplaySize.y = static_cast<float>(fWindow->height()); |
| 96 | |
| 97 | io.KeyAlt = io.KeysDown[static_cast<int>(Window::Key::kOption)]; |
| 98 | io.KeyCtrl = io.KeysDown[static_cast<int>(Window::Key::kCtrl)]; |
| 99 | io.KeyShift = io.KeysDown[static_cast<int>(Window::Key::kShift)]; |
| 100 | |
| 101 | ImGui::NewFrame(); |
| 102 | } |
| 103 | |
| 104 | void ImGuiLayer::onPaint(SkCanvas* canvas) { |
| 105 | // This causes ImGui to rebuild vertex/index data based on all immediate-mode commands |
| 106 | // (widgets, etc...) that have been issued |
| 107 | ImGui::Render(); |
| 108 | |
| 109 | // Then we fetch the most recent data, and convert it so we can render with Skia |
| 110 | const ImDrawData* drawData = ImGui::GetDrawData(); |
| 111 | SkTDArray<SkPoint> pos; |
| 112 | SkTDArray<SkPoint> uv; |
| 113 | SkTDArray<SkColor> color; |
| 114 | |
| 115 | for (int i = 0; i < drawData->CmdListsCount; ++i) { |
| 116 | const ImDrawList* drawList = drawData->CmdLists[i]; |
| 117 | |
| 118 | // De-interleave all vertex data (sigh), convert to Skia types |
| 119 | pos.rewind(); uv.rewind(); color.rewind(); |
| 120 | for (int i = 0; i < drawList->VtxBuffer.size(); ++i) { |
| 121 | const ImDrawVert& vert = drawList->VtxBuffer[i]; |
| 122 | pos.push(SkPoint::Make(vert.pos.x, vert.pos.y)); |
| 123 | uv.push(SkPoint::Make(vert.uv.x, vert.uv.y)); |
| 124 | color.push(vert.col); |
| 125 | } |
| 126 | // ImGui colors are RGBA |
| 127 | SkSwapRB(color.begin(), color.begin(), color.count()); |
| 128 | |
| 129 | int indexOffset = 0; |
| 130 | |
| 131 | // Draw everything with canvas.drawVertices... |
| 132 | for (int j = 0; j < drawList->CmdBuffer.size(); ++j) { |
| 133 | const ImDrawCmd* drawCmd = &drawList->CmdBuffer[j]; |
| 134 | |
| 135 | SkAutoCanvasRestore acr(canvas, true); |
| 136 | |
| 137 | // TODO: Find min/max index for each draw, so we know how many vertices (sigh) |
| 138 | if (drawCmd->UserCallback) { |
| 139 | drawCmd->UserCallback(drawList, drawCmd); |
| 140 | } else { |
| 141 | intptr_t idIndex = (intptr_t)drawCmd->TextureId; |
| 142 | if (idIndex < fSkiaWidgetFuncs.count()) { |
| 143 | // Small image IDs are actually indices into a list of callbacks. We directly |
| 144 | // examing the vertex data to deduce the image rectangle, then reconfigure the |
| 145 | // canvas to be clipped and translated so that the callback code gets to use |
| 146 | // Skia to render a widget in the middle of an ImGui panel. |
| 147 | ImDrawIdx rectIndex = drawList->IdxBuffer[indexOffset]; |
| 148 | SkPoint tl = pos[rectIndex], br = pos[rectIndex + 2]; |
| 149 | canvas->clipRect(SkRect::MakeLTRB(tl.fX, tl.fY, br.fX, br.fY)); |
| 150 | canvas->translate(tl.fX, tl.fY); |
| 151 | fSkiaWidgetFuncs[idIndex](canvas); |
| 152 | } else { |
| 153 | SkPaint* paint = static_cast<SkPaint*>(drawCmd->TextureId); |
| 154 | SkASSERT(paint); |
| 155 | |
| 156 | canvas->clipRect(SkRect::MakeLTRB(drawCmd->ClipRect.x, drawCmd->ClipRect.y, |
| 157 | drawCmd->ClipRect.z, drawCmd->ClipRect.w)); |
| 158 | auto vertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, |
| 159 | drawList->VtxBuffer.size(), |
| 160 | pos.begin(), uv.begin(), color.begin(), |
| 161 | drawCmd->ElemCount, |
| 162 | drawList->IdxBuffer.begin() + indexOffset); |
| 163 | canvas->drawVertices(vertices, SkBlendMode::kModulate, *paint); |
| 164 | indexOffset += drawCmd->ElemCount; |
| 165 | } |
| 166 | } |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | fSkiaWidgetFuncs.reset(); |
| 171 | } |
| 172 | |
| 173 | bool ImGuiLayer::onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers) { |
| 174 | ImGuiIO& io = ImGui::GetIO(); |
| 175 | io.KeysDown[static_cast<int>(key)] = (Window::kDown_InputState == state); |
| 176 | return io.WantCaptureKeyboard; |
| 177 | } |
| 178 | |
| 179 | bool ImGuiLayer::onChar(SkUnichar c, uint32_t modifiers) { |
| 180 | ImGuiIO& io = ImGui::GetIO(); |
| 181 | if (io.WantTextInput) { |
| 182 | if (c > 0 && c < 0x10000) { |
| 183 | io.AddInputCharacter(c); |
| 184 | } |
| 185 | return true; |
| 186 | } |
| 187 | return false; |
| 188 | } |