Skip to content

Commit

Permalink
Merge pull request #193 from sents/selector
Browse files Browse the repository at this point in the history
Replace mover tool by selector tool
  • Loading branch information
lovasoa authored May 24, 2021
2 parents 8e90aa3 + 16d1140 commit 9127340
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 43 deletions.
1 change: 0 additions & 1 deletion client-data/board.css
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,6 @@ circle.opcursor {
transition: 0s;
}


/* Internet Explorer specific CSS */
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
#chooseColor {
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

0 comments on commit 9127340

Please sign in to comment.