Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace mover tool by selector tool #193

Merged
merged 8 commits into from
May 24, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions client-data/board.css
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ circle.opcursor {
transition: 0s;
}

#board #selectionRect {
fill: none;
}

/* Internet Explorer specific CSS */
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
Expand Down
1 change: 1 addition & 0 deletions client-data/board.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
<script type="application/json" id="configuration">{{{ json configuration }}}</script>
<script src="../js/path-data-polyfill.js"></script>
<script src="../js/minitpl.js"></script>
<script src="../js/intersect.js"></script>
<script src="../js/board.js"></script>
<script src="../tools/pencil/wbo_pencil_point.js"></script>
<script src="../tools/pencil/pencil.js"></script>
Expand Down
92 changes: 92 additions & 0 deletions client-data/js/intersect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* INTERSEC
*********************************************************
* @licstart The following is the entire license notice for the
* JavaScript code in this page.
*
* Copyright (C) 2021 Ophir LOJKINE
*
*
* The JavaScript code in this page is free software: you can
* redistribute it and/or modify it under the terms of the GNU
* General Public License (GNU GPL) as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version. The code is distributed WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
*
* As additional permission under GNU GPL version 3 section 7, you
* may distribute non-source (e.g., minimized or compacted) forms of
* that code without the copy of the GNU GPL normally required by
* section 4, provided you include this license notice and a URL
* through which recipients can access the Corresponding Source.
*
* @licend
*/

if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototype.transformedBBoxContains) {
[pointInTransformedBBox,
transformedBBoxIntersects] = (function () {

let applyTransform = function (m,t) {
return [
m.a*t[0]+m.c*t[1],
m.b*t[0]+m.d*t[1]
]
}

SVGGraphicsElement.prototype.transformedBBox = function (scale=1) {
bbox = this.getBBox();
tmatrix = this.getCTM();
return {
r: [bbox.x + tmatrix.e/scale, bbox.y + tmatrix.f/scale],
a: applyTransform(tmatrix,[bbox.width/scale,0]),
b: applyTransform(tmatrix,[0,bbox.height/scale])
}
}

SVGSVGElement.prototype.transformedBBox = function (scale=1) {
bbox = {
x: this.x.baseVal.value,
y: this.y.baseVal.value,
width: this.width.baseVal.value,
height: this.height.baseVal.value
};
tmatrix = this.getCTM();
return {
r: [bbox.x + tmatrix.e/scale, bbox.y + tmatrix.f/scale],
a: applyTransform(tmatrix,[bbox.width/scale,0]),
b: applyTransform(tmatrix,[0,bbox.height/scale])
}
}

let pointInTransformedBBox = function ([x,y],{r,a,b}) {
var d = [x-r[0],y-r[1]];
var idet = (a[0]*b[1]-a[1]*b[0]);
var c1 = (d[0]*b[1]-d[1]*b[0]) / idet;
var c2 = (d[1]*a[0]-d[0]*a[1]) / idet;
return (c1>=0 && c1<=1 && c2>=0 && c2<=1)
}

SVGGraphicsElement.prototype.transformedBBoxContains = function (x,y) {
return pointInTransformedBBox([x, y], this.transformedBBox())
}

function transformedBBoxIntersects(bbox_a,bbox_b) {
var corners = [
bbox_b.r,
[bbox_b.r[0] + bbox_b.a[0], bbox_b.r[1] + bbox_b.a[1]],
[bbox_b.r[0] + bbox_b.b[0], bbox_b.r[1] + bbox_b.b[1]],
[bbox_b.r[0] + bbox_b.a[0] + bbox_b.b[0], bbox_b.r[1] + bbox_b.a[1] + bbox_b.b[1]]
]
return corners.every(corner=>pointInTransformedBBox(corner,bbox_a))
}

SVGGraphicsElement.prototype.transformedBBoxIntersects= function (bbox) {
return transformedBBoxIntersects(this.transformedBBox(),bbox)
}

return [pointInTransformedBBox,
transformedBBoxIntersects]
})();
}
185 changes: 161 additions & 24 deletions client-data/tools/hand/hand.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,115 @@
*/

