Skip to content

Commit 9127340

Browse files
authored
Merge pull request #193 from sents/selector
Replace mover tool by selector tool
2 parents 8e90aa3 + 16d1140 commit 9127340

File tree

8 files changed

+317
-43
lines changed

8 files changed

+317
-43
lines changed

client-data/board.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,6 @@ circle.opcursor {
275275
transition: 0s;
276276
}
277277

278-
279278
/* Internet Explorer specific CSS */
280279
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
281280
#chooseColor {

client-data/board.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
<script type="application/json" id="configuration">{{{ json configuration }}}</script>
8989
<script src="../js/path-data-polyfill.js"></script>
9090
<script src="../js/minitpl.js"></script>
91+
<script src="../js/intersect.js"></script>
9192
<script src="../js/board.js"></script>
9293
<script src="../tools/pencil/wbo_pencil_point.js"></script>
9394
<script src="../tools/pencil/pencil.js"></script>

client-data/js/intersect.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* INTERSEC
3+
*********************************************************
4+
* @licstart The following is the entire license notice for the
5+
* JavaScript code in this page.
6+
*
7+
* Copyright (C) 2021 Ophir LOJKINE
8+
*
9+
*
10+
* The JavaScript code in this page is free software: you can
11+
* redistribute it and/or modify it under the terms of the GNU
12+
* General Public License (GNU GPL) as published by the Free Software
13+
* Foundation, either version 3 of the License, or (at your option)
14+
* any later version. The code is distributed WITHOUT ANY WARRANTY;
15+
* without even the implied warranty of MERCHANTABILITY or FITNESS
16+
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
17+
*
18+
* As additional permission under GNU GPL version 3 section 7, you
19+
* may distribute non-source (e.g., minimized or compacted) forms of
20+
* that code without the copy of the GNU GPL normally required by
21+
* section 4, provided you include this license notice and a URL
22+
* through which recipients can access the Corresponding Source.
23+
*
24+
* @licend
25+
*/
26+
27+
if (!SVGGraphicsElement.prototype.transformedBBox || !SVGGraphicsElement.prototype.transformedBBoxContains) {
28+
[pointInTransformedBBox,
29+
transformedBBoxIntersects] = (function () {
30+
31+
let applyTransform = function (m,t) {
32+
return [
33+
m.a*t[0]+m.c*t[1],
34+
m.b*t[0]+m.d*t[1]
35+
]
36+
}
37+
38+
SVGGraphicsElement.prototype.transformedBBox = function (scale=1) {
39+
bbox = this.getBBox();
40+
tmatrix = this.getCTM();
41+
return {
42+
r: [bbox.x + tmatrix.e/scale, bbox.y + tmatrix.f/scale],
43+
a: applyTransform(tmatrix,[bbox.width/scale,0]),
44+
b: applyTransform(tmatrix,[0,bbox.height/scale])
45+
}
46+
}
47+
48+
SVGSVGElement.prototype.transformedBBox = function (scale=1) {
49+
bbox = {
50+
x: this.x.baseVal.value,
51+
y: this.y.baseVal.value,
52+
width: this.width.baseVal.value,
53+
height: this.height.baseVal.value
54+
};
55+
tmatrix = this.getCTM();
56+
return {
57+
r: [bbox.x + tmatrix.e/scale, bbox.y + tmatrix.f/scale],
58+
a: applyTransform(tmatrix,[bbox.width/scale,0]),
59+
b: applyTransform(tmatrix,[0,bbox.height/scale])
60+
}
61+
}
62+
63+
let pointInTransformedBBox = function ([x,y],{r,a,b}) {
64+
var d = [x-r[0],y-r[1]];
65+
var idet = (a[0]*b[1]-a[1]*b[0]);
66+
var c1 = (d[0]*b[1]-d[1]*b[0]) / idet;
67+
var c2 = (d[1]*a[0]-d[0]*a[1]) / idet;
68+
return (c1>=0 && c1<=1 && c2>=0 && c2<=1)
69+
}
70+
71+
SVGGraphicsElement.prototype.transformedBBoxContains = function (x,y) {
72+
return pointInTransformedBBox([x, y], this.transformedBBox())
73+
}
74+
75+
function transformedBBoxIntersects(bbox_a,bbox_b) {
76+
var corners = [
77+
bbox_b.r,
78+
[bbox_b.r[0] + bbox_b.a[0], bbox_b.r[1] + bbox_b.a[1]],
79+
[bbox_b.r[0] + bbox_b.b[0], bbox_b.r[1] + bbox_b.b[1]],
80+
[bbox_b.r[0] + bbox_b.a[0] + bbox_b.b[0], bbox_b.r[1] + bbox_b.a[1] + bbox_b.b[1]]
81+
]
82+
return corners.every(corner=>pointInTransformedBBox(corner,bbox_a))
83+
}
84+
85+
SVGGraphicsElement.prototype.transformedBBoxIntersects= function (bbox) {
86+
return transformedBBoxIntersects(this.transformedBBox(),bbox)
87+
}
88+
89+
return [pointInTransformedBBox,
90+
transformedBBoxIntersects]
91+
})();
92+
}

