blob: 342bef4b1a2080959046d429717cfdf998c779c2 [file] [log] [blame]
刘洪青6266f992017-05-15 21:21:03 +08001<?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;
58 touch-action: none; /* Disable touch behaviors, like pan and zoom */
59 cursor: crosshair;
60 }
61
62 #labelContainer {
63 margin-bottom: 15px;
64 }
65
66 #drawContainer, #console-container {
67 box-shadow: 0px 0px 8px 3px #bbb;
68 border: 1px solid #CCCCCC;
69 }
70
71 ]]></style>
72 <script type="application/javascript"><![CDATA[
73 "use strict";
74
75 (function() {
76
77 document.addEventListener("DOMContentLoaded", function() {
78 // Remove elements with "noscript" class - <noscript> is not
79 // allowed in XHTML
80 var noscripts = document.getElementsByClassName("noscript");
81 for (var i = 0; i < noscripts.length; i++) {
82 noscripts[i].parentNode.removeChild(noscripts[i]);
83 }
84
85 // Add script for expand content.
86 var expandElements = document.getElementsByClassName("expand");
87 for (var ixx = 0; ixx < expandElements.length; ixx++) {
88 (function(el) {
89 var expandContent = document.getElementById(el.getAttribute("data-content-id"));
90 expandContent.style.display = "none";
91 var arrow = document.createTextNode("◢ ");
92 var arrowSpan = document.createElement("span");
93 arrowSpan.appendChild(arrow);
94
95 var link = document.createElement("a");
96 link.setAttribute("href", "#!");
97 while (el.firstChild != null) {
98 link.appendChild(el.removeChild(el.firstChild));
99 }
100 el.appendChild(arrowSpan);
101 el.appendChild(link);
102
103 var textSpan = document.createElement("span");
104 textSpan.setAttribute("style", "font-weight: normal;");
105 textSpan.appendChild(document.createTextNode(" (click to expand)"));
106 el.appendChild(textSpan);
107
108
109 var visible = true;
110
111 var switchExpand = function() {
112 visible = !visible;
113 expandContent.style.display = visible ? "block" : "none";
114 arrowSpan.style.color = visible ? "#000" : "#888";
115 return false;
116 };
117
118 link.onclick = switchExpand;
119 switchExpand();
120
121 })(expandElements[ixx]);
122 }
123
124
125 var Console = {};
126
127 Console.log = (function() {
128 var consoleContainer =
129 document.getElementById("console-container");
130 var console = document.createElement("div");
131 console.setAttribute("id", "console");
132 consoleContainer.appendChild(console);
133
134 return function(message) {
135 var p = document.createElement('p');
136 p.style.wordWrap = "break-word";
137 p.appendChild(document.createTextNode(message));
138 console.appendChild(p);
139 while (console.childNodes.length > 25) {
140 console.removeChild(console.firstChild);
141 }
142 console.scrollTop = console.scrollHeight;
143 }
144 })();
145
146
147 function Room(drawContainer) {
148
149 /* A pausable event forwarder that can be used to pause and
150 * resume handling of events (e.g. when we need to wait
151 * for a Image's load event before we can process further
152 * WebSocket messages).
153 * The object's callFunction(func) should be called from an
154 * event handler and give the function to handle the event as
155 * argument.
156 * Call pauseProcessing() to suspend event forwarding and
157 * resumeProcessing() to resume it.
158 */
159 function PausableEventForwarder() {
160
161 var pauseProcessing = false;
162 // Queue for buffering functions to be called.
163 var functionQueue = [];
164
165 this.callFunction = function(func) {
166 // If message processing is paused, we push it
167 // into the queue - otherwise we process it directly.
168 if (pauseProcessing) {
169 functionQueue.push(func);
170 } else {
171 func();
172 }
173 };
174
175 this.pauseProcessing = function() {
176 pauseProcessing = true;
177 };
178
179 this.resumeProcessing = function() {
180 pauseProcessing = false;
181
182 // Process all queued functions until some handler calls
183 // pauseProcessing() again.
184 while (functionQueue.length > 0 && !pauseProcessing) {
185 var func = functionQueue.pop();
186 func();
187 }
188 };
189 }
190
191 // The WebSocket object.
192 var socket;
193 // ID of the timer which sends ping messages.
194 var pingTimerId;
195
196 var isStarted = false;
197 var playerCount = 0;
198
199 // An array of PathIdContainer objects that the server
200 // did not yet handle.
201 // They are ordered by id (ascending).
202 var pathsNotHandled = [];
203
204 var nextMsgId = 1;
205
206 var canvasDisplay = document.createElement("canvas");
207 var canvasBackground = document.createElement("canvas");
208 var canvasServerImage = document.createElement("canvas");
209 var canvasArray = [canvasDisplay, canvasBackground,
210 canvasServerImage];
211 canvasDisplay.addEventListener("mousedown", function(e) {
212 // Prevent default mouse event to prevent browsers from marking text
213 // (and Chrome from displaying the "text" cursor).
214 e.preventDefault();
215 }, false);
216
217 var labelPlayerCount = document.createTextNode("0");
218 var optionContainer = document.createElement("div");
219
220
221 var canvasDisplayCtx = canvasDisplay.getContext("2d");
222 var canvasBackgroundCtx = canvasBackground.getContext("2d");
223 var canvasServerImageCtx = canvasServerImage.getContext("2d");
224 var canvasMouseMoveHandler;
225 var canvasMouseDownHandler;
226
227 var isActive = false;
228 var mouseInWindow = false;
229 var mouseDown = false;
230 var currentMouseX = 0, currentMouseY = 0;
231 var currentPreviewPath = null;
232
233 var availableColors = [];
234 var currentColorIndex;
235 var colorContainers;
236 var previewTransparency = 0.65;
237
238 var availableThicknesses = [2, 3, 6, 10, 16, 28, 50];
239 var currentThicknessIndex;
240 var thicknessContainers;
241
242 var availableDrawTypes = [
243 { name: "Brush", id: 1, continuous: true },
244 { name: "Line", id: 2, continuous: false },
245 { name: "Rectangle", id: 3, continuous: false },
246 { name: "Ellipse", id: 4, continuous: false }
247 ];
248 var currentDrawTypeIndex;
249 var drawTypeContainers;
250
251
252 var labelContainer = document.getElementById("labelContainer");
253 var placeholder = document.createElement("div");
254 placeholder.appendChild(document.createTextNode("Loading... "));
255 var progressElem = document.createElement("progress");
256 placeholder.appendChild(progressElem);
257
258 labelContainer.appendChild(placeholder);
259
260 function rgb(color) {
261 return "rgba(" + color[0] + "," + color[1] + ","
262 + color[2] + "," + color[3] + ")";
263 }
264
265 function PathIdContainer(path, id) {
266 this.path = path;
267 this.id = id;
268 }
269
270 function Path(type, color, thickness, x1, y1, x2, y2, lastInChain) {
271 this.type = type;
272 this.color = color;
273 this.thickness = thickness;
274 this.x1 = x1;
275 this.y1 = y1;
276 this.x2 = x2;
277 this.y2 = y2;
278 this.lastInChain = lastInChain;
279
280 function ellipse(ctx, x, y, w, h) {
281 /* Drawing a ellipse cannot be done directly in a
282 * CanvasRenderingContext2D - we need to use drawArc()
283 * in conjunction with scaling the context so that we
284 * get the needed proportion.
285 */
286 ctx.save();
287
288 // Translate and scale the context so that we can draw
289 // an arc at (0, 0) with a radius of 1.
290 ctx.translate(x + w / 2, y + h / 2);
291 ctx.scale(w / 2, h / 2);
292
293 ctx.beginPath();
294 ctx.arc(0, 0, 1, 0, Math.PI * 2, false);
295
296 ctx.restore();
297 }
298
299 this.draw = function(ctx) {
300 ctx.beginPath();
301 ctx.lineCap = "round";
302 ctx.lineWidth = thickness;
303 var style = rgb(color);
304 ctx.strokeStyle = style;
305
306 if (x1 == x2 && y1 == y2) {
307 // Always draw as arc to meet the behavior
308 // in Java2D.
309 ctx.fillStyle = style;
310 ctx.arc(x1, y1, thickness / 2.0, 0,
311 Math.PI * 2.0, false);
312 ctx.fill();
313 } else {
314 if (type == 1 || type == 2) {
315 // Draw a line.
316 ctx.moveTo(x1, y1);
317 ctx.lineTo(x2, y2);
318 ctx.stroke();
319 } else if (type == 3) {
320 // Draw a rectangle.
321 if (x1 == x2 || y1 == y2) {
322 // Draw as line
323 ctx.moveTo(x1, y1);
324 ctx.lineTo(x2, y2);
325 ctx.stroke();
326 } else {
327 ctx.strokeRect(x1, y1, x2 - x1, y2 - y1);
328 }
329 } else if (type == 4) {
330 // Draw a ellipse.
331 ellipse(ctx, x1, y1, x2 - x1, y2 - y1);
332 ctx.closePath();
333 ctx.stroke();
334 }
335 }
336 };
337 }
338
339
340 function connect() {
341 var host = (window.location.protocol == "https:"
342 ? "wss://" : "ws://") + window.location.host
343 + "/examples/websocket/drawboard";
344 socket = new WebSocket(host);
345
346 /* Use a pausable event forwarder.
347 * This is needed when we load an Image object with data
348 * from a previous message, because we must wait until the
349 * Image's load event it raised before we can use it (and
350 * in the meantime the socket.message event could be
351 * raised).
352 * Therefore we need this pausable event handler to handle
353 * e.g. socket.onmessage and socket.onclose.
354 */
355 var eventForwarder = new PausableEventForwarder();
356
357 socket.onopen = function () {
358 // Socket has opened. Now wait for the server to
359 // send us the initial packet.
360 Console.log("WebSocket connection opened.");
361
362 // Set up a timer for pong messages.
363 pingTimerId = window.setInterval(function() {
364 socket.send("0");
365 }, 30000);
366 };
367
368 socket.onclose = function () {
369 eventForwarder.callFunction(function() {
370 Console.log("WebSocket connection closed.");
371 disableControls();
372
373 // Disable pong timer.
374 window.clearInterval(pingTimerId);
375 });
376 };
377
378 // Handles an incoming Websocket message.
379 var handleOnMessage = function(message) {
380
381 // Split joined message and process them
382 // individually.
383 var messages = message.data.split(";");
384 for (var msgArrIdx = 0; msgArrIdx < messages.length;
385 msgArrIdx++) {
386 var msg = messages[msgArrIdx];
387 var type = msg.substring(0, 1);
388
389 if (type == "0") {
390 // Error message.
391 var error = msg.substring(1);
392 // Log it to the console and show an alert.
393 Console.log("Error: " + error);
394 alert(error);
395
396 } else {
397 if (!isStarted) {
398 if (type == "2") {
399 // Initial message. It contains the
400 // number of players.
401 // After this message we will receive
402 // a binary message containing the current
403 // room image as PNG.
404 playerCount = parseInt(msg.substring(1));
405
406 refreshPlayerCount();
407
408 // The next message will be a binary
409 // message containing the room images
410 // as PNG. Therefore we temporarily swap
411 // the message handler.
412 var originalHandler = handleOnMessage;
413 handleOnMessage = function(message) {
414 // First, we restore the original handler.
415 handleOnMessage = originalHandler;
416
417 // Read the image.
418 var blob = message.data;
419 // Create new blob with correct MIME type.
420 blob = new Blob([blob], {type : "image/png"});
421
422 var url = URL.createObjectURL(blob);
423
424 var img = new Image();
425
426 // We must wait until the onload event is
427 // raised until we can draw the image onto
428 // the canvas.
429 // Therefore we need to pause the event
430 // forwarder until the image is loaded.
431 eventForwarder.pauseProcessing();
432
433 img.onload = function() {
434
435 // Release the object URL.
436 URL.revokeObjectURL(url);
437
438 // Set the canvases to the correct size.
439 for (var i = 0; i < canvasArray.length; i++) {
440 canvasArray[i].width = img.width;
441 canvasArray[i].height = img.height;
442 }
443
444 // Now draw the image on the last canvas.
445 canvasServerImageCtx.clearRect(0, 0,
446 canvasServerImage.width,
447 canvasServerImage.height);
448 canvasServerImageCtx.drawImage(img, 0, 0);
449
450 // Draw it on the background canvas.
451 canvasBackgroundCtx.drawImage(canvasServerImage,
452 0, 0);
453
454 isStarted = true;
455 startControls();
456
457 // Refresh the display canvas.
458 refreshDisplayCanvas();
459
460
461 // Finally, resume the event forwarder.
462 eventForwarder.resumeProcessing();
463 };
464
465 img.src = url;
466 };
467 }
468 } else {
469 if (type == "3") {
470 // The number of players in this room changed.
471 var playerAdded = msg.substring(1) == "+";
472 playerCount += playerAdded ? 1 : -1;
473 refreshPlayerCount();
474
475 Console.log("Player " + (playerAdded
476 ? "joined." : "left."));
477
478 } else if (type == "1") {
479 // We received a new DrawMessage.
480 var maxLastHandledId = -1;
481 var drawMessages = msg.substring(1).split("|");
482 for (var i = 0; i < drawMessages.length; i++) {
483 var elements = drawMessages[i].split(",");
484 var lastHandledId = parseInt(elements[0]);
485 maxLastHandledId = Math.max(maxLastHandledId,
486 lastHandledId);
487
488 var path = new Path(
489 parseInt(elements[1]),
490 [parseInt(elements[2]),
491 parseInt(elements[3]),
492 parseInt(elements[4]),
493 parseInt(elements[5]) / 255.0],
494 parseFloat(elements[6]),
495 parseFloat(elements[7]),
496 parseFloat(elements[8]),
497 parseFloat(elements[9]),
498 parseFloat(elements[10]),
499 elements[11] != "0");
500
501 // Draw the path onto the last canvas.
502 path.draw(canvasServerImageCtx);
503 }
504
505 // Draw the last canvas onto the background one.
506 canvasBackgroundCtx.drawImage(canvasServerImage,
507 0, 0);
508
509 // Now go through the pathsNotHandled array and
510 // remove the paths that were already handled by
511 // the server.
512 while (pathsNotHandled.length > 0
513 && pathsNotHandled[0].id <= maxLastHandledId)
514 pathsNotHandled.shift();
515
516 // Now me must draw the remaining paths onto
517 // the background canvas.
518 for (var i = 0; i < pathsNotHandled.length; i++) {
519 pathsNotHandled[i].path.draw(canvasBackgroundCtx);
520 }
521
522 refreshDisplayCanvas();
523 }
524 }
525 }
526 }
527 };
528
529 socket.onmessage = function(message) {
530 eventForwarder.callFunction(function() {
531 handleOnMessage(message);
532 });
533 };
534
535 }
536
537
538 function refreshPlayerCount() {
539 labelPlayerCount.nodeValue = String(playerCount);
540 }
541
542 function refreshDisplayCanvas() {
543 if (!isActive) { // Don't draw a curser when not active.
544 return;
545 }
546
547 canvasDisplayCtx.drawImage(canvasBackground, 0, 0);
548 if (currentPreviewPath != null) {
549 // Draw the preview path.
550 currentPreviewPath.draw(canvasDisplayCtx);
551
552 } else if (mouseInWindow && !mouseDown) {
553 canvasDisplayCtx.beginPath();
554 var color = availableColors[currentColorIndex].slice(0);
555 color[3] = previewTransparency;
556 canvasDisplayCtx.fillStyle = rgb(color);
557
558 canvasDisplayCtx.arc(currentMouseX, currentMouseY,
559 availableThicknesses[currentThicknessIndex] / 2,
560 0, Math.PI * 2.0, true);
561 canvasDisplayCtx.fill();
562 }
563
564 }
565
566 function startControls() {
567 isActive = true;
568
569 labelContainer.removeChild(placeholder);
570 placeholder = undefined;
571
572 labelContainer.appendChild(
573 document.createTextNode("Number of Players: "));
574 labelContainer.appendChild(labelPlayerCount);
575
576
577 drawContainer.style.display = "block";
578 drawContainer.appendChild(canvasDisplay);
579
580 drawContainer.appendChild(optionContainer);
581
582 canvasMouseDownHandler = function(e) {
583 if (e.button == 0) {
584 currentMouseX = e.pageX - canvasDisplay.offsetLeft;
585 currentMouseY = e.pageY - canvasDisplay.offsetTop;
586
587 mouseDown = true;
588 canvasMouseMoveHandler(e);
589
590 } else if (mouseDown) {
591 // Cancel drawing.
592 mouseDown = false;
593 currentPreviewPath = null;
594
595 currentMouseX = e.pageX - canvasDisplay.offsetLeft;
596 currentMouseY = e.pageY - canvasDisplay.offsetTop;
597
598 refreshDisplayCanvas();
599 }
600 };
601 canvasDisplay.addEventListener("mousedown", canvasMouseDownHandler, false);
602
603 canvasMouseMoveHandler = function(e) {
604 var mouseX = e.pageX - canvasDisplay.offsetLeft;
605 var mouseY = e.pageY - canvasDisplay.offsetTop;
606
607 if (mouseDown) {
608 var drawType = availableDrawTypes[currentDrawTypeIndex];
609
610 if (drawType.continuous) {
611
612 var path = new Path(drawType.id,
613 availableColors[currentColorIndex],
614 availableThicknesses[currentThicknessIndex],
615 currentMouseX, currentMouseY, mouseX,
616 mouseY, false);
617 // Draw it on the background canvas.
618 path.draw(canvasBackgroundCtx);
619
620 // Send it to the sever.
621 pushPath(path);
622
623 // Refresh old coordinates
624 currentMouseX = mouseX;
625 currentMouseY = mouseY;
626
627 } else {
628 // Create a new preview path.
629 var color = availableColors[currentColorIndex].slice(0);
630 color[3] = previewTransparency;
631 currentPreviewPath = new Path(drawType.id,
632 color,
633 availableThicknesses[currentThicknessIndex],
634 currentMouseX, currentMouseY, mouseX,
635 mouseY, false);
636 }
637
638 refreshDisplayCanvas();
639 } else {
640 currentMouseX = mouseX;
641 currentMouseY = mouseY;
642
643 if (mouseInWindow) {
644 refreshDisplayCanvas();
645 }
646 }
647
648 };
649 document.addEventListener("mousemove", canvasMouseMoveHandler, false);
650
651 document.addEventListener("mouseup", function(e) {
652 if (e.button == 0) {
653 if (mouseDown) {
654 mouseDown = false;
655 currentPreviewPath = null;
656
657 var mouseX = e.pageX - canvasDisplay.offsetLeft;
658 var mouseY = e.pageY - canvasDisplay.offsetTop;
659 var drawType = availableDrawTypes[currentDrawTypeIndex];
660
661 var path = new Path(drawType.id, availableColors[currentColorIndex],
662 availableThicknesses[currentThicknessIndex],
663 currentMouseX, currentMouseY, mouseX,
664 mouseY, true);
665 // Draw it on the background canvas.
666 path.draw(canvasBackgroundCtx);
667
668 // Send it to the sever.
669 pushPath(path);
670
671 // Refresh old coordinates
672 currentMouseX = mouseX;
673 currentMouseY = mouseY;
674
675 refreshDisplayCanvas();
676 }
677 }
678 }, false);
679
680 canvasDisplay.addEventListener("mouseout", function(e) {
681 mouseInWindow = false;
682 refreshDisplayCanvas();
683 }, false);
684
685 canvasDisplay.addEventListener("mousemove", function(e) {
686 if (!mouseInWindow) {
687 mouseInWindow = true;
688 refreshDisplayCanvas();
689 }
690 }, false);
691
692
693 // Create color and thickness controls.
694 var colorContainersBox = document.createElement("div");
695 colorContainersBox.setAttribute("style",
696 "margin: 4px; border: 1px solid #bbb; border-radius: 3px;");
697 optionContainer.appendChild(colorContainersBox);
698
699 colorContainers = new Array(3 * 3 * 3);
700 for (var i = 0; i < colorContainers.length; i++) {
701 var colorContainer = colorContainers[i] =
702 document.createElement("div");
703 var color = availableColors[i] =
704 [
705 Math.floor((i % 3) * 255 / 2),
706 Math.floor((Math.floor(i / 3) % 3) * 255 / 2),
707 Math.floor((Math.floor(i / (3 * 3)) % 3) * 255 / 2),
708 1.0
709 ];
710 colorContainer.setAttribute("style",
711 "margin: 3px; width: 18px; height: 18px; "
712 + "float: left; background-color: " + rgb(color));
713 colorContainer.style.border = '2px solid #000';
714 colorContainer.addEventListener("mousedown", (function(ix) {
715 return function() {
716 setColor(ix);
717 };
718 })(i), false);
719
720 colorContainersBox.appendChild(colorContainer);
721 }
722
723 var divClearLeft = document.createElement("div");
724 divClearLeft.setAttribute("style", "clear: left;");
725 colorContainersBox.appendChild(divClearLeft);
726
727
728 var drawTypeContainersBox = document.createElement("div");
729 drawTypeContainersBox.setAttribute("style",
730 "float: right; margin-right: 3px; margin-top: 1px;");
731 optionContainer.appendChild(drawTypeContainersBox);
732
733 drawTypeContainers = new Array(availableDrawTypes.length);
734 for (var i = 0; i < drawTypeContainers.length; i++) {
735 var drawTypeContainer = drawTypeContainers[i] =
736 document.createElement("div");
737 drawTypeContainer.setAttribute("style",
738 "text-align: center; margin: 3px; padding: 0 3px;"
739 + "height: 18px; float: left;");
740 drawTypeContainer.style.border = "2px solid #000";
741 drawTypeContainer.appendChild(document.createTextNode(
742 String(availableDrawTypes[i].name)));
743 drawTypeContainer.addEventListener("mousedown", (function(ix) {
744 return function() {
745 setDrawType(ix);
746 };
747 })(i), false);
748
749 drawTypeContainersBox.appendChild(drawTypeContainer);
750 }
751
752
753 var thicknessContainersBox = document.createElement("div");
754 thicknessContainersBox.setAttribute("style",
755 "margin: 3px; border: 1px solid #bbb; border-radius: 3px;");
756 optionContainer.appendChild(thicknessContainersBox);
757
758 thicknessContainers = new Array(availableThicknesses.length);
759 for (var i = 0; i < thicknessContainers.length; i++) {
760 var thicknessContainer = thicknessContainers[i] =
761 document.createElement("div");
762 thicknessContainer.setAttribute("style",
763 "text-align: center; margin: 3px; width: 18px; "
764 + "height: 18px; float: left;");
765 thicknessContainer.style.border = "2px solid #000";
766 thicknessContainer.appendChild(document.createTextNode(
767 String(availableThicknesses[i])));
768 thicknessContainer.addEventListener("mousedown", (function(ix) {
769 return function() {
770 setThickness(ix);
771 };
772 })(i), false);
773
774 thicknessContainersBox.appendChild(thicknessContainer);
775 }
776
777
778 divClearLeft = document.createElement("div");
779 divClearLeft.setAttribute("style", "clear: left;");
780 thicknessContainersBox.appendChild(divClearLeft);
781
782
783 setColor(0);
784 setThickness(0);
785 setDrawType(0);
786
787 }
788
789 function disableControls() {
790 document.removeEventListener("mousedown", canvasMouseDownHandler);
791 document.removeEventListener("mousemove", canvasMouseMoveHandler);
792 mouseInWindow = false;
793 refreshDisplayCanvas();
794
795 isActive = false;
796 }
797
798 function pushPath(path) {
799
800 // Push it into the pathsNotHandled array.
801 var container = new PathIdContainer(path, nextMsgId++);
802 pathsNotHandled.push(container);
803
804 // Send the path to the server.
805 var message = container.id + "|" + path.type + ","
806 + path.color[0] + "," + path.color[1] + ","
807 + path.color[2] + ","
808 + Math.round(path.color[3] * 255.0) + ","
809 + path.thickness + "," + path.x1 + ","
810 + path.y1 + "," + path.x2 + "," + path.y2 + ","
811 + (path.lastInChain ? "1" : "0");
812
813 socket.send("1" + message);
814 }
815
816 function setThickness(thicknessIndex) {
817 if (typeof currentThicknessIndex !== "undefined")
818 thicknessContainers[currentThicknessIndex]
819 .style.borderColor = "#000";
820 currentThicknessIndex = thicknessIndex;
821 thicknessContainers[currentThicknessIndex]
822 .style.borderColor = "#d08";
823 }
824
825 function setColor(colorIndex) {
826 if (typeof currentColorIndex !== "undefined")
827 colorContainers[currentColorIndex]
828 .style.borderColor = "#000";
829 currentColorIndex = colorIndex;
830 colorContainers[currentColorIndex]
831 .style.borderColor = "#d08";
832 }
833
834 function setDrawType(drawTypeIndex) {
835 if (typeof currentDrawTypeIndex !== "undefined")
836 drawTypeContainers[currentDrawTypeIndex]
837 .style.borderColor = "#000";
838 currentDrawTypeIndex = drawTypeIndex;
839 drawTypeContainers[currentDrawTypeIndex]
840 .style.borderColor = "#d08";
841 }
842
843
844 connect();
845
846 }
847
848
849 // Initialize the room
850 var room = new Room(document.getElementById("drawContainer"));
851
852
853 }, false);
854
855 })();
856 ]]></script>
857</head>
858<body>
859 <div class="noscript"><div style="color: #ff0000; font-size: 16pt;">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable
860 Javascript and reload this page!</div></div>
861 <div id="labelContainer"/>
862 <div id="drawContainer"/>
863 <div id="console-container"/>
864 <div style="clear: left;"/>
865
866 <h1 class="expand" data-content-id="expandContent" style="font-size: 1.3em;"
867 >About Drawboard WebSocket Example</h1>
868 <div id="expandContent">
869 <p>
870 This drawboard is a page where you can draw with your mouse or touch input
871 (using different colors) and everybody else which has the page open will
872 <em>immediately</em> see what you are drawing.<br/>
873 If someone opens the page later, they will get the current room image (so they
874 can see what was already drawn by other people).
875 </p>
876 <p>
877 It uses asynchronous sending of messages so that it doesn't need separate threads
878 for each client to send messages (this needs NIO or APR connector to be used).<br/>
879 Each "Room" (where the drawing happens) uses a ReentrantLock to synchronize access
880 (currently, only a single Room is implemented).
881 </p>
882 <p>
883 When you open the page, first you will receive a binary websocket message containing
884 the current room image as PNG image. After that, you will receive string messages
885 that contain the drawing actions (line from x1,y1 to x2,y2).<br/>
886 <small>Note that it currently only uses simple string messages instead of JSON because
887 I did not want to introduce a dependency on a JSON lib.</small>
888 </p>
889 <p>
890 It uses synchronization mechanisms to ensure that the final image will look the same
891 for every user, regardless of what their network latency/speed is – e.g. if two user
892 draw at the same time on the same place, the server will decide which line was the
893 first one, and that will be reflected on every client.
894 </p>
895 </div>
896</body>
897</html>