Wire up mouse and keyboard events in CanvasKit viewer
Change-Id: I10b57f18edb516b48be3ba16f98a540370ec689f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/292793
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/modules/canvaskit/canvaskit/viewer.html b/modules/canvaskit/canvaskit/viewer.html
index 10e944e..9cbaa77 100644
--- a/modules/canvaskit/canvaskit/viewer.html
+++ b/modules/canvaskit/canvaskit/viewer.html
@@ -25,7 +25,6 @@
location.reload();
};
- var CanvasKit = null;
CanvasKitInit({
locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
}).then((CK) => {
@@ -54,15 +53,14 @@
}
});
} else {
- let slide = CanvasKit.MakeSlide(slideName);
- if (!slide) {
- throw 'Could not make slide ' + slideName;
- }
- ViewerMain(CanvasKit, slide);
+ ViewerMain(CanvasKit, CanvasKit.MakeSlide(slideName));
}
}
function ViewerMain(CanvasKit, slide) {
+ if (!slide) {
+ throw 'Failed to parse slide.'
+ }
const width = window.innerWidth;
const height = window.innerHeight;
const htmlCanvas = document.getElementById('viewer_canvas');
@@ -70,9 +68,7 @@
htmlCanvas.height = height;
slide.load(width, height);
- const doMSAA = (flags.msaa > 1);
- let surface;
- if (doMSAA) {
+ if (flags.msaa > 1) {
let ctx = CanvasKit.GetWebGLContext(htmlCanvas);
let grContext = CanvasKit.MakeGrContext(ctx);
let sampleCnt = parseInt(flags.msaa);
@@ -80,6 +76,7 @@
if (!surface) {
throw 'Could not create offscreen msaa render target.';
}
+ surface.isMSAA = true;
} else {
surface = CanvasKit.MakeCanvasSurface(htmlCanvas);
if (!surface) {
@@ -87,30 +84,114 @@
}
}
+ window.onmousedown = (event) => (event.button === 0) && Mouse(CanvasKit.InputState.Down, event);
+ window.onmouseup = (event) => (event.button === 0) && Mouse(CanvasKit.InputState.Up, event);
+ window.onmousemove = (event) => Mouse(CanvasKit.InputState.Move, event);
+ window.onkeypress = function(event) {
+ if (slide.onChar(event.keyCode)) {
+ ScheduleDraw();
+ return false;
+ }
+ return true;
+ }
+ window.onkeydown = function(event) {
+ if (event.keyCode == '38') { // up arrow
+ ScaleCanvas((event.shiftKey) ? Infinity : 1.1);
+ return false;
+ }
+ if (event.keyCode == '40') { // down arrow
+ ScaleCanvas((event.shiftKey) ? 0 : 1/1.1);
+ return false;
+ }
+ return true;
+ }
+
+ let [canvasScale, canvasTranslateX, canvasTranslateY] = [1, 0, 0];
+ function ScaleCanvas(factor) {
+ factor = Math.min(Math.max(1/(5*canvasScale), factor), 5/canvasScale);
+ canvasTranslateX *= factor;
+ canvasTranslateY *= factor;
+ canvasScale *= factor;
+ ScheduleDraw();
+ }
+ function TranslateCanvas(dx, dy) {
+ canvasTranslateX += dx;
+ canvasTranslateY += dy;
+ ScheduleDraw();
+ }
+
+ function Mouse(state, event) {
+ let modifierKeys = CanvasKit.ModifierKey.None;
+ if (event.shiftKey) {
+ modifierKeys |= CanvasKit.ModifierKey.Shift;
+ }
+ if (event.altKey) {
+ modifierKeys |= CanvasKit.ModifierKey.Option;
+ }
+ if (event.ctrlKey) {
+ modifierKeys |= CanvasKit.ModifierKey.Ctrl;
+ }
+ if (event.metaKey) {
+ modifierKeys |= CanvasKit.ModifierKey.Command;
+ }
+ let [dx, dy] = [event.pageX - this.lastX, event.pageY - this.lastY];
+ this.lastX = event.pageX;
+ this.lastY = event.pageY;
+ if (slide.onMouse(event.pageX, event.pageY, state, modifierKeys)) {
+ ScheduleDraw();
+ return false;
+ } else if (event.buttons & 1) { // Left-button pressed.
+ TranslateCanvas(dx, dy);
+ return false;
+ }
+ return true;
+ }
+
const fps = {
frames: 0,
startMs: window.performance.now()
};
- surface.requestAnimationFrame(function(canvas) {
- slide.draw(canvas);
- if (doMSAA) {
- CanvasKit.BlitOffscreenFramebuffer(surface, 0, 0, width, height, 0, 0, width, height,
- CanvasKit.GLFilter.Nearest);
+ function ScheduleDraw() {
+ if (this.hasPendingAnimationRequest) {
+ // It's possible for this ScheduleDraw() method to be called multiple times before an
+ // animation callback actually gets invoked. Make sure we only ever have one single
+ // requestAnimationFrame scheduled at a time, because otherwise we can get stuck in a
+ // position where multiple callbacks are coming in on a single compositing frame, and then
+ // rescheduling multiple more for the next frame.
+ return;
}
+ this.hasPendingAnimationRequest = true;
+ surface.requestAnimationFrame((canvas) => {
+ this.hasPendingAnimationRequest = false;
- ++fps.frames;
- const ms = window.performance.now();
- const sec = (ms - fps.startMs) / 1000;
- if (sec > 2) {
- console.log(Math.round(fps.frames / sec) + ' fps');
- fps.frames = 0;
- fps.startMs = ms;
- }
+ canvas.save();
+ canvas.translate(canvasTranslateX, canvasTranslateY);
+ canvas.scale(canvasScale, canvasScale);
+ canvas.clear(CanvasKit.WHITE);
+ slide.draw(canvas);
+ if (surface.isMSAA) {
+ let [w, h] = [surface.width(), surface.height()];
+ CanvasKit.BlitOffscreenFramebuffer(surface, 0,0,w,h, 0,0,w,h, CanvasKit.GLFilter.Nearest);
+ }
+ canvas.restore();
- if (slide.animate(ms * 1e6)) {
- surface.requestAnimationFrame(arguments.callee);
- }
- });
+ ++fps.frames;
+ const ms = window.performance.now();
+ const sec = (ms - fps.startMs) / 1000;
+ if (sec > 2) {
+ console.log(Math.round(fps.frames / sec) + ' fps');
+ fps.frames = 0;
+ fps.startMs = ms;
+ }
+
+ if (slide.animate(ms * 1e6)) {
+ ScheduleDraw();
+ }
+ });
+ }
+
+ ScheduleDraw();
}
+
</script>
diff --git a/modules/canvaskit/viewer_bindings.cpp b/modules/canvaskit/viewer_bindings.cpp
index acb57b6..cf394f5 100644
--- a/modules/canvaskit/viewer_bindings.cpp
+++ b/modules/canvaskit/viewer_bindings.cpp
@@ -10,6 +10,8 @@
#include "include/core/SkCanvas.h"
#include "include/core/SkSurface.h"
#include "include/gpu/GrContext.h"
+#include "tools/skui/InputState.h"
+#include "tools/skui/ModifierKey.h"
#include "tools/viewer/SKPSlide.h"
#include "tools/viewer/SampleSlide.h"
#include "tools/viewer/SvgSlide.h"
@@ -18,21 +20,25 @@
using namespace emscripten;
-sk_sp<Slide> MakeSlide(std::string name) {
+static sk_sp<Slide> MakeSlide(std::string name) {
if (name == "PathText") {
extern Sample* MakePathTextSample();
return sk_make_sp<SampleSlide>(MakePathTextSample);
}
+ if (name == "TessellatedWedge") {
+ extern Sample* MakeTessellatedWedgeSample();
+ return sk_make_sp<SampleSlide>(MakeTessellatedWedgeSample);
+ }
return nullptr;
}
-sk_sp<Slide> MakeSkpSlide(std::string name, std::string skpData) {
+static sk_sp<Slide> MakeSkpSlide(std::string name, std::string skpData) {
auto stream = std::make_unique<SkMemoryStream>(skpData.data(), skpData.size(),
/*copyData=*/true);
return sk_make_sp<SKPSlide>(SkString(name.c_str()), std::move(stream));
}
-sk_sp<Slide> MakeSvgSlide(std::string name, std::string svgText) {
+static sk_sp<Slide> MakeSvgSlide(std::string name, std::string svgText) {
auto stream = std::make_unique<SkMemoryStream>(svgText.data(), svgText.size(),
/*copyData=*/true);
return sk_make_sp<SvgSlide>(SkString(name.c_str()), std::move(stream));
@@ -43,8 +49,8 @@
glDeleteFramebuffers(1, &framebuffer);
}
-sk_sp<SkSurface> MakeOffscreenFramebuffer(sk_sp<GrContext> grContext, int width, int height,
- int sampleCnt) {
+static sk_sp<SkSurface> MakeOffscreenFramebuffer(sk_sp<GrContext> grContext, int width, int height,
+ int sampleCnt) {
GLuint colorBuffer;
glGenRenderbuffers(1, &colorBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorBuffer);
@@ -87,8 +93,9 @@
kLinear = GL_LINEAR
};
-void BlitOffscreenFramebuffer(sk_sp<SkSurface> surface, int srcX0, int srcY0, int srcX1, int srcY1,
- int dstX0, int dstY0, int dstX1, int dstY1, GLFilter filter) {
+static void BlitOffscreenFramebuffer(sk_sp<SkSurface> surface, int srcX0, int srcY0, int srcX1, int
+ srcY1, int dstX0, int dstY0, int dstX1, int dstY1,
+ GLFilter filter) {
surface->flush(SkSurface::BackendSurfaceAccess::kPresent, GrFlushInfo());
GrGLFramebufferInfo glInfo;
auto backendRT = surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess);
@@ -112,8 +119,23 @@
.function("animate", &Slide::animate)
.function("draw", optional_override([](Slide& self, SkCanvas& canvas) {
self.draw(&canvas);
- }));
+ }))
+ .function("onChar", &Slide::onChar)
+ .function("onMouse", &Slide::onMouse);
enum_<GLFilter>("GLFilter")
.value("Nearest", GLFilter::kNearest)
.value("Linear", GLFilter::kLinear);
+ enum_<skui::InputState>("InputState")
+ .value("Down", skui::InputState::kDown)
+ .value("Up", skui::InputState::kUp)
+ .value("Move", skui::InputState::kMove)
+ .value("Right", skui::InputState::kRight)
+ .value("Left", skui::InputState::kLeft);
+ enum_<skui::ModifierKey>("ModifierKey")
+ .value("None", skui::ModifierKey::kNone)
+ .value("Shift", skui::ModifierKey::kShift)
+ .value("Control", skui::ModifierKey::kControl)
+ .value("Option", skui::ModifierKey::kOption)
+ .value("Command", skui::ModifierKey::kCommand)
+ .value("FirstPress", skui::ModifierKey::kFirstPress);
}
diff --git a/samplecode/SampleTessellatedWedge.cpp b/samplecode/SampleTessellatedWedge.cpp
index a46adce..89731cb 100644
--- a/samplecode/SampleTessellatedWedge.cpp
+++ b/samplecode/SampleTessellatedWedge.cpp
@@ -22,9 +22,9 @@
// This sample enables wireframe and visualizes the triangulation generated by
// GrTessellateWedgeShader.
-class TessellatedWedgeView : public Sample {
+class TessellatedWedge : public Sample {
public:
- TessellatedWedgeView() {
+ TessellatedWedge() {
#if 0
fPath.moveTo(1, 0);
int numSides = 32 * 3;
@@ -56,7 +56,7 @@
class Click;
};
-void TessellatedWedgeView::onDrawContent(SkCanvas* canvas) {
+void TessellatedWedge::onDrawContent(SkCanvas* canvas) {
canvas->clear(SK_ColorBLACK);
GrContext* ctx = canvas->getGrContext();
@@ -65,8 +65,8 @@
SkString error;
if (!rtc || !ctx) {
error = "GPU Only.";
- } else if (!ctx->priv().caps()->shaderCaps()->tessellationSupport()) {
- error = "GPU tessellation not supported.";
+ } else if (!ctx->priv().caps()->drawInstancedSupport()) {
+ error = "Instanced rendering not supported.";
} else if (1 == rtc->numSamples() && !ctx->priv().caps()->mixedSamplesSupport()) {
error = "MSAA/mixed samples only.";
}
@@ -110,7 +110,7 @@
fLastViewMatrix = canvas->getTotalMatrix();
}
-class TessellatedWedgeView::Click : public Sample::Click {
+class TessellatedWedge::Click : public Sample::Click {
public:
Click(int ptIdx) : fPtIdx(ptIdx) {}
@@ -128,7 +128,7 @@
int fPtIdx;
};
-Sample::Click* TessellatedWedgeView::onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) {
+Sample::Click* TessellatedWedge::onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) {
const SkPoint* pts = SkPathPriv::PointData(fPath);
float fuzz = 20 / fLastViewMatrix.getMaxScale();
for (int i = 0; i < fPath.countPoints(); ++i) {
@@ -140,13 +140,13 @@
return new Click(-1);
}
-bool TessellatedWedgeView::onClick(Sample::Click* click) {
+bool TessellatedWedge::onClick(Sample::Click* click) {
Click* myClick = (Click*)click;
myClick->doClick(&fPath);
return true;
}
-bool TessellatedWedgeView::onChar(SkUnichar unichar) {
+bool TessellatedWedge::onChar(SkUnichar unichar) {
switch (unichar) {
case 'w':
fFlags = (GrTessellatePathOp::Flags)(
@@ -160,6 +160,7 @@
return false;
}
-DEF_SAMPLE(return new TessellatedWedgeView;)
+Sample* MakeTessellatedWedgeSample() { return new TessellatedWedge; }
+static SampleRegistry gTessellatedWedgeSample(MakeTessellatedWedgeSample);
#endif // SK_SUPPORT_GPU