(function hand() { //Code isolation
const selectorStates = {
pointing: 0,
selecting: 1,
moving: 2
}
var selected = null;
var selected_els = [];
var selectionRect = createSelectorRect();
var selectionRectTranslation;
var translation_elements = [];
var selectorState = selectorStates.pointing;
var last_sent = 0;

function getParentMathematics(el) {
var target
var a = el
var els = [];
while (a) {
els.unshift(a);
a = a.parentElement;
}
var parentMathematics = els.find(el => el.getAttribute("class") === "MathElement");
if ((parentMathematics) && parentMathematics.tagName === "svg") {
target = parentMathematics;
}
return target ?? el;
}

function createSelectorRect() {
var shape = Tools.createSVGElement("rect");
shape.id = "selectionRect";
shape.x.baseVal.value = 0;
shape.y.baseVal.value = 0;
shape.width.baseVal.value = 0;
shape.height.baseVal.value = 0;
shape.setAttribute("stroke", "black");
shape.setAttribute("stroke-width", 1);
shape.setAttribute("vector-effect", "non-scaling-stroke");
shape.setAttribute("fill", "none");
shape.setAttribute("stroke-dasharray", "5 5");
shape.setAttribute("opacity", 1);
Tools.svg.appendChild(shape);
return shape;
}

function startMovingElement(x, y, evt) {
//Prevent the press from being interpreted by the browser
function startMovingElements(x, y, evt) {
evt.preventDefault();
if (!evt.target || !Tools.drawingArea.contains(evt.target)) return;
var tmatrix = get_translate_matrix(evt.target);
selected = { x: x - tmatrix.e, y: y - tmatrix.f, elem: evt.target };
selectorState = selectorStates.moving;
selected = { x: x, y: y };
// Some of the selected elements could have been deleted
selected_els = selected_els.filter(el => {
return Tools.svg.getElementById(el.id) !== null
});
translation_elements = selected_els.map(el => {
let tmatrix = get_translate_matrix(el);
return { x: tmatrix.e, y: tmatrix.f }
});
{
let tmatrix = get_translate_matrix(selectionRect);
selectionRectTranslation = { x: tmatrix.e, y: tmatrix.f };
}
}

function moveElement(x, y) {
if (!selected) return;
var deltax = x - selected.x;
var deltay = y - selected.y;
var msg = { type: "update", id: selected.elem.id, deltax: deltax, deltay: deltay };
function startSelector(x, y, evt) {
evt.preventDefault();
selected = { x: x, y: y };
selected_els = [];
selectorState = selectorStates.selecting;
selectionRect.x.baseVal.value = x;
selectionRect.y.baseVal.value = y;
selectionRect.width.baseVal.value = 0;
selectionRect.height.baseVal.value = 0;
selectionRect.style.display = "";
tmatrix = get_translate_matrix(selectionRect);
tmatrix.e = 0;
tmatrix.f = 0;
}


function calculateSelection() {
var scale = Tools.drawingArea.getCTM().a;
var selectionTBBox = selectionRect.transformedBBox(scale);
return Array.from(Tools.drawingArea.children).filter(el => {
return transformedBBoxIntersects(
selectionTBBox,
el.transformedBBox(scale)
)
});
}

function moveSelection(x, y) {
var dx = x - selected.x;
var dy = y - selected.y;
var msgs = selected_els.map((el, i) => {
return {
type: "update",
id: el.id,
deltax: dx + translation_elements[i].x,
deltay: dy + translation_elements[i].y
}
})
var msg = {
_children: msgs
};
{
let tmatrix = get_translate_matrix(selectionRect);
tmatrix.e = dx + selectionRectTranslation.x;
tmatrix.f = dy + selectionRectTranslation.y;
}
var now = performance.now();
if (now - last_sent > 70) {
last_sent = now;
Expand All @@ -51,6 +143,13 @@
}
}

function updateRect(x, y, rect) {
rect.x.baseVal.value = Math.min(x, selected.x);
rect.y.baseVal.value = Math.min(y, selected.y);
rect.width.baseVal.value = Math.abs(x - selected.x);
rect.height.baseVal.value = Math.abs(y - selected.y);
}

function get_translate_matrix(elem) {
// Returns the first translate or transform matrix or makes one
var translate = null;
Expand All @@ -71,17 +170,54 @@
}

function draw(data) {
switch (data.type) {
case "update":
var elem = Tools.svg.getElementById(data.id);
if (!elem) throw new Error("Mover: Tried to move an element that does not exist.");
var tmatrix = get_translate_matrix(elem);
tmatrix.e = data.deltax || 0;
tmatrix.f = data.deltay || 0;
break;
if (data._children) {
batchCall(draw, data._children);
}
else {
switch (data.type) {
case "update":
var elem = Tools.svg.getElementById(data.id);
if (!elem) throw new Error("Mover: Tried to move an element that does not exist.");
var tmatrix = get_translate_matrix(elem);
tmatrix.e = data.deltax || 0;
tmatrix.f = data.deltay || 0;
break;
default:
throw new Error("Mover: 'move' instruction with unknown type. ", data);
}
}
}

function clickSelector(x, y, evt) {
var scale = Tools.drawingArea.getCTM().a
selectionRect = selectionRect ?? createSelectorRect();
if (pointInTransformedBBox([x, y], selectionRect.transformedBBox(scale))) {
startMovingElements(x, y, evt);
} else if (Tools.drawingArea.contains(evt.target)) {
selectionRect.style.display = "none";
selected_els = [getParentMathematics(evt.target)];
startMovingElements(x, y, evt);
} else {
startSelector(x, y, evt);
}
}

function releaseSelector(x, y, evt) {
if (selectorState == selectorStates.selecting) {
selected_els = calculateSelection();
if (selected_els.length == 0) {
selectionRect.style.display = "none";
}
}
translation_elements = [];
selectorState = selectorStates.pointing;
}

default:
throw new Error("Mover: 'move' instruction with unknown type. ", data);
function moveSelector(x, y, evt) {
if (selectorState == selectorStates.selecting) {
updateRect(x, y, selectionRect);
} else if (selectorState == selectorStates.moving) {
moveSelection(x, y, selectionRect);
}
}

Expand All @@ -101,17 +237,18 @@

function press(x, y, evt, isTouchEvent) {
if (!handTool.secondary.active) startHand(x, y, evt, isTouchEvent);
else startMovingElement(x, y, evt, isTouchEvent);
else clickSelector(x, y, evt, isTouchEvent);
}


function move(x, y, evt, isTouchEvent) {
if (!handTool.secondary.active) moveHand(x, y, evt, isTouchEvent);
else moveElement(x, y, evt, isTouchEvent);
else moveSelector(x, y, evt, isTouchEvent);
}

function release(x, y, evt, isTouchEvent) {
move(x, y, evt, isTouchEvent);
if (handTool.secondary.active) releaseSelector(x, y, evt, isTouchEvent);
selected = null;
}

Expand All @@ -128,8 +265,8 @@
"release": release,
},
"secondary": {
"name": "Mover",
"icon": "tools/hand/mover.svg",
"name": "Selector",
"icon": "tools/hand/selector.svg",
"active": false,
"switch": switchTool,
},
Expand Down
19 changes: 19 additions & 0 deletions client-data/tools/hand/selector.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "whitebophir",
"description": "Online collaborative whiteboard",
"version": "1.10.2",
"version": "1.11.0",
"keywords": [
"collaborative",
"whiteboard"
Expand Down
Loading