client-data/tools/hand/hand.js

Lines changed: 161 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,115 @@
2525
*/
2626

2727
(function hand() { //Code isolation
28+
const selectorStates = {
29+
pointing: 0,
30+
selecting: 1,
31+
moving: 2
32+
}
2833
var selected = null;
34+
var selected_els = [];
35+
var selectionRect = createSelectorRect();
36+
var selectionRectTranslation;
37+
var translation_elements = [];
38+
var selectorState = selectorStates.pointing;
2939
var last_sent = 0;
3040

41+
function getParentMathematics(el) {
42+
var target
43+
var a = el
44+
var els = [];
45+
while (a) {
46+
els.unshift(a);
47+
a = a.parentElement;
48+
}
49+
var parentMathematics = els.find(el => el.getAttribute("class") === "MathElement");
50+
if ((parentMathematics) && parentMathematics.tagName === "svg") {
51+
target = parentMathematics;
52+
}
53+
return target ?? el;
54+
}
55+
56+
function createSelectorRect() {
57+
var shape = Tools.createSVGElement("rect");
58+
shape.id = "selectionRect";
59+
shape.x.baseVal.value = 0;
60+
shape.y.baseVal.value = 0;
61+
shape.width.baseVal.value = 0;
62+
shape.height.baseVal.value = 0;
63+
shape.setAttribute("stroke", "black");
64+
shape.setAttribute("stroke-width", 1);
65+
shape.setAttribute("vector-effect", "non-scaling-stroke");
66+
shape.setAttribute("fill", "none");
67+
shape.setAttribute("stroke-dasharray", "5 5");
68+
shape.setAttribute("opacity", 1);
69+
Tools.svg.appendChild(shape);
70+
return shape;
71+
}
3172

32-
function startMovingElement(x, y, evt) {
33-
//Prevent the press from being interpreted by the browser
73+
function startMovingElements(x, y, evt) {
3474
evt.preventDefault();
35-
if (!evt.target || !Tools.drawingArea.contains(evt.target)) return;
36-
var tmatrix = get_translate_matrix(evt.target);
37-
selected = { x: x - tmatrix.e, y: y - tmatrix.f, elem: evt.target };
75+
selectorState = selectorStates.moving;
76+
selected = { x: x, y: y };
77+
// Some of the selected elements could have been deleted
78+
selected_els = selected_els.filter(el => {
79+
return Tools.svg.getElementById(el.id) !== null
80+
});
81+
translation_elements = selected_els.map(el => {
82+
let tmatrix = get_translate_matrix(el);
83+
return { x: tmatrix.e, y: tmatrix.f }
84+
});
85+
{
86+
let tmatrix = get_translate_matrix(selectionRect);
87+
selectionRectTranslation = { x: tmatrix.e, y: tmatrix.f };
88+
}
3889
}
3990

40-
function moveElement(x, y) {
41-
if (!selected) return;
42-
var deltax = x - selected.x;
43-
var deltay = y - selected.y;
44-
var msg = { type: "update", id: selected.elem.id, deltax: deltax, deltay: deltay };
91+
function startSelector(x, y, evt) {
92+
evt.preventDefault();
93+
selected = { x: x, y: y };
94+
selected_els = [];
95+
selectorState = selectorStates.selecting;
96+
selectionRect.x.baseVal.value = x;
97+
selectionRect.y.baseVal.value = y;
98+
selectionRect.width.baseVal.value = 0;
99+
selectionRect.height.baseVal.value = 0;
100+
selectionRect.style.display = "";
101+
tmatrix = get_translate_matrix(selectionRect);
102+
tmatrix.e = 0;
103+
tmatrix.f = 0;
104+
}
105+
106+
107+
function calculateSelection() {
108+
var scale = Tools.drawingArea.getCTM().a;
109+
var selectionTBBox = selectionRect.transformedBBox(scale);
110+
return Array.from(Tools.drawingArea.children).filter(el => {
111+
return transformedBBoxIntersects(
112+
selectionTBBox,
113+
el.transformedBBox(scale)
114+
)
115+
});
116+
}
117+
118+
function moveSelection(x, y) {
119+
var dx = x - selected.x;
120+
var dy = y - selected.y;
121+
var msgs = selected_els.map((el, i) => {
122+
return {
123+
type: "update",
124+
id: el.id,
125+
deltax: dx + translation_elements[i].x,
126+
deltay: dy + translation_elements[i].y
127+
}
128+
})
129+
var msg = {
130+
_children: msgs
131+
};
132+
{
133+
let tmatrix = get_translate_matrix(selectionRect);
134+
tmatrix.e = dx + selectionRectTranslation.x;
135+
tmatrix.f = dy + selectionRectTranslation.y;
136+
}
45137
var now = performance.now();
46138
if (now - last_sent > 70) {
47139
last_sent = now;
@@ -51,6 +143,13 @@
51143
}
52144
}
53145

146+
function updateRect(x, y, rect) {
147+
rect.x.baseVal.value = Math.min(x, selected.x);
148+
rect.y.baseVal.value = Math.min(y, selected.y);
149+
rect.width.baseVal.value = Math.abs(x - selected.x);
150+
rect.height.baseVal.value = Math.abs(y - selected.y);
151+
}
152+
54153
function get_translate_matrix(elem) {
55154
// Returns the first translate or transform matrix or makes one
56155
var translate = null;
@@ -71,17 +170,54 @@
71170
}
72171

73172
function draw(data) {
74-
switch (data.type) {
75-
case "update":
76-
var elem = Tools.svg.getElementById(data.id);
77-
if (!elem) throw new Error("Mover: Tried to move an element that does not exist.");
78-
var tmatrix = get_translate_matrix(elem);
79-
tmatrix.e = data.deltax || 0;
80-
tmatrix.f = data.deltay || 0;
81-
break;
173+
if (data._children) {
174+
batchCall(draw, data._children);
175+
}
176+
else {
177+
switch (data.type) {
178+
case "update":
179+
var elem = Tools.svg.getElementById(data.id);
180+
if (!elem) throw new Error("Mover: Tried to move an element that does not exist.");
181+
var tmatrix = get_translate_matrix(elem);
182+
tmatrix.e = data.deltax || 0;
183+
tmatrix.f = data.deltay || 0;
184+
break;
185+
default:
186+
throw new Error("Mover: 'move' instruction with unknown type. ", data);
187+
}
188+
}
189+
}
190+
191+
function clickSelector(x, y, evt) {
192+
var scale = Tools.drawingArea.getCTM().a
193+
selectionRect = selectionRect ?? createSelectorRect();
194+
if (pointInTransformedBBox([x, y], selectionRect.transformedBBox(scale))) {
195+
startMovingElements(x, y, evt);
196+
} else if (Tools.drawingArea.contains(evt.target)) {
197+
selectionRect.style.display = "none";
198+
selected_els = [getParentMathematics(evt.target)];
199+
startMovingElements(x, y, evt);
200+
} else {
201+
startSelector(x, y, evt);
202+
}
203+
}
204+
205+
function releaseSelector(x, y, evt) {
206+
if (selectorState == selectorStates.selecting) {
207+
selected_els = calculateSelection();
208+
if (selected_els.length == 0) {
209+
selectionRect.style.display = "none";
210+
}
211+
}
212+
translation_elements = [];
213+
selectorState = selectorStates.pointing;
214+
}
82215

83-
default:
84-
throw new Error("Mover: 'move' instruction with unknown type. ", data);
216+
function moveSelector(x, y, evt) {
217+
if (selectorState == selectorStates.selecting) {
218+
updateRect(x, y, selectionRect);
219+
} else if (selectorState == selectorStates.moving) {
220+
moveSelection(x, y, selectionRect);
85221
}
86222
}
87223

@@ -101,17 +237,18 @@
101237

102238
function press(x, y, evt, isTouchEvent) {
103239
if (!handTool.secondary.active) startHand(x, y, evt, isTouchEvent);
104-
else startMovingElement(x, y, evt, isTouchEvent);
240+
else clickSelector(x, y, evt, isTouchEvent);
105241
}
106242

107243

108244
function move(x, y, evt, isTouchEvent) {
109245
if (!handTool.secondary.active) moveHand(x, y, evt, isTouchEvent);
110-
else moveElement(x, y, evt, isTouchEvent);
246+
else moveSelector(x, y, evt, isTouchEvent);
111247
}
112248

113249
function release(x, y, evt, isTouchEvent) {
114250
move(x, y, evt, isTouchEvent);
251+
if (handTool.secondary.active) releaseSelector(x, y, evt, isTouchEvent);
115252
selected = null;
116253
}
117254

@@ -128,8 +265,8 @@
128265
"release": release,
129266
},
130267
"secondary": {
131-
"name": "Mover",
132-
"icon": "tools/hand/mover.svg",
268+
"name": "Selector",
269+
"icon": "tools/hand/selector.svg",
133270
"active": false,
134271
"switch": switchTool,
135272
},
Lines changed: 19 additions & 0 deletions
Loading

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "whitebophir",
33
"description": "Online collaborative whiteboard",
4-
"version": "1.10.2",
4+
"version": "1.11.0",
55
"keywords": [
66
"collaborative",
77
"whiteboard"

0 commit comments

Comments
 (0)