Track mouse for cursor and hilites

Change-Id: Icfc8a28fa0c9e3118ef4ddc5ae12162aab3fb484
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/398556
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Mike Reed 2021-04-30 16:48:24 -04:00
parent 31fddc3769
commit 097263bb50
2 changed files with 263 additions and 3 deletions

View File

@ -32,6 +32,7 @@
<h2> Paragraph </h2>
<canvas id=para1 width=600 height=600></canvas>
<canvas id=para2 width=600 height=600></canvas>
<h2> CanvasKit can serialize/deserialize .skp files</h2>
<canvas id=skp width=500 height=500></canvas>
@ -141,6 +142,7 @@
Promise.all([ckLoaded, loadFont]).then((results) => {
ParagraphAPI1(...results);
ParagraphAPI2(...results);
GlyphGame(...results)
});
Promise.all([ckLoaded, loadSkp]).then((results) => {SkpExample(...results)});
@ -303,6 +305,253 @@
return surface;
}
function MakeCursor(CanvasKit) {
const linePaint = new CanvasKit.Paint();
linePaint.setColor([0,0,1,1]);
linePaint.setStyle(CanvasKit.PaintStyle.Stroke);
linePaint.setStrokeWidth(2);
linePaint.setAntiAlias(true);
const pathPaint = new CanvasKit.Paint();
pathPaint.setColor([0,0,1,0.25]);
linePaint.setAntiAlias(true);
return {
_line_paint: linePaint, // wrap in weak-ref so we can delete it?
_path_paint: pathPaint,
_x: 0,
_top: 0,
_bottom: 0,
_path: null, // only use x,top,bottom if path is null
_draws_per_sec: 2,
// pass 0 for no-draw, pass inf. for always on
setBlinkRate: function(blinks_per_sec) {
this._draws_per_sec = blinks_per_sec;
},
place: function(x, y, fontMetrics) {
this._x = x;
this._top = y + fontMetrics.ascent;
this._bottom = y + fontMetrics.descent;
this._path = null;
},
setPath: function(path) {
this._path = path;
},
draw_before: function(canvas) {
if (this._path) {
canvas.drawPath(this._path, this._path_paint);
}
},
draw_after: function(canvas) {
if (this._path) {
return;
}
if (Math.floor(Date.now() * this._draws_per_sec / 1000) & 1) {
canvas.drawLine(this._x, this._top, this._x, this._bottom, this._line_paint);
}
},
};
}
let mouse = {
_start_x: 0, _start_y: 0,
_curr_x: 0, _curr_y: 0,
_active: false,
isActive: function() {
return this._active;
},
setDown: function(x, y) {
this._start_x = this._curr_x = x;
this._start_y = this._curr_y = y;
this._active = true;
},
setMove: function(x, y) {
this._curr_x = x;
this._curr_y = y;
},
setUp: function(x, y) {
this._curr_x = x;
this._curr_y = y;
this._active = false;
},
getPos: function() {
return [ this._start_x, this._start_y, this._curr_x, this._curr_y ];
},
}
function ParagraphAPI2(CanvasKit, fontData) {
if (!CanvasKit || !fontData) {
return;
}
const surface = CanvasKit.MakeCanvasSurface('para2');
if (!surface) {
console.error('Could not make surface');
return;
}
const cursor = MakeCursor(CanvasKit);
const canvas = surface.getCanvas();
const fontMgr = CanvasKit.FontMgr.FromData([fontData]);
const paraStyle = new CanvasKit.ParagraphStyle({
textStyle: {
color: CanvasKit.GRAY,
fontFamilies: ['Roboto'],
fontSize: 40,
},
textAlign: CanvasKit.TextAlign.Left,
});
const builder = CanvasKit.ParagraphBuilder.Make(paraStyle, fontMgr);
const text = "In a hole in the ground there lived a hobbit. Not a nasty, dirty, " +
"wet hole full of worms and oozy smells. This was a hobbit-hole and " +
"that means good food, a warm hearth, and all the comforts of home.";
builder.addText(text);
const paragraph = builder.build();
const WIDTH = 600;
paragraph.layout(WIDTH);
const tf = fontMgr.MakeTypefaceFromData(fontData);
const font = new CanvasKit.Font(tf, 40);
const fm = font.getMetrics();
const fontPaint = new CanvasKit.Paint();
fontPaint.setStyle(CanvasKit.PaintStyle.Fill);
fontPaint.setAntiAlias(true);
function mid(a, b) { return (a + b) * 0.5; }
function pos_to_index(runs, x, y) {
if (y < runs[0].positions[1] + fm.ascent) {
return 0;
}
for (const r of runs) {
if (y >= r.positions[1] + fm.ascent &&
y <= r.positions[1] + fm.descent) {
for (let i = 1; i < r.offsets.length; i += 1) {
if (x < r.positions[i*2]) {
if (x <= mid(r.positions[i*2-2], r.positions[i*2])) {
return r.offsets[i-1];
} else {
return r.offsets[i];
}
}
}
return r.offsets[r.offsets.length-1];
}
}
const r = runs[runs.length-1];
return r.offsets[r.offsets.length-1]; // last
}
function find_run(runs, index) {
for (const r of runs) {
if (index <= r.offsets[r.offsets.length-1]) {
return r;
}
}
return null;
}
function get_run_pos(r, index) {
for (const i in r.offsets) {
if (index == r.offsets[i]) {
return r.positions.slice(i*2, i*2+2);
}
}
return null;
}
function index_to_pos(runs, index) {
const r = find_run(runs, index);
return get_run_pos(r, index);
}
function indices_to_path(runs, a, b, fm, width) {
if (a == b) {
return null;
}
if (a > b) {
const tmp = a;
a = b;
b = tmp;
}
const path = new CanvasKit.Path();
const first = find_run(runs, a);
const last = find_run(runs, b);
const p = get_run_pos(first, a);
const q = get_run_pos(last, b);
if (first == last) {
path.addRect([p[0], p[1] + fm.ascent, q[0], q[1] + fm.descent]);
} else {
const p_top = p[1] + fm.ascent;
const p_bot = p[1] + fm.descent;
path.addRect([p[0], p_top, width, p_bot]);
const q_top = q[1] + fm.ascent;
const q_bot = q[1] + fm.descent;
path.addRect([ 0, q_top, q[0], q_bot]);
if (p_bot < q_top) {
path.addRect([0, p_bot, width, q_top]); // extra lines inbetween
}
}
return path;
}
let INDEX = 0;
let runs;
function drawFrame(canvas) {
canvas.clear(CanvasKit.WHITE);
canvas.drawParagraph(paragraph, 0, 0);
if (!runs) {
runs = paragraph.getShapedRuns();
}
if (mouse.isActive()) {
const pos = mouse.getPos();
const a = pos_to_index(runs, pos[0], pos[1]);
const b = pos_to_index(runs, pos[2], pos[3]);
if (a == b) {
INDEX = a;
const p = index_to_pos(runs, INDEX);
cursor.place(p[0], p[1], fm);
} else {
cursor.setPath(indices_to_path(runs, a, b, fm, WIDTH));
}
}
cursor.draw_before(canvas);
for (let r of runs) {
canvas.drawGlyphs(r.glyphs, r.positions, 0, 0, font, fontPaint);
}
cursor.draw_after(canvas);
surface.requestAnimationFrame(drawFrame);
}
surface.requestAnimationFrame(drawFrame);
function interact(e) {
const type = e.type;
if (type === 'pointerup') {
mouse.setUp(e.offsetX, e.offsetY);
} else if (type === 'pointermove') {
mouse.setMove(e.offsetX, e.offsetY);
} else if (type === 'pointerdown') {
mouse.setDown(e.offsetX, e.offsetY);
}
};
document.getElementById('para2').addEventListener('pointermove', interact);
document.getElementById('para2').addEventListener('pointerdown', interact);
// document.getElementById('para2').addEventListener('lostpointercapture', interact);
// document.getElementById('para2').addEventListener('pointerleave', interact);
document.getElementById('para2').addEventListener('pointerup', interact);
return surface;
}
function RTShaderAPI1(CanvasKit) {
if (!CanvasKit) {
return;

View File

@ -94,9 +94,13 @@ protected:
}
void drawFromVisitor(SkCanvas* canvas, skia::textlayout::Paragraph* para) const {
SkPaint p;
SkPaint p, p2;
p.setColor(0xFF0000FF);
para->visit([canvas, p](const skia::textlayout::Paragraph::VisitorInfo& info) {
p2.setColor(0xFFFF0000);
p2.setStrokeWidth(4);
p2.setStrokeCap(SkPaint::kSquare_Cap);
para->visit([&](const skia::textlayout::Paragraph::VisitorInfo& info) {
canvas->drawGlyphs(info.count, info.glyphs, info.positions, info.origin, info.font, p);
if (info.utf8Starts && false) {
@ -106,6 +110,13 @@ protected:
}
SkDebugf("'%s'\n", str.c_str());
}
if (false) { // show position points
for (int i = 0; i < info.count; ++i) {
auto pos = info.positions[i];
canvas->drawPoint(pos.fX + info.origin.fX, pos.fY + info.origin.fY, p2);
}
}
});
}
@ -151,7 +162,7 @@ protected:
bool runAsBench() const override { return true; }
bool onAnimate(double /*nanos*/) override {
return true;
return false;
}
private: