Line 0
Link Here
|
|
|
1 |
<?xml version="1.0" encoding="UTF-8"?> |
2 |
<!-- |
3 |
Licensed to the Apache Software Foundation (ASF) under one or more |
4 |
contributor license agreements. See the NOTICE file distributed with |
5 |
this work for additional information regarding copyright ownership. |
6 |
The ASF licenses this file to You under the Apache License, Version 2.0 |
7 |
(the "License"); you may not use this file except in compliance with |
8 |
the License. You may obtain a copy of the License at |
9 |
|
10 |
http://www.apache.org/licenses/LICENSE-2.0 |
11 |
|
12 |
Unless required by applicable law or agreed to in writing, software |
13 |
distributed under the License is distributed on an "AS IS" BASIS, |
14 |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
15 |
See the License for the specific language governing permissions and |
16 |
limitations under the License. |
17 |
--> |
18 |
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> |
19 |
<head> |
20 |
<title>Apache Tomcat WebSocket Examples: Drawboard</title> |
21 |
<style type="text/css"><![CDATA[ |
22 |
|
23 |
body { |
24 |
font-family: Arial, sans-serif; |
25 |
font-size: 11pt; |
26 |
background-color: #eeeeea; |
27 |
padding: 10px; |
28 |
} |
29 |
|
30 |
#console-container { |
31 |
float: left; |
32 |
background-color: #fff; |
33 |
width: 250px; |
34 |
} |
35 |
|
36 |
#console { |
37 |
font-size: 10pt; |
38 |
height: 600px; |
39 |
overflow-y: scroll; |
40 |
padding-left: 5px; |
41 |
padding-right: 5px; |
42 |
} |
43 |
|
44 |
#console p { |
45 |
padding: 0; |
46 |
margin: 0; |
47 |
} |
48 |
|
49 |
#drawContainer { |
50 |
float: left; |
51 |
display: none; |
52 |
margin-right: 25px; |
53 |
} |
54 |
|
55 |
#drawContainer canvas { |
56 |
display: block; |
57 |
-ms-touch-action: none; /* Disable touch behaviors, like pan and zoom */ |
58 |
} |
59 |
|
60 |
#labelContainer { |
61 |
margin-bottom: 15px; |
62 |
} |
63 |
|
64 |
#drawContainer, #console-container { |
65 |
box-shadow: 0px 0px 8px 3px #bbb; |
66 |
border: 1px solid #CCCCCC; |
67 |
|
68 |
} |
69 |
|
70 |
]]></style> |
71 |
<script type="application/javascript"><![CDATA[ |
72 |
"use strict"; |
73 |
|
74 |
(function() { |
75 |
|
76 |
document.addEventListener("DOMContentLoaded", function() { |
77 |
// Remove elements with "noscript" class - <noscript> is not |
78 |
// allowed in XHTML |
79 |
var noscripts = document.getElementsByClassName("noscript"); |
80 |
for (var i = 0; i < noscripts.length; i++) { |
81 |
noscripts[i].parentNode.removeChild(noscripts[i]); |
82 |
} |
83 |
|
84 |
|
85 |
var Console = {}; |
86 |
|
87 |
Console.log = (function() { |
88 |
var consoleContainer = |
89 |
document.getElementById("console-container"); |
90 |
var console = document.createElement("div"); |
91 |
console.setAttribute("id", "console"); |
92 |
consoleContainer.appendChild(console); |
93 |
|
94 |
return function(message) { |
95 |
var p = document.createElement('p'); |
96 |
p.style.wordWrap = "break-word"; |
97 |
p.appendChild(document.createTextNode(message)); |
98 |
console.appendChild(p); |
99 |
while (console.childNodes.length > 25) { |
100 |
console.removeChild(console.firstChild); |
101 |
} |
102 |
console.scrollTop = console.scrollHeight; |
103 |
} |
104 |
})(); |
105 |
|
106 |
|
107 |
function Room(drawContainer) { |
108 |
|
109 |
// The WebSocket object. |
110 |
var socket; |
111 |
// ID of the timer which sends ping messages. |
112 |
var pingTimerId; |
113 |
|
114 |
var isStarted = false; |
115 |
var playerCount = 0; |
116 |
|
117 |
// An array of PathIdContainer objects that the server |
118 |
// did not yet handle. |
119 |
// They are ordered by id (ascending). |
120 |
var pathsNotHandled = []; |
121 |
|
122 |
var nextMsgId = 1; |
123 |
|
124 |
var canvasDisplay = document.createElement("canvas"); |
125 |
var canvasBackground = document.createElement("canvas"); |
126 |
var canvasServerImage = document.createElement("canvas"); |
127 |
var canvasArray = [canvasDisplay, canvasBackground, |
128 |
canvasServerImage]; |
129 |
|
130 |
var labelPlayerCount = document.createTextNode("0"); |
131 |
var optionContainer = document.createElement("div"); |
132 |
|
133 |
|
134 |
var canvasDisplayCtx = canvasDisplay.getContext("2d"); |
135 |
var canvasBackgroundCtx = canvasBackground.getContext("2d"); |
136 |
var canvasServerImageCtx = canvasServerImage.getContext("2d"); |
137 |
|
138 |
var mouseInWindow = false; |
139 |
var mouseDown = false; |
140 |
var currentMouseX = 0, currentMouseY = 0; |
141 |
|
142 |
var availableColors = []; |
143 |
var currentColorIndex; |
144 |
var colorContainers; |
145 |
|
146 |
var availableThicknesses = [2, 3, 6, 10, 16, 28, 50]; |
147 |
var currentThicknessIndex; |
148 |
var thicknessContainers; |
149 |
|
150 |
|
151 |
var placeholder = document.createElement("div"); |
152 |
placeholder.appendChild(document.createTextNode("Loading... ")); |
153 |
var progressElem = document.createElement("progress"); |
154 |
placeholder.appendChild(progressElem); |
155 |
|
156 |
labelContainer.appendChild(placeholder); |
157 |
|
158 |
function rgb(color) { |
159 |
return "rgba(" + color[0] + "," + color[1] + "," |
160 |
+ color[2] + "," + color[3] + ")"; |
161 |
} |
162 |
|
163 |
function PathIdContainer(path, id) { |
164 |
this.path = path; |
165 |
this.id = id; |
166 |
} |
167 |
|
168 |
function Path(type, color, thickness, x1, y1, x2, y2) { |
169 |
this.type = type; |
170 |
this.color = color; |
171 |
this.thickness = thickness; |
172 |
this.x1 = x1; |
173 |
this.y1 = y1; |
174 |
this.x2 = x2; |
175 |
this.y2 = y2; |
176 |
|
177 |
this.draw = function(ctx) { |
178 |
ctx.beginPath(); |
179 |
ctx.lineCap = "round"; |
180 |
ctx.lineWidth = thickness; |
181 |
var style = rgb(color); |
182 |
ctx.strokeStyle = style; |
183 |
|
184 |
if (x1 == x2 && y1 == y2) { |
185 |
// Always draw as arc to meet the behavior |
186 |
// in Java2D. |
187 |
ctx.fillStyle = style; |
188 |
ctx.arc(x1, y1, thickness / 2.0, 0, |
189 |
Math.PI * 2.0, false); |
190 |
ctx.fill(); |
191 |
} else { |
192 |
if (type == 1) { |
193 |
// Draw a line. |
194 |
ctx.moveTo(x1, y1); |
195 |
ctx.lineTo(x2, y2); |
196 |
ctx.stroke(); |
197 |
} |
198 |
} |
199 |
}; |
200 |
} |
201 |
|
202 |
|
203 |
function connect() { |
204 |
var host = (window.location.protocol == "https:" |
205 |
? "wss://" : "ws://") + window.location.host |
206 |
+ "/examples/websocket/drawboard"; |
207 |
socket = new WebSocket(host); |
208 |
|
209 |
socket.onopen = function () { |
210 |
// Socket has opened. Now wait for the server to |
211 |
// send us the initial packet. |
212 |
Console.log("WebSocket connection opened."); |
213 |
|
214 |
// Set up a timer for pong messages. |
215 |
pingTimerId = window.setInterval(function() { |
216 |
socket.send("0"); |
217 |
}, 30000); |
218 |
} |
219 |
|
220 |
socket.onclose = function () { |
221 |
Console.log("WebSocket connection closed."); |
222 |
disableControls(); |
223 |
|
224 |
// Disable pong timer. |
225 |
window.clearInterval(pingTimerId); |
226 |
} |
227 |
|
228 |
socket.onmessage = function(message) { |
229 |
|
230 |
// Split joined message and process them |
231 |
// invidividually. |
232 |
var messages = message.data.split(";"); |
233 |
for (var msgArrIdx = 0; msgArrIdx < messages.length; |
234 |
msgArrIdx++) { |
235 |
var msg = messages[msgArrIdx]; |
236 |
var type = msg.substring(0, 1); |
237 |
|
238 |
if (type == "0") { |
239 |
// Error message. |
240 |
var error = msg.substring(1); |
241 |
// Log it to the console and show an alert. |
242 |
Console.log("Error: " + error); |
243 |
alert(error); |
244 |
|
245 |
} else { |
246 |
if (!isStarted) { |
247 |
if (type == "2") { |
248 |
// Initial message. It contains the |
249 |
// number of players. |
250 |
// After this message we will receive |
251 |
// a binary message containing the current |
252 |
// room image as PNG. |
253 |
playerCount = parseInt(msg.substring(1)); |
254 |
|
255 |
refreshPlayerCount(); |
256 |
|
257 |
// The next message will be a binary |
258 |
// message containing the room images |
259 |
// as PNG. Therefore we temporarily swap |
260 |
// the message handler. |
261 |
var originalHandler = socket.onmessage; |
262 |
socket.onmessage = function(message) { |
263 |
// First, we restore the original handler. |
264 |
socket.onmessage = originalHandler; |
265 |
|
266 |
// Read the image. |
267 |
var blob = message.data; |
268 |
// Create new blob with correct MIME type. |
269 |
blob = new Blob([blob], {type : "image/png"}); |
270 |
|
271 |
var url = URL.createObjectURL(blob); |
272 |
|
273 |
var img = new Image(); |
274 |
|
275 |
// We must wait until the onload event is |
276 |
// raised until we can draw the image onto |
277 |
// the canvas. |
278 |
|
279 |
// TODO: I don't know if there is a guarantee |
280 |
// that no WebSocket events (onmessage) will |
281 |
// be raised until the onload event of this |
282 |
// image is raised. Maybe we need to need to |
283 |
// push websocket messages on a queue until |
284 |
// this onload function is called. |
285 |
img.onload = function() { |
286 |
|
287 |
// Release the object URL. |
288 |
URL.revokeObjectURL(url); |
289 |
|
290 |
// Set the canvases to the correct size. |
291 |
|
292 |
for (var i = 0; i < canvasArray.length; i++) { |
293 |
canvasArray[i].width = img.width; |
294 |
canvasArray[i].height = img.height; |
295 |
} |
296 |
|
297 |
// Now draw the image on the last canvas. |
298 |
canvasServerImageCtx.clearRect(0, 0, |
299 |
canvasServerImage.width, |
300 |
canvasServerImage.height); |
301 |
canvasServerImageCtx.drawImage(img, 0, 0); |
302 |
|
303 |
// Draw it on the background canvas. |
304 |
canvasBackgroundCtx.drawImage(canvasServerImage, |
305 |
0, 0); |
306 |
|
307 |
// Refresh the display canvas. |
308 |
refreshDisplayCanvas(); |
309 |
|
310 |
isStarted = true; |
311 |
startControls(); |
312 |
}; |
313 |
|
314 |
img.src = url; |
315 |
}; |
316 |
} |
317 |
} else { |
318 |
if (type == "3") { |
319 |
// The number of players in this room changed. |
320 |
var playerAdded = msg.substring(1) == "+"; |
321 |
playerCount += playerAdded ? 1 : -1; |
322 |
refreshPlayerCount(); |
323 |
|
324 |
Console.log("Player " + (playerAdded |
325 |
? "joined." : "left.")); |
326 |
|
327 |
} else if (type == "1") { |
328 |
// We received a new DrawMessage. |
329 |
var maxLastHandledId = -1; |
330 |
var drawMessages = msg.substring(1).split("|"); |
331 |
for (var i = 0; i < drawMessages.length; i++) { |
332 |
var elements = drawMessages[i].split(","); |
333 |
var lastHandledId = parseInt(elements[0]); |
334 |
maxLastHandledId = Math.max(maxLastHandledId, |
335 |
lastHandledId); |
336 |
|
337 |
var path = new Path( |
338 |
parseInt(elements[1]), |
339 |
[parseInt(elements[2]), |
340 |
parseInt(elements[3]), |
341 |
parseInt(elements[4]), |
342 |
parseInt(elements[5]) / 255.0], |
343 |
parseFloat(elements[6]), |
344 |
parseInt(elements[7]), |
345 |
parseInt(elements[8]), |
346 |
parseInt(elements[9]), |
347 |
parseInt(elements[10])); |
348 |
|
349 |
// Draw the path onto the last canvas. |
350 |
path.draw(canvasServerImageCtx); |
351 |
} |
352 |
|
353 |
// Draw the last canvas onto the background one. |
354 |
canvasBackgroundCtx.drawImage(canvasServerImage, |
355 |
0, 0); |
356 |
|
357 |
// Now go through the pathsNotHandled array and |
358 |
// remove the paths that were already handled by |
359 |
// the server. |
360 |
while (pathsNotHandled.length > 0 |
361 |
&& pathsNotHandled[0].id <= maxLastHandledId) |
362 |
pathsNotHandled.shift(); |
363 |
|
364 |
// Now me must draw the remaining paths onto |
365 |
// the background canvas. |
366 |
for (var i = 0; i < pathsNotHandled.length; i++) { |
367 |
pathsNotHandled[i].path.draw(canvasBackgroundCtx); |
368 |
} |
369 |
|
370 |
refreshDisplayCanvas(); |
371 |
} |
372 |
} |
373 |
} |
374 |
} |
375 |
}; |
376 |
|
377 |
} |
378 |
|
379 |
function refreshPlayerCount() { |
380 |
labelPlayerCount.nodeValue = String(playerCount); |
381 |
} |
382 |
|
383 |
function refreshDisplayCanvas() { |
384 |
canvasDisplayCtx.drawImage(canvasBackground, 0, 0); |
385 |
if (mouseInWindow && !mouseDown) { |
386 |
canvasDisplayCtx.beginPath(); |
387 |
var color = availableColors[currentColorIndex].slice(0); |
388 |
color[3] = 0.5; |
389 |
canvasDisplayCtx.fillStyle = rgb(color); |
390 |
|
391 |
canvasDisplayCtx.arc(currentMouseX, currentMouseY, availableThicknesses[currentThicknessIndex] / 2, 0, Math.PI * 2.0, true); |
392 |
|
393 |
canvasDisplayCtx.fill(); |
394 |
} |
395 |
} |
396 |
|
397 |
function startControls() { |
398 |
var labelContainer = document.getElementById("labelContainer"); |
399 |
labelContainer.removeChild(placeholder); |
400 |
placeholder = undefined; |
401 |
|
402 |
labelContainer.appendChild( |
403 |
document.createTextNode("Number of Players: ")); |
404 |
labelContainer.appendChild(labelPlayerCount); |
405 |
|
406 |
|
407 |
drawContainer.style.display = "block"; |
408 |
drawContainer.appendChild(canvasDisplay); |
409 |
|
410 |
drawContainer.appendChild(optionContainer); |
411 |
|
412 |
canvasDisplay.onmousemove = function(e) { |
413 |
mouseInWindow = true; |
414 |
var oldMouseX = currentMouseX, oldMouseY = currentMouseY; |
415 |
currentMouseX = e.pageX - canvasDisplay.offsetLeft; |
416 |
currentMouseY = e.pageY - canvasDisplay.offsetTop; |
417 |
|
418 |
if (mouseDown) { |
419 |
var path = new Path(1, availableColors[currentColorIndex], |
420 |
availableThicknesses[currentThicknessIndex], |
421 |
oldMouseX, oldMouseY, currentMouseX, |
422 |
currentMouseY); |
423 |
// Draw it on the background canvas. |
424 |
path.draw(canvasBackgroundCtx); |
425 |
|
426 |
// Send it to the sever. |
427 |
pushPath(path); |
428 |
} |
429 |
|
430 |
refreshDisplayCanvas(); |
431 |
}; |
432 |
|
433 |
canvasDisplay.onmousedown = function(e) { |
434 |
currentMouseX = e.pageX - canvasDisplay.offsetLeft; |
435 |
currentMouseY = e.pageY - canvasDisplay.offsetTop; |
436 |
|
437 |
if (e.button == 0) { |
438 |
mouseDown = true; |
439 |
|
440 |
var path = new Path(1, availableColors[currentColorIndex], |
441 |
availableThicknesses[currentThicknessIndex], |
442 |
currentMouseX, currentMouseY, currentMouseX, |
443 |
currentMouseY); |
444 |
// Draw it on the background canvas. |
445 |
path.draw(canvasBackgroundCtx); |
446 |
|
447 |
// Send it to the sever. |
448 |
pushPath(path); |
449 |
|
450 |
refreshDisplayCanvas(); |
451 |
|
452 |
} else if (mouseDown) { |
453 |
// Cancel drawing. |
454 |
mouseDown = false; |
455 |
|
456 |
refreshDisplayCanvas(); |
457 |
} |
458 |
} |
459 |
|
460 |
canvasDisplay.onmouseup = function(e) { |
461 |
if (e.button == 0) { |
462 |
if (mouseDown) { |
463 |
mouseDown = false; |
464 |
|
465 |
refreshDisplayCanvas(); |
466 |
} |
467 |
} |
468 |
}; |
469 |
|
470 |
canvasDisplay.onmouseout = function() { |
471 |
mouseInWindow = false; |
472 |
refreshDisplayCanvas(); |
473 |
}; |
474 |
|
475 |
|
476 |
// Create color and thickness controls. |
477 |
var colorContainersBox = document.createElement("div"); |
478 |
colorContainersBox.setAttribute("style", |
479 |
"margin: 4px; border: 1px solid #bbb; border-radius: 3px;"); |
480 |
optionContainer.appendChild(colorContainersBox); |
481 |
|
482 |
|
483 |
colorContainers = new Array(3 * 3 * 3); |
484 |
for (var i = 0; i < colorContainers.length; i++) { |
485 |
var colorContainer = colorContainers[i] = |
486 |
document.createElement("div"); |
487 |
var color = availableColors[i] = |
488 |
[ |
489 |
Math.floor((i % 3) * 255 / 2), |
490 |
Math.floor((Math.floor(i / 3) % 3) * 255 / 2), |
491 |
Math.floor((Math.floor(i / (3 * 3)) % 3) * 255 / 2), |
492 |
1.0 |
493 |
]; |
494 |
colorContainer.setAttribute("style", |
495 |
"margin: 3px; width: 18px; height: 18px; " |
496 |
+ "float: left; background-color: " + rgb(color)); |
497 |
colorContainer.style.border = '2px solid #000'; |
498 |
colorContainer.onmousedown = (function(ix) { |
499 |
return function() { |
500 |
setColor(ix); |
501 |
}; |
502 |
})(i); |
503 |
|
504 |
colorContainersBox.appendChild(colorContainer); |
505 |
} |
506 |
|
507 |
var divClearLeft = document.createElement("div"); |
508 |
divClearLeft.setAttribute("style", "clear: left;"); |
509 |
colorContainersBox.appendChild(divClearLeft); |
510 |
|
511 |
var thicknessContainersBox = document.createElement("div"); |
512 |
thicknessContainersBox.setAttribute("style", |
513 |
"margin: 3px; border: 1px solid #bbb; border-radius: 3px;"); |
514 |
optionContainer.appendChild(thicknessContainersBox); |
515 |
|
516 |
|
517 |
thicknessContainers = new Array(availableThicknesses.length); |
518 |
for (var i = 0; i < thicknessContainers.length; i++) { |
519 |
var thicknessContainer = thicknessContainers[i] = |
520 |
document.createElement("div"); |
521 |
thicknessContainer.setAttribute("style", |
522 |
"text-align: center; margin: 3px; width: 18px; " |
523 |
+ "height: 18px; float: left;"); |
524 |
thicknessContainer.style.border = "2px solid #000"; |
525 |
thicknessContainer.appendChild(document.createTextNode( |
526 |
String(availableThicknesses[i]))); |
527 |
thicknessContainer.onmousedown = (function(ix) { |
528 |
return function() { |
529 |
setThickness(ix); |
530 |
}; |
531 |
})(i); |
532 |
|
533 |
thicknessContainersBox.appendChild(thicknessContainer); |
534 |
} |
535 |
|
536 |
divClearLeft = document.createElement("div"); |
537 |
divClearLeft.setAttribute("style", "clear: left;"); |
538 |
thicknessContainersBox.appendChild(divClearLeft); |
539 |
|
540 |
|
541 |
setColor(0); |
542 |
setThickness(0); |
543 |
|
544 |
} |
545 |
|
546 |
function disableControls() { |
547 |
canvasDisplay.onmousemove = canvasDisplay.onmousedown = |
548 |
undefined; |
549 |
mouseInWindow = false; |
550 |
refreshDisplayCanvas(); |
551 |
} |
552 |
|
553 |
function pushPath(path) { |
554 |
|
555 |
// Push it into the pathsNotHandled array. |
556 |
var container = new PathIdContainer(path, nextMsgId++); |
557 |
pathsNotHandled.push(container); |
558 |
|
559 |
// Send the path to the server. |
560 |
var message = container.id + "|" + path.type + "," |
561 |
+ path.color[0] + "," + path.color[1] + "," |
562 |
+ path.color[2] + "," |
563 |
+ Math.round(path.color[3] * 255.0) + "," |
564 |
+ path.thickness + "," + path.x1 + "," |
565 |
+ path.y1 + "," + path.x2 + "," + path.y2; |
566 |
|
567 |
socket.send("1" + message); |
568 |
} |
569 |
|
570 |
function setThickness(thicknessIndex) { |
571 |
if (typeof currentThicknessIndex !== "undefined") |
572 |
thicknessContainers[currentThicknessIndex] |
573 |
.style.borderColor = "#000"; |
574 |
currentThicknessIndex = thicknessIndex; |
575 |
thicknessContainers[currentThicknessIndex] |
576 |
.style.borderColor = "#d08"; |
577 |
} |
578 |
|
579 |
function setColor(colorIndex) { |
580 |
if (typeof currentColorIndex !== "undefined") |
581 |
colorContainers[currentColorIndex] |
582 |
.style.borderColor = "#000"; |
583 |
currentColorIndex = colorIndex; |
584 |
colorContainers[currentColorIndex] |
585 |
.style.borderColor = "#d08"; |
586 |
} |
587 |
|
588 |
|
589 |
connect(); |
590 |
|
591 |
} |
592 |
|
593 |
|
594 |
// Initialize the room |
595 |
var room = new Room(document.getElementById("drawContainer")); |
596 |
|
597 |
|
598 |
}, false); |
599 |
|
600 |
})(); |
601 |
]]></script> |
602 |
</head> |
603 |
<body> |
604 |
<div class="noscript"><h2 style="color: #ff0000;">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable |
605 |
Javascript and reload this page!</h2></div> |
606 |
<div id="labelContainer"/> |
607 |
<div id="drawContainer"/> |
608 |
<div id="console-container"/> |
609 |
|
610 |
</body> |
611 |
</html> |