mirror of
https://github.com/skishore/makemeahanzi.git
synced 2025-11-02 21:41:28 +08:00
Cleanly extract handwriting UI module
This commit is contained in:
123
client/handwriting.js
Normal file
123
client/handwriting.js
Normal file
@ -0,0 +1,123 @@
|
||||
// Helper methods used by the handwriting class.
|
||||
|
||||
const createSketch = (element, handwriting) => {
|
||||
let mousedown = false;
|
||||
Sketch.create({
|
||||
container: element[0],
|
||||
autoclear: false,
|
||||
fullscreen: false,
|
||||
width: element.width(),
|
||||
height: element.height(),
|
||||
mousedown(e) {
|
||||
mousedown = true;
|
||||
handwriting._pushPoint([e.x, e.y]);
|
||||
},
|
||||
mouseup(e) {
|
||||
mousedown = false;
|
||||
handwriting._endStroke();
|
||||
},
|
||||
touchmove() {
|
||||
if (mousedown && this.touches.length > 0) {
|
||||
const touch = this.touches[0];
|
||||
handwriting._maybePushPoint([touch.ox, touch.oy]);
|
||||
handwriting._pushPoint([touch.x, touch.y]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const dottedLine = (x1, y1, x2, y2) => {
|
||||
const result = new createjs.Shape();
|
||||
result.graphics.setStrokeDash([2, 2], 0);
|
||||
result.graphics.setStrokeStyle(2)
|
||||
result.graphics.beginStroke('#ccc');
|
||||
result.graphics.moveTo(x1, y1);
|
||||
result.graphics.lineTo(x2, y2);
|
||||
return result;
|
||||
}
|
||||
|
||||
const renderCross = (stage) => {
|
||||
const cross = new createjs.Container();
|
||||
const height = stage.canvas.height;
|
||||
const width = stage.canvas.width;
|
||||
cross.addChild(dottedLine(0, 0, width, height));
|
||||
cross.addChild(dottedLine(width, 0, 0, height));
|
||||
cross.addChild(dottedLine(width / 2, 0, width / 2, height));
|
||||
cross.addChild(dottedLine(0, height / 2, width, height / 2));
|
||||
cross.cache(0, 0, width, height);
|
||||
stage.addChild(cross);
|
||||
}
|
||||
|
||||
// Methods for actually executing drawing commands.
|
||||
|
||||
this.makemeahanzi.Handwriting = class Handwriting {
|
||||
constructor(element, callback, zoom) {
|
||||
this._callback = callback;
|
||||
this._shape = null;
|
||||
this._stroke = [];
|
||||
this._zoom = zoom;
|
||||
|
||||
createSketch(element, this);
|
||||
this._container = new createjs.Container();
|
||||
this._stage = new createjs.Stage(element.find('canvas')[0]);
|
||||
|
||||
renderCross(this._stage);
|
||||
this._stage.addChild(this._container);
|
||||
this._stage.update();
|
||||
}
|
||||
clear() {
|
||||
this._container.removeAllChildren();
|
||||
this._reset();
|
||||
}
|
||||
undo() {
|
||||
this._container.removeChildAt(this._container.children.length - 1);
|
||||
this._reset();
|
||||
}
|
||||
_distance(point1, point2) {
|
||||
const diagonal = this._stage.canvas.width * this._stage.canvas.width +
|
||||
this._stage.canvas.height * this._stage.canvas.height;
|
||||
const diff = [point1[0] - point2[0], point1[1] - point2[1]];
|
||||
return (diff[0] * diff[0] + diff[1] * diff[1]) / diagonal;
|
||||
}
|
||||
_endStroke() {
|
||||
if (this._shape) {
|
||||
this._callback(this._stroke);
|
||||
this._shape.cache(0, 0, this._stage.canvas.width,
|
||||
this._stage.canvas.height);
|
||||
}
|
||||
this._reset();
|
||||
}
|
||||
_maybePushPoint(point) {
|
||||
if (this._stroke.length === 0) {
|
||||
this._pushPoint(point);
|
||||
}
|
||||
}
|
||||
_pushPoint(point) {
|
||||
if (point[0] != null && point[1] != null) {
|
||||
this._stroke.push(point.map((x) => Math.round(x / this._zoom)));
|
||||
this._refresh();
|
||||
}
|
||||
}
|
||||
_refresh() {
|
||||
if (this._stroke.length < 2) {
|
||||
return;
|
||||
}
|
||||
if (!this._shape) {
|
||||
this._shape = new createjs.Shape();
|
||||
this._container.addChild(this._shape);
|
||||
}
|
||||
const i = this._stroke.length - 2;
|
||||
const d = this._distance(this._stroke[i], this._stroke[i + 1]);
|
||||
const graphics = this._shape.graphics;
|
||||
graphics.setStrokeStyle(-Math.log(d), 'round');
|
||||
graphics.beginStroke('black');
|
||||
graphics.moveTo(this._stroke[i][0], this._stroke[i][1]);
|
||||
graphics.lineTo(this._stroke[i + 1][0], this._stroke[i + 1][1]);
|
||||
this._stage.update();
|
||||
}
|
||||
_reset() {
|
||||
this._shape = null;
|
||||
this._stroke = [];
|
||||
this._stage.update();
|
||||
}
|
||||
}
|
||||
160
client/search.js
160
client/search.js
@ -2,157 +2,44 @@ const candidates = new ReactiveVar([]);
|
||||
const strokes = new ReactiveVar([]);
|
||||
const zoom = new ReactiveVar(1);
|
||||
|
||||
let container = null;
|
||||
let current = null;
|
||||
let stroke = [];
|
||||
let stage = null;
|
||||
let handwriting = null;
|
||||
|
||||
makemeahanzi.mediansPromise.then((medians) => {
|
||||
const matcher = new makemeahanzi.Matcher(medians);
|
||||
Deps.autorun(() => candidates.set(matcher.match(strokes.get(), 8)));
|
||||
}).catch(console.error.bind(console));
|
||||
|
||||
// Methods needed to initialize the drawing canvas.
|
||||
|
||||
const createSketch = function() {
|
||||
let mousedown = false;
|
||||
const element = $(this.firstNode).find('.handwriting');
|
||||
Sketch.create({
|
||||
container: element[0],
|
||||
autoclear: false,
|
||||
fullscreen: false,
|
||||
width: element.width(),
|
||||
height: element.height(),
|
||||
mousedown(e) {
|
||||
mousedown = true;
|
||||
pushPoint([e.x, e.y]);
|
||||
},
|
||||
mouseup(e) {
|
||||
mousedown = false;
|
||||
endStroke();
|
||||
},
|
||||
touchmove() {
|
||||
if (mousedown && this.touches.length > 0) {
|
||||
const touch = this.touches[0];
|
||||
maybePushPoint([touch.ox, touch.oy]);
|
||||
pushPoint([touch.x, touch.y]);
|
||||
}
|
||||
}
|
||||
});
|
||||
initializeStage(element);
|
||||
}
|
||||
|
||||
const dottedLine = (x1, y1, x2, y2) => {
|
||||
const result = new createjs.Shape();
|
||||
result.graphics.setStrokeDash([2, 2], 0);
|
||||
result.graphics.setStrokeStyle(2)
|
||||
result.graphics.beginStroke('#ccc');
|
||||
result.graphics.moveTo(x1, y1);
|
||||
result.graphics.lineTo(x2, y2);
|
||||
return result;
|
||||
}
|
||||
|
||||
const initializeStage = (element) => {
|
||||
container = new createjs.Container();
|
||||
stage = new createjs.Stage(element.find('canvas')[0]);
|
||||
|
||||
const cross = new createjs.Container();
|
||||
const height = stage.canvas.height;
|
||||
const width = stage.canvas.width;
|
||||
cross.addChild(dottedLine(0, 0, width, height));
|
||||
cross.addChild(dottedLine(width, 0, 0, height));
|
||||
cross.addChild(dottedLine(width / 2, 0, width / 2, height));
|
||||
cross.addChild(dottedLine(0, height / 2, width, height / 2));
|
||||
cross.cache(0, 0, width, height);
|
||||
|
||||
stage.addChild(cross, container);
|
||||
stage.update();
|
||||
}
|
||||
|
||||
const resize = function() {
|
||||
const outer = $(this.firstNode);
|
||||
const getZoom = (outer) => {
|
||||
const inner = outer.children();
|
||||
const x_zoom = outer.width() / inner.outerWidth();
|
||||
const y_zoom = outer.height() / inner.outerHeight();
|
||||
zoom.set(Math.min(x_zoom, y_zoom));
|
||||
return Math.min(x_zoom, y_zoom);
|
||||
}
|
||||
|
||||
// Methods for actually executing drawing commands.
|
||||
|
||||
const clear = () => {
|
||||
const onRendered = function() {
|
||||
const outer = $(this.firstNode);
|
||||
const element = outer.find('.handwriting');
|
||||
const callback = strokes.push.bind(strokes);
|
||||
strokes.set([]);
|
||||
container.removeAllChildren();
|
||||
clearCurrent();
|
||||
}
|
||||
|
||||
const clearCurrent = () => {
|
||||
current = null;
|
||||
stroke = [];
|
||||
stage.update();
|
||||
}
|
||||
|
||||
const distance = (point1, point2) => {
|
||||
const diagonal = stage.canvas.width * stage.canvas.width +
|
||||
stage.canvas.height * stage.canvas.height;
|
||||
const diff = [point1[0] - point2[0], point1[1] - point2[1]];
|
||||
return (diff[0] * diff[0] + diff[1] * diff[1]) / diagonal;
|
||||
}
|
||||
|
||||
const endStroke = () => {
|
||||
if (stroke.length >= 2) {
|
||||
strokes.push(stroke);
|
||||
}
|
||||
if (current) current.cache(0, 0, stage.canvas.width, stage.canvas.height);
|
||||
clearCurrent();
|
||||
}
|
||||
|
||||
const maybePushPoint = (point) => {
|
||||
if (stroke.length === 0) {
|
||||
pushPoint(point);
|
||||
}
|
||||
}
|
||||
|
||||
const pushPoint = (point) => {
|
||||
if (point[0] != null && point[1] != null) {
|
||||
stroke.push(point.map((x) => Math.round(x / zoom.get())));
|
||||
refreshStage();
|
||||
}
|
||||
}
|
||||
|
||||
const refreshStage = () => {
|
||||
if (stroke.length < 2) {
|
||||
return;
|
||||
}
|
||||
if (!current) {
|
||||
current = new createjs.Shape();
|
||||
container.addChild(current);
|
||||
}
|
||||
const i = stroke.length - 2;
|
||||
const d = distance(stroke[i], stroke[i + 1]);
|
||||
current.graphics.setStrokeStyle(-Math.log(d), 'round');
|
||||
current.graphics.beginStroke('black');
|
||||
current.graphics.moveTo(stroke[i][0], stroke[i][1]);
|
||||
current.graphics.lineTo(stroke[i + 1][0], stroke[i + 1][1]);
|
||||
stage.update();
|
||||
}
|
||||
|
||||
const undo = () => {
|
||||
strokes.pop();
|
||||
container.removeChildAt(container.children.length - 1);
|
||||
clearCurrent();
|
||||
zoom.set(getZoom(outer));
|
||||
handwriting = new makemeahanzi.Handwriting(element, callback, zoom.get());
|
||||
}
|
||||
|
||||
// Meteor template bindings.
|
||||
|
||||
const log = function() {
|
||||
Meteor.call('recordHandwriting', strokes.get(), candidates.get(), this[0],
|
||||
(error, result) => { if (error) console.error(error); });
|
||||
}
|
||||
|
||||
Template.search.events({
|
||||
'click .controls .clear.button': clear,
|
||||
'click .controls .undo.button': undo,
|
||||
'click .candidate': log,
|
||||
'click .controls .clear.button': () => {
|
||||
strokes.set([]);
|
||||
handwriting.clear();
|
||||
},
|
||||
'click .controls .undo.button': () => {
|
||||
strokes.pop();
|
||||
handwriting.undo();
|
||||
},
|
||||
'click .candidate': function() {
|
||||
Meteor.call('recordHandwriting', strokes.get(), candidates.get(), this[0],
|
||||
(error, result) => { if (error) console.error(error); });
|
||||
},
|
||||
});
|
||||
|
||||
Template.search.helpers({
|
||||
@ -161,7 +48,4 @@ Template.search.helpers({
|
||||
zoom: () => zoom.get(),
|
||||
});
|
||||
|
||||
Template.search.onRendered(createSketch);
|
||||
Template.search.onRendered(resize);
|
||||
|
||||
window.addEventListener('hashchange', clear, false);
|
||||
Template.search.onRendered(onRendered);
|
||||
|
||||
Reference in New Issue
Block a user