forked from enviPath/enviPy
211 lines
6.9 KiB
JavaScript
211 lines
6.9 KiB
JavaScript
/****************************************************************************
|
|
* Copyright 2017 EPAM Systems
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
***************************************************************************/
|
|
|
|
var Raphael = require('../raphael-ext');
|
|
var Box2Abs = require('../util/box2abs');
|
|
var Vec2 = require('../util/vec2');
|
|
var scale = require('../util/scale');
|
|
|
|
var Struct = require('../chem/struct');
|
|
var ReStruct = require('./restruct');
|
|
|
|
var defaultOptions = require('./options');
|
|
|
|
var DEBUG = { debug: false, logcnt: 0, logmouse: false, hl: false };
|
|
DEBUG.logMethod = function () { };
|
|
// DEBUG.logMethod = function (method) {addionalAtoms("METHOD: " + method);
|
|
|
|
function Render(clientArea, opt) {
|
|
this.userOpts = opt;
|
|
this.clientArea = clientArea;
|
|
this.paper = new Raphael(clientArea, 0, 0);
|
|
this.sz = Vec2.ZERO;
|
|
this.ctab = new ReStruct(new Struct(), this);
|
|
this.options = defaultOptions(this.userOpts);
|
|
}
|
|
|
|
Render.prototype.view2obj = function (p, isRelative) {
|
|
var scroll = this.scrollPos();
|
|
if (!this.useOldZoom) {
|
|
p = p.scaled(1 / this.options.zoom);
|
|
scroll = scroll.scaled(1 / this.options.zoom);
|
|
}
|
|
p = isRelative ? p : p.add(scroll).sub(this.options.offset);
|
|
return scale.scaled2obj(p, this.options);
|
|
};
|
|
|
|
Render.prototype.obj2view = function (v, isRelative) {
|
|
var p = scale.obj2scaled(v, this.options);
|
|
p = isRelative ? p : p.add(this.options.offset).sub(this.scrollPos().scaled(1 / this.options.zoom));
|
|
if (!this.useOldZoom)
|
|
p = p.scaled(this.options.zoom);
|
|
return p;
|
|
};
|
|
|
|
Render.prototype.scrollPos = function () {
|
|
return new Vec2(this.clientArea.scrollLeft, this.clientArea.scrollTop);
|
|
};
|
|
|
|
function cumulativeOffset(el) {
|
|
var curtop = 0;
|
|
var curleft = 0;
|
|
if (el.parentNode) {
|
|
do {
|
|
curtop += el.offsetTop || 0;
|
|
curleft += el.offsetLeft || 0;
|
|
el = el.offsetParent;
|
|
} while (el);
|
|
}
|
|
return { left: curleft, top: curtop };
|
|
}
|
|
|
|
Render.prototype.page2obj = function (pagePos) {
|
|
var offset = cumulativeOffset(this.clientArea);
|
|
var pp = new Vec2(pagePos.pageX - offset.left, pagePos.pageY - offset.top);
|
|
return this.view2obj(pp);
|
|
};
|
|
|
|
Render.prototype.setPaperSize = function (sz) {
|
|
DEBUG.logMethod('setPaperSize');
|
|
this.sz = sz;
|
|
this.paper.setSize(sz.x * this.options.zoom, sz.y * this.options.zoom);
|
|
this.setViewBox(this.options.zoom);
|
|
};
|
|
|
|
Render.prototype.setOffset = function (newoffset) {
|
|
DEBUG.logMethod('setOffset');
|
|
var delta = new Vec2(newoffset.x - this.options.offset.x, newoffset.y - this.options.offset.y);
|
|
this.clientArea.scrollLeft += delta.x;
|
|
this.clientArea.scrollTop += delta.y;
|
|
this.options.offset = newoffset;
|
|
};
|
|
|
|
Render.prototype.setZoom = function (zoom) {
|
|
// when scaling the canvas down it may happen that the scaled canvas is smaller than the view window
|
|
// don't forget to call setScrollOffset after zooming (or use extendCanvas directly)
|
|
console.info('set zoom', zoom);
|
|
this.options.zoom = zoom;
|
|
this.paper.setSize(this.sz.x * zoom, this.sz.y * zoom);
|
|
this.setViewBox(zoom);
|
|
};
|
|
|
|
function calcExtend(sSz, x0, y0, x1, y1) { // eslint-disable-line max-params
|
|
var ex = (x0 < 0) ? -x0 : 0;
|
|
var ey = (y0 < 0) ? -y0 : 0;
|
|
|
|
if (sSz.x < x1)
|
|
ex += x1 - sSz.x;
|
|
if (sSz.y < y1)
|
|
ey += y1 - sSz.y;
|
|
return new Vec2(ex, ey);
|
|
}
|
|
|
|
Render.prototype.setScrollOffset = function (x, y) {
|
|
var clientArea = this.clientArea;
|
|
var cx = clientArea.clientWidth;
|
|
var cy = clientArea.clientHeight;
|
|
var e = calcExtend(this.sz.scaled(this.options.zoom), x, y,
|
|
cx + x, cy + y).scaled(1 / this.options.zoom);
|
|
if (e.x > 0 || e.y > 0) {
|
|
this.setPaperSize(this.sz.add(e));
|
|
var d = new Vec2((x < 0) ? -x : 0,
|
|
(y < 0) ? -y : 0).scaled(1 / this.options.zoom);
|
|
if (d.x > 0 || d.y > 0) {
|
|
this.ctab.translate(d);
|
|
this.setOffset(this.options.offset.add(d));
|
|
}
|
|
}
|
|
clientArea.scrollLeft = x;
|
|
clientArea.scrollTop = y;
|
|
// TODO: store drag position in scaled systems
|
|
// scrollLeft = clientArea.scrollLeft;
|
|
// scrollTop = clientArea.scrollTop;
|
|
this.update(false);
|
|
};
|
|
|
|
Render.prototype.setScale = function (z) {
|
|
if (this.options.offset)
|
|
this.options.offset = this.options.offset.scaled(1 / z).scaled(z);
|
|
this.userOpts.scale *= z;
|
|
this.options = null;
|
|
this.update(true);
|
|
};
|
|
|
|
Render.prototype.setViewBox = function (z) {
|
|
if (!this.useOldZoom)
|
|
this.paper.canvas.setAttribute('viewBox', '0 0 ' + this.sz.x + ' ' + this.sz.y);
|
|
else
|
|
this.setScale(z);
|
|
};
|
|
|
|
Render.prototype.setMolecule = function (ctab) {
|
|
debugger;
|
|
DEBUG.logMethod('setMolecule');
|
|
this.paper.clear();
|
|
this.ctab = new ReStruct(ctab, this);
|
|
this.options.offset = new Vec2();
|
|
this.update(false);
|
|
};
|
|
|
|
Render.prototype.update = function (force, viewSz) { // eslint-disable-line max-statements
|
|
viewSz = viewSz || new Vec2(this.clientArea.clientWidth || 100,
|
|
this.clientArea.clientHeight || 100);
|
|
|
|
var changes = this.ctab.update(force);
|
|
this.ctab.setSelection(); // [MK] redraw the selection bits where necessary
|
|
if (changes) {
|
|
var sf = this.options.scale;
|
|
var bb = this.ctab.getVBoxObj().transform(scale.obj2scaled, this.options).translate(this.options.offset || new Vec2());
|
|
|
|
if (!this.options.autoScale) {
|
|
var ext = Vec2.UNIT.scaled(sf);
|
|
var eb = bb.sz().length() > 0 ? bb.extend(ext, ext) : bb;
|
|
var vb = new Box2Abs(this.scrollPos(), viewSz.scaled(1 / this.options.zoom).sub(Vec2.UNIT.scaled(20)));
|
|
var cb = Box2Abs.union(vb, eb);
|
|
if (!this.oldCb)
|
|
this.oldCb = new Box2Abs();
|
|
|
|
var sz = cb.sz().floor();
|
|
var delta = this.oldCb.p0.sub(cb.p0).ceil();
|
|
this.oldBb = bb;
|
|
if (!this.sz || sz.x != this.sz.x || sz.y != this.sz.y)
|
|
this.setPaperSize(sz);
|
|
|
|
this.options.offset = this.options.offset || new Vec2();
|
|
if (delta.x != 0 || delta.y != 0) {
|
|
this.setOffset(this.options.offset.add(delta));
|
|
this.ctab.translate(delta);
|
|
}
|
|
} else {
|
|
var sz1 = bb.sz();
|
|
var marg = this.options.autoScaleMargin;
|
|
var mv = new Vec2(marg, marg);
|
|
var csz = viewSz;
|
|
if (csz.x < (2 * marg) + 1 || csz.y < (2 * marg) + 1)
|
|
throw new Error('View box too small for the given margin');
|
|
var rescale = Math.max(sz1.x / (csz.x - (2 * marg)), sz1.y / (csz.y - (2 * marg)));
|
|
if (this.options.maxBondLength / rescale > 1.0)
|
|
rescale = 1.0;
|
|
var sz2 = sz1.add(mv.scaled(2 * rescale));
|
|
/* eslint-disable no-mixed-operators*/
|
|
this.paper.setViewBox(bb.pos().x - marg * rescale - (csz.x * rescale - sz2.x) / 2, bb.pos().y - marg * rescale - (csz.y * rescale - sz2.y) / 2, csz.x * rescale, csz.y * rescale);
|
|
/* eslint-enable no-mixed-operators*/
|
|
}
|
|
}
|
|
};
|
|
|
|
module.exports = Render;
|