Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 0d0758e

Browse files
csmartdalton86Skia Commit-Bot
authored andcommitted
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 <[email protected]> Reviewed-by: Kevin Lubick <[email protected]>
1 parent 6c6caf4 commit 0d0758e

File tree

3 files changed

+148
-44
lines changed

3 files changed

+148
-44
lines changed

modules/canvaskit/canvaskit/viewer.html

Lines changed: 107 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
location.reload();
2626
};
2727

28-
var CanvasKit = null;
2928
CanvasKitInit({
3029
locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
3130
}).then((CK) => {
@@ -54,63 +53,145 @@
5453
}
5554
});
5655
} else {
57-
let slide = CanvasKit.MakeSlide(slideName);
58-
if (!slide) {
59-
throw 'Could not make slide ' + slideName;
60-
}
61-
ViewerMain(CanvasKit, slide);
56+
ViewerMain(CanvasKit, CanvasKit.MakeSlide(slideName));
6257
}
6358
}
6459

6560
function ViewerMain(CanvasKit, slide) {
61+
if (!slide) {
62+
throw 'Failed to parse slide.'
63+
}
6664
const width = window.innerWidth;
6765
const height = window.innerHeight;
6866
const htmlCanvas = document.getElementById('viewer_canvas');
6967
htmlCanvas.width = width;
7068
htmlCanvas.height = height;
7169
slide.load(width, height);
7270

73-
const doMSAA = (flags.msaa > 1);
74-
let surface;
75-
if (doMSAA) {
71+
if (flags.msaa > 1) {
7672
let ctx = CanvasKit.GetWebGLContext(htmlCanvas);
7773
let grContext = CanvasKit.MakeGrContext(ctx);
7874
let sampleCnt = parseInt(flags.msaa);
7975
surface = CanvasKit.MakeOffscreenFramebuffer(grContext, width, height, sampleCnt);
8076
if (!surface) {
8177
throw 'Could not create offscreen msaa render target.';
8278
}
79+
surface.isMSAA = true;
8380
} else {
8481
surface = CanvasKit.MakeCanvasSurface(htmlCanvas);
8582
if (!surface) {
8683
throw 'Could not make canvas surface';
8784
}
8885
}
8986

87+
window.onmousedown = (event) => (event.button === 0) && Mouse(CanvasKit.InputState.Down, event);
88+
window.onmouseup = (event) => (event.button === 0) && Mouse(CanvasKit.InputState.Up, event);
89+
window.onmousemove = (event) => Mouse(CanvasKit.InputState.Move, event);
90+
window.onkeypress = function(event) {
91+
if (slide.onChar(event.keyCode)) {
92+
ScheduleDraw();
93+
return false;
94+
}
95+
return true;
96+
}
97+
window.onkeydown = function(event) {
98+
if (event.keyCode == '38') { // up arrow
99+
ScaleCanvas((event.shiftKey) ? Infinity : 1.1);
100+
return false;
101+
}
102+
if (event.keyCode == '40') { // down arrow
103+
ScaleCanvas((event.shiftKey) ? 0 : 1/1.1);
104+
return false;
105+
}
106+
return true;
107+
}
108+
109+
let [canvasScale, canvasTranslateX, canvasTranslateY] = [1, 0, 0];
110+
function ScaleCanvas(factor) {
111+
factor = Math.min(Math.max(1/(5*canvasScale), factor), 5/canvasScale);
112+
canvasTranslateX *= factor;
113+
canvasTranslateY *= factor;
114+
canvasScale *= factor;
115+
ScheduleDraw();
116+
}
117+
function TranslateCanvas(dx, dy) {
118+
canvasTranslateX += dx;
119+
canvasTranslateY += dy;
120+
ScheduleDraw();
121+
}
122+
123+
function Mouse(state, event) {
124+
let modifierKeys = CanvasKit.ModifierKey.None;
125+
if (event.shiftKey) {
126+
modifierKeys |= CanvasKit.ModifierKey.Shift;
127+
}
128+
if (event.altKey) {
129+
modifierKeys |= CanvasKit.ModifierKey.Option;
130+
}
131+
if (event.ctrlKey) {
132+
modifierKeys |= CanvasKit.ModifierKey.Ctrl;
133+
}
134+
if (event.metaKey) {
135+
modifierKeys |= CanvasKit.ModifierKey.Command;
136+
}
137+
let [dx, dy] = [event.pageX - this.lastX, event.pageY - this.lastY];
138+
this.lastX = event.pageX;
139+
this.lastY = event.pageY;
140+
if (slide.onMouse(event.pageX, event.pageY, state, modifierKeys)) {
141+
ScheduleDraw();
142+
return false;
143+
} else if (event.buttons & 1) { // Left-button pressed.
144+
TranslateCanvas(dx, dy);
145+
return false;
146+
}
147+
return true;
148+
}
149+
90150
const fps = {
91151
frames: 0,
92152
startMs: window.performance.now()
93153
};
94154

95-
surface.requestAnimationFrame(function(canvas) {
96-
slide.draw(canvas);
97-
if (doMSAA) {
98-
CanvasKit.BlitOffscreenFramebuffer(surface, 0, 0, width, height, 0, 0, width, height,
99-
CanvasKit.GLFilter.Nearest);
155+
function ScheduleDraw() {
156+
if (this.hasPendingAnimationRequest) {
157+
// It's possible for this ScheduleDraw() method to be called multiple times before an
158+
// animation callback actually gets invoked. Make sure we only ever have one single
159+
// requestAnimationFrame scheduled at a time, because otherwise we can get stuck in a
160+
// position where multiple callbacks are coming in on a single compositing frame, and then
161+
// rescheduling multiple more for the next frame.
162+
return;
100163
}
164+
this.hasPendingAnimationRequest = true;
165+
surface.requestAnimationFrame((canvas) => {
166+
this.hasPendingAnimationRequest = false;
101167

102-
++fps.frames;
103-
const ms = window.performance.now();
104-
const sec = (ms - fps.startMs) / 1000;
105-
if (sec > 2) {
106-
console.log(Math.round(fps.frames / sec) + ' fps');
107-
fps.frames = 0;
108-
fps.startMs = ms;
109-
}
168+
canvas.save();
169+
canvas.translate(canvasTranslateX, canvasTranslateY);
170+
canvas.scale(canvasScale, canvasScale);
171+
canvas.clear(CanvasKit.WHITE);
172+
slide.draw(canvas);
173+
if (surface.isMSAA) {
174+
let [w, h] = [surface.width(), surface.height()];
175+
CanvasKit.BlitOffscreenFramebuffer(surface, 0,0,w,h, 0,0,w,h, CanvasKit.GLFilter.Nearest);
176+
}
177+
canvas.restore();
110178

111-
if (slide.animate(ms * 1e6)) {
112-
surface.requestAnimationFrame(arguments.callee);
113-
}
114-
});
179+
++fps.frames;
180+
const ms = window.performance.now();
181+
const sec = (ms - fps.startMs) / 1000;
182+
if (sec > 2) {
183+
console.log(Math.round(fps.frames / sec) + ' fps');
184+
fps.frames = 0;
185+
fps.startMs = ms;
186+
}
187+
188+
if (slide.animate(ms * 1e6)) {
189+
ScheduleDraw();
190+
}
191+
});
192+
}
193+
194+
ScheduleDraw();
115195
}
196+
116197
</script>

modules/canvaskit/viewer_bindings.cpp

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include "include/core/SkCanvas.h"
1111
#include "include/core/SkSurface.h"
1212
#include "include/gpu/GrContext.h"
13+
#include "tools/skui/InputState.h"
14+
#include "tools/skui/ModifierKey.h"
1315
#include "tools/viewer/SKPSlide.h"
1416
#include "tools/viewer/SampleSlide.h"
1517
#include "tools/viewer/SvgSlide.h"
@@ -18,21 +20,25 @@
1820

1921
using namespace emscripten;
2022

21-
sk_sp<Slide> MakeSlide(std::string name) {
23+
static sk_sp<Slide> MakeSlide(std::string name) {
2224
if (name == "PathText") {
2325
extern Sample* MakePathTextSample();
2426
return sk_make_sp<SampleSlide>(MakePathTextSample);
2527
}
28+
if (name == "TessellatedWedge") {
29+
extern Sample* MakeTessellatedWedgeSample();
30+
return sk_make_sp<SampleSlide>(MakeTessellatedWedgeSample);
31+
}
2632
return nullptr;
2733
}
2834

29-
sk_sp<Slide> MakeSkpSlide(std::string name, std::string skpData) {
35+
static sk_sp<Slide> MakeSkpSlide(std::string name, std::string skpData) {
3036
auto stream = std::make_unique<SkMemoryStream>(skpData.data(), skpData.size(),
3137
/*copyData=*/true);
3238
return sk_make_sp<SKPSlide>(SkString(name.c_str()), std::move(stream));
3339
}
3440

35-
sk_sp<Slide> MakeSvgSlide(std::string name, std::string svgText) {
41+
static sk_sp<Slide> MakeSvgSlide(std::string name, std::string svgText) {
3642
auto stream = std::make_unique<SkMemoryStream>(svgText.data(), svgText.size(),
3743
/*copyData=*/true);
3844
return sk_make_sp<SvgSlide>(SkString(name.c_str()), std::move(stream));
@@ -43,8 +49,8 @@ static void delete_wrapped_framebuffer(SkSurface::ReleaseContext context) {
4349
glDeleteFramebuffers(1, &framebuffer);
4450
}
4551

46-
sk_sp<SkSurface> MakeOffscreenFramebuffer(sk_sp<GrContext> grContext, int width, int height,
47-
int sampleCnt) {
52+
static sk_sp<SkSurface> MakeOffscreenFramebuffer(sk_sp<GrContext> grContext, int width, int height,
53+
int sampleCnt) {
4854
GLuint colorBuffer;
4955
glGenRenderbuffers(1, &colorBuffer);
5056
glBindRenderbuffer(GL_RENDERBUFFER, colorBuffer);
@@ -87,8 +93,9 @@ enum class GLFilter {
8793
kLinear = GL_LINEAR
8894
};
8995

90-
void BlitOffscreenFramebuffer(sk_sp<SkSurface> surface, int srcX0, int srcY0, int srcX1, int srcY1,
91-
int dstX0, int dstY0, int dstX1, int dstY1, GLFilter filter) {
96+
static void BlitOffscreenFramebuffer(sk_sp<SkSurface> surface, int srcX0, int srcY0, int srcX1, int
97+
srcY1, int dstX0, int dstY0, int dstX1, int dstY1,
98+
GLFilter filter) {
9299
surface->flush(SkSurface::BackendSurfaceAccess::kPresent, GrFlushInfo());
93100
GrGLFramebufferInfo glInfo;
94101
auto backendRT = surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess);
@@ -112,8 +119,23 @@ EMSCRIPTEN_BINDINGS(Viewer) {
112119
.function("animate", &Slide::animate)
113120
.function("draw", optional_override([](Slide& self, SkCanvas& canvas) {
114121
self.draw(&canvas);
115-
}));
122+
}))
123+
.function("onChar", &Slide::onChar)
124+
.function("onMouse", &Slide::onMouse);
116125
enum_<GLFilter>("GLFilter")
117126
.value("Nearest", GLFilter::kNearest)
118127
.value("Linear", GLFilter::kLinear);
128+
enum_<skui::InputState>("InputState")
129+
.value("Down", skui::InputState::kDown)
130+
.value("Up", skui::InputState::kUp)
131+
.value("Move", skui::InputState::kMove)
132+
.value("Right", skui::InputState::kRight)
133+
.value("Left", skui::InputState::kLeft);
134+
enum_<skui::ModifierKey>("ModifierKey")
135+
.value("None", skui::ModifierKey::kNone)
136+
.value("Shift", skui::ModifierKey::kShift)
137+
.value("Control", skui::ModifierKey::kControl)
138+
.value("Option", skui::ModifierKey::kOption)
139+
.value("Command", skui::ModifierKey::kCommand)
140+
.value("FirstPress", skui::ModifierKey::kFirstPress);
119141
}

samplecode/SampleTessellatedWedge.cpp

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222

2323
// This sample enables wireframe and visualizes the triangulation generated by
2424
// GrTessellateWedgeShader.
25-
class TessellatedWedgeView : public Sample {
25+
class TessellatedWedge : public Sample {
2626
public:
27-
TessellatedWedgeView() {
27+
TessellatedWedge() {
2828
#if 0
2929
fPath.moveTo(1, 0);
3030
int numSides = 32 * 3;
@@ -56,7 +56,7 @@ class TessellatedWedgeView : public Sample {
5656
class Click;
5757
};
5858

59-
void TessellatedWedgeView::onDrawContent(SkCanvas* canvas) {
59+
void TessellatedWedge::onDrawContent(SkCanvas* canvas) {
6060
canvas->clear(SK_ColorBLACK);
6161

6262
GrContext* ctx = canvas->getGrContext();
@@ -65,8 +65,8 @@ void TessellatedWedgeView::onDrawContent(SkCanvas* canvas) {
6565
SkString error;
6666
if (!rtc || !ctx) {
6767
error = "GPU Only.";
68-
} else if (!ctx->priv().caps()->shaderCaps()->tessellationSupport()) {
69-
error = "GPU tessellation not supported.";
68+
} else if (!ctx->priv().caps()->drawInstancedSupport()) {
69+
error = "Instanced rendering not supported.";
7070
} else if (1 == rtc->numSamples() && !ctx->priv().caps()->mixedSamplesSupport()) {
7171
error = "MSAA/mixed samples only.";
7272
}
@@ -110,7 +110,7 @@ void TessellatedWedgeView::onDrawContent(SkCanvas* canvas) {
110110
fLastViewMatrix = canvas->getTotalMatrix();
111111
}
112112

113-
class TessellatedWedgeView::Click : public Sample::Click {
113+
class TessellatedWedge::Click : public Sample::Click {
114114
public:
115115
Click(int ptIdx) : fPtIdx(ptIdx) {}
116116

@@ -128,7 +128,7 @@ class TessellatedWedgeView::Click : public Sample::Click {
128128
int fPtIdx;
129129
};
130130

131-
Sample::Click* TessellatedWedgeView::onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) {
131+
Sample::Click* TessellatedWedge::onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) {
132132
const SkPoint* pts = SkPathPriv::PointData(fPath);
133133
float fuzz = 20 / fLastViewMatrix.getMaxScale();
134134
for (int i = 0; i < fPath.countPoints(); ++i) {
@@ -140,13 +140,13 @@ Sample::Click* TessellatedWedgeView::onFindClickHandler(SkScalar x, SkScalar y,
140140
return new Click(-1);
141141
}
142142

143-
bool TessellatedWedgeView::onClick(Sample::Click* click) {
143+
bool TessellatedWedge::onClick(Sample::Click* click) {
144144
Click* myClick = (Click*)click;
145145
myClick->doClick(&fPath);
146146
return true;
147147
}
148148

149-
bool TessellatedWedgeView::onChar(SkUnichar unichar) {
149+
bool TessellatedWedge::onChar(SkUnichar unichar) {
150150
switch (unichar) {
151151
case 'w':
152152
fFlags = (GrTessellatePathOp::Flags)(
@@ -160,6 +160,7 @@ bool TessellatedWedgeView::onChar(SkUnichar unichar) {
160160
return false;
161161
}
162162

163-
DEF_SAMPLE(return new TessellatedWedgeView;)
163+
Sample* MakeTessellatedWedgeSample() { return new TessellatedWedge; }
164+
static SampleRegistry gTessellatedWedgeSample(MakeTessellatedWedgeSample);
164165

165166
#endif // SK_SUPPORT_GPU

0 commit comments

Comments
 (